mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-27 00:52:50 +00:00
refactor( iam ): remove local auth service delegate OTP and password reset to IAM
This commit is contained in:
@@ -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 };
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {}
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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<void> {
|
||||
try {
|
||||
await this.dataSource.query(`DELETE FROM iam.sessions WHERE email = $1`, [email]);
|
||||
|
||||
Reference in New Issue
Block a user