rename to core

This commit is contained in:
2025-12-15 13:46:07 +01:00
parent aedf58643d
commit 5c22f8820c
559 changed files with 415 additions and 767 deletions

View File

@@ -0,0 +1,14 @@
import type { AuthSessionDTO } from '../dto/AuthSessionDTO';
import type { IdentitySessionPort } from '../ports/IdentitySessionPort';
export class GetCurrentUserSessionUseCase {
private readonly sessionPort: IdentitySessionPort;
constructor(sessionPort: IdentitySessionPort) {
this.sessionPort = sessionPort;
}
async execute(): Promise<AuthSessionDTO | null> {
return this.sessionPort.getCurrentSession();
}
}

View File

@@ -0,0 +1,21 @@
import type { AuthCallbackCommandDTO } from '../dto/AuthCallbackCommandDTO';
import type { AuthSessionDTO } from '../dto/AuthSessionDTO';
import type { AuthenticatedUserDTO } from '../dto/AuthenticatedUserDTO';
import type { IdentityProviderPort } from '../ports/IdentityProviderPort';
import type { IdentitySessionPort } from '../ports/IdentitySessionPort';
export class HandleAuthCallbackUseCase {
private readonly provider: IdentityProviderPort;
private readonly sessionPort: IdentitySessionPort;
constructor(provider: IdentityProviderPort, sessionPort: IdentitySessionPort) {
this.provider = provider;
this.sessionPort = sessionPort;
}
async execute(command: AuthCallbackCommandDTO): Promise<AuthSessionDTO> {
const user: AuthenticatedUserDTO = await this.provider.completeAuth(command);
const session = await this.sessionPort.createSession(user);
return session;
}
}

View File

@@ -0,0 +1,75 @@
/**
* Login with Email Use Case
*
* Authenticates a user with email and password.
*/
import type { IUserRepository } from '../../domain/repositories/IUserRepository';
import type { AuthenticatedUserDTO } from '../dto/AuthenticatedUserDTO';
import type { AuthSessionDTO } from '../dto/AuthSessionDTO';
import type { IdentitySessionPort } from '../ports/IdentitySessionPort';
export interface LoginCommandDTO {
email: string;
password: string;
}
export class LoginWithEmailUseCase {
constructor(
private readonly userRepository: IUserRepository,
private readonly sessionPort: IdentitySessionPort,
) {}
async execute(command: LoginCommandDTO): Promise<AuthSessionDTO> {
// Validate inputs
if (!command.email || !command.password) {
throw new Error('Email and password are required');
}
// Find user by email
const user = await this.userRepository.findByEmail(command.email.toLowerCase().trim());
if (!user) {
throw new Error('Invalid email or password');
}
// Verify password
const passwordHash = await this.hashPassword(command.password, user.salt);
if (passwordHash !== user.passwordHash) {
throw new Error('Invalid email or password');
}
// Create session
const authenticatedUserBase: AuthenticatedUserDTO = {
id: user.id,
displayName: user.displayName,
email: user.email,
};
const authenticatedUser: AuthenticatedUserDTO =
user.primaryDriverId !== undefined
? { ...authenticatedUserBase, primaryDriverId: user.primaryDriverId }
: authenticatedUserBase;
return this.sessionPort.createSession(authenticatedUser);
}
private async hashPassword(password: string, salt: string): Promise<string> {
// Simple hash for demo - in production, use bcrypt or argon2
const data = password + salt;
if (typeof crypto !== 'undefined' && crypto.subtle) {
const encoder = new TextEncoder();
const dataBuffer = encoder.encode(data);
const hashBuffer = await crypto.subtle.digest('SHA-256', dataBuffer);
const hashArray = Array.from(new Uint8Array(hashBuffer));
return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
}
// Fallback for environments without crypto.subtle
let hash = 0;
for (let i = 0; i < data.length; i++) {
const char = data.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash = hash & hash;
}
return Math.abs(hash).toString(16).padStart(16, '0');
}
}

View File

@@ -0,0 +1,13 @@
import type { IdentitySessionPort } from '../ports/IdentitySessionPort';
export class LogoutUseCase {
private readonly sessionPort: IdentitySessionPort;
constructor(sessionPort: IdentitySessionPort) {
this.sessionPort = sessionPort;
}
async execute(): Promise<void> {
await this.sessionPort.clearSession();
}
}

View File

@@ -0,0 +1,122 @@
/**
* Signup with Email Use Case
*
* Creates a new user account with email and password.
*/
import type { IUserRepository, StoredUser } from '../../domain/repositories/IUserRepository';
import type { AuthenticatedUserDTO } from '../dto/AuthenticatedUserDTO';
import type { AuthSessionDTO } from '../dto/AuthSessionDTO';
import type { IdentitySessionPort } from '../ports/IdentitySessionPort';
export interface SignupCommandDTO {
email: string;
password: string;
displayName: string;
}
export interface SignupResultDTO {
session: AuthSessionDTO;
isNewUser: boolean;
}
export class SignupWithEmailUseCase {
constructor(
private readonly userRepository: IUserRepository,
private readonly sessionPort: IdentitySessionPort,
) {}
async execute(command: SignupCommandDTO): Promise<SignupResultDTO> {
// Validate email format
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(command.email)) {
throw new Error('Invalid email format');
}
// Validate password strength
if (command.password.length < 8) {
throw new Error('Password must be at least 8 characters');
}
// Validate display name
if (!command.displayName || command.displayName.trim().length < 2) {
throw new Error('Display name must be at least 2 characters');
}
// Check if email already exists
const existingUser = await this.userRepository.findByEmail(command.email);
if (existingUser) {
throw new Error('An account with this email already exists');
}
// Hash password (simple hash for demo - in production use bcrypt)
const salt = this.generateSalt();
const passwordHash = await this.hashPassword(command.password, salt);
// Create user
const userId = this.generateUserId();
const newUser: StoredUser = {
id: userId,
email: command.email.toLowerCase().trim(),
displayName: command.displayName.trim(),
passwordHash,
salt,
createdAt: new Date(),
};
await this.userRepository.create(newUser);
// Create session
const authenticatedUser: AuthenticatedUserDTO = {
id: newUser.id,
displayName: newUser.displayName,
email: newUser.email,
};
const session = await this.sessionPort.createSession(authenticatedUser);
return {
session,
isNewUser: true,
};
}
private generateSalt(): string {
const array = new Uint8Array(16);
if (typeof crypto !== 'undefined' && crypto.getRandomValues) {
crypto.getRandomValues(array);
} else {
for (let i = 0; i < array.length; i++) {
array[i] = Math.floor(Math.random() * 256);
}
}
return Array.from(array, byte => byte.toString(16).padStart(2, '0')).join('');
}
private async hashPassword(password: string, salt: string): Promise<string> {
// Simple hash for demo - in production, use bcrypt or argon2
const data = password + salt;
if (typeof crypto !== 'undefined' && crypto.subtle) {
const encoder = new TextEncoder();
const dataBuffer = encoder.encode(data);
const hashBuffer = await crypto.subtle.digest('SHA-256', dataBuffer);
const hashArray = Array.from(new Uint8Array(hashBuffer));
return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
}
// Fallback for environments without crypto.subtle
let hash = 0;
for (let i = 0; i < data.length; i++) {
const char = data.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash = hash & hash;
}
return Math.abs(hash).toString(16).padStart(16, '0');
}
private generateUserId(): string {
if (typeof crypto !== 'undefined' && crypto.randomUUID) {
return crypto.randomUUID();
}
return 'user-' + Date.now().toString(36) + Math.random().toString(36).substr(2, 9);
}
}

View File

@@ -0,0 +1,14 @@
import type { StartAuthCommandDTO } from '../dto/StartAuthCommandDTO';
import type { IdentityProviderPort } from '../ports/IdentityProviderPort';
export class StartAuthUseCase {
private readonly provider: IdentityProviderPort;
constructor(provider: IdentityProviderPort) {
this.provider = provider;
}
async execute(command: StartAuthCommandDTO): Promise<{ redirectUrl: string; state: string }> {
return this.provider.startAuth(command);
}
}