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

/**
 * Local development convenience: while the Vite dev server is running, any
 * request without a session is served as a seeded admin so the game and the
 * administration screens are reachable without signing in.
 *
 * `import.meta.env.DEV` is statically replaced with `false` in the production
 * bundle, so none of this can ever run on a deployed build.
 */

export const DEV_USER_EMAIL = "dev@localhost";
export const DEV_USER_NAME = "Dev Admin";

const DEV_LOGOUT_COOKIE = "profectus_dev_signed_out";

export function devAutoLoginEnabled(): boolean {
  return import.meta.env.DEV && Deno.env.get("DEV_AUTOLOGIN") !== "off";
}

function hasCookie(req: Request, name: string): boolean {
  const header = req.headers.get("cookie");
  if (!header) return false;
  return header.split(";").some((part) => {
    const idx = part.indexOf("=");
    return idx !== -1 && part.slice(0, idx).trim() === name;
  });
}

/** True when the developer explicitly used /logout and wants the login page. */
export function devSignedOut(req: Request): boolean {
  return hasCookie(req, DEV_LOGOUT_COOKIE);
}

export function devSignOutCookie(): string {
  return `${DEV_LOGOUT_COOKIE}=1; Path=/; SameSite=Lax; Max-Age=${
    60 * 60 * 24
  }`;
}

export function clearDevSignOutCookie(): string {
  return `${DEV_LOGOUT_COOKIE}=; Path=/; SameSite=Lax; Max-Age=0`;
}

let cached: SessionUser | null = null;

/** Finds or creates the local dev account and guarantees it holds admin. */
export async function ensureDevAdmin(): Promise<SessionUser> {
  if (cached) return cached;
  await initDb();

  const existing = await query("SELECT id FROM users WHERE email = ?", [
    DEV_USER_EMAIL,
  ]);
  let id = existing.rows[0] ? Number(existing.rows[0].id) : null;

  if (id === null) {
    // The password is unusable on purpose: this account exists for the local
    // dev server only and is never meant to be signed into by hand.
    const salt = randomBytes(16).toString("hex");
    const hash = scryptSync(randomBytes(32), salt, 64).toString("hex");
    const inserted = await query(
      `INSERT INTO users (email, name, password_hash, role)
       VALUES (?, ?, ?, 'admin')`,
      [DEV_USER_EMAIL, DEV_USER_NAME, `${salt}:${hash}`],
    );
    id = Number(inserted.lastInsertRowid);
  } else {
    await query("UPDATE users SET role = 'admin' WHERE id = ?", [id]);
  }

  cached = { id, email: DEV_USER_EMAIL, name: DEV_USER_NAME, role: "admin" };
  return cached;
}