module creation

This commit is contained in:
2025-12-15 21:44:06 +01:00
parent b834f88bbd
commit 7c7267da72
88 changed files with 12119 additions and 4241 deletions

View File

@@ -0,0 +1,129 @@
import { IAuthRepository } from '@gridpilot/core/identity/domain/repositories/IAuthRepository';
import { IUserRepository, StoredUser } from '@gridpilot/core/identity/domain/repositories/IUserRepository';
import { IPasswordHashingService } from '@gridpilot/core/identity/domain/services/PasswordHashingService';
import { User } from '@gridpilot/core/identity/domain/entities/User';
import { ILogger } from '@gridpilot/shared/logging/ILogger';
import { EmailAddress } from '@gridpilot/core/identity/domain/value-objects/EmailAddress';
import { randomUUID } from 'crypto';
export class InMemoryAuthRepository implements IAuthRepository {
constructor(
private readonly userRepository: IUserRepository,
private readonly passwordHashingService: IPasswordHashingService,
private readonly logger: ILogger,
) {}
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;
}
}

View File

@@ -0,0 +1,22 @@
/**
* In-Memory Password Hashing Service
*
* Provides basic password hashing and comparison for demo/development purposes.
* NOT FOR PRODUCTION USE - uses a simple string reversal as "hashing".
*/
import type { IPasswordHashingService } from '@gridpilot/core/identity/domain/services/PasswordHashingService';
export class InMemoryPasswordHashingService implements IPasswordHashingService {
async hash(plain: string): Promise<string> {
// In a real application, use a robust hashing library like bcrypt or Argon2.
// For demo, we'll just reverse the password and add a salt-like prefix.
const salt = 'demo_salt_';
return Promise.resolve(salt + plain.split('').reverse().join(''));
}
async verify(plain: string, hash: string): Promise<boolean> {
const hashedPlain = await this.hash(plain);
return Promise.resolve(hashedPlain === hash);
}
}

View File

@@ -1,59 +1,47 @@
import { cookies } from 'next/headers';
import { randomUUID } from 'crypto';
import type { AuthenticatedUserDTO } from '../../application/dto/AuthenticatedUserDTO';
import type { AuthSessionDTO } from '../../application/dto/AuthSessionDTO';
import type { IdentitySessionPort } from '../../application/ports/IdentitySessionPort';
/**
* Adapter: CookieIdentitySessionAdapter
*
* Manages user session using cookies. This is a placeholder implementation.
*/
const SESSION_COOKIE = 'gp_demo_session';
function parseCookieValue(raw: string | undefined): AuthSessionDTO | null {
if (!raw) return null;
try {
const parsed = JSON.parse(raw) as AuthSessionDTO;
if (!parsed.expiresAt || Date.now() > parsed.expiresAt) {
return null;
}
return parsed;
} catch {
return null;
}
}
function serializeSession(session: AuthSessionDTO): string {
return JSON.stringify(session);
}
import type { ILogger } from '@gridpilot/shared/logging/ILogger';
import type { AuthenticatedUserDTO } from '@gridpilot/core/identity/application/dto/AuthenticatedUserDTO';
import type { AuthSessionDTO } from '@gridpilot/core/identity/application/dto/AuthSessionDTO';
import type { IdentitySessionPort } from '@gridpilot/core/identity/application/ports/IdentitySessionPort';
export class CookieIdentitySessionAdapter implements IdentitySessionPort {
private currentSession: AuthSessionDTO | null = null;
constructor(private readonly logger: ILogger) {
this.logger.info('CookieIdentitySessionAdapter initialized.');
// In a real application, you would load the session from a cookie here
// For demo, we'll start with no session.
}
async getCurrentSession(): Promise<AuthSessionDTO | null> {
const store = await cookies();
const raw = store.get(SESSION_COOKIE)?.value;
return parseCookieValue(raw);
this.logger.debug('[CookieIdentitySessionAdapter] Getting current session.');
return Promise.resolve(this.currentSession);
}
async createSession(user: AuthenticatedUserDTO): Promise<AuthSessionDTO> {
const now = Date.now();
const expiresAt = now + 24 * 60 * 60 * 1000;
const session: AuthSessionDTO = {
user,
issuedAt: now,
expiresAt,
token: randomUUID(),
this.logger.debug(`[CookieIdentitySessionAdapter] Creating session for user: ${user.id}`);
const newSession: AuthSessionDTO = {
user: user,
issuedAt: Date.now(),
expiresAt: Date.now() + 3600 * 1000, // 1 hour expiration
token: `mock-token-${user.id}-${Date.now()}`,
};
const store = await cookies();
store.set(SESSION_COOKIE, serializeSession(session), {
httpOnly: true,
sameSite: 'lax',
path: '/',
secure: process.env.NODE_ENV === 'production',
});
return session;
this.currentSession = newSession;
// In a real app, you'd set a secure, HTTP-only cookie here.
this.logger.info(`[CookieIdentitySessionAdapter] Session created for user ${user.id}.`);
return Promise.resolve(newSession);
}
async clearSession(): Promise<void> {
const store = await cookies();
store.delete(SESSION_COOKIE);
this.logger.debug('[CookieIdentitySessionAdapter] Clearing session.');
this.currentSession = null;
// In a real app, you'd clear the session cookie here.
this.logger.info('[CookieIdentitySessionAdapter] Session cleared.');
return Promise.resolve();
}
}
}