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 #### Devices - List of registered devices with platform, name, push token, and last seen time #### 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 6. **Device Management**: Get list of user's registered devices --- ### 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' }, devices: [ { id: 'device-uuid-1', platform: 'WEB', name: 'Chrome on Windows', pushToken: 'token-abc123', trusted: true, lastSeenAt: '2024-01-20T14:22:00.000Z' }, { id: 'device-uuid-2', platform: 'IOS', name: 'iPhone 14', pushToken: 'token-xyz789', trusted: false, lastSeenAt: '2024-01-19T10:15:00.000Z' } ] } } }) @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); } }