360 lines
9.6 KiB
TypeScript
360 lines
9.6 KiB
TypeScript
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 {
|
|
RegisterDto,
|
|
LoginDto,
|
|
SocialAuthDto,
|
|
ForgotPasswordDto,
|
|
ResetPasswordDto,
|
|
RefreshTokenDto,
|
|
VerifyEmailDto,
|
|
} from './dto';
|
|
import { JwtAuthGuard } from './guards';
|
|
import { Public, CurrentUser } from './decorators';
|
|
|
|
@ApiTags('Auth')
|
|
@Controller('auth')
|
|
export class AuthController {
|
|
constructor(private readonly authService: AuthService) {}
|
|
|
|
// ==========================================
|
|
// 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);
|
|
}
|
|
}
|