const argon2 = require('argon2'); const { db } = require('../../db'); const insertStmt = db.prepare( `INSERT INTO users (email, display_name, password_hash, role) VALUES (?, ?, ?, ?)` ); const findByEmailStmt = db.prepare('SELECT * FROM users WHERE email = ?'); const findByIdStmt = db.prepare('SELECT * FROM users WHERE id = ?'); const updateNameStmt = db.prepare('UPDATE users SET display_name = ? WHERE id = ?'); const updatePasswordStmt = db.prepare('UPDATE users SET password_hash = ? WHERE id = ?'); const countAdminsStmt = db.prepare("SELECT COUNT(*) AS c FROM users WHERE role = 'admin'"); function publicView(row) { if (!row) return null; return { id: row.id, email: row.email, displayName: row.display_name, role: row.role }; } async function createUser({ email, displayName, password }) { email = email.trim().toLowerCase(); const hash = await argon2.hash(password, { type: argon2.argon2id }); // Auto-promote to admin if no admins exist yet and env bootstrap email matches. let role = 'user'; const bootstrap = (process.env.ADMIN_BOOTSTRAP_EMAIL || '').trim().toLowerCase(); if (bootstrap && email === bootstrap && countAdminsStmt.get().c === 0) { role = 'admin'; } const info = insertStmt.run(email, displayName.trim(), hash, role); return publicView(findByIdStmt.get(info.lastInsertRowid)); } async function verifyCredentials(email, password) { const row = findByEmailStmt.get(email.trim().toLowerCase()); if (!row) return null; const ok = await argon2.verify(row.password_hash, password); return ok ? publicView(row) : null; } function findByEmail(email) { return publicView(findByEmailStmt.get(email.trim().toLowerCase())); } function findById(id) { return publicView(findByIdStmt.get(id)); } function updateDisplayName(id, displayName) { updateNameStmt.run(displayName.trim(), id); return findById(id); } async function changePassword(id, currentPassword, newPassword) { const row = findByIdStmt.get(id); if (!row) return { ok: false, reason: 'not_found' }; const ok = await argon2.verify(row.password_hash, currentPassword); if (!ok) return { ok: false, reason: 'bad_current' }; const hash = await argon2.hash(newPassword, { type: argon2.argon2id }); updatePasswordStmt.run(hash, id); return { ok: true }; } module.exports = { createUser, verifyCredentials, findByEmail, findById, updateDisplayName, changePassword, };