feat: add LDAP authentication settings and toggle

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>
This commit is contained in:
LittleYellow
2026-06-26 07:20:08 +08:00
parent 02fa631f02
commit 1c977f9266
25 changed files with 992 additions and 19 deletions

View File

@@ -0,0 +1,14 @@
import { describe, it, expect } from 'vitest';
import { buildFilter, escapeFilterValue } from './ldapClient.js';
describe('LDAP filter building', () => {
it('substitutes {{username}} into the filter template', () => {
expect(buildFilter('(mail={{username}})', 'alice@example.com')).toBe('(mail=alice@example.com)');
});
it('escapes RFC 4515 special characters to prevent filter injection', () => {
// An attacker-supplied "*" or parentheses must not alter the filter shape.
expect(escapeFilterValue('*)(uid=*')).toBe('\\2a\\29\\28uid=\\2a');
expect(buildFilter('(uid={{username}})', 'a*b')).toBe('(uid=a\\2ab)');
});
});