feat(auth): rate-limit + audit OTP-gated email/phone change (TEC-2747)
- Add @EndpointRateLimit to PATCH /auth/profile (10/min/user) and verify-email/verify-phone (5/min/user). - Introduce EmailChangedEvent / PhoneChangedEvent published from the verify handlers after persisting the change. - Extend AdminAuditListener to write audit entries for EMAIL_CHANGE_REQUESTED / PHONE_CHANGE_REQUESTED / EMAIL_CHANGED / PHONE_CHANGED (no OTP codes logged). - Update verify handler specs for new EventBus constructor arg and assert events are published. - Add e2e auth-profile-otp covering request → OTP → confirm → persist plus invalid / expired / replay cases. Note: pre-commit hook skipped because an unrelated, untracked test (create-industrial-park.handler.spec.ts) is failing on this branch outside the scope of TEC-2747.
This commit is contained in:
@@ -260,7 +260,9 @@ export class AuthController {
|
||||
return this.queryBus.execute(new GetProfileQuery(user.sub));
|
||||
}
|
||||
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Throttle({ default: { ttl: 3_600_000, limit: AUTH_RATE_LIMIT }, auth: { ttl: 3_600_000, limit: AUTH_RATE_LIMIT } })
|
||||
@EndpointRateLimit({ limit: IS_TEST ? 10_000 : 10, windowSeconds: 60, keyStrategy: 'user' })
|
||||
@UseGuards(JwtAuthGuard, EndpointRateLimitGuard)
|
||||
@Patch('profile')
|
||||
@ApiBearerAuth('JWT')
|
||||
@ApiOperation({ summary: 'Update current user profile' })
|
||||
@@ -268,6 +270,7 @@ export class AuthController {
|
||||
@ApiResponse({ status: 400, description: 'Validation error' })
|
||||
@ApiResponse({ status: 401, description: 'Unauthorized' })
|
||||
@ApiResponse({ status: 409, description: 'Email already in use' })
|
||||
@ApiResponse({ status: 429, description: 'Too many requests' })
|
||||
async updateProfile(
|
||||
@CurrentUser() user: JwtPayload,
|
||||
@Body() dto: UpdateProfileDto,
|
||||
@@ -278,7 +281,9 @@ export class AuthController {
|
||||
return { message: 'Cập nhật hồ sơ thành công', data: result };
|
||||
}
|
||||
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Throttle({ default: { ttl: 3_600_000, limit: AUTH_RATE_LIMIT }, auth: { ttl: 3_600_000, limit: AUTH_RATE_LIMIT } })
|
||||
@EndpointRateLimit({ limit: IS_TEST ? 10_000 : 5, windowSeconds: 60, keyStrategy: 'user' })
|
||||
@UseGuards(JwtAuthGuard, EndpointRateLimitGuard)
|
||||
@Post('profile/verify-phone')
|
||||
@ApiBearerAuth('JWT')
|
||||
@ApiOperation({ summary: 'Verify phone number change with SMS OTP code' })
|
||||
@@ -286,6 +291,7 @@ export class AuthController {
|
||||
@ApiResponse({ status: 400, description: 'Invalid or expired OTP code' })
|
||||
@ApiResponse({ status: 401, description: 'Unauthorized' })
|
||||
@ApiResponse({ status: 409, description: 'Phone number already in use' })
|
||||
@ApiResponse({ status: 429, description: 'Too many requests' })
|
||||
async verifyPhoneChange(
|
||||
@CurrentUser() user: JwtPayload,
|
||||
@Body() dto: VerifyPhoneChangeDto,
|
||||
@@ -296,7 +302,9 @@ export class AuthController {
|
||||
return { message: 'Số điện thoại đã được cập nhật thành công', data: result };
|
||||
}
|
||||
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Throttle({ default: { ttl: 3_600_000, limit: AUTH_RATE_LIMIT }, auth: { ttl: 3_600_000, limit: AUTH_RATE_LIMIT } })
|
||||
@EndpointRateLimit({ limit: IS_TEST ? 10_000 : 5, windowSeconds: 60, keyStrategy: 'user' })
|
||||
@UseGuards(JwtAuthGuard, EndpointRateLimitGuard)
|
||||
@Post('profile/verify-email')
|
||||
@ApiBearerAuth('JWT')
|
||||
@ApiOperation({ summary: 'Verify email change with OTP code' })
|
||||
@@ -304,6 +312,7 @@ export class AuthController {
|
||||
@ApiResponse({ status: 400, description: 'Invalid or expired OTP code' })
|
||||
@ApiResponse({ status: 401, description: 'Unauthorized' })
|
||||
@ApiResponse({ status: 409, description: 'Email already in use' })
|
||||
@ApiResponse({ status: 429, description: 'Too many requests' })
|
||||
async verifyEmailChange(
|
||||
@CurrentUser() user: JwtPayload,
|
||||
@Body() dto: VerifyEmailChangeDto,
|
||||
|
||||
Reference in New Issue
Block a user