Files
gridpilot.gg/adapters/identity/persistence/inmemory/InMemoryAuthRepository.ts
2026-01-16 21:57:44 +01:00

129 lines
5.2 KiB
TypeScript

import { User } from '@core/identity/domain/entities/User';
import type { AuthRepository } from '@core/identity/domain/repositories/AuthRepository';
import type { UserRepository, StoredUser } from '@core/identity/domain/repositories/UserRepository';
import type { PasswordHashingService } from '@core/identity/domain/services/PasswordHashingService';
import { EmailAddress } from '@core/identity/domain/value-objects/EmailAddress';
import type { Logger } from '@core/shared/domain/Logger';
export class InMemoryAuthRepository implements AuthRepository {
constructor(
private readonly userRepository: UserRepository,
private readonly passwordHashingService: PasswordHashingService,
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;
}
}