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,57 @@
import { LdapSettingsType } from '@/types/ldap';
import Config from '@/config/config';
import { logError } from '@/utils/errorHandler';
const apiServer = Config.apiServer;
async function fetchLdapSettings(jwt: string): Promise<LdapSettingsType> {
const response = await fetch(`${apiServer}/ldap`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${jwt}`,
},
});
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
return response.json();
}
async function updateLdapSettings(jwt: string, settings: LdapSettingsType): Promise<LdapSettingsType> {
const response = await fetch(`${apiServer}/ldap`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${jwt}`,
},
body: JSON.stringify(settings),
});
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
return response.json();
}
async function testLdapConnection(
jwt: string,
settings: LdapSettingsType,
testUsername: string,
testPassword: string
): Promise<{ ok: boolean; message?: string }> {
try {
const response = await fetch(`${apiServer}/ldap/test`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${jwt}`,
},
body: JSON.stringify({ ...settings, testUsername, testPassword }),
});
return response.json();
} catch (error: unknown) {
logError('Error testing LDAP connection:', error);
return { ok: false, message: 'Request failed' };
}
}
export { fetchLdapSettings, updateLdapSettings, testLdapConnection };