feat(ldap): support anonymous bind (no bind DN/password required)
Tessera CI/CD / Lint & Type Check (push) Successful in 41s
Tessera CI/CD / Tests (push) Successful in 39s
Tessera CI/CD / Build & Publish Images (push) Successful in 1m40s

bindDn and bindPassword are now optional on LdapConfig (nullable
migration) and throughout the DTOs/service/client -- an admin can
leave both blank to connect to directories that permit anonymous
read access. LdapService.bind() falls back to an RFC 4513 anonymous
bind (empty DN + empty password) whenever either field is missing,
shared across testConnection, listGroups, and syncUsersForTenant.

Frontend: removed the required attribute from Bind-DN/Bind-Passwort,
added a placeholder hint ("leer = anonymous bind"), and the
"Verbindung testen" button now only needs a Server-URL to enable
(not bindDn+bindPassword). Config responses now return bindPassword
as null (not a misleading "********") when no password is set.

Verified locally: submitted only a Server-URL with both bind fields
empty and confirmed the request reached the anonymous-bind code path
(DNS failure for the unreachable test host, not a validation error).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-08 12:57:26 +02:00
parent 39aa4bff2a
commit 010aceb1ac
7 changed files with 194 additions and 31 deletions
+8 -7
View File
@@ -52,7 +52,7 @@ export class LdapController {
// Never return bindPassword in API responses (T-02-17)
return {
...config,
bindPassword: '********',
bindPassword: config.bindPassword ? '********' : null,
};
}
@@ -80,7 +80,7 @@ export class LdapController {
return {
...config,
bindPassword: '********',
bindPassword: config.bindPassword ? '********' : null,
};
}
@@ -104,7 +104,7 @@ export class LdapController {
return {
...config,
bindPassword: '********',
bindPassword: config.bindPassword ? '********' : null,
};
}
@@ -114,8 +114,9 @@ export class LdapController {
* Accepts optional ad-hoc serverUrl/bindDn/bindPassword so an admin can
* validate connection details before ever saving a config. Any field left
* out (e.g. bindPassword, which the form never re-sends once masked)
* falls back to the tenant's saved config. If no config is saved yet and
* the body doesn't supply all three fields, there is nothing to test.
* falls back to the tenant's saved config. bindDn/bindPassword are fully
* optional -- omitting both attempts an anonymous bind. Only serverUrl is
* required (directly, or from a saved config).
*/
@Post('test-connection')
@Roles(Role.ADMIN, Role.SUPER_ADMIN)
@@ -131,9 +132,9 @@ export class LdapController {
const bindDn = dto.bindDn || config?.bindDn;
const bindPassword = dto.bindPassword || config?.bindPassword;
if (!serverUrl || !bindDn || !bindPassword) {
if (!serverUrl) {
throw new BadRequestException(
'serverUrl, bindDn, and bindPassword are required (either provided directly or from a saved config)',
'serverUrl is required (either provided directly or from a saved config)',
);
}