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:
@@ -20,6 +20,7 @@ vi.mock('../prisma/prisma-tenant.extension', () => ({
|
||||
forTenant: vi.fn((p: unknown) => p),
|
||||
}));
|
||||
|
||||
import { Client } from 'ldapts';
|
||||
import { LdapService } from './ldap.service';
|
||||
|
||||
describe('LdapService.syncUsersForTenant — per-user exclude list', () => {
|
||||
@@ -265,3 +266,45 @@ describe('LdapService — individual user search & import (dedup)', () => {
|
||||
expect(userService.create).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('LdapService.testConnection — TLS verification opt-out (ldaps)', () => {
|
||||
let service: LdapService;
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
mockBind.mockResolvedValue(undefined);
|
||||
mockUnbind.mockResolvedValue(undefined);
|
||||
service = new LdapService({} as any, {} as any);
|
||||
});
|
||||
|
||||
it('passes tlsOptions.rejectUnauthorized=false for ldaps when opted out', async () => {
|
||||
await service.testConnection({
|
||||
serverUrl: 'ldaps://ad:636',
|
||||
tlsRejectUnauthorized: false,
|
||||
});
|
||||
expect(Client).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
url: 'ldaps://ad:636',
|
||||
tlsOptions: { rejectUnauthorized: false },
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('keeps verification on for ldaps when tlsRejectUnauthorized is true', async () => {
|
||||
await service.testConnection({
|
||||
serverUrl: 'ldaps://ad:636',
|
||||
tlsRejectUnauthorized: true,
|
||||
});
|
||||
const opts = (Client as any).mock.calls.at(-1)[0];
|
||||
expect(opts.tlsOptions).toBeUndefined();
|
||||
});
|
||||
|
||||
it('ignores the flag for plain ldap:// (no TLS)', async () => {
|
||||
await service.testConnection({
|
||||
serverUrl: 'ldap://ad:389',
|
||||
tlsRejectUnauthorized: false,
|
||||
});
|
||||
const opts = (Client as any).mock.calls.at(-1)[0];
|
||||
expect(opts.tlsOptions).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user