Files
tessera-ctl/apps/api/src/ldap/ldap-config.service.ts
T
schalli af9e968c6f
Tessera CI/CD / Lint & Type Check (push) Successful in 45s
Tessera CI/CD / Tests (push) Successful in 49s
Tessera CI/CD / Build & Publish Images (push) Successful in 1m45s
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>
2026-07-22 14:18:55 +02:00

146 lines
4.2 KiB
TypeScript

import { Injectable } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import {
CreateFieldMappingDto,
CreateLdapConfigDto,
UpdateLdapConfigDto,
} from './dto/ldap-config.dto';
/**
* Per-tenant LDAP configuration CRUD (D-18).
* Manages LDAP connection settings and field mappings.
*/
@Injectable()
export class LdapConfigService {
constructor(private prisma: PrismaService) {}
/**
* Get LDAP config for a tenant, including field mappings.
*/
async getConfig(tenantId: string) {
return this.prisma.ldapConfig.findUnique({
where: { tenantId },
include: { fieldMappings: true },
});
}
/**
* Create LDAP config for a tenant with default field mappings (D-16).
* Defaults: displayName -> displayName, mail -> email, sAMAccountName -> username
*/
async createConfig(tenantId: string, dto: CreateLdapConfigDto) {
return this.prisma.ldapConfig.create({
data: {
tenantId,
serverUrl: dto.serverUrl,
baseDn: dto.baseDn,
bindDn: dto.bindDn,
bindPassword: dto.bindPassword,
searchFilter: dto.searchFilter ?? '(objectClass=person)',
syncIntervalMin: dto.syncIntervalMin ?? 60,
isActive: dto.isActive ?? true,
tlsRejectUnauthorized: dto.tlsRejectUnauthorized ?? true,
groupFilterDns: dto.groupFilterDns ?? [],
userExcludeList: dto.userExcludeList ?? [],
fieldMappings: {
create: [
{
ldapField: 'displayName',
tesseraField: 'displayName',
isDefault: true,
},
{ ldapField: 'mail', tesseraField: 'email', isDefault: true },
{
ldapField: 'sAMAccountName',
tesseraField: 'username',
isDefault: true,
},
],
},
},
include: { fieldMappings: true },
});
}
/**
* Update LDAP config for a tenant.
*/
async updateConfig(tenantId: string, dto: UpdateLdapConfigDto) {
return this.prisma.ldapConfig.update({
where: { tenantId },
data: {
...(dto.serverUrl !== undefined && { serverUrl: dto.serverUrl }),
...(dto.baseDn !== undefined && { baseDn: dto.baseDn }),
...(dto.bindDn !== undefined && { bindDn: dto.bindDn }),
...(dto.bindPassword !== undefined && {
bindPassword: dto.bindPassword,
}),
...(dto.searchFilter !== undefined && {
searchFilter: dto.searchFilter,
}),
...(dto.syncIntervalMin !== undefined && {
syncIntervalMin: dto.syncIntervalMin,
}),
...(dto.isActive !== undefined && { isActive: dto.isActive }),
...(dto.tlsRejectUnauthorized !== undefined && {
tlsRejectUnauthorized: dto.tlsRejectUnauthorized,
}),
...(dto.groupFilterDns !== undefined && {
groupFilterDns: dto.groupFilterDns,
}),
...(dto.userExcludeList !== undefined && {
userExcludeList: dto.userExcludeList,
}),
},
include: { fieldMappings: true },
});
}
/**
* Add a custom field mapping to an LDAP config (D-17).
*/
async addFieldMapping(configId: string, dto: CreateFieldMappingDto) {
return this.prisma.ldapFieldMapping.create({
data: {
ldapConfigId: configId,
ldapField: dto.ldapField,
tesseraField: dto.tesseraField,
isDefault: dto.isDefault ?? false,
},
});
}
/**
* Remove a field mapping. Only non-default mappings can be deleted.
* System-provided defaults (isDefault=true) are protected.
*/
async removeFieldMapping(mappingId: string) {
const mapping = await this.prisma.ldapFieldMapping.findUnique({
where: { id: mappingId },
});
if (!mapping) {
return null;
}
if (mapping.isDefault) {
throw new Error('Cannot delete default field mappings');
}
return this.prisma.ldapFieldMapping.delete({
where: { id: mappingId },
});
}
/**
* Get all active LDAP configs. Used by the scheduler to determine which
* tenants need auto-sync.
*/
async getAllActiveConfigs() {
return this.prisma.ldapConfig.findMany({
where: { isActive: true },
include: { tenant: true, fieldMappings: true },
});
}
}