feat(02-03): password reset flow, force-change interceptor, MailModule

- MailModule with SMTP transport configured from ENV variables
- MailService for password reset and welcome emails (plain text, i18n)
- Password reset flow: request-reset (public), reset-password (token-based)
- Change password for logged-in users with current password verification
- Admin reset password endpoint (ADMIN/SUPER_ADMIN only, D-03)
- ForcePasswordChangeInterceptor blocks all routes except change-password,
  logout, me when mustChangePassword=true (D-06, Pitfall 5)
- Frontend: reset-password request page, token reset page, change-password page
- Forgot password link added to login page
- MailHog service added to docker-compose.dev.yml for dev email testing
- SMTP env vars added to docker-compose.yml (defaults to MailHog)
- Complete DE/EN i18n coverage for reset and change password flows
- SUS packages installed: @nestjs-modules/mailer, nodemailer, ldapts

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-18 13:48:23 +02:00
parent eaaa9adfa5
commit ac617f4fe5
20 changed files with 4216 additions and 12 deletions
+32
View File
@@ -0,0 +1,32 @@
import { Module } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { MailerModule } from '@nestjs-modules/mailer';
import { MailService } from './mail.service';
@Module({
imports: [
MailerModule.forRootAsync({
useFactory: (configService: ConfigService) => ({
transport: {
host: configService.get<string>('TESSERA_SMTP_HOST', 'localhost'),
port: configService.get<number>('TESSERA_SMTP_PORT', 1025),
secure: configService.get<string>('TESSERA_SMTP_SECURE', 'false') === 'true',
auth: {
user: configService.get<string>('TESSERA_SMTP_USER', ''),
pass: configService.get<string>('TESSERA_SMTP_PASSWORD', ''),
},
},
defaults: {
from: configService.get<string>(
'TESSERA_SMTP_FROM',
'Tessera <tessera@tessera.local>',
),
},
}),
inject: [ConfigService],
}),
],
providers: [MailService],
exports: [MailService],
})
export class MailModule {}