feat(ldap): opt-in skip TLS verification for ldaps (internal CA)
Add a per-tenant "Skip TLS certificate verification" toggle to the LDAP admin page so admins can connect to an AD whose ldaps:// certificate is signed by an internal/self-signed CA (Node error: "unable to verify the first certificate"). When enabled, ldapts is given tlsOptions.rejectUnauthorized=false; the flag is ignored for plain ldap:// (no TLS). Defaults to full verification. New Boolean column LdapConfig.tlsRejectUnauthorized (@default(true)) + migration; wired through DTOs, config service, all Client creations (test/groups/user-search/import/sync) and the test-connection endpoint. UI checkbox with an insecure-network warning (de/en). 3 new service specs; API 218 green, web 131 green, both apps tsc clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -23,6 +23,7 @@ interface LdapConfig {
|
||||
searchFilter: string;
|
||||
syncIntervalMin: number;
|
||||
isActive: boolean;
|
||||
tlsRejectUnauthorized: boolean;
|
||||
groupFilterDns: string[];
|
||||
userExcludeList: string[];
|
||||
lastSyncAt: string | null;
|
||||
@@ -85,6 +86,7 @@ export default function AdminLdapPage() {
|
||||
searchFilter: '(objectClass=person)',
|
||||
syncIntervalMin: 60,
|
||||
isActive: true,
|
||||
tlsRejectUnauthorized: true,
|
||||
});
|
||||
|
||||
// New mapping form
|
||||
@@ -143,6 +145,7 @@ export default function AdminLdapPage() {
|
||||
searchFilter: data.searchFilter || '(objectClass=person)',
|
||||
syncIntervalMin: data.syncIntervalMin ?? 60,
|
||||
isActive: data.isActive ?? true,
|
||||
tlsRejectUnauthorized: data.tlsRejectUnauthorized ?? true,
|
||||
});
|
||||
setGroupFilterDns(data.groupFilterDns ?? []);
|
||||
setUserExcludeList(data.userExcludeList ?? []);
|
||||
@@ -201,10 +204,12 @@ export default function AdminLdapPage() {
|
||||
// before ever saving a config. bindPassword is omitted when blank so
|
||||
// the backend falls back to the saved config's password (masked
|
||||
// fields never get re-sent once a config already exists).
|
||||
const body: Record<string, string> = {};
|
||||
const body: Record<string, unknown> = {};
|
||||
if (formData.serverUrl) body.serverUrl = formData.serverUrl;
|
||||
if (formData.bindDn) body.bindDn = formData.bindDn;
|
||||
if (formData.bindPassword) body.bindPassword = formData.bindPassword;
|
||||
// Always send the current TLS-verification choice so the test reflects it.
|
||||
body.tlsRejectUnauthorized = formData.tlsRejectUnauthorized;
|
||||
|
||||
const res = await fetch(`${API_URL}/ldap/test-connection`, {
|
||||
method: 'POST',
|
||||
@@ -504,6 +509,23 @@ export default function AdminLdapPage() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1">
|
||||
<label className="flex items-center gap-2 text-sm text-foreground">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={!formData.tlsRejectUnauthorized}
|
||||
onChange={(e) =>
|
||||
setFormData({
|
||||
...formData,
|
||||
tlsRejectUnauthorized: !e.target.checked,
|
||||
})
|
||||
}
|
||||
/>
|
||||
{t('tlsSkip.label')}
|
||||
</label>
|
||||
<p className="text-xs text-muted-foreground">{t('tlsSkip.hint')}</p>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-4 pt-2">
|
||||
<button
|
||||
type="submit"
|
||||
|
||||
Reference in New Issue
Block a user