Skip to content
import { randomBytes, scryptSync, timingSafeEqual } from "node:crypto";
import { initDb, query } from "./db.ts";
import type { SessionUser } from "../utils.ts";

const SESSION_COOKIE = "profectus_session";
const SESSION_TTL_SECONDS = 60 * 60 * 24 * 30;

export const ADMIN_EMAIL = Deno.env.get("ADMIN_EMAIL")?.toLowerCase() ??
  "silen.locatelli@gmx.ch";

export interface UserRecord extends SessionUser {
  password_hash: string;
  created_at: string;
}

export function hashPassword(password: string): string {
  const salt = randomBytes(16).toString("hex");
  const hash = scryptSync(password, salt, 64).toString("hex");
  return `${salt}:${hash}`;
}

export function verifyPassword(password: string, stored: string): boolean {
  const [salt, hash] = stored.split(":");
  if (!salt || !hash) return false;
  const candidate = scryptSync(password, salt, 64);
  const expected = Buffer.from(hash, "hex");
  return candidate.length === expected.length &&
    timingSafeEqual(candidate, expected);
}

function getSessionToken(req: Request): string | null {
  const header = req.headers.get("cookie");
  if (!header) return null;
  for (const part of header.split(";")) {
    const idx = part.indexOf("=");
    if (idx === -1) continue;
    if (part.slice(0, idx).trim() === SESSION_COOKIE) {
      return decodeURIComponent(part.slice(idx + 1).trim());
    }
  }
  return null;
}

function cookieAttributes(req: Request, maxAge: number): string {
  const secure = new URL(req.url).protocol === "https:";
  return `Path=/; HttpOnly; SameSite=Lax; Max-Age=${maxAge}${
    secure ? "; Secure" : ""
  }`;
}

export async function getUserFromRequest(
  req: Request,
): Promise<SessionUser | null> {
  await initDb();
  const token = getSessionToken(req);
  if (!token) return null;
  const result = await query(
    `SELECT u.id, u.email, u.name, u.role
     FROM sessions s JOIN users u ON u.id = s.user_id
     WHERE s.token = ? AND s.expires_at > datetime('now')`,
    [token],
  );
  const row = result.rows[0];
  if (!row) return null;
  return {
    id: Number(row.id),
    email: String(row.email),
    name: row.name === null ? null : String(row.name),
    role: String(row.role) as SessionUser["role"],
  };
}

export async function createSession(userId: number): Promise<string> {
  await initDb();
  const token = randomBytes(32).toString("hex");
  await query(
    `INSERT INTO sessions (token, user_id, expires_at)
     VALUES (?, ?, datetime('now', '+${SESSION_TTL_SECONDS} seconds'))`,
    [token, userId],
  );
  return token;
}

export function sessionCookie(req: Request, token: string): string {
  return `${SESSION_COOKIE}=${encodeURIComponent(token)}; ${
    cookieAttributes(req, SESSION_TTL_SECONDS)
  }`;
}

export async function destroySession(req: Request): Promise<void> {
  const token = getSessionToken(req);
  if (token) {
    await initDb();
    await query("DELETE FROM sessions WHERE token = ?", [token]);
  }
}

export function clearSessionCookie(req: Request): string {
  return `${SESSION_COOKIE}=; ${cookieAttributes(req, 0)}`;
}

export async function findUserByEmail(
  email: string,
): Promise<UserRecord | null> {
  await initDb();
  const result = await query(
    `SELECT id, email, name, password_hash, role, created_at
     FROM users WHERE email = ?`,
    [email],
  );
  const row = result.rows[0];
  if (!row) return null;
  return {
    id: Number(row.id),
    email: String(row.email),
    name: row.name === null ? null : String(row.name),
    role: String(row.role) as UserRecord["role"],
    password_hash: String(row.password_hash),
    created_at: String(row.created_at),
  };
}

export async function createUser(
  email: string,
  name: string | null,
  passwordHash: string,
): Promise<UserRecord> {
  await initDb();
  const role = email.toLowerCase() === ADMIN_EMAIL ? "admin" : "user";
  const result = await query(
    `INSERT INTO users (email, name, password_hash, role)
     VALUES (?, ?, ?, ?)`,
    [email, name, passwordHash, role],
  );
  return {
    id: Number(result.lastInsertRowid),
    email,
    name,
    role: role as UserRecord["role"],
    password_hash: passwordHash,
    created_at: new Date().toISOString(),
  };
}

export async function setUserRole(
  userId: number,
  role: UserRecord["role"],
): Promise<void> {
  await query("UPDATE users SET role = ? WHERE id = ?", [role, userId]);
}

export async function deleteUser(userId: number): Promise<void> {
  await query("DELETE FROM sessions WHERE user_id = ?", [userId]);
  await query("DELETE FROM users WHERE id = ?", [userId]);
}