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:
@@ -1,17 +1,25 @@
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Get,
|
||||
HttpCode,
|
||||
Param,
|
||||
Post,
|
||||
Req,
|
||||
Res,
|
||||
UseGuards,
|
||||
} from '@nestjs/common';
|
||||
import { AuthGuard } from '@nestjs/passport';
|
||||
import { Role } from '@prisma/client';
|
||||
import { Request, Response } from 'express';
|
||||
import { AuthService } from './auth.service';
|
||||
import { CurrentUser } from './decorators/current-user.decorator';
|
||||
import { Public } from './decorators/public.decorator';
|
||||
import { Roles } from './decorators/roles.decorator';
|
||||
import { AdminResetPasswordDto } from './dto/admin-reset-password.dto';
|
||||
import { ChangePasswordDto } from './dto/change-password.dto';
|
||||
import { RequestResetDto, ResetPasswordDto } from './dto/reset-password.dto';
|
||||
import { RolesGuard } from './guards/roles.guard';
|
||||
|
||||
@Controller('auth')
|
||||
export class AuthController {
|
||||
@@ -51,4 +59,71 @@ export class AuthController {
|
||||
me(@CurrentUser() user: any) {
|
||||
return user;
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /auth/request-reset
|
||||
* Request a password reset email (D-03 self-service).
|
||||
* @Public() -- no authentication required.
|
||||
* T-02-12: Always returns 200 regardless of email existence.
|
||||
*/
|
||||
@Public()
|
||||
@Post('request-reset')
|
||||
@HttpCode(200)
|
||||
async requestReset(@Body() dto: RequestResetDto) {
|
||||
await this.authService.requestPasswordReset(dto.email);
|
||||
return { message: 'If an account with this email exists, a reset link has been sent.' };
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /auth/reset-password
|
||||
* Reset password using a valid token (D-03 self-service).
|
||||
* @Public() -- no authentication required (uses token for verification).
|
||||
*/
|
||||
@Public()
|
||||
@Post('reset-password')
|
||||
@HttpCode(200)
|
||||
async resetPassword(@Body() dto: ResetPasswordDto) {
|
||||
await this.authService.resetPassword(dto.token, dto.newPassword);
|
||||
return { message: 'Password has been reset successfully.' };
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /auth/change-password
|
||||
* Change password for the currently logged-in user.
|
||||
* Requires authentication (not @Public).
|
||||
*/
|
||||
@Post('change-password')
|
||||
@HttpCode(200)
|
||||
async changePassword(
|
||||
@CurrentUser() user: any,
|
||||
@Body() dto: ChangePasswordDto,
|
||||
) {
|
||||
await this.authService.changePassword(
|
||||
user.sub,
|
||||
dto.currentPassword,
|
||||
dto.newPassword,
|
||||
);
|
||||
return { message: 'Password changed successfully.' };
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /auth/admin-reset-password/:userId
|
||||
* Admin resets a user's password (D-03 admin reset).
|
||||
* T-02-15: Only ADMIN/SUPER_ADMIN via RolesGuard.
|
||||
*/
|
||||
@Post('admin-reset-password/:userId')
|
||||
@Roles(Role.ADMIN, Role.SUPER_ADMIN)
|
||||
@UseGuards(RolesGuard)
|
||||
@HttpCode(200)
|
||||
async adminResetPassword(
|
||||
@Param('userId') userId: string,
|
||||
@Body() dto: AdminResetPasswordDto,
|
||||
) {
|
||||
await this.authService.adminResetPassword(
|
||||
userId,
|
||||
dto.newPassword,
|
||||
dto.mustChangePassword ?? true,
|
||||
);
|
||||
return { message: 'User password has been reset.' };
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user