163 lines
5.2 KiB
JavaScript
163 lines
5.2 KiB
JavaScript
import { randomUUID } from 'crypto';
|
|
import { Router } from 'express';
|
|
import db from '../db.js';
|
|
import { loadIntegrations } from '../integrations.js';
|
|
import { authenticateLdap } from '../ldap-auth.js';
|
|
import { badRequest } from '../lib/http.js';
|
|
import { hashPassword, verifyPassword } from '../password.js';
|
|
|
|
const router = Router();
|
|
|
|
function userPayload(row) {
|
|
if (!row) return null;
|
|
const role = row.role;
|
|
const canAdmin = role === 'admin';
|
|
const canEditCrm = canAdmin || role === 'after_sales';
|
|
return {
|
|
id: row.id,
|
|
username: row.username,
|
|
role,
|
|
firstName: row.firstname ?? null,
|
|
lastName: row.lastname ?? null,
|
|
canAdmin,
|
|
canEditCrm,
|
|
};
|
|
}
|
|
|
|
router.get('/status', (req, res) => {
|
|
const count = db.prepare('SELECT COUNT(*) AS c FROM users').get().c;
|
|
const needsBootstrap = count === 0;
|
|
if (needsBootstrap) {
|
|
return res.json({ needsBootstrap: true, loggedIn: false, user: null });
|
|
}
|
|
if (!req.session?.userId) {
|
|
return res.json({ needsBootstrap: false, loggedIn: false, user: null });
|
|
}
|
|
const u = db
|
|
.prepare(
|
|
'SELECT id, username, firstname, lastname, role, active FROM users WHERE id = ?',
|
|
)
|
|
.get(req.session.userId);
|
|
if (!u || !u.active) {
|
|
req.session.destroy(() => {});
|
|
return res.json({ needsBootstrap: false, loggedIn: false, user: null });
|
|
}
|
|
res.json({
|
|
needsBootstrap: false,
|
|
loggedIn: true,
|
|
user: userPayload(u),
|
|
});
|
|
});
|
|
|
|
router.post('/bootstrap', async (req, res) => {
|
|
const count = db.prepare('SELECT COUNT(*) AS c FROM users').get().c;
|
|
if (count > 0) {
|
|
return res.status(403).json({ message: 'Initialisierung nicht mehr möglich.' });
|
|
}
|
|
const { username, password } = req.body || {};
|
|
const un = String(username || '')
|
|
.trim()
|
|
.toLowerCase();
|
|
if (!un || !password || password.length < 8) {
|
|
return badRequest(res, 'Benutzername und Passwort (min. 8 Zeichen) erforderlich.');
|
|
}
|
|
const id = randomUUID();
|
|
const ph = await hashPassword(password);
|
|
db.prepare(
|
|
`INSERT INTO users (id, username, password_hash, role, source, active, updated_at)
|
|
VALUES (?, ?, ?, 'admin', 'local', 1, datetime('now'))`,
|
|
).run(id, un, ph);
|
|
req.session.userId = id;
|
|
req.session.role = 'admin';
|
|
req.session.username = un;
|
|
res.status(201).json({
|
|
user: userPayload(
|
|
db.prepare(
|
|
'SELECT id, username, firstname, lastname, role FROM users WHERE id = ?',
|
|
).get(id),
|
|
),
|
|
});
|
|
});
|
|
|
|
router.post('/login', async (req, res) => {
|
|
const { username, password } = req.body || {};
|
|
const rawUsername = String(username || '').trim();
|
|
const unLower = rawUsername.toLowerCase();
|
|
if (!rawUsername || !password) {
|
|
return badRequest(res, 'Benutzername und Passwort erforderlich.');
|
|
}
|
|
|
|
const ldapCfg = loadIntegrations().ldap || {};
|
|
const useLdap =
|
|
Boolean(ldapCfg.syncEnabled) &&
|
|
String(ldapCfg.serverUrl || '').trim() !== '' &&
|
|
String(ldapCfg.searchBase || '').trim() !== '';
|
|
|
|
if (useLdap) {
|
|
const ar = await authenticateLdap(rawUsername, password, loadIntegrations);
|
|
if (ar.ok) {
|
|
const lookup = ar.canonicalUsername || rawUsername;
|
|
const u = db
|
|
.prepare('SELECT * FROM users WHERE username = ? COLLATE NOCASE')
|
|
.get(lookup);
|
|
if (!u || !u.active) {
|
|
return res.status(401).json({
|
|
message:
|
|
'Benutzer nach LDAP nicht in der Anwendung gefunden. Bitte zuerst eine LDAP-Synchronisation ausführen.',
|
|
});
|
|
}
|
|
req.session.userId = u.id;
|
|
req.session.role = u.role;
|
|
req.session.username = u.username;
|
|
return res.json({
|
|
user: userPayload(u),
|
|
});
|
|
}
|
|
if (!ar.skipLdap) {
|
|
const u = db
|
|
.prepare('SELECT * FROM users WHERE username = ? COLLATE NOCASE')
|
|
.get(unLower);
|
|
if (u && u.active && u.password_hash) {
|
|
const pwOk = await verifyPassword(password, u.password_hash);
|
|
if (pwOk) {
|
|
req.session.userId = u.id;
|
|
req.session.role = u.role;
|
|
req.session.username = u.username;
|
|
return res.json({
|
|
user: userPayload(u),
|
|
});
|
|
}
|
|
}
|
|
return res.status(401).json({ message: 'Ungültige Zugangsdaten.' });
|
|
}
|
|
}
|
|
|
|
const u = db.prepare('SELECT * FROM users WHERE username = ?').get(unLower);
|
|
if (!u || !u.active) {
|
|
return res.status(401).json({ message: 'Ungültige Zugangsdaten.' });
|
|
}
|
|
if (!u.password_hash) {
|
|
return res.status(401).json({
|
|
message:
|
|
'Kein lokales Passwort. LDAP ist nicht konfiguriert oder die Anmeldung ist nur mit Verzeichnis möglich.',
|
|
});
|
|
}
|
|
const ok = await verifyPassword(password, u.password_hash);
|
|
if (!ok) return res.status(401).json({ message: 'Ungültige Zugangsdaten.' });
|
|
req.session.userId = u.id;
|
|
req.session.role = u.role;
|
|
req.session.username = u.username;
|
|
res.json({
|
|
user: userPayload(u),
|
|
});
|
|
});
|
|
|
|
router.post('/logout', (req, res) => {
|
|
req.session.destroy((err) => {
|
|
if (err) return res.status(500).json({ message: 'Abmelden fehlgeschlagen.' });
|
|
res.json({ ok: true });
|
|
});
|
|
});
|
|
|
|
export default router;
|