mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-27 00:52:50 +00:00
221 lines
7.8 KiB
TypeScript
221 lines
7.8 KiB
TypeScript
import { Body, Controller, Post, HttpCode, HttpStatus, UseGuards, Get, Request, UnauthorizedException } from '@nestjs/common';
|
|
import { ApiTags, ApiOperation, ApiResponse, ApiBody, ApiBearerAuth } from '@nestjs/swagger';
|
|
import { AuthService } from './auth.service';
|
|
import { RegisterDto, LoginDto, RequestOtpDto, VerifyOtpDto, RequestPasswordResetDto, ResetPasswordDto } from './auth.dto';
|
|
import { JwtGuard } from '../../common/jwt.guard';
|
|
|
|
@ApiTags('Auth')
|
|
@Controller('auth')
|
|
export class AuthController {
|
|
constructor(private service: AuthService) {}
|
|
|
|
@Post('register')
|
|
@ApiOperation({
|
|
summary: 'Register new passenger account',
|
|
description: 'Create a new passenger account with email, phone, and password. Returns user details and JWT token for immediate login.'
|
|
})
|
|
@ApiResponse({ status: 201, description: 'Account created successfully. Returns user object and JWT token.' })
|
|
@ApiResponse({ status: 400, description: 'Validation error (invalid email, weak password, etc.)' })
|
|
@ApiResponse({ status: 409, description: 'Email or phone already registered' })
|
|
@ApiBody({ type: RegisterDto })
|
|
register(@Body() dto: RegisterDto) { return this.service.register(dto); }
|
|
|
|
@Post('login')
|
|
@HttpCode(HttpStatus.OK)
|
|
@ApiOperation({
|
|
summary: 'Login with email and password',
|
|
description: 'Authenticate user and receive JWT token. Token expires in 7 days by default. Failed login attempts are tracked and account may be locked after 5 consecutive failures.'
|
|
})
|
|
@ApiResponse({ status: 200, description: 'Login successful. Returns JWT token and user details.' })
|
|
@ApiResponse({ status: 401, description: 'Invalid credentials or account locked' })
|
|
@ApiResponse({ status: 403, description: 'Account temporarily blocked due to fraud detection' })
|
|
@ApiBody({ type: LoginDto })
|
|
login(@Body() dto: LoginDto) { return this.service.login(dto); }
|
|
|
|
@Post('otp/request')
|
|
@HttpCode(HttpStatus.OK)
|
|
@ApiOperation({
|
|
summary: 'Request OTP verification code',
|
|
description: 'Send a 6-digit OTP code to user email. Code expires in 10 minutes. Used for registration verification, password reset, or two-factor authentication.'
|
|
})
|
|
@ApiResponse({ status: 200, description: 'OTP sent successfully to email' })
|
|
@ApiResponse({ status: 404, description: 'Email not found (for PASSWORD_RESET purpose)' })
|
|
@ApiResponse({ status: 429, description: 'Too many OTP requests. Please wait before requesting again.' })
|
|
@ApiBody({ type: RequestOtpDto })
|
|
requestOtp(@Body() dto: RequestOtpDto) { return this.service.requestOtp(dto); }
|
|
|
|
@Post('otp/verify')
|
|
@HttpCode(HttpStatus.OK)
|
|
@ApiOperation({
|
|
summary: 'Verify OTP code',
|
|
description: 'Validate the 6-digit OTP code sent to user email. Code must match and not be expired.'
|
|
})
|
|
@ApiResponse({ status: 200, description: 'OTP verified successfully' })
|
|
@ApiResponse({ status: 400, description: 'Invalid or expired OTP code' })
|
|
@ApiResponse({ status: 404, description: 'No OTP found for this email and purpose' })
|
|
@ApiBody({ type: VerifyOtpDto })
|
|
verifyOtp(@Body() dto: VerifyOtpDto) { return this.service.verifyOtp(dto); }
|
|
|
|
@Post('password/reset-request')
|
|
@HttpCode(HttpStatus.OK)
|
|
@ApiOperation({
|
|
summary: 'Request password reset link',
|
|
description: 'Send password reset link to user email. Link contains a secure token valid for 1 hour.'
|
|
})
|
|
@ApiResponse({ status: 200, description: 'Password reset email sent successfully' })
|
|
@ApiResponse({ status: 404, description: 'Email not found' })
|
|
@ApiResponse({ status: 429, description: 'Too many reset requests. Please wait before trying again.' })
|
|
@ApiBody({ type: RequestPasswordResetDto })
|
|
requestPasswordReset(@Body() dto: RequestPasswordResetDto) { return this.service.requestPasswordReset(dto); }
|
|
|
|
@Post('password/reset')
|
|
@HttpCode(HttpStatus.OK)
|
|
@ApiOperation({
|
|
summary: 'Reset password with token',
|
|
description: 'Reset user password using the token received via email. Token is single-use and expires after 1 hour.'
|
|
})
|
|
@ApiResponse({ status: 200, description: 'Password reset successfully' })
|
|
@ApiResponse({ status: 400, description: 'Invalid, expired, or already used token' })
|
|
@ApiResponse({ status: 404, description: 'User not found' })
|
|
@ApiBody({ type: ResetPasswordDto })
|
|
resetPassword(@Body() dto: ResetPasswordDto) { return this.service.resetPassword(dto); }
|
|
|
|
@Post('logout')
|
|
@HttpCode(HttpStatus.OK)
|
|
@UseGuards(JwtGuard)
|
|
@ApiBearerAuth('JWT-auth')
|
|
@ApiOperation({
|
|
summary: 'Logout current user',
|
|
description: `Logout the authenticated user and invalidate their session.
|
|
|
|
### What happens:
|
|
- Invalidates the current session token
|
|
- Records logout in audit log
|
|
- Frontend should clear stored token and redirect to home
|
|
|
|
### Authentication:
|
|
- **Required**: JWT Bearer Token
|
|
- Token will be invalidated after successful logout`
|
|
})
|
|
@ApiResponse({
|
|
status: 200,
|
|
description: 'Logout successful',
|
|
schema: {
|
|
example: {
|
|
success: true,
|
|
message: 'Logged out successfully'
|
|
}
|
|
}
|
|
})
|
|
@ApiResponse({ status: 401, description: 'Unauthorized - Invalid or missing token' })
|
|
logout(@Request() req: any) {
|
|
if (!req.user || !req.user.userId) {
|
|
throw new UnauthorizedException('User not authenticated');
|
|
}
|
|
return this.service.logout(req.user.userId);
|
|
}
|
|
|
|
@Get('profile')
|
|
@UseGuards(JwtGuard)
|
|
@ApiBearerAuth('JWT-auth')
|
|
@ApiOperation({
|
|
summary: 'Get current user profile',
|
|
description: `**Returns complete user profile with all connected data**
|
|
|
|
---
|
|
|
|
### Response Includes
|
|
|
|
#### User Information
|
|
- Basic details (id, email, phone, fullName, role)
|
|
- Nationality and document information
|
|
- Fayda verification status
|
|
- Account timestamps (created, last login)
|
|
|
|
#### Passenger Data (if role=PASSENGER)
|
|
- Passenger ID and preferences
|
|
- **Loyalty Account**: Tier, points balance, lifetime points
|
|
- **Wallet Account**: Balance (minor units), currency
|
|
|
|
#### User Preferences
|
|
- Language, notification settings, etc.
|
|
|
|
---
|
|
|
|
### Use Cases
|
|
|
|
1. **App Initialization**: Fetch on app load to get user context
|
|
|
|
2. **Profile Pre-fill**: Use data to auto-fill booking forms
|
|
|
|
3. **Verification Check**: Check \`faydaVerified\` before registration
|
|
|
|
4. **Loyalty Display**: Show tier and points in UI
|
|
|
|
5. **Wallet Balance**: Display available balance
|
|
|
|
---
|
|
|
|
### Authentication
|
|
- **Required**: JWT Bearer Token
|
|
- Token must be valid and not expired
|
|
- Returns profile for authenticated user only`,
|
|
})
|
|
@ApiResponse({
|
|
status: 200,
|
|
description: 'User profile retrieved successfully',
|
|
schema: {
|
|
example: {
|
|
id: 'user-uuid-123',
|
|
email: 'kelemu@email.com',
|
|
phone: '+251911234567',
|
|
fullName: 'Kelemu Abebe',
|
|
role: 'PASSENGER',
|
|
nationality: 'Ethiopian',
|
|
nationalityCode: 'ET',
|
|
nationalId: null,
|
|
passportNumber: null,
|
|
faydaVerified: true,
|
|
faydaVerifiedAt: '2024-01-15T10:30:00.000Z',
|
|
lastLoginAt: '2024-01-20T14:22:00.000Z',
|
|
createdAt: '2023-12-01T08:00:00.000Z',
|
|
passenger: {
|
|
id: 'passenger-uuid-456',
|
|
preferredLanguage: 'am',
|
|
loyalty: {
|
|
tier: 'SILVER',
|
|
pointsBalance: 1500,
|
|
lifetimePoints: 3000
|
|
},
|
|
wallet: {
|
|
balanceMinor: 50000,
|
|
currency: 'ETB'
|
|
}
|
|
},
|
|
preferences: {
|
|
emailNotifications: true,
|
|
smsNotifications: true,
|
|
language: 'am'
|
|
}
|
|
}
|
|
}
|
|
})
|
|
@ApiResponse({
|
|
status: 401,
|
|
description: 'Unauthorized - Invalid or missing JWT token',
|
|
schema: {
|
|
example: {
|
|
statusCode: 401,
|
|
message: 'Unauthorized'
|
|
}
|
|
}
|
|
})
|
|
getProfile(@Request() req: any) {
|
|
console.log('Profile request - User from JWT:', req.user);
|
|
if (!req.user || !req.user.userId) {
|
|
throw new UnauthorizedException('User not authenticated');
|
|
}
|
|
return this.service.getProfile(req.user.userId);
|
|
}
|
|
}
|