import { Injectable, UnauthorizedException, BadRequestException, } from '@nestjs/common'; import { JwtService } from '@nestjs/jwt'; import { PrismaService } from '../../shared/services/prisma.service'; import { OperatorsService } from '../operators/operators.service'; import * as bcrypt from 'bcrypt'; import { AuthInitDto, AuthValidateDto, LoginDto } from './dto/auth.dto'; @Injectable() export class AuthService { constructor( private readonly prisma: PrismaService, private readonly jwtService: JwtService, private readonly operatorsService: OperatorsService, ) {} async initializeUserAuth(partnerId: string, dto: AuthInitDto) { // Vérifier le partenaire const partner = await this.prisma.partner.findUnique({ where: { id: partnerId }, }); if (!partner || partner.status !== 'ACTIVE') { throw new UnauthorizedException('Invalid partner'); } // Déterminer l'opérateur basé sur le numéro const operator = this.detectOperator(dto.msisdn, dto.country); // Obtenir l'adaptateur approprié const adapter = this.operatorsService.getAdapter(operator, dto.country); // Initialiser l'authentification avec l'opérateur const authResponse = await adapter.initializeAuth({ msisdn: dto.msisdn, country: dto.country, metadata: dto.metadata, }); // Créer une session temporaire const session = await this.prisma.authSession.create({ data: { sessionId: authResponse.sessionId, partnerId: partnerId, msisdn: dto.msisdn, operator: operator, country: dto.country, authMethod: dto.authMethod, challengeId: authResponse.challengeId, status: 'PENDING', expiresAt: authResponse.expiresAt, }, }); return { sessionId: session.sessionId, authMethod: dto.authMethod, status: 'PENDING', redirectUrl: authResponse.redirectUrl, challengeId: authResponse.challengeId, expiresAt: authResponse.expiresAt, }; } async validateUserAuth(dto: AuthValidateDto) { // Récupérer la session const session = await this.prisma.authSession.findUnique({ where: { sessionId: dto.sessionId }, }); if (!session) { throw new BadRequestException('Invalid session'); } if (session.status !== 'PENDING') { throw new BadRequestException('Session already processed'); } if (new Date() > session.expiresAt) { throw new BadRequestException('Session expired'); } // Obtenir l'adaptateur const adapter = this.operatorsService.getAdapter( session.operator, session.country, ); // Valider avec l'opérateur const validationResponse = await adapter.validateAuth({ challengeId: session.challengeId, otpCode: dto.otpCode, msisdn: session.msisdn, country: session.country, }); if (!validationResponse.success) { await this.prisma.authSession.update({ where: { id: session.id }, data: { status: 'FAILED' }, }); throw new UnauthorizedException('Authentication failed'); } // Créer ou mettre à jour l'utilisateur const user = await this.prisma.user.upsert({ where: { msisdn: session.msisdn }, update: { userToken: validationResponse.userToken, userAlias: validationResponse.userAlias, updatedAt: new Date(), }, create: { msisdn: session.msisdn, userToken: validationResponse.userToken, userAlias: validationResponse.userAlias, operatorId: await this.getOperatorId(session.operator, session.country), partnerId: session.partnerId, country: session.country, }, }); // Mettre à jour la session await this.prisma.authSession.update({ where: { id: session.id }, data: { status: 'SUCCESS', userId: user.id, }, }); // Créer un JWT pour le partenaire const payload = { userId: user.id, partnerId: session.partnerId, msisdn: user.msisdn, operator: session.operator, }; return { success: true, accessToken: this.jwtService.sign(payload), userToken: validationResponse.userToken, userAlias: validationResponse.userAlias, msisdn: session.msisdn, operator: session.operator, country: session.country, expiresAt: validationResponse.expiresAt, }; } async loginPartner(dto: LoginDto) { const partner = await this.prisma.partner.findUnique({ where: { email: dto.email }, }); if (!partner) { throw new UnauthorizedException('Invalid credentials'); } const isPasswordValid = await bcrypt.compare( dto.password, partner.passwordHash, ); if (!isPasswordValid) { throw new UnauthorizedException('Invalid credentials'); } const payload = { partnerId: partner.id, email: partner.email, type: 'partner', }; return { accessToken: this.jwtService.sign(payload), partner: { id: partner.id, name: partner.name, email: partner.email, status: partner.status, }, }; } private detectOperator(msisdn: string, country: string): string { // Logique pour détecter l'opérateur basé sur le préfixe const prefixMap = { CI: { '07': 'ORANGE', '08': 'ORANGE', '09': 'ORANGE', '04': 'MTN', '05': 'MTN', '06': 'MTN', '01': 'MOOV', }, SN: { '77': 'ORANGE', '78': 'ORANGE', '76': 'FREE', '70': 'EXPRESSO', }, // Ajouter d'autres pays }; const countryPrefixes = prefixMap[country]; if (!countryPrefixes) { throw new BadRequestException(`Country ${country} not supported`); } const prefix = msisdn.substring(0, 2); const operator = countryPrefixes[prefix]; if (!operator) { throw new BadRequestException(`Cannot detect operator for ${msisdn}`); } return operator; } private async getOperatorId( operatorCode: string, country: string, ): Promise { const operator = await this.prisma.operator.findFirst({ where: { code: operatorCode as any, country: country, }, }); if (!operator) { throw new BadRequestException( `Operator ${operatorCode} not found in ${country}`, ); } return operator.id; } }