LDAP sync
This commit is contained in:
@@ -19,7 +19,7 @@ export function createAdminRouter() {
|
||||
admin.get('/users', (_req, res) => {
|
||||
const rows = db
|
||||
.prepare(
|
||||
'SELECT id, username, role, source, active, ldap_dn, created_at, updated_at FROM users ORDER BY username ASC',
|
||||
'SELECT id, username, firstname, lastname, role, source, active, ldap_dn, created_at, updated_at FROM users ORDER BY username ASC',
|
||||
)
|
||||
.all();
|
||||
res.json(rows.map(mapPublicUser));
|
||||
@@ -138,7 +138,7 @@ export function createAdminRouter() {
|
||||
}
|
||||
if (incoming.syncIntervalMinutes != null) {
|
||||
const n = Number(incoming.syncIntervalMinutes);
|
||||
incoming.syncIntervalMinutes = Number.isFinite(n) ? Math.max(0, Math.floor(n)) : 1440;
|
||||
incoming.syncIntervalMinutes = Number.isFinite(n) ? Math.max(0, Math.floor(n)) : 0;
|
||||
}
|
||||
Object.assign(cur.ldap, incoming);
|
||||
if (b.ldap.userSearchFilter != null) {
|
||||
|
||||
@@ -1,11 +1,24 @@
|
||||
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;
|
||||
return {
|
||||
id: row.id,
|
||||
username: row.username,
|
||||
role: row.role,
|
||||
firstName: row.firstname ?? null,
|
||||
lastName: row.lastname ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
router.get('/status', (req, res) => {
|
||||
const count = db.prepare('SELECT COUNT(*) AS c FROM users').get().c;
|
||||
const needsBootstrap = count === 0;
|
||||
@@ -16,7 +29,9 @@ router.get('/status', (req, res) => {
|
||||
return res.json({ needsBootstrap: false, loggedIn: false, user: null });
|
||||
}
|
||||
const u = db
|
||||
.prepare('SELECT id, username, role, active FROM users WHERE id = ?')
|
||||
.prepare(
|
||||
'SELECT id, username, firstname, lastname, role, active FROM users WHERE id = ?',
|
||||
)
|
||||
.get(req.session.userId);
|
||||
if (!u || !u.active) {
|
||||
req.session.destroy(() => {});
|
||||
@@ -25,7 +40,7 @@ router.get('/status', (req, res) => {
|
||||
res.json({
|
||||
needsBootstrap: false,
|
||||
loggedIn: true,
|
||||
user: { id: u.id, username: u.username, role: u.role },
|
||||
user: userPayload(u),
|
||||
});
|
||||
});
|
||||
|
||||
@@ -51,25 +66,75 @@ router.post('/bootstrap', async (req, res) => {
|
||||
req.session.role = 'admin';
|
||||
req.session.username = un;
|
||||
res.status(201).json({
|
||||
user: { id, username: un, role: 'admin' },
|
||||
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 un = String(username || '')
|
||||
.trim()
|
||||
.toLowerCase();
|
||||
if (!un || !password) {
|
||||
const rawUsername = String(username || '').trim();
|
||||
const unLower = rawUsername.toLowerCase();
|
||||
if (!rawUsername || !password) {
|
||||
return badRequest(res, 'Benutzername und Passwort erforderlich.');
|
||||
}
|
||||
const u = db.prepare('SELECT * FROM users WHERE username = ?').get(un);
|
||||
|
||||
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). Anmeldung folgt mit Verzeichnis-Sync.',
|
||||
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);
|
||||
@@ -78,7 +143,7 @@ router.post('/login', async (req, res) => {
|
||||
req.session.role = u.role;
|
||||
req.session.username = u.username;
|
||||
res.json({
|
||||
user: { id: u.id, username: u.username, role: u.role },
|
||||
user: userPayload(u),
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user