Reviewed-on: #1
LDAP Portal
Self-Service-Portal fuer LLDAP mit NestJS API, Angular Frontend und eigenem OpenID-Connect-Provider fuer Web-SSO.
Die Anwendung ist als Identity-Portal vor einem bestehenden LLDAP gedacht: Nutzer koennen sich registrieren, E-Mail und Passwort verwalten, Administratoren koennen Registrierungen, Nutzer, Gruppen und OIDC-Clients pflegen.
Funktionen
- Registrierung mit E-Mail-Verifikation und anschliessender Admin-Freigabe
- Login gegen LDAP/LLDAP
- Passwortaenderung nach erfolgreichem LDAP-Login
- Passwort-Reset ueber eigene Tokens und SMTP
- Profilbearbeitung, E-Mail-Aenderung mit Verifikation und Account-Loeschanfrage
- Admin-Bereiche fuer Registrierungen, Nutzer, Gruppen und Audit
- Audit-Events fuer sicherheitsrelevante Aktionen in MySQL
- OpenID Connect Provider fuer Web-SSO
- Single-Container-Deployment mit Nginx, Angular und NestJS API
Architektur
Das Portal besteht aus drei Teilen:
- Angular Web-UI: Login, Registrierung, Konto- und Admin-Oberflaechen.
- NestJS API: REST-Endpunkte, OIDC Provider, Mailversand, Audit, LLDAP-Integration.
- Nginx im Container: liefert die Web-UI aus und routet API/OIDC-Pfade intern zur NestJS API.
Externe Dienste werden nicht mitgeliefert und muessen vorhanden sein:
- MySQL-Datenbank fuer Portal-Daten, Registrierungen, Tokens, Audit und OIDC-Speicher.
- LLDAP HTTP/GraphQL-Endpunkt fuer administrative Operationen.
- LLDAP LDAP-Endpunkt fuer Passwortpruefung und Passwortaenderung.
- SMTP-Server fuer Verifikations- und Passwort-Reset-Mails.
Lokale Entwicklung
cp .env.example .env
npm install
npm run start:api
npm run start:web
In der lokalen Entwicklung laeuft die API standardmaessig auf http://localhost:3000, das Frontend auf http://localhost:4200.
Wichtige lokale URLs:
- Web UI:
http://localhost:4200 - API:
http://localhost:3000 - OIDC Discovery bei direkter API-Nutzung:
http://localhost:3000/.well-known/openid-configuration
Deployment
Voraussetzungen
Vor dem Start muessen erreichbar sein:
- MySQL, z. B.
mysql://ldap_portal:secret@mysql.example.com:3306/ldap_portal - LLDAP Web/GraphQL, z. B.
https://lldap.example.com - LLDAP LDAP, z. B.
ldap://lldap.example.com:3890 - SMTP, z. B.
smtp.example.com:587
Erzeuge produktive Secrets fuer mindestens:
JWT_SECRETTOKEN_SECRETOIDC_COOKIE_SECRETLLDAP_ADMIN_PASSWORDSMTP_PASS
JWT_SECRET, TOKEN_SECRET und OIDC_COOKIE_SECRET muessen lang, zufaellig und stabil sein. Wenn sie nach dem Deployment geaendert werden, werden bestehende Sessions, Tokens oder verschluesselte Portal-Daten unbrauchbar.
Single Container
Das Root-Dockerfile baut API und Angular in ein einzelnes Image. Der Container startet:
- Nginx Web-UI und Reverse Proxy auf Port
8080 - NestJS API / IdP intern auf Port
3000
Build und Push:
docker build -t registry.example.com/ldap-portal/idp:latest .
docker push registry.example.com/ldap-portal/idp:latest
Start:
docker run -d --name ldap-portal-idp \
--env-file .env \
-p 8080:8080 \
registry.example.com/ldap-portal/idp:latest
Docker Compose:
cp .env.example .env
docker compose up --build
Reverse Proxy und externe URLs
Der Container erwartet eingehenden Traffic auf Port 8080. Ein externer Reverse Proxy kann davor TLS terminieren.
Bei produktivem Betrieb hinter https://idp.example.com sollten diese Werte zusammenpassen:
NODE_ENV=production
PUBLIC_WEB_URL=https://idp.example.com
OIDC_ISSUER=https://idp.example.com
OIDC_TRUST_PROXY=true
API_BASE_URL=/api
PUBLIC_WEB_URL wird fuer CORS, E-Mail-Links und den Registrierungslink auf der SSO-Loginseite verwendet.
OIDC_ISSUER muss exakt die externe Issuer-URL sein, die SSO-Clients verwenden. Wenn der Dienst hinter TLS laeuft, muss hier die https://-URL stehen.
API_BASE_URL sollte im Single-Container-Deployment bei /api bleiben. Das Frontend ruft dann /api/... auf; Nginx entfernt den Prefix intern und routet zur NestJS API. Die OIDC-Pfade /.well-known/, /oidc/ und /interaction/ werden ebenfalls zur API geroutet.
Konfiguration
Die wichtigsten Variablen aus .env.example:
| Variable | Bedeutung |
|---|---|
NODE_ENV |
In Produktion production; deaktiviert TypeORM synchronize und aktiviert sichere OIDC-Cookies. |
API_PORT |
Interner Port der NestJS API, im Container standardmaessig 3000. |
PUBLIC_WEB_URL |
Externe Web-URL fuer CORS, Mail-Links und Registrierung aus dem SSO-Login. |
API_BASE_URL |
API-Basis aus Sicht des Browsers, im Single Container /api. |
DATABASE_URL |
MySQL-Verbindungs-URL. Alternativ DB_HOST, DB_PORT, DB_USERNAME, DB_PASSWORD, DB_DATABASE. |
DATABASE_SSL |
true, wenn MySQL TLS verlangt. |
JWT_SECRET |
Signatur-Secret fuer Portal-JWTs. |
TOKEN_SECRET |
Verschluesselung/Hashing fuer Portal-Tokens und gespeicherte Secrets. |
LLDAP_URL |
LLDAP Web/GraphQL-Basis-URL ohne /api/graphql. |
LLDAP_LDAP_URL |
LDAP-Endpunkt fuer Bind und Password Modify. |
LLDAP_BASE_DN |
LDAP Base DN, z. B. dc=example,dc=com. |
LLDAP_ADMIN_USERNAME |
LLDAP Admin-User fuer GraphQL und LDAP-Admin-Bind. |
LLDAP_ADMIN_PASSWORD |
Passwort des LLDAP Admin-Users. |
LLDAP_GRAPHQL_TOKEN |
Optionaler statischer Bearer Token fuer GraphQL statt Login per Admin-User. |
LLDAP_DEFAULT_GROUP |
Optionale numerische LLDAP-Gruppen-ID, in die freigegebene neue Nutzer aufgenommen werden. |
SMTP_HOST, SMTP_PORT, SMTP_SECURE |
SMTP-Verbindung. |
SMTP_USER, SMTP_PASS |
Optionale SMTP-Authentifizierung. |
SMTP_FROM |
Absenderadresse fuer Portal-Mails. |
MAIL_PRODUCT_NAME, MAIL_COMPANY_NAME |
Zentrale Branding-Namen fuer Mail-Templates. |
MAIL_PRIMARY_COLOR |
Primaerfarbe fuer Mail-Buttons und Links. |
MAIL_SUPPORT_EMAIL, MAIL_LOGO_URL, MAIL_IMPRINT_URL, MAIL_PRIVACY_URL |
Optionale Branding- und Footer-Werte fuer Mails. |
OIDC_ISSUER |
Externe Issuer-URL des OIDC Providers. |
OIDC_COOKIE_SECRET |
Cookie-Secret fuer OIDC Sessions; Fallback ist TOKEN_SECRET. |
OIDC_ADMIN_GROUP |
Gruppe fuer OIDC-Clientverwaltung, Standard client_manager. |
OIDC_ADMIN_GROUP_UUID |
Zusaetzlich akzeptierte Gruppen-UUID fuer OIDC-Clientverwaltung. |
OIDC_TRUST_PROXY |
true, wenn ein Reverse Proxy TLS/Host-Header setzt. |
USER_MANAGER_GROUP |
LLDAP-Gruppe, deren Mitglieder per E-Mail benachrichtigt werden, wenn eine Registrierung nach E-Mail-Bestaetigung auf Freigabe wartet. Standard user_manager. |
Hinweis: REGISTRATION_MANAGER_GROUP, GROUP_MANAGER_GROUP und AUDIT_VIEWER_GROUP stehen aktuell in .env.example, werden im Code aber nicht ausgewertet. Die Admin-Gruppennamen sind derzeit fest verdrahtet, siehe "Admin-Rollen".
Datenbank
Die App nutzt TypeORM mit MySQL. In NODE_ENV=production ist synchronize deaktiviert. Fuer produktive Deployments muss das Schema vorab vorhanden sein oder es muessen Migrationen ergaenzt und ausgefuehrt werden.
In Entwicklung kann TypeORM das Schema automatisch synchronisieren, solange NODE_ENV nicht production ist.
Registrierung und Nutzerfluss
Die Registrierung laeuft in zwei Schritten:
- Nutzer registrieren sich mit E-Mail, Anzeigename und Passwort.
- Der Nutzer bestaetigt seine E-Mail ueber den Mail-Link.
- Ein Admin mit Gruppe
registration_managergibt die Registrierung frei. - Erst bei Freigabe wird der LLDAP-User erstellt und optional in
LLDAP_DEFAULT_GROUPaufgenommen.
Nach Schritt 2 sendet das Portal eine Benachrichtigung an die E-Mail-Adressen der Mitglieder aus USER_MANAGER_GROUP, damit offene Freigaben sichtbar werden. Wenn die Gruppe nicht gefunden wird, keine Mitglieder mit E-Mail-Adresse hat oder der Mailversand fehlschlaegt, bleibt die Registrierung trotzdem im Status pending_approval; der Fehler wird im Audit protokolliert.
Passwortregeln:
- mindestens 12 Zeichen
- mindestens ein Kleinbuchstabe
- mindestens ein Grossbuchstabe
- mindestens eine Ziffer
Der Benutzername in LLDAP ist die E-Mail-Adresse der Registrierung.
Admin-Rollen
Admin-Berechtigungen werden ueber LLDAP-Gruppen gesteuert:
client_manager: OIDC-Clients verwalten.registration_manager: Registrierungen freigeben oder ablehnen.user_manager: Nutzer anzeigen, bearbeiten, loeschen und Gruppenmitgliedschaften aendern.group_manager: Gruppen anzeigen, erstellen, bearbeiten und loeschen.audit_viewer: Audit-Events anzeigen.
Die allgemeinen Admin-Rollen werden aktuell per Gruppen-Displayname geprueft. Die OIDC-Clientverwaltung akzeptiert zusaetzlich die konfigurierte OIDC_ADMIN_GROUP_UUID.
Wichtig fuer die Erstinbetriebnahme: Der erste Admin muss in LLDAP bereits existieren und in die benoetigten Gruppen aufgenommen werden, bevor er die Admin-Bereiche im Portal nutzen kann.
OpenID Connect
Die API stellt einen OIDC Provider bereit. Die wichtigsten Endpunkte:
- Discovery:
/.well-known/openid-configuration - Authorization:
/oidc/auth - Token:
/oidc/token - UserInfo:
/oidc/me - JWKS:
/oidc/jwks - Logout:
/oidc/session/end - Revocation:
/oidc/token/revocation - Introspection:
/oidc/token/introspection - Interactions/Login/Consent:
/interaction/...
OIDC-Clients werden im Frontend unter /admin/oidc-clients verwaltet. Zugriff erhaelt ein eingeloggter Nutzer mit OIDC-Admin-Berechtigung. Client Secrets werden nur direkt nach Erstellung oder Secret-Rotation angezeigt.
V1 unterstuetzt Authorization Code Flow mit verpflichtendem PKCE. Dynamic Client Registration und SAML sind nicht aktiviert.
Client-Optionen
Redirect URIs: erlaubte Callback-URLs des SSO-Clients.Logout Redirect URIs: erlaubte Redirects nach RP-initiated Logout.Scopes: Scopes, die der Client verwenden darf, z. B.openid profile email groups.Public Client ohne Secret: erstellt einen Client ohne Secret und mittoken_endpoint_auth_method=none; gedacht fuer Browser/native Apps, die kein Secret sicher halten koennen.- Confidential Client: Standard, erzeugt ein Client Secret und verwendet
client_secret_basic. Gruppen-Claim ausgeben: steuert, ob dergroupsClaim in OIDC-Claims ausgegeben wird.First-Party Client: fuer eigene/interne Clients. Nach erfolgreichem Login wird die Consent-Seite uebersprungen und die angeforderten Berechtigungen werden automatisch bestaetigt.
Nach der Erstellung koennen Name, Redirect URIs, Logout Redirect URIs, Scopes, First-Party-Flag, Gruppen-Claim und Aktivstatus im Admin-UI bearbeitet werden. Die client_id bleibt stabil.
Confidential Clients koennen ihr Client Secret im Admin-UI rotieren. Das neue Secret wird nur einmal angezeigt und muss danach in der angebundenen Anwendung hinterlegt werden. Bestehende Grants, Sessions und Refresh Tokens werden durch die Rotation nicht aktiv widerrufen; neue Token-Requests muessen aber das neue Secret verwenden. Public Clients haben kein Secret und koennen daher nicht rotiert werden.
LLDAP-Hinweise
Die API nutzt:
- LDAP-Bind fuer Login und Passwortpruefung.
- LDAP Password Modify Extended Operation fuer Passwortaenderungen.
- GraphQL fuer User-, Gruppen- und Admin-Operationen.
Der LLDAP Admin-User muss ausreichende Rechte fuer diese Operationen haben.
Falls sich GraphQL-Mutationsnamen zwischen LLDAP-Versionen unterscheiden, muessen die Queries in apps/api/src/lldap/lldap.service.ts an die Zielversion angepasst werden.
Produktions-Checkliste
- Externe MySQL-Datenbank angelegt und erreichbar.
- Datenbankschema/Migrationen fuer Produktion geklaert.
- LLDAP Web/GraphQL und LDAP vom Container aus erreichbar.
- SMTP erreichbar und Absender korrekt gesetzt.
PUBLIC_WEB_URLundOIDC_ISSUERauf die externe HTTPS-URL gesetzt.OIDC_TRUST_PROXY=true, wenn TLS am Reverse Proxy terminiert.API_BASE_URL=/apifuer Single-Container-Deployment gesetzt.- Lange zufaellige Secrets gesetzt und gesichert.
- LLDAP-Gruppen fuer Admin-Rollen angelegt.
- Mindestens ein bestehender Admin-User ist Mitglied der benoetigten Gruppen.
- OIDC-Clients mit korrekten Redirect URIs angelegt.
- Reverse Proxy leitet
/.well-known/,/oidc/,/interaction/,/api/und die Web-UI an den Container weiter.