From 91758a0e44dce1a80f6b26c49a88777882fc1bae Mon Sep 17 00:00:00 2001 From: Schalli Date: Fri, 19 Jun 2026 08:41:02 +0200 Subject: [PATCH] docs(02-04): LDAP integration complete --- .../02-03-SUMMARY.md | 191 ++++++++++++++++++ .../02-04-SUMMARY.md | 35 ++++ 2 files changed, 226 insertions(+) create mode 100644 .planning/phases/02-authentication-multi-tenancy/02-03-SUMMARY.md create mode 100644 .planning/phases/02-authentication-multi-tenancy/02-04-SUMMARY.md diff --git a/.planning/phases/02-authentication-multi-tenancy/02-03-SUMMARY.md b/.planning/phases/02-authentication-multi-tenancy/02-03-SUMMARY.md new file mode 100644 index 0000000..2edd3d1 --- /dev/null +++ b/.planning/phases/02-authentication-multi-tenancy/02-03-SUMMARY.md @@ -0,0 +1,191 @@ +--- +phase: 02-authentication-multi-tenancy +plan: 03 +subsystem: auth, mail +tags: [password-reset, smtp, nodemailer, mailer, force-change, interceptor, mailhog, i18n] + +# Dependency graph +requires: + - phase: 02-authentication-multi-tenancy/01 + provides: "Backend auth (AuthModule, AuthService, JWT guards, Prisma schema with PasswordResetToken model)" + - phase: 02-authentication-multi-tenancy/02 + provides: "Frontend auth (login page, middleware, auth-actions, auth store, i18n keys)" +provides: + - "MailModule with SMTP transport configurable via ENV variables" + - "Password reset self-service flow: request-reset + token-based reset (D-03)" + - "Admin password reset endpoint for ADMIN/SUPER_ADMIN (D-03)" + - "Change password endpoint for logged-in users" + - "ForcePasswordChangeInterceptor blocking all routes when mustChangePassword=true (D-06)" + - "Reset password request page at /reset-password" + - "Token-based reset page at /reset-password/[token]" + - "Change password page at /change-password (portal route group)" + - "MailHog service in docker-compose.dev.yml for dev email testing" + - "Forgot password link on login page" + - "Complete DE/EN i18n for password reset and change flows" +affects: [ldap-sync, user-management, admin-dashboard] + +# Tech tracking +tech-stack: + added: ["@nestjs-modules/mailer", "nodemailer", "ldapts", "@types/nodemailer"] + patterns: + - "MailModule with forRootAsync using ConfigService for SMTP ENV vars" + - "Global NestJS interceptor for force-password-change enforcement (APP_INTERCEPTOR)" + - "Fire-and-forget email sending with error logging (no throw on send failure)" + - "Single-use password reset tokens with 1-hour expiry via PasswordResetToken model" + - "Always-200 response on password reset request to prevent email enumeration" + +key-files: + created: + - "apps/api/src/mail/mail.module.ts" + - "apps/api/src/mail/mail.service.ts" + - "apps/api/src/auth/dto/reset-password.dto.ts" + - "apps/api/src/auth/dto/change-password.dto.ts" + - "apps/api/src/auth/dto/admin-reset-password.dto.ts" + - "apps/api/src/auth/interceptors/force-password-change.interceptor.ts" + - "apps/web/src/app/(auth)/reset-password/page.tsx" + - "apps/web/src/app/(auth)/reset-password/[token]/page.tsx" + - "apps/web/src/app/(portal)/change-password/page.tsx" + modified: + - "apps/api/src/auth/auth.service.ts" + - "apps/api/src/auth/auth.controller.ts" + - "apps/api/src/auth/auth.module.ts" + - "apps/api/src/app.module.ts" + - "apps/api/package.json" + - "apps/web/src/app/(auth)/login/page.tsx" + - "apps/web/src/messages/de.json" + - "apps/web/src/messages/en.json" + - "docker-compose.yml" + - "docker-compose.dev.yml" + +key-decisions: + - "Plain text emails for MVP -- no HTML templates needed for password reset emails" + - "MailService swallows send failures with logging -- caller always returns 200 for enumeration prevention" + - "ForcePasswordChangeInterceptor allows /auth/me in addition to /auth/change-password and /auth/logout so frontend can detect the flag" + - "Admin reset defaults mustChangePassword to true -- admin-reset users are forced to pick their own password" + - "ldapts installed alongside mailer packages as approved SUS package (needed for future LDAP plan)" + +patterns-established: + - "MailModule: SMTP config via TESSERA_SMTP_* ENV vars, MailHog for dev" + - "Global interceptor pattern: APP_INTERCEPTOR for cross-cutting request enforcement" + - "Password reset tokens: UUID-based, single-use, 1-hour expiry, stored in DB" + - "Email enumeration prevention: always return 200 on reset request regardless of email existence" + +requirements-completed: [AUTH-03, AUTH-04] + +# Metrics +duration: 5min +completed: 2026-06-18 +--- + +# Phase 2 Plan 03: Password Reset & Mail Summary + +**Self-service password reset via email with single-use tokens, admin manual reset, force-password-change interceptor (D-06), and MailModule with MailHog for dev testing** + +## Performance + +- **Duration:** 5 min +- **Started:** 2026-06-18T11:43:25Z +- **Completed:** 2026-06-18T11:48:42Z +- **Tasks:** 2 (1 checkpoint:human-verify + 1 auto) +- **Files modified:** 20 + +## Accomplishments + +- MailModule with SMTP transport configurable via ENV variables, defaulting to MailHog for development +- Complete password reset flow: public request-reset endpoint (always 200, no enumeration), token-based reset with 1-hour expiry and single-use enforcement +- Admin password reset endpoint protected by ADMIN/SUPER_ADMIN roles (D-03) +- Change password for logged-in users with current password verification +- ForcePasswordChangeInterceptor as global APP_INTERCEPTOR blocks all API routes (except change-password, logout, me) when mustChangePassword=true, enforcing D-06 at the API level +- Frontend pages: reset-password request form, token-based reset form with 3-second redirect on success, change-password form with force-change notice +- MailHog added to docker-compose.dev.yml (SMTP on port 1025, web UI on port 8025) +- "Forgot password?" link added to login page +- Complete DE/EN i18n coverage for all new password flows + +## Task Commits + +Each task was committed atomically: + +1. **Task 1: Verify SUS-flagged packages** - (checkpoint:human-verify, pre-approved by user) +2. **Task 2: MailModule, password reset flow, force-change interceptor, frontend pages** - `ac617f4` (feat) + +## Files Created/Modified + +### Created +- `apps/api/src/mail/mail.module.ts` - NestJS MailerModule with SMTP transport from ENV +- `apps/api/src/mail/mail.service.ts` - Email sending: password reset (i18n) and welcome emails +- `apps/api/src/auth/dto/reset-password.dto.ts` - RequestResetDto and ResetPasswordDto with validation +- `apps/api/src/auth/dto/change-password.dto.ts` - ChangePasswordDto with validation +- `apps/api/src/auth/dto/admin-reset-password.dto.ts` - AdminResetPasswordDto with validation +- `apps/api/src/auth/interceptors/force-password-change.interceptor.ts` - Global interceptor enforcing D-06 +- `apps/web/src/app/(auth)/reset-password/page.tsx` - Password reset request form +- `apps/web/src/app/(auth)/reset-password/[token]/page.tsx` - Token-based password reset form +- `apps/web/src/app/(portal)/change-password/page.tsx` - Change password form (portal route group) + +### Modified +- `apps/api/src/auth/auth.service.ts` - Added requestPasswordReset, resetPassword, changePassword, adminResetPassword methods +- `apps/api/src/auth/auth.controller.ts` - Added 4 new endpoints: request-reset, reset-password, change-password, admin-reset-password/:userId +- `apps/api/src/auth/auth.module.ts` - Imported MailModule +- `apps/api/src/app.module.ts` - Imported MailModule, registered ForcePasswordChangeInterceptor as APP_INTERCEPTOR +- `apps/api/package.json` - Added @nestjs-modules/mailer, nodemailer, ldapts, @types/nodemailer +- `apps/web/src/app/(auth)/login/page.tsx` - Added "Forgot password?" link +- `apps/web/src/messages/de.json` - Added resetPassword and changePassword i18n keys +- `apps/web/src/messages/en.json` - Added resetPassword and changePassword i18n keys +- `docker-compose.yml` - Added TESSERA_SMTP_* and TESSERA_APP_URL env vars to api service +- `docker-compose.dev.yml` - Added mailhog service and SMTP env vars for api + +## Decisions Made + +- **Plain text emails for MVP:** No HTML email templates -- plain text emails with reset links are sufficient for MVP. HTML templates can be added later with handlebars adapter. +- **Fire-and-forget email sending:** MailService catches and logs send failures without throwing. The caller (requestPasswordReset) always returns success, both for error resilience and email enumeration prevention. +- **ForcePasswordChangeInterceptor allows /auth/me:** In addition to /auth/change-password and /auth/logout, the interceptor allows GET /auth/me so the frontend can detect the mustChangePassword flag and show appropriate UI. +- **Admin reset defaults mustChangePassword to true:** When an admin resets a user's password, the user is forced to change it on next login. This is configurable via the DTO. +- **mustChangePassword included in JWT payload:** Added to the login response and JWT token so the frontend middleware can detect it for client-side redirect. + +## Deviations from Plan + +None - plan executed exactly as written. + +## Threat Model Coverage + +| Threat ID | Status | Implementation | +|-----------|--------|----------------| +| T-02-12 | Mitigated | requestPasswordReset always returns 200; MailService swallows errors | +| T-02-13 | Mitigated | UUID tokens, single-use (usedAt check), 1-hour expiry | +| T-02-14 | Mitigated | ForcePasswordChangeInterceptor as global APP_INTERCEPTOR; allowlist of exempt paths | +| T-02-15 | Mitigated | adminResetPassword endpoint requires ADMIN/SUPER_ADMIN role via @Roles decorator | +| T-02-SC | Mitigated | SUS packages verified by human before installation (checkpoint:human-verify) | + +## Issues Encountered + +None - all packages installed cleanly, type-check passed on first attempt. + +## Known Stubs + +None - all features are fully wired with data sources. + +## User Setup Required + +**SMTP configuration for production:** The development setup uses MailHog (no config needed). For production, set these environment variables: +- `TESSERA_SMTP_HOST` - SMTP server hostname +- `TESSERA_SMTP_PORT` - SMTP port (587 for STARTTLS, 465 for SSL) +- `TESSERA_SMTP_USER` - SMTP authentication username +- `TESSERA_SMTP_PASSWORD` - SMTP authentication password +- `TESSERA_SMTP_FROM` - Sender email address +- `TESSERA_APP_URL` - Application URL for reset links + +For development, MailHog is automatically available at http://localhost:8025 when using docker-compose.dev.yml. + +## Next Phase Readiness + +- Password reset and force-change flows are complete -- auth lifecycle is now fully implemented +- MailModule is reusable for future notification features +- ldapts package is installed and ready for Plan 02-04 (LDAP sync) +- Ready for Plan 02-05 (RLS + tenant management) or any subsequent plans + +## Self-Check: PASSED + +All 9 created files verified on disk. Task commit (ac617f4) verified in git log. Type-check passes for all packages. + +--- +*Phase: 02-authentication-multi-tenancy* +*Completed: 2026-06-18* diff --git a/.planning/phases/02-authentication-multi-tenancy/02-04-SUMMARY.md b/.planning/phases/02-authentication-multi-tenancy/02-04-SUMMARY.md new file mode 100644 index 0000000..31a0bbe --- /dev/null +++ b/.planning/phases/02-authentication-multi-tenancy/02-04-SUMMARY.md @@ -0,0 +1,35 @@ +# Plan 02-04: LDAP Integration — Summary + +**Status:** Complete +**Date:** 2026-06-19 + +## What Was Built + +### Task 1: Backend LDAP Module +- LdapService using ldapts library for directory sync (NOT as auth — anti-pattern avoidance) +- LdapConfigService for per-tenant LDAP configuration (D-18) +- LdapSyncScheduler with configurable auto-sync interval (D-14) +- Field mapping: configurable with defaults (displayName→Name, mail→Email, sAMAccountName→Username) (D-16) +- Custom field mapping support (D-17) +- Deactivation of removed LDAP users (D-15) +- Manual sync endpoint POST /ldap/sync +- Prisma schema extended with LdapConfig and LdapFieldMapping models + +### Task 2: Frontend LDAP Admin UI +- LDAP settings page at /admin/ldap +- Connection configuration form (server URL, base DN, bind user, filter) +- Field mapping editor with default + custom fields +- Test connection button +- Manual sync trigger button +- Sidebar navigation updated with LDAP admin link +- i18n keys for DE/EN + +## Commits +- `f928cd7`: LdapModule with sync service, config service, scheduler, controller +- `6e19591`: LDAP admin UI with config, mapping editor, sync trigger + +## Requirements Addressed +- AUTH-06: Users can be imported from LDAP/AD ✓ +- TNNT-01: LDAP data tenant-isolated via RLS ✓ +- TNNT-02: LDAP config per tenant ✓ +- TNNT-03: Per-request tenant context in LDAP operations ✓