import crypto from 'node:crypto' import argon2 from 'argon2' import type { DB } from './db.js' export interface UserRow { id: number username: string password_hash: string is_admin: number created_at: string } const SESSION_TTL_MS = 30 * 24 * 60 * 60 * 1000 // 30 days export function hashPassword(password: string): Promise { return argon2.hash(password) } export function verifyPassword(hash: string, password: string): Promise { return argon2.verify(hash, password) } export function createUser(db: DB, username: string, passwordHash: string, isAdmin: boolean): UserRow { const info = db .prepare('INSERT INTO users (username, password_hash, is_admin) VALUES (?, ?, ?)') .run(username, passwordHash, isAdmin ? 1 : 0) return db.prepare('SELECT * FROM users WHERE id = ?').get(info.lastInsertRowid) as UserRow } export function getUserByUsername(db: DB, username: string): UserRow | undefined { return db.prepare('SELECT * FROM users WHERE username = ?').get(username) as UserRow | undefined } export function getUserById(db: DB, id: number): UserRow | undefined { return db.prepare('SELECT * FROM users WHERE id = ?').get(id) as UserRow | undefined } export function createSession(db: DB, userId: number): string { const token = crypto.randomBytes(32).toString('hex') const expiresAt = new Date(Date.now() + SESSION_TTL_MS).toISOString() db.prepare('INSERT INTO sessions (token, user_id, expires_at) VALUES (?, ?, ?)').run( token, userId, expiresAt ) return token } export function getUserBySession(db: DB, token: string, now: () => number = Date.now): UserRow | null { const row = db .prepare( `SELECT u.*, s.expires_at FROM sessions s JOIN users u ON u.id = s.user_id WHERE s.token = ?` ) .get(token) as (UserRow & { expires_at: string }) | undefined if (!row) return null if (new Date(row.expires_at).getTime() < now()) { deleteSession(db, token) return null } return row } export function deleteSession(db: DB, token: string): void { db.prepare('DELETE FROM sessions WHERE token = ?').run(token) }