131 lines
5.3 KiB
TypeScript
131 lines
5.3 KiB
TypeScript
import { IAuthRepository } from '@core/identity/domain/repositories/IAuthRepository';
|
|
import { IUserRepository, StoredUser } from '@core/identity/domain/repositories/IUserRepository';
|
|
import { IPasswordHashingService } from '@core/identity/domain/services/PasswordHashingService';
|
|
import { User } from '@core/identity/domain/entities/User';
|
|
|
|
import { EmailAddress } from '@core/identity/domain/value-objects/EmailAddress';
|
|
import { randomUUID } from 'crypto';
|
|
import { Logger } from '@core/shared/application';
|
|
|
|
export class InMemoryAuthRepository implements IAuthRepository {
|
|
constructor(
|
|
private readonly userRepository: IUserRepository,
|
|
private readonly passwordHashingService: IPasswordHashingService,
|
|
private readonly logger: Logger,
|
|
) {}
|
|
|
|
async findByEmail(email: EmailAddress): Promise<User | null> {
|
|
this.logger.debug(`[InMemoryAuthRepository] Finding user by email: ${email.value}`);
|
|
const storedUser = await this.userRepository.findByEmail(email.value);
|
|
if (!storedUser) {
|
|
return null;
|
|
}
|
|
return User.fromStored(storedUser);
|
|
}
|
|
|
|
async save(user: User): Promise<void> {
|
|
this.logger.debug(`[InMemoryAuthRepository] Saving user with email: ${user.getEmail()}`);
|
|
|
|
const userId = user.getId().value;
|
|
const userEmail = user.getEmail() ?? '';
|
|
const userDisplayName = user.getDisplayName();
|
|
const userPasswordHash = user.getPasswordHash()?.value ?? '';
|
|
|
|
const storedUser: StoredUser = {
|
|
id: userId,
|
|
email: userEmail,
|
|
passwordHash: userPasswordHash,
|
|
displayName: userDisplayName,
|
|
salt: '', // In-memory doesn't use actual salt, but Stub has to provide.
|
|
primaryDriverId: user.getPrimaryDriverId() ?? undefined,
|
|
createdAt: new Date(),
|
|
};
|
|
|
|
// Check if user exists to decide between create or update
|
|
const existingStoredUser = await this.userRepository.findById(userId);
|
|
if (existingStoredUser) {
|
|
// For update, ensure createdAt is preserved from existing user
|
|
const updatedStoredUser: StoredUser = {
|
|
...storedUser,
|
|
createdAt: existingStoredUser.createdAt,
|
|
};
|
|
await this.userRepository.update(updatedStoredUser);
|
|
} else {
|
|
await this.userRepository.create(storedUser);
|
|
}
|
|
}
|
|
|
|
async create(user: User, passwordPlain: string): Promise<User> {
|
|
this.logger.debug(`[InMemoryAuthRepository] Creating user with email: ${user.getEmail()}`);
|
|
const passwordHashValue = await this.passwordHashingService.hash(passwordPlain);
|
|
|
|
// Ensure email is not undefined before using
|
|
const userEmail = user.getEmail() ?? '';
|
|
const userDisplayName = user.getDisplayName();
|
|
const userId = user.getId().value;
|
|
|
|
const storedUser: StoredUser = {
|
|
id: userId,
|
|
email: userEmail,
|
|
passwordHash: passwordHashValue,
|
|
displayName: userDisplayName,
|
|
salt: '', // Salt is handled by PasswordHashingService, but StoredUser requires it.
|
|
primaryDriverId: user.getPrimaryDriverId() ?? undefined,
|
|
createdAt: new Date(),
|
|
};
|
|
const createdStoredUser = await this.userRepository.create(storedUser);
|
|
return User.fromStored(createdStoredUser);
|
|
}
|
|
|
|
async update(user: User): Promise<User> {
|
|
this.logger.debug(`[InMemoryAuthRepository] Updating user with email: ${user.getEmail()}`);
|
|
// Ensure values are not undefined before using
|
|
const userId = user.getId().value;
|
|
const userEmail = user.getEmail() ?? '';
|
|
const userPasswordHash = user.getPasswordHash()?.value ?? '';
|
|
const userDisplayName = user.getDisplayName();
|
|
|
|
const storedUser: StoredUser = {
|
|
id: userId,
|
|
email: userEmail,
|
|
passwordHash: userPasswordHash,
|
|
displayName: userDisplayName,
|
|
salt: '', // Salt is handled by PasswordHashingService, but StoredUser requires it.
|
|
primaryDriverId: user.getPrimaryDriverId() ?? undefined,
|
|
createdAt: new Date(), // Assuming creation date is maintained or updated within UserRepository
|
|
};
|
|
const updatedStoredUser = await this.userRepository.update(storedUser);
|
|
return User.fromStored(updatedStoredUser);
|
|
}
|
|
|
|
async delete(userId: string): Promise<boolean> {
|
|
// Deletion needs to be implemented in InMemoryUserRepository and exposed via IUserRepository
|
|
// For now, it's not supported.
|
|
this.logger.warn(`[InMemoryAuthRepository] Delete operation not implemented for user: ${userId}`);
|
|
return false;
|
|
}
|
|
|
|
async userExists(email: string): Promise<boolean> {
|
|
this.logger.debug(`[InMemoryAuthRepository] Checking if user exists for email: ${email}`);
|
|
return this.userRepository.emailExists(email);
|
|
}
|
|
|
|
async verifyPassword(email: string, passwordPlain: string): Promise<User | null> {
|
|
this.logger.debug(`[InMemoryAuthRepository] Verifying password for user with email: ${email}`);
|
|
const user = await this.findByEmail(EmailAddress.create(email)); // Use EmailAddress value object
|
|
if (!user) {
|
|
this.logger.warn(`[InMemoryAuthRepository] User not found for email: ${email}`);
|
|
return null;
|
|
}
|
|
const isPasswordValid = await this.passwordHashingService.verify(
|
|
passwordPlain,
|
|
user.getPasswordHash()?.value ?? '', // Access value and provide fallback
|
|
);
|
|
if (!isPasswordValid) {
|
|
this.logger.warn(`[InMemoryAuthRepository] Invalid password for user with email: ${email}`);
|
|
return null;
|
|
}
|
|
return user;
|
|
}
|
|
}
|