Files
backend/src/auth/auth.controller.ts

529 lines
14 KiB
TypeScript
Raw Normal View History

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);
}
}