diff --git a/apps/api/src/ldap/ldap.service.ts b/apps/api/src/ldap/ldap.service.ts index f9ee53f..e0e6ea4 100644 --- a/apps/api/src/ldap/ldap.service.ts +++ b/apps/api/src/ldap/ldap.service.ts @@ -211,15 +211,20 @@ export class LdapService { const dn = entry.dn; syncedDns.push(dn); - // Map LDAP fields to Tessera fields + // Map LDAP fields to Tessera fields. + // ldapts represents a missing/absent attribute as an empty array + // ([]), not undefined -- naively doing String(value[0]) on that + // produces the literal string "undefined", identical across every + // entry lacking the attribute (e.g. no `mail` set), which then + // collides on unique constraints like email. Resolve to the first + // array element (or the raw value) and skip when it's actually + // missing/empty. const mappedData: Record = {}; for (const mapping of config.fieldMappings) { const value = entry[mapping.ldapField]; - if (value !== undefined && value !== null) { - // LDAP attributes can be arrays; take first value - mappedData[mapping.tesseraField] = Array.isArray(value) - ? String(value[0]) - : String(value); + const resolved = Array.isArray(value) ? value[0] : value; + if (resolved !== undefined && resolved !== null && resolved !== '') { + mappedData[mapping.tesseraField] = String(resolved); } }