feat: Implement comprehensive user authentication with JWT, social login, and password management.
This commit is contained in:
308
src/auth/auth.controller.ts
Normal file
308
src/auth/auth.controller.ts
Normal file
@@ -0,0 +1,308 @@
|
||||
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,
|
||||
} 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);
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user