From 254d2a171c3a9703654e71eda56e37917511ae1c Mon Sep 17 00:00:00 2001 From: Abubeker Yasin Date: Sat, 6 Jun 2026 10:23:53 +0300 Subject: [PATCH] refactor( iam ): remove local auth service delegate OTP and password reset to IAM --- .../src/common/jwt.strategy.ts | 17 ---- .../src/modules/auth/auth.controller.ts | 40 +-------- .../src/modules/auth/auth.module.ts | 3 +- .../src/modules/auth/auth.service.ts | 86 ------------------- .../modules/auth/passenger-auth.service.ts | 35 ++++++++ 5 files changed, 40 insertions(+), 141 deletions(-) delete mode 100644 apps/edr-passenger-api/src/common/jwt.strategy.ts delete mode 100644 apps/edr-passenger-api/src/modules/auth/auth.service.ts diff --git a/apps/edr-passenger-api/src/common/jwt.strategy.ts b/apps/edr-passenger-api/src/common/jwt.strategy.ts deleted file mode 100644 index 7b62bec40..000000000 --- a/apps/edr-passenger-api/src/common/jwt.strategy.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { PassportStrategy } from '@nestjs/passport'; -import { ExtractJwt, Strategy } from 'passport-jwt'; -import { ConfigService } from '@nestjs/config'; - -@Injectable() -export class JwtStrategy extends PassportStrategy(Strategy) { - constructor(config: ConfigService) { - super({ - jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), - secretOrKey: config.get('JWT_SECRET'), - }); - } - async validate(payload: any) { - return { userId: payload.sub, email: payload.email, role: payload.role, passengerId: payload.passengerId }; - } -} diff --git a/apps/edr-passenger-api/src/modules/auth/auth.controller.ts b/apps/edr-passenger-api/src/modules/auth/auth.controller.ts index 12b44c97a..2482316cd 100644 --- a/apps/edr-passenger-api/src/modules/auth/auth.controller.ts +++ b/apps/edr-passenger-api/src/modules/auth/auth.controller.ts @@ -1,17 +1,13 @@ 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 { PassengerAuthService } from './passenger-auth.service'; -import { RegisterDto, LoginDto, RequestOtpDto, VerifyOtpDto, RequestPasswordResetDto, ResetPasswordDto } from './auth.dto'; +import { RegisterDto, LoginDto } from './auth.dto'; import { JwtGuard } from '../../common/jwt.guard'; @ApiTags('Auth') @Controller('auth') export class AuthController { - constructor( - private service: AuthService, - private passengerAuthService: PassengerAuthService, - ) {} + constructor(private passengerAuthService: PassengerAuthService) {} @Post('register') @ApiOperation({ summary: 'Register new passenger account' }) @@ -32,34 +28,6 @@ export class AuthController { return this.passengerAuthService.login(dto, req); } - @Post('otp/request') - @HttpCode(HttpStatus.OK) - @ApiOperation({ summary: 'Request OTP verification code' }) - @ApiResponse({ status: 200, description: 'OTP sent successfully' }) - @ApiBody({ type: RequestOtpDto }) - requestOtp(@Body() dto: RequestOtpDto) { return this.service.requestOtp(dto); } - - @Post('otp/verify') - @HttpCode(HttpStatus.OK) - @ApiOperation({ summary: 'Verify OTP code' }) - @ApiResponse({ status: 200, description: 'OTP verified successfully' }) - @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' }) - @ApiResponse({ status: 200, description: 'Password reset email sent' }) - @ApiBody({ type: RequestPasswordResetDto }) - requestPasswordReset(@Body() dto: RequestPasswordResetDto) { return this.service.requestPasswordReset(dto); } - - @Post('password/reset') - @HttpCode(HttpStatus.OK) - @ApiOperation({ summary: 'Reset password with token' }) - @ApiResponse({ status: 200, description: 'Password reset successfully' }) - @ApiBody({ type: ResetPasswordDto }) - resetPassword(@Body() dto: ResetPasswordDto) { return this.service.resetPassword(dto); } - @Post('logout') @HttpCode(HttpStatus.OK) @UseGuards(JwtGuard) @@ -89,8 +57,8 @@ export class AuthController { @ApiResponse({ status: 200, description: 'User profile retrieved successfully' }) @ApiResponse({ status: 401, description: 'Unauthorized' }) getProfile(@Request() req: any) { - const userId = req.user?.id ?? req.user?.userId; + const userId = req.user?.id; if (!userId) throw new UnauthorizedException('User not authenticated'); - return this.service.getProfile(userId); + return this.passengerAuthService.getProfile(userId); } } diff --git a/apps/edr-passenger-api/src/modules/auth/auth.module.ts b/apps/edr-passenger-api/src/modules/auth/auth.module.ts index 78dbb1022..3beb0276d 100644 --- a/apps/edr-passenger-api/src/modules/auth/auth.module.ts +++ b/apps/edr-passenger-api/src/modules/auth/auth.module.ts @@ -1,10 +1,9 @@ import { Module } from '@nestjs/common'; import { AuthController } from './auth.controller'; -import { AuthService } from './auth.service'; import { PassengerAuthService } from './passenger-auth.service'; @Module({ controllers: [AuthController], - providers: [AuthService, PassengerAuthService], + providers: [PassengerAuthService], }) export class AuthModule {} diff --git a/apps/edr-passenger-api/src/modules/auth/auth.service.ts b/apps/edr-passenger-api/src/modules/auth/auth.service.ts deleted file mode 100644 index 6234c48d5..000000000 --- a/apps/edr-passenger-api/src/modules/auth/auth.service.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { Injectable, UnauthorizedException, BadRequestException } from '@nestjs/common'; -import { PrismaService } from '../../common/prisma.service'; -import { RequestOtpDto, VerifyOtpDto, RequestPasswordResetDto, ResetPasswordDto } from './auth.dto'; -import * as crypto from 'crypto'; - -@Injectable() -export class AuthService { - constructor(private prisma: PrismaService) {} - - async requestOtp(dto: RequestOtpDto) { - const code = Math.floor(100000 + Math.random() * 900000).toString(); - const expiresAt = new Date(Date.now() + 10 * 60 * 1000); - await this.prisma.otpCode.create({ - data: { email: dto.email, code, purpose: dto.purpose, expiresAt }, - }); - console.log(`[OTP] ${dto.email} - ${code} (${dto.purpose})`); - return { sent: true, expiresIn: 600 }; - } - - async verifyOtp(dto: VerifyOtpDto) { - const otp = await this.prisma.otpCode.findFirst({ - where: { email: dto.email, code: dto.code, purpose: dto.purpose, verified: false, expiresAt: { gt: new Date() } }, - orderBy: { createdAt: 'desc' }, - }); - if (!otp) throw new BadRequestException('Invalid or expired OTP'); - await this.prisma.otpCode.update({ where: { id: otp.id }, data: { verified: true } }); - return { verified: true }; - } - - async requestPasswordReset(dto: RequestPasswordResetDto) { - const user = await this.prisma.user.findUnique({ where: { email: dto.email } }); - if (!user) return { sent: true }; - const token = crypto.randomBytes(32).toString('hex'); - const expiresAt = new Date(Date.now() + 60 * 60 * 1000); - await this.prisma.passwordResetToken.create({ - data: { userId: user.id, token, expiresAt }, - }); - console.log(`[PASSWORD_RESET] ${dto.email} - ${token}`); - return { sent: true }; - } - - async resetPassword(dto: ResetPasswordDto) { - const resetToken = await this.prisma.passwordResetToken.findUnique({ where: { token: dto.token } }); - if (!resetToken || resetToken.used || resetToken.expiresAt < new Date()) { - throw new BadRequestException('Invalid or expired reset token'); - } - await this.prisma.passwordResetToken.update({ where: { id: resetToken.id }, data: { used: true } }); - return { reset: true }; - } - - async getProfile(userId: string) { - if (!userId) throw new UnauthorizedException('User ID not found in token'); - const user = await this.prisma.user.findFirst({ - where: { OR: [{ id: userId }, { passenger: { iamUserId: userId } }] }, - include: { - passenger: { include: { loyalty: true, wallet: true } }, - preferences: true, - }, - }); - if (!user) throw new UnauthorizedException('User not found'); - return { - id: user.id, - iamUserId: user.passenger?.iamUserId, - email: user.email, - phone: user.phone, - fullName: user.fullName, - role: user.role, - nationality: user.nationality, - nationalId: user.nationalId, - passportNumber: user.passportNumber, - faydaVerified: user.faydaVerified, - createdAt: user.createdAt, - passenger: user.passenger ? { - id: user.passenger.id, - preferredLanguage: user.passenger.preferredLanguage, - loyalty: user.passenger.loyalty - ? { tier: user.passenger.loyalty.tier, pointsBalance: user.passenger.loyalty.pointsBalance, lifetimePoints: user.passenger.loyalty.lifetimePoints } - : null, - wallet: user.passenger.wallet - ? { balanceMinor: user.passenger.wallet.balanceMinor, currency: user.passenger.wallet.currency } - : null, - } : null, - preferences: user.preferences, - }; - } -} diff --git a/apps/edr-passenger-api/src/modules/auth/passenger-auth.service.ts b/apps/edr-passenger-api/src/modules/auth/passenger-auth.service.ts index 482628db9..b634c607c 100644 --- a/apps/edr-passenger-api/src/modules/auth/passenger-auth.service.ts +++ b/apps/edr-passenger-api/src/modules/auth/passenger-auth.service.ts @@ -212,6 +212,41 @@ export class PassengerAuthService { return { success: true, message: 'Logged out successfully' }; } + async getProfile(iamUserId: string) { + const passenger = await this.prisma.passenger.findUnique({ + where: { iamUserId }, + include: { + user: true, + loyalty: true, + wallet: true, + }, + }); + if (!passenger) { + throw new Error('Passenger not found'); + } + return { + iamUserId, + email: passenger.user?.email, + phone: passenger.user?.phone, + fullName: passenger.user?.fullName, + nationality: passenger.user?.nationality, + nationalId: passenger.user?.nationalId, + passportNumber: passenger.user?.passportNumber, + faydaVerified: passenger.user?.faydaVerified, + createdAt: passenger.createdAt, + passenger: { + id: passenger.id, + preferredLanguage: passenger.preferredLanguage, + loyalty: passenger.loyalty + ? { tier: passenger.loyalty.tier, pointsBalance: passenger.loyalty.pointsBalance, lifetimePoints: passenger.loyalty.lifetimePoints } + : null, + wallet: passenger.wallet + ? { balanceMinor: passenger.wallet.balanceMinor, currency: passenger.wallet.currency } + : null, + }, + }; + } + private async compensateIamSignup(email: string): Promise { try { await this.dataSource.query(`DELETE FROM iam.sessions WHERE email = $1`, [email]);