79 lines
2.9 KiB
JavaScript
79 lines
2.9 KiB
JavaScript
'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 };
|