import { Controller, Post, Get, Body, UseGuards, Req, HttpCode, HttpStatus, } from '@nestjs/common'; import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth, ApiBody, } from '@nestjs/swagger'; import type { Request } from 'express'; import { AuthService } from './auth.service'; import { TwoFactorService } from './two-factor/two-factor.service'; import { RegisterDto, LoginDto, SocialAuthDto, ForgotPasswordDto, ResetPasswordDto, 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, private readonly twoFactorService: TwoFactorService, ) {} // ========================================== // REGISTER // ========================================== @Public() @Post('register') @HttpCode(HttpStatus.CREATED) @ApiOperation({ summary: 'Register a new user' }) @ApiBody({ type: RegisterDto }) @ApiResponse({ status: 201, description: 'User registered successfully', schema: { example: { success: true, data: { user: { id: 'uuid', email: 'john@example.com', role: 'USER', firstName: 'John', lastName: 'Doe', }, accessToken: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...', refreshToken: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...', }, }, }, }) @ApiResponse({ status: 400, description: 'Validation error' }) @ApiResponse({ status: 409, description: 'Email already registered' }) async register(@Body() dto: RegisterDto) { return this.authService.register(dto); } // ========================================== // LOGIN // ========================================== @Public() @Post('login') @HttpCode(HttpStatus.OK) @ApiOperation({ summary: 'Login with email and password' }) @ApiBody({ type: LoginDto }) @ApiResponse({ status: 200, description: 'Login successful', schema: { example: { success: true, data: { user: { id: 'uuid', email: 'john@example.com', role: 'USER', firstName: 'John', lastName: 'Doe', avatar: null, }, accessToken: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...', refreshToken: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...', }, }, }, }) @ApiResponse({ status: 401, description: 'Invalid credentials' }) async login(@Body() dto: LoginDto, @Req() req: Request) { const userAgent = req.headers['user-agent']; const ipAddress = req.ip || req.headers['x-forwarded-for']?.toString() || 'unknown'; return this.authService.login(dto, userAgent, ipAddress); } // ========================================== // SOCIAL AUTH // ========================================== @Public() @Post('social') @HttpCode(HttpStatus.OK) @ApiOperation({ summary: 'Login or register with social provider' }) @ApiBody({ type: SocialAuthDto }) @ApiResponse({ status: 200, description: 'Social auth successful', schema: { example: { success: true, data: { user: { id: 'uuid', email: 'john@example.com', role: 'USER', firstName: 'John', lastName: 'Doe', avatar: 'https://example.com/avatar.jpg', }, accessToken: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...', refreshToken: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...', }, }, }, }) @ApiResponse({ status: 400, description: 'Invalid provider data' }) async socialAuth(@Body() dto: SocialAuthDto) { return this.authService.socialAuth(dto); } // ========================================== // FORGOT PASSWORD // ========================================== @Public() @Post('forgot-password') @HttpCode(HttpStatus.OK) @ApiOperation({ summary: 'Request password reset email' }) @ApiBody({ type: ForgotPasswordDto }) @ApiResponse({ status: 200, description: 'Password reset email sent (if email exists)', schema: { example: { success: true, data: { message: 'If the email exists, a password reset link will be sent', }, }, }, }) async forgotPassword(@Body() dto: ForgotPasswordDto) { return this.authService.forgotPassword(dto); } // ========================================== // RESET PASSWORD // ========================================== @Public() @Post('reset-password') @HttpCode(HttpStatus.OK) @ApiOperation({ summary: 'Reset password with token' }) @ApiBody({ type: ResetPasswordDto }) @ApiResponse({ status: 200, description: 'Password reset successful', schema: { example: { success: true, data: { message: 'Password reset successful. Please login with your new password.', }, }, }, }) @ApiResponse({ status: 400, description: 'Invalid or expired token' }) async resetPassword(@Body() dto: ResetPasswordDto) { return this.authService.resetPassword(dto); } // ========================================== // VERIFY EMAIL // ========================================== @Public() @Post('verify-email') @HttpCode(HttpStatus.OK) @ApiOperation({ summary: 'Verify email with token' }) @ApiBody({ type: VerifyEmailDto }) @ApiResponse({ status: 200, description: 'Email verified successfully', schema: { example: { success: true, data: { message: 'Email verified successfully', }, }, }, }) @ApiResponse({ status: 400, description: 'Invalid or expired token' }) async verifyEmail(@Body() dto: VerifyEmailDto) { return this.authService.verifyEmail(dto); } // ========================================== // RESEND VERIFICATION EMAIL // ========================================== @Post('resend-verification') @UseGuards(JwtAuthGuard) @HttpCode(HttpStatus.OK) @ApiBearerAuth('JWT-auth') @ApiOperation({ summary: 'Resend email verification' }) @ApiResponse({ status: 200, description: 'Verification email sent', schema: { example: { success: true, data: { message: 'Verification email sent', }, }, }, }) @ApiResponse({ status: 401, description: 'Unauthorized' }) async resendVerification(@CurrentUser('id') userId: string) { return this.authService.resendVerificationEmail(userId); } // ========================================== // REFRESH TOKEN // ========================================== @Public() @Post('refresh') @HttpCode(HttpStatus.OK) @ApiOperation({ summary: 'Refresh access token' }) @ApiBody({ type: RefreshTokenDto }) @ApiResponse({ status: 200, description: 'Tokens refreshed successfully', schema: { example: { success: true, data: { accessToken: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...', refreshToken: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...', }, }, }, }) @ApiResponse({ status: 401, description: 'Invalid refresh token' }) async refreshToken(@Body() dto: RefreshTokenDto) { return this.authService.refreshToken(dto); } // ========================================== // GET CURRENT USER // ========================================== @Get('me') @UseGuards(JwtAuthGuard) @ApiBearerAuth('JWT-auth') @ApiOperation({ summary: 'Get current user profile' }) @ApiResponse({ status: 200, description: 'Current user profile', schema: { example: { success: true, data: { id: 'uuid', email: 'john@example.com', role: 'USER', status: 'ACTIVE', emailVerified: true, authProvider: 'LOCAL', firstName: 'John', lastName: 'Doe', avatar: null, phone: null, city: null, state: null, country: null, createdAt: '2024-01-01T00:00:00.000Z', lastLoginAt: '2024-01-01T00:00:00.000Z', }, }, }, }) @ApiResponse({ status: 401, description: 'Unauthorized' }) async getCurrentUser(@CurrentUser('id') userId: string) { return this.authService.getCurrentUser(userId); } // ========================================== // LOGOUT // ========================================== @Post('logout') @UseGuards(JwtAuthGuard) @HttpCode(HttpStatus.OK) @ApiBearerAuth('JWT-auth') @ApiOperation({ summary: 'Logout current session' }) @ApiResponse({ status: 200, description: 'Logged out successfully', schema: { example: { success: true, data: { message: 'Logged out successfully', }, }, }, }) @ApiResponse({ status: 401, description: 'Unauthorized' }) async logout(@CurrentUser('id') userId: string, @Req() req: Request) { const authHeader = req.headers.authorization; const accessToken = authHeader?.replace('Bearer ', ''); return this.authService.logout(userId, accessToken); } // ========================================== // LOGOUT ALL SESSIONS // ========================================== @Post('logout-all') @UseGuards(JwtAuthGuard) @HttpCode(HttpStatus.OK) @ApiBearerAuth('JWT-auth') @ApiOperation({ summary: 'Logout all sessions' }) @ApiResponse({ status: 200, description: 'All sessions logged out', schema: { example: { success: true, data: { message: 'Logged out successfully', }, }, }, }) @ApiResponse({ status: 401, description: 'Unauthorized' }) 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); } }