fix(ldap): treat ldapts empty-array attributes as absent, not "undefined"
Tessera CI/CD / Lint & Type Check (push) Successful in 45s
Tessera CI/CD / Tests (push) Successful in 39s
Tessera CI/CD / Build & Publish Images (push) Successful in 25s

ldapts represents a missing/absent LDAP attribute as an empty array
([]), not undefined -- entry['mail'] is [] when an account has no mail
set. The field-mapping loop did Array.isArray(value) ? String(value[0])
: ..., and String(undefined) is the literal string "undefined". Every
synced entry without that attribute got mappedData['email'] = "undefined"
(a truthy string, so the `|| fallback` never kicked in), and the second
such entry onward crashed with a unique constraint violation on email
since they all shared the exact same literal string.

Found live: syncing against a real Zentyal/Samba AD directory failed
on every entry after the first (Kevin Schaller, krbtgt, Guest, the DC
computer object, etc.) with "Unique constraint failed on the fields:
(email)".

Fix: resolve array values to their first element (or use the raw
value for non-arrays) and only keep it when actually present and
non-empty, so a genuinely missing attribute falls through to the
`${username}@ldap.local` fallback instead of the string "undefined".

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-09 15:28:48 +02:00
parent baff7ce4db
commit 246dc89a98
+11 -6
View File
@@ -211,15 +211,20 @@ export class LdapService {
const dn = entry.dn; const dn = entry.dn;
syncedDns.push(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<string, string> = {}; const mappedData: Record<string, string> = {};
for (const mapping of config.fieldMappings) { for (const mapping of config.fieldMappings) {
const value = entry[mapping.ldapField]; const value = entry[mapping.ldapField];
if (value !== undefined && value !== null) { const resolved = Array.isArray(value) ? value[0] : value;
// LDAP attributes can be arrays; take first value if (resolved !== undefined && resolved !== null && resolved !== '') {
mappedData[mapping.tesseraField] = Array.isArray(value) mappedData[mapping.tesseraField] = String(resolved);
? String(value[0])
: String(value);
} }
} }