const bcrypt = require('bcryptjs') const usersDb = require('./users.db') const SALT_ROUNDS = 10 // Strip secrets (password hash, TOTP secret) before sending a user anywhere. function sanitize(user) { if (!user) return null const { password_hash, totp_secret, ...safe } = user return safe } async function createUser({ username, password, role = 'admin' }) { const passwordHash = await bcrypt.hash(password, SALT_ROUNDS) const id = await usersDb.insertUser({ username, passwordHash, role }) return sanitize(await usersDb.findById(id)) } // Returns the raw row (incl. hash) — used by login only. async function getRawByUsername(username) { return usersDb.findByUsername(username) } async function getById(id) { return sanitize(await usersDb.findById(id)) } // Raw row incl. totp_secret — server-side only (TOTP setup/verify). Never sent // to a client; sanitize() strips the secret from anything user-facing. async function getRawById(id) { return usersDb.findById(id) } async function setTotpSecret(id, secret) { return usersDb.setTotpSecret(id, secret) } async function enableTotp(id) { return usersDb.enableTotp(id) } async function disableTotp(id) { return usersDb.disableTotp(id) } async function validatePassword(user, password) { if (!user || !user.password_hash) return false return bcrypt.compare(password, user.password_hash) } async function list() { return usersDb.listUsers() } async function update(id, { username, password, role }) { const fields = {} if (username !== undefined) fields.username = username if (role !== undefined) fields.role = role if (password) fields.password_hash = await bcrypt.hash(password, SALT_ROUNDS) await usersDb.updateUser(id, fields) // A password change must revoke existing sessions ("change password to log // everyone out"), so bump the cutoff whenever the hash was rotated. if (password) await usersDb.bumpTokensValidAfter(id) return getById(id) } // Invalidate every session token this user currently holds ("log out everywhere") // by advancing their tokens_valid_after cutoff to now. async function invalidateSessions(id) { return usersDb.bumpTokensValidAfter(id) } async function remove(id) { return usersDb.deleteUser(id) } async function count() { return usersDb.countUsers() } async function countAdmins() { return usersDb.countAdmins() } async function recordLogin(id) { return usersDb.touchLastLogin(id) } module.exports = { createUser, getRawByUsername, getById, getRawById, validatePassword, list, update, invalidateSessions, remove, count, countAdmins, recordLogin, setTotpSecret, enableTotp, disableTotp, }