import { getGameDb } from '@/lib/db/client'; import { createSession, verifyPassword, DUMMY_HASH_FOR_UNKNOWN_USER } from '@/lib/auth'; // v0.71.0 — POST /api/login // body { username: string, password: string } (both required non-empty; unknown fields 400). // Success → 200 { sessionId, user: { username, admin } }; the session row lives 7 days and is // sent back as X-Flight-Session on later requests. Failure → 401 { error }. An unknown // username still runs one full scrypt against a fixed dummy digest so timing stays uniform. export async function POST(req: Request) { let body: unknown; try { const text = await req.text(); body = text ? JSON.parse(text) : undefined; } catch { return Response.json({ error: 'invalid JSON body' }, { status: 400 }); } if (typeof body !== 'object' || body === null || Array.isArray(body)) { return Response.json({ error: 'body must be a JSON object' }, { status: 400 }); } const input = body as Record; for (const key of Object.keys(input)) { if (key !== 'username' && key !== 'password') { return Response.json({ error: `unknown field "${key}"` }, { status: 400 }); } } const username = input.username; const password = input.password; if (typeof username !== 'string' || username.length === 0) { return Response.json({ error: 'username is required and must be a non-empty string' }, { status: 400 }); } if (typeof password !== 'string' || password.length === 0) { return Response.json({ error: 'password is required and must be a non-empty string' }, { status: 400 }); } const inst = await getGameDb(); const res = inst.raw.exec('SELECT id, password_hash, admin FROM users WHERE username = ?', [username]); const row = res[0]?.values?.[0]; if (!row) { verifyPassword(password, DUMMY_HASH_FOR_UNKNOWN_USER); // uniform timing — result discarded return Response.json({ error: 'invalid credentials' }, { status: 401 }); } if (!verifyPassword(password, String(row[1]))) { return Response.json({ error: 'invalid credentials' }, { status: 401 }); } const sessionId = await createSession(inst, String(row[0])); return Response.json({ sessionId, user: { username, admin: Number(row[2]) === 1 }, }); }