# 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 ```bash 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_SECRET` - `TOKEN_SECRET` - `OIDC_COOKIE_SECRET` - `LLDAP_ADMIN_PASSWORD` - `SMTP_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: ```bash docker build -t registry.example.com/ldap-portal/idp:latest . docker push registry.example.com/ldap-portal/idp:latest ``` Start: ```bash docker run -d --name ldap-portal-idp \ --env-file .env \ -p 8080:8080 \ registry.example.com/ldap-portal/idp:latest ``` Docker Compose: ```bash 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: ```env 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` | Optionale Branding-Namen fuer HTML-Mail-Templates. | | `MAIL_PRIMARY_COLOR` | Primaerfarbe fuer Mail-Buttons und Links, Standard `#2563eb`. | | `MAIL_SUPPORT_EMAIL` | Support-Adresse im Mail-Footer, Fallback aus `SMTP_FROM`. | | `MAIL_LOGO_URL`, `MAIL_IMPRINT_URL`, `MAIL_PRIVACY_URL` | Optionale oeffentlich erreichbare HTTPS-Links fuer Mail-Logo, Impressum und Datenschutz. | | `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: 1. Nutzer registrieren sich mit E-Mail, Anzeigename und Passwort. 2. Der Nutzer bestaetigt seine E-Mail ueber den Mail-Link. 3. Ein Admin mit Gruppe `registration_manager` gibt die Registrierung frei. 4. Erst bei Freigabe wird der LLDAP-User erstellt und optional in `LLDAP_DEFAULT_GROUP` aufgenommen. 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 mit `token_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 der `groups` Claim 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_URL` und `OIDC_ISSUER` auf die externe HTTPS-URL gesetzt. - `OIDC_TRUST_PROXY=true`, wenn TLS am Reverse Proxy terminiert. - `API_BASE_URL=/api` fuer 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.