xpeditis2.0/apps/backend/src/application/auth/auth.service.ts
David-Henri ARNAUD cfef7005b3 fix test
2025-10-09 16:38:22 +02:00

209 lines
5.4 KiB
TypeScript

import { Injectable, UnauthorizedException, ConflictException, Logger } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { ConfigService } from '@nestjs/config';
import * as argon2 from 'argon2';
import { UserRepository } from '../../domain/ports/out/user.repository';
import { User, UserRole } from '../../domain/entities/user.entity';
import { v4 as uuidv4 } from 'uuid';
export interface JwtPayload {
sub: string; // user ID
email: string;
role: string;
organizationId: string;
type: 'access' | 'refresh';
}
@Injectable()
export class AuthService {
private readonly logger = new Logger(AuthService.name);
constructor(
private readonly userRepository: UserRepository,
private readonly jwtService: JwtService,
private readonly configService: ConfigService,
) {}
/**
* Register a new user
*/
async register(
email: string,
password: string,
firstName: string,
lastName: string,
organizationId: string,
): Promise<{ accessToken: string; refreshToken: string; user: any }> {
this.logger.log(`Registering new user: ${email}`);
// Check if user already exists
const existingUser = await this.userRepository.findByEmail(email);
if (existingUser) {
throw new ConflictException('User with this email already exists');
}
// Hash password with Argon2
const passwordHash = await argon2.hash(password, {
type: argon2.argon2id,
memoryCost: 65536, // 64 MB
timeCost: 3,
parallelism: 4,
});
// Create user entity
const user = User.create({
id: uuidv4(),
organizationId,
email,
passwordHash,
firstName,
lastName,
role: UserRole.USER, // Default role
});
// Save to database
const savedUser = await this.userRepository.save(user);
// Generate tokens
const tokens = await this.generateTokens(savedUser);
this.logger.log(`User registered successfully: ${email}`);
return {
...tokens,
user: {
id: savedUser.id,
email: savedUser.email,
firstName: savedUser.firstName,
lastName: savedUser.lastName,
role: savedUser.role,
organizationId: savedUser.organizationId,
},
};
}
/**
* Login user with email and password
*/
async login(
email: string,
password: string,
): Promise<{ accessToken: string; refreshToken: string; user: any }> {
this.logger.log(`Login attempt for: ${email}`);
// Find user by email
const user = await this.userRepository.findByEmail(email);
if (!user) {
throw new UnauthorizedException('Invalid credentials');
}
if (!user.isActive) {
throw new UnauthorizedException('User account is inactive');
}
// Verify password
const isPasswordValid = await argon2.verify(user.passwordHash, password);
if (!isPasswordValid) {
throw new UnauthorizedException('Invalid credentials');
}
// Generate tokens
const tokens = await this.generateTokens(user);
this.logger.log(`User logged in successfully: ${email}`);
return {
...tokens,
user: {
id: user.id,
email: user.email,
firstName: user.firstName,
lastName: user.lastName,
role: user.role,
organizationId: user.organizationId,
},
};
}
/**
* Refresh access token using refresh token
*/
async refreshAccessToken(refreshToken: string): Promise<{ accessToken: string; refreshToken: string }> {
try {
// Verify refresh token
const payload = await this.jwtService.verifyAsync<JwtPayload>(refreshToken, {
secret: this.configService.get('JWT_SECRET'),
});
if (payload.type !== 'refresh') {
throw new UnauthorizedException('Invalid token type');
}
// Get user
const user = await this.userRepository.findById(payload.sub);
if (!user || !user.isActive) {
throw new UnauthorizedException('User not found or inactive');
}
// Generate new tokens
const tokens = await this.generateTokens(user);
this.logger.log(`Access token refreshed for user: ${user.email}`);
return tokens;
} catch (error: any) {
this.logger.error(`Token refresh failed: ${error?.message || 'Unknown error'}`);
throw new UnauthorizedException('Invalid or expired refresh token');
}
}
/**
* Validate user from JWT payload
*/
async validateUser(payload: JwtPayload): Promise<User | null> {
const user = await this.userRepository.findById(payload.sub);
if (!user || !user.isActive) {
return null;
}
return user;
}
/**
* Generate access and refresh tokens
*/
private async generateTokens(user: User): Promise<{ accessToken: string; refreshToken: string }> {
const accessPayload: JwtPayload = {
sub: user.id,
email: user.email,
role: user.role,
organizationId: user.organizationId,
type: 'access',
};
const refreshPayload: JwtPayload = {
sub: user.id,
email: user.email,
role: user.role,
organizationId: user.organizationId,
type: 'refresh',
};
const [accessToken, refreshToken] = await Promise.all([
this.jwtService.signAsync(accessPayload, {
expiresIn: this.configService.get('JWT_ACCESS_EXPIRATION', '15m'),
}),
this.jwtService.signAsync(refreshPayload, {
expiresIn: this.configService.get('JWT_REFRESH_EXPIRATION', '7d'),
}),
]);
return { accessToken, refreshToken };
}
}