Add an admin-only LDAP configuration UI with an enable toggle and full sign-in integration. Backend: - ldapSettings model + migration (single-row config) - GET/PUT/test routes under /ldap (admin-gated; bind password masked) - shared ldapClient with RFC 4515 filter escaping and empty-password guard - signin tries local auth first, then LDAP when enabled (find-or-create local user) so the bootstrap admin is never locked out Frontend: - LDAP settings page (Switch + form + test connection) under /admin/ldap - AdminNav tabs between user management and LDAP - ldapControl util, types, and Ldap i18n namespace for all 6 locales Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
58 lines
2.1 KiB
JavaScript
58 lines
2.1 KiB
JavaScript
import express from 'express';
|
|
import { DataTypes } from 'sequelize';
|
|
import { Client } from 'ldapts';
|
|
import defineLdapSetting from '../../models/ldapSettings.js';
|
|
import authMiddleware from '../../middleware/auth.js';
|
|
import { authenticateLdap, getLdapSettings } from './ldapClient.js';
|
|
const router = express.Router();
|
|
|
|
export default function (sequelize) {
|
|
const { verifySignedIn, verifyAdmin } = authMiddleware(sequelize);
|
|
const LdapSetting = defineLdapSetting(sequelize, DataTypes);
|
|
|
|
router.post('/test', verifySignedIn, verifyAdmin, async (req, res) => {
|
|
try {
|
|
const stored = await getLdapSettings(LdapSetting);
|
|
|
|
// Use the values from the form, falling back to the stored bind
|
|
// password when the (masked) field is left empty.
|
|
const settings = {
|
|
url: req.body.url ?? stored.url,
|
|
bindDn: req.body.bindDn ?? stored.bindDn,
|
|
bindCredentials: req.body.bindCredentials || stored.bindCredentials,
|
|
searchBase: req.body.searchBase ?? stored.searchBase,
|
|
searchFilter: req.body.searchFilter ?? stored.searchFilter,
|
|
emailAttribute: req.body.emailAttribute ?? stored.emailAttribute,
|
|
usernameAttribute: req.body.usernameAttribute ?? stored.usernameAttribute,
|
|
};
|
|
|
|
if (!settings.url) {
|
|
return res.status(400).json({ ok: false, message: 'Server URL is required' });
|
|
}
|
|
|
|
const { testUsername, testPassword } = req.body;
|
|
if (testUsername && testPassword) {
|
|
const user = await authenticateLdap(settings, testUsername, testPassword);
|
|
return res.json({ ok: true, user });
|
|
}
|
|
|
|
// No test user supplied: just verify connectivity and the service bind.
|
|
const client = new Client({ url: settings.url });
|
|
try {
|
|
await client.bind(settings.bindDn, settings.bindCredentials);
|
|
} finally {
|
|
try {
|
|
await client.unbind();
|
|
} catch {
|
|
// ignore
|
|
}
|
|
}
|
|
return res.json({ ok: true });
|
|
} catch (error) {
|
|
return res.status(200).json({ ok: false, message: error.message || 'LDAP test failed' });
|
|
}
|
|
});
|
|
|
|
return router;
|
|
}
|