246 lines
6.4 KiB
TypeScript
246 lines
6.4 KiB
TypeScript
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<string> {
|
|
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;
|
|
}
|
|
}
|