Files

79 lines
2.9 KiB
JavaScript
Raw Permalink Normal View History

2026-09-13 20:12:28 +01:00
'use strict';
const express = require('express');
const bcrypt = require('bcryptjs');
const { db } = require('../db');
const { log } = require('../lib/audit');
const { normaliseEmail, validateEmail, validateUsername, validatePassword } = require('../lib/validate');
const router = express.Router();
/** True while the instance has no users at all (i.e. needs first-boot setup). */
function needsSetup() {
return db.prepare('SELECT COUNT(*) c FROM users').get().c === 0;
}
// Lets the front-end know whether the wizard should be shown.
router.get('/status', (req, res) => {
res.json({ needs_setup: needsSetup() });
});
// Create the very first administrator. Only ever available while no users exist.
router.post('/', (req, res) => {
if (!needsSetup()) {
return res.status(409).json({ error: 'Setup has already been completed.' });
}
const username = String((req.body && req.body.username) || '').trim();
const email = normaliseEmail(req.body && req.body.email); // mandatory
const password = String((req.body && req.body.password) || '');
const confirm = String((req.body && req.body.confirm) || '');
// --- validation (same rules as the Users tab) -------------------------
const err = validateUsername(username) || validateEmail(email) || validatePassword(password);
if (err) return res.status(400).json({ error: err });
if (password !== confirm) {
return res.status(400).json({ error: 'The two passwords do not match.' });
}
// --- create -----------------------------------------------------------
// Guard against two browsers racing through the wizard simultaneously:
// the transaction re-checks the user count before inserting.
let created;
try {
created = db.transaction(() => {
if (db.prepare('SELECT COUNT(*) c FROM users').get().c !== 0) {
const e = new Error('Setup has already been completed.');
e.status = 409;
throw e;
}
const hash = bcrypt.hashSync(password, 12);
const info = db
.prepare('INSERT INTO users (username, email, password_hash, role) VALUES (?, ?, ?, ?)')
.run(username, email, hash, 'admin');
return db.prepare('SELECT * FROM users WHERE id = ?').get(info.lastInsertRowid);
})();
} catch (err) {
if (err && err.status === 409) return res.status(409).json({ error: err.message });
if (String(err.message || '').includes('UNIQUE')) {
return res.status(409).json({ error: 'That username or email is already in use.' });
}
throw err;
}
log(req, 'SETUP_COMPLETED', 'Setup', `First administrator "${created.username}" created`);
// Sign the new administrator straight in — no need to re-type credentials.
req.session.user = {
id: created.id,
username: created.username,
email: created.email,
role: created.role,
mfa_enabled: false,
};
log(req, 'LOGIN', 'Login', `${created.username} signed in`);
res.json({ ok: true, user: req.session.user });
});
module.exports = { router, needsSetup };