# Clean Architecture Compliant Auth Layer Design ## Current State Analysis (Diagnosis) - Existing [`core/identity/index.ts](core/identity/index.ts) exports User entity, IUserRepository port, but lacks credential-based auth ports (IAuthRepository), value objects (PasswordHash), and use cases (LoginUseCase, SignupUseCase). - iRacing-specific auth use cases present (StartAuthUseCase, HandleAuthCallbackUseCase), but no traditional email/password flows. - No domain services for password hashing/validation. - In-memory impls limited to ratings/achievements; missing User/Auth repo impls. - [`apps/website/lib/auth/InMemoryAuthService.ts](apps/website/lib/auth/InMemoryAuthService.ts) (visible) embeds business logic, violating dependency inversion. - App-layer AuthService exists but thick; no thin delivery via DI-injected use cases. - No AuthContext integration with clean AuthService. - DI setup in [`apps/website/lib/di-tokens.ts](apps/website/lib/di-tokens.ts) etc. needs auth bindings. ## Architectural Plan 1. Add `PasswordHash` value object in `core/identity/domain/value-objects/PasswordHash.ts`. 2. Create `IAuthRepository` port in `core/identity/domain/repositories/IAuthRepository.ts` with `findByCredentials(email: EmailAddress, passwordHash: PasswordHash): Promise`, `save(user: User): Promise`. 3. Extend `IUserRepository` if needed for non-auth user ops. 4. Implement `InMemoryUserRepository` in `adapters/identity/inmem/InMemoryUserRepository.ts` satisfying `IUserRepository & IAuthRepository`. 5. Add domain service `PasswordHashingService` in `core/identity/domain/services/PasswordHashingService.ts` (interface + dummy impl). 6. Create `LoginUseCase` in `core/identity/application/use-cases/LoginUseCase.ts` orchestrating repo find + hashing service. 7. Create `SignupUseCase` in `core/identity/application/use-cases/SignupUseCase.ts` validating, hashing, save via repos. 8. Create `GetUserUseCase` in `core/identity/application/use-cases/GetUserUseCase.ts` via IUserRepository. 9. Refactor `apps/website/lib/auth/AuthService.ts` as thin adapter: DTO -> use case calls via injected deps. 10. Update `apps/website/lib/auth/AuthContext.tsx` to provide/use AuthService via React Context. 11. Add DI tokens in `apps/website/lib/di-tokens.ts`: `AUTH_REPOSITORY_TOKEN`, `USER_REPOSITORY_TOKEN`, `LOGIN_USE_CASE_TOKEN`, etc. 12. Bind in `apps/website/lib/di-config.ts` / `di-container.ts`: ports -> inmem impls, use cases -> deps, AuthService -> use cases. ## Summary Layer auth into domain entities/VOs/services, application use cases/ports, infrastructure adapters (inmem), thin app delivery (AuthService) wired via DI. Coexists with existing iRacing provider auth. ## Design Overview Follows strict Clean Architecture: - **Entities**: User with EmailAddress, PasswordHash VOs. - **Use Cases**: Pure orchestrators, depend on ports/services. - **Ports**: IAuthRepository (credential ops), IUserRepository (user data). - **Adapters**: Inmem impls. - **Delivery**: AuthService maps HTTP/JS DTOs to use cases. - **DI**: Inversion via tokens/container. ```mermaid graph TB DTO[DTOs
apps/website/lib/auth] --> AuthService[AuthService
apps/website/lib/auth] AuthService --> LoginUC[LoginUseCase
core/identity/application] AuthService --> SignupUC[SignupUseCase] LoginUC --> IAuthRepo[IAuthRepository
core/identity/domain] SignupUC --> PasswordSvc[PasswordHashingService
core/identity/domain] IAuthRepo --> InMemRepo[InMemoryUserRepository
adapters/identity/inmem] AuthService -.-> DI[DI Container
apps/website/lib/di-*] AuthContext[AuthContext.tsx] --> AuthService ``` ## Files Structure ``` core/identity/ ├── domain/ │ ├── value-objects/PasswordHash.ts (new) │ ├── entities/User.ts (extend if needed) │ ├── repositories/IAuthRepository.ts (new) │ └── services/PasswordHashingService.ts (new) ├── application/ │ └── use-cases/LoginUseCase.ts (new) │ SignupUseCase.ts (new) │ GetUserUseCase.ts (new) adapters/identity/inmem/ ├── InMemoryUserRepository.ts (new) apps/website/lib/auth/ ├── AuthService.ts (refactor) └── AuthContext.tsx (update) apps/website/lib/ ├── di-tokens.ts (update) ├── di-config.ts (update) └── di-container.ts (update) ``` ## Code Snippets ### PasswordHash VO ```ts // core/identity/domain/value-objects/PasswordHash.ts import { ValueObject } from '../../../shared/domain/ValueObject'; // assume shared export class PasswordHash extends ValueObject { static create(plain: string): PasswordHash { // dummy bcrypt hash return new PasswordHash(btoa(plain)); // prod: use bcrypt } verify(plain: string): boolean { return btoa(plain) === this.value; } } ``` ### IAuthRepository Port ```ts // core/identity/domain/repositories/IAuthRepository.ts import { UserId, EmailAddress } from '../value-objects'; import { User } from '../entities/User'; import { PasswordHash } from '../value-objects/PasswordHash'; export interface IAuthRepository { findByCredentials(email: EmailAddress, passwordHash: PasswordHash): Promise; save(user: User): Promise; } ``` ### LoginUseCase ```ts // core/identity/application/use-cases/LoginUseCase.ts import { Injectable } from 'di'; // assume import { IAuthRepository } from '../../domain/repositories/IAuthRepository'; import { PasswordHash } from '../../domain/value-objects/PasswordHash'; import { EmailAddress } from '../../domain/value-objects/EmailAddress'; import { User } from '../../domain/entities/User'; export class LoginUseCase { constructor( private authRepo: IAuthRepository ) {} async execute(email: string, password: string): Promise { const emailVO = EmailAddress.create(email); const passwordHash = PasswordHash.create(password); const user = await this.authRepo.findByCredentials(emailVO, passwordHash); if (!user) throw new Error('Invalid credentials'); return user; } } ``` ### AuthService (Thin Adapter) ```ts // apps/website/lib/auth/AuthService.ts import { injectable, inject } from 'di'; // assume import { LoginUseCase } from '@core/identity'; import type { LoginDto } from '@core/identity/application/dto'; // define DTO @injectable() export class AuthService { constructor( @inject(LoginUseCase_TOKEN) private loginUC: LoginUseCase ) {} async login(credentials: {email: string, password: string}) { return await this.loginUC.execute(credentials.email, credentials.password); } // similar for signup, getUser } ``` ### DI Tokens Update ```ts // apps/website/lib/di-tokens.ts export const LOGIN_USE_CASE_TOKEN = Symbol('LoginUseCase'); export const AUTH_REPOSITORY_TOKEN = Symbol('IAuthRepository'); // etc. ``` ### AuthContext Usage Example ```tsx // apps/website/lib/auth/AuthContext.tsx import { createContext, useContext } from 'react'; import { AuthService } from './AuthService'; import { diContainer } from '../di-container'; const AuthContext = createContext(null!); export function AuthProvider({ children }) { const authService = diContainer.resolve(AuthService); return {children}; } export const useAuth = () => useContext(AuthContext); ``` ## Implementation Notes - Update `core/identity/index.ts` exports for new modules. - Update `core/identity/package.json` exports if needed. - Use dummy hashing for dev; prod adapter swaps repo impl. - No business logic in app/website: all in core use cases. - Coexists with iRacing auth: separate use cases/services.