Bri-Tunes/src/services/users.js

69 lines
2.4 KiB
JavaScript

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,
};