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