feat: Implement two-factor authentication (2FA) with dedicated endpoints for setup, enabling, disabling, verification, and backup code management.
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user