af9e968c6f
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>
146 lines
4.2 KiB
TypeScript
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 },
|
|
});
|
|
}
|
|
}
|