import { randomBytes } from 'crypto'; import { hashPassword } from './accounts'; /** * Admin-console accounts. Replaces the original static two-account env map * with a persisted table; the env values remain the seed for empty databases. */ export interface AdminUser { id: string; // usr_ username: string; passwordHash: string; active: boolean; createdMs: number; } export interface AdminUserRepo { list(): AdminUser[]; findByUsername(username: string): AdminUser | undefined; save(user: AdminUser): void; } export function newAdminUserId(): string { return `usr_${randomBytes(6).toString('hex')}`; } /** The env-backed seed accounts (preserved from the pre-table behavior). */ export function seedAdminUsersFromEnv(): { username: string; password: string }[] { return [ { username: process.env.ADMIN_USER ?? 'admin', password: process.env.ADMIN_KEY ?? 'admin-dev-key', }, { username: process.env.DEMO_ADMIN_USER ?? 'demo', password: process.env.DEMO_ADMIN_PASSWORD ?? '$$$Adm1n###', }, ]; } export function makeAdminUser(username: string, password: string): AdminUser { return { id: newAdminUserId(), username, passwordHash: hashPassword(password), active: true, createdMs: Date.now(), }; } export class InMemoryAdminUserRepo implements AdminUserRepo { private users: AdminUser[]; private constructor(users: AdminUser[]) { this.users = users.map((u) => ({ ...u })); } static seeded(seed: { username: string; password: string }[]): InMemoryAdminUserRepo { return new InMemoryAdminUserRepo(seed.map((s) => makeAdminUser(s.username, s.password))); } list(): AdminUser[] { return [...this.users]; } findByUsername(username: string): AdminUser | undefined { return this.users.find((u) => u.username === username); } save(user: AdminUser): void { const i = this.users.findIndex((u) => u.id === user.id || u.username === user.username); if (i >= 0) this.users[i] = user; else this.users.push(user); } }