feat: Implement two-factor authentication (2FA) with dedicated endpoints for setup, enabling, disabling, verification, and backup code management.

This commit is contained in:
pradeepkumar
2026-02-06 09:30:12 +05:30
parent ea05e18c15
commit 750cb55764
10 changed files with 989 additions and 19 deletions

View File

@@ -18,6 +18,7 @@ import {
import type { Request } from 'express';
import { AuthService } from './auth.service';
import { TwoFactorService } from './two-factor/two-factor.service';
import {
RegisterDto,
LoginDto,
@@ -27,13 +28,23 @@ import {
RefreshTokenDto,
VerifyEmailDto,
} from './dto';
import {
VerifyTwoFactorDto,
VerifyTwoFactorLoginDto,
VerifyBackupCodeDto,
DisableTwoFactorDto,
RegenerateBackupCodesDto,
} from './two-factor/dto';
import { JwtAuthGuard } from './guards';
import { Public, CurrentUser } from './decorators';
@ApiTags('Auth')
@Controller('auth')
export class AuthController {
constructor(private readonly authService: AuthService) {}
constructor(
private readonly authService: AuthService,
private readonly twoFactorService: TwoFactorService,
) {}
// ==========================================
// REGISTER
@@ -356,4 +367,162 @@ export class AuthController {
async logoutAll(@CurrentUser('id') userId: string) {
return this.authService.logout(userId);
}
// ==========================================
// TWO-FACTOR AUTHENTICATION ENDPOINTS
// ==========================================
// Setup 2FA - Generate secret and QR code
@Post('2fa/setup')
@UseGuards(JwtAuthGuard)
@HttpCode(HttpStatus.OK)
@ApiBearerAuth('JWT-auth')
@ApiOperation({ summary: 'Setup two-factor authentication' })
@ApiResponse({
status: 200,
description: 'Returns secret and QR code for 2FA setup',
schema: {
example: {
secret: 'JBSWY3DPEHPK3PXP',
qrCode: 'data:image/png;base64,...',
manualEntry: 'JBSWY3DPEHPK3PXP',
},
},
})
async setupTwoFactor(@CurrentUser('id') userId: string) {
return this.twoFactorService.setupTwoFactor(userId);
}
// Enable 2FA - Verify token and enable
@Post('2fa/enable')
@UseGuards(JwtAuthGuard)
@HttpCode(HttpStatus.OK)
@ApiBearerAuth('JWT-auth')
@ApiOperation({ summary: 'Enable two-factor authentication' })
@ApiBody({ type: VerifyTwoFactorDto })
@ApiResponse({
status: 200,
description: '2FA enabled successfully with backup codes',
schema: {
example: {
backupCodes: ['ABC12345', 'DEF67890', '...'],
},
},
})
async enableTwoFactor(
@CurrentUser('id') userId: string,
@Body() dto: VerifyTwoFactorDto,
) {
return this.twoFactorService.enableTwoFactor(userId, dto.token);
}
// Disable 2FA
@Post('2fa/disable')
@UseGuards(JwtAuthGuard)
@HttpCode(HttpStatus.OK)
@ApiBearerAuth('JWT-auth')
@ApiOperation({ summary: 'Disable two-factor authentication' })
@ApiBody({ type: DisableTwoFactorDto })
@ApiResponse({
status: 200,
description: '2FA disabled successfully',
})
async disableTwoFactor(
@CurrentUser('id') userId: string,
@Body() dto: DisableTwoFactorDto,
) {
await this.twoFactorService.disableTwoFactor(userId, dto.password);
return { message: 'Two-factor authentication disabled successfully' };
}
// Verify 2FA token during login
@Public()
@Post('2fa/verify')
@HttpCode(HttpStatus.OK)
@ApiOperation({ summary: 'Verify 2FA token during login' })
@ApiBody({ type: VerifyTwoFactorLoginDto })
@ApiResponse({
status: 200,
description: 'Login successful with 2FA verification',
schema: {
example: {
user: {
id: 'uuid',
email: 'john@example.com',
role: 'USER',
},
accessToken: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...',
refreshToken: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...',
},
},
})
async verifyTwoFactorLogin(@Body() dto: VerifyTwoFactorLoginDto) {
return this.authService.verifyTwoFactorLogin(
dto.tempToken,
dto.token,
dto.userAgent,
dto.ipAddress,
);
}
// Verify backup code during login
@Public()
@Post('2fa/verify-backup')
@HttpCode(HttpStatus.OK)
@ApiOperation({ summary: 'Verify backup code during login' })
@ApiBody({ type: VerifyBackupCodeDto })
@ApiResponse({
status: 200,
description: 'Login successful with backup code verification',
})
async verifyBackupCode(@Body() dto: VerifyBackupCodeDto) {
return this.authService.verifyBackupCodeLogin(
dto.tempToken,
dto.backupCode,
dto.userAgent,
dto.ipAddress,
);
}
// Get 2FA status
@Get('2fa/status')
@UseGuards(JwtAuthGuard)
@ApiBearerAuth('JWT-auth')
@ApiOperation({ summary: 'Get 2FA status' })
@ApiResponse({
status: 200,
description: '2FA status',
schema: {
example: {
enabled: true,
verifiedAt: '2024-01-01T00:00:00.000Z',
},
},
})
async getTwoFactorStatus(@CurrentUser('id') userId: string) {
return this.twoFactorService.getStatus(userId);
}
// Regenerate backup codes
@Post('2fa/backup-codes')
@UseGuards(JwtAuthGuard)
@HttpCode(HttpStatus.OK)
@ApiBearerAuth('JWT-auth')
@ApiOperation({ summary: 'Regenerate backup codes' })
@ApiBody({ type: RegenerateBackupCodesDto })
@ApiResponse({
status: 200,
description: 'New backup codes generated',
schema: {
example: {
backupCodes: ['ABC12345', 'DEF67890', '...'],
},
},
})
async regenerateBackupCodes(
@CurrentUser('id') userId: string,
@Body() dto: RegenerateBackupCodesDto,
) {
return this.twoFactorService.regenerateBackupCodes(userId, dto.password);
}
}