# Auth Solution Finalization Plan ## Overview This plan outlines the comprehensive enhancement of the GridPilot authentication system to meet production requirements while maintaining clean architecture principles and supporting both in-memory and TypeORM implementations. ## Current State Analysis ### ✅ What's Working - Clean Architecture with proper separation of concerns - Email/password signup and login - iRacing OAuth flow (placeholder) - Session management with cookies - Basic route protection (mode-based) - Dev tools overlay with demo login - In-memory and TypeORM persistence adapters ### ❌ What's Missing/Needs Enhancement 1. **Real Name Validation**: Current system allows any displayName, but we need to enforce real names 2. **Modern Auth Features**: No password reset, magic links, or modern recovery flows 3. **Production-Ready Demo Login**: Current demo uses cookies but needs proper integration 4. **Proper Route Protection**: Website middleware only checks app mode, not authentication status 5. **Enhanced Error Handling**: Need better validation and user-friendly error messages 6. **Security Hardening**: Need to ensure all endpoints are properly protected ## Enhanced Architecture Design ### 1. Domain Layer Changes #### User Entity Updates ```typescript // Enhanced validation for real names export class User { // ... existing properties private validateDisplayName(displayName: string): void { const trimmed = displayName.trim(); // Must be a real name (no nicknames) if (trimmed.length < 2) { throw new Error('Name must be at least 2 characters'); } // No special characters except basic punctuation if (!/^[A-Za-z\s\-']{2,50}$/.test(trimmed)) { throw new Error('Name can only contain letters, spaces, hyphens, and apostrophes'); } // No common nickname patterns const nicknamePatterns = [/^user/i, /^test/i, /^[a-z0-9_]+$/i]; if (nicknamePatterns.some(pattern => pattern.test(trimmed))) { throw new Error('Please use your real name, not a nickname'); } // Capitalize first letter of each word this.displayName = trimmed.replace(/\b\w/g, l => l.toUpperCase()); } } ``` #### New Value Objects - `MagicLinkToken`: Secure token for password reset - `EmailVerificationToken`: For email verification (future) - `PasswordResetRequest`: Entity for tracking reset requests #### New Repositories - `IMagicLinkRepository`: Store and validate magic links - `IPasswordResetRepository`: Track password reset requests ### 2. Application Layer Changes #### New Use Cases ```typescript // Forgot Password Use Case export class ForgotPasswordUseCase { async execute(email: string): Promise> { // 1. Validate email exists // 2. Generate secure token // 3. Store token with expiration // 4. Send magic link email (or return link for dev) // 5. Rate limiting } } // Reset Password Use Case export class ResetPasswordUseCase { async execute(token: string, newPassword: string): Promise> { // 1. Validate token // 2. Check expiration // 3. Update password // 4. Invalidate token // 5. Clear other sessions } } // Demo Login Use Case (Dev Only) export class DemoLoginUseCase { async execute(role: 'driver' | 'sponsor'): Promise> { // 1. Check environment (dev only) // 2. Create demo user if doesn't exist // 3. Generate session // 4. Return session } } ``` #### Enhanced Signup Use Case ```typescript export class SignupUseCase { // Add real name validation // Add email format validation // Add password strength requirements // Optional: Email verification flow } ``` ### 3. API Layer Changes #### New Auth Endpoints ```typescript @Public() @Controller('auth') export class AuthController { // Existing: // POST /auth/signup // POST /auth/login // GET /auth/session // POST /auth/logout // GET /auth/iracing/start // GET /auth/iracing/callback // New: // POST /auth/forgot-password // POST /auth/reset-password // POST /auth/demo-login (dev only) // POST /auth/verify-email (future) } ``` #### Enhanced DTOs ```typescript export class SignupParamsDTO { @ApiProperty() @IsEmail() email: string; @ApiProperty() @IsString() @MinLength(8) @Matches(/(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/, { message: 'Password must contain uppercase, lowercase, and number' }) password: string; @ApiProperty() @IsString() @Matches(/^[A-Za-z\s\-']{2,50}$/, { message: 'Please use your real name (letters, spaces, hyphens only)' }) displayName: string; } export class ForgotPasswordDTO { @ApiProperty() @IsEmail() email: string; } export class ResetPasswordDTO { @ApiProperty() @IsString() token: string; @ApiProperty() @IsString() @MinLength(8) newPassword: string; } export class DemoLoginDTO { @ApiProperty({ enum: ['driver', 'sponsor'] }) role: 'driver' | 'sponsor'; } ``` #### Enhanced Guards ```typescript @Injectable() export class AuthenticationGuard implements CanActivate { async canActivate(context: ExecutionContext): Promise { const request = context.switchToHttp().getRequest(); // Check session const session = await this.sessionPort.getCurrentSession(); if (!session?.user?.id) { throw new UnauthorizedException('Authentication required'); } // Attach user to request request.user = { userId: session.user.id }; return true; } } @Injectable() export class ProductionGuard implements CanActivate { async canActivate(context: ExecutionContext): Promise { // Block demo login in production if (process.env.NODE_ENV === 'production') { const request = context.switchToHttp().getRequest(); if (request.path === '/auth/demo-login') { throw new ForbiddenException('Demo login not available in production'); } } return true; } } ``` ### 4. Website Layer Changes #### Enhanced Middleware ```typescript export function middleware(request: NextRequest) { const { pathname } = request.nextUrl; // Public routes (always accessible) const publicRoutes = [ '/', '/auth/login', '/auth/signup', '/auth/forgot-password', '/auth/reset-password', '/auth/iracing', '/auth/iracing/start', '/auth/iracing/callback', '/api/auth/signup', '/api/auth/login', '/api/auth/forgot-password', '/api/auth/reset-password', '/api/auth/demo-login', // dev only '/api/auth/session', '/api/auth/logout' ]; // Protected routes (require authentication) const protectedRoutes = [ '/dashboard', '/profile', '/leagues', '/races', '/teams', '/sponsor', '/onboarding' ]; // Check if route is public if (publicRoutes.includes(pathname)) { return NextResponse.next(); } // Check if route is protected if (protectedRoutes.some(route => pathname.startsWith(route))) { // Verify authentication by calling API const response = NextResponse.next(); // Add a header that can be checked by client components // This is a simple approach - in production, consider server-side session validation return response; } // Allow other routes return NextResponse.next(); } ``` #### Client-Side Route Protection ```typescript // Higher-order component for route protection export function withAuth

(Component: React.ComponentType

) { return function ProtectedComponent(props: P) { const { session, loading } = useAuth(); const router = useRouter(); useEffect(() => { if (!loading && !session) { router.push(`/auth/login?returnTo=${encodeURIComponent(window.location.pathname)}`); } }, [session, loading, router]); if (loading) { return ; } if (!session) { return null; // or redirecting indicator } return ; }; } // Hook for protected data fetching export function useProtectedData(fetcher: () => Promise) { const { session, loading } = useAuth(); const [data, setData] = useState(null); const [error, setError] = useState(null); useEffect(() => { if (!loading && !session) { setError(new Error('Authentication required')); return; } if (session) { fetcher() .then(setData) .catch(setError); } }, [session, loading, fetcher]); return { data, error, loading }; } ``` #### Enhanced Auth Pages - **Login**: Add "Forgot Password" link - **Signup**: Add real name validation with helpful hints - **New**: Forgot Password page - **New**: Reset Password page - **New**: Magic Link landing page #### Enhanced Dev Tools ```typescript // Add proper demo login flow const handleDemoLogin = async (role: 'driver' | 'sponsor') => { try { const response = await fetch('/api/auth/demo-login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ role }) }); if (!response.ok) throw new Error('Demo login failed'); // Refresh session await refreshSession(); // Redirect based on role if (role === 'sponsor') { router.push('/sponsor/dashboard'); } else { router.push('/dashboard'); } } catch (error) { console.error('Demo login failed:', error); } }; ``` ### 5. Persistence Layer Changes #### Enhanced Repositories Both InMemory and TypeORM implementations need to support: - Storing magic link tokens with expiration - Password reset request tracking - Rate limiting (failed login attempts) #### Database Schema Updates (TypeORM) ```typescript @Entity() export class MagicLinkToken { @PrimaryGeneratedColumn('uuid') id: string; @Column() userId: string; @Column() token: string; @Column() expiresAt: Date; @Column({ default: false }) used: boolean; @CreateDateColumn() createdAt: Date; } @Entity() export class PasswordResetRequest { @PrimaryGeneratedColumn('uuid') id: string; @Column() email: string; @Column() token: string; @Column() expiresAt: Date; @Column({ default: false }) used: boolean; @Column({ default: 0 }) attemptCount: number; @CreateDateColumn() createdAt: Date; } ``` ### 6. Security & Validation #### Rate Limiting - Implement rate limiting on auth endpoints - Track failed login attempts - Lock accounts after too many failures #### Input Validation - Email format validation - Password strength requirements - Real name validation - Token format validation #### Environment Detection ```typescript export function isDevelopment(): boolean { return process.env.NODE_ENV === 'development'; } export function isProduction(): boolean { return process.env.NODE_ENV === 'production'; } export function allowDemoLogin(): boolean { return isDevelopment() || process.env.ALLOW_DEMO_LOGIN === 'true'; } ``` ### 7. Integration Points #### API Routes (Next.js) ```typescript // app/api/auth/forgot-password/route.ts export async function POST(request: Request) { // Validate input // Call ForgotPasswordUseCase // Return appropriate response } // app/api/auth/reset-password/route.ts export async function POST(request: Request) { // Validate token // Call ResetPasswordUseCase // Return success/error } // app/api/auth/demo-login/route.ts (dev only) export async function POST(request: Request) { if (!allowDemoLogin()) { return NextResponse.json({ error: 'Not available' }, { status: 403 }); } // Call DemoLoginUseCase } ``` #### Website Components ```typescript // ProtectedPageWrapper.tsx export function ProtectedPageWrapper({ children }: { children: React.ReactNode }) { const { session, loading } = useAuth(); const router = useRouter(); if (loading) return ; if (!session) { router.push(`/auth/login?returnTo=${encodeURIComponent(window.location.pathname)}`); return null; } return <>{children}; } // AuthForm.tsx - Reusable form with validation // MagicLinkNotification.tsx - Show success message // PasswordStrengthMeter.tsx - Visual feedback ``` ## Implementation Phases ### Phase 1: Core Domain & Use Cases - [ ] Update User entity with real name validation - [ ] Create new use cases (ForgotPassword, ResetPassword, DemoLogin) - [ ] Create new repositories/interfaces - [ ] Add new value objects ### Phase 2: API Layer - [ ] Add new auth endpoints - [ ] Create new DTOs with validation - [ ] Update existing endpoints with enhanced validation - [ ] Add guards and middleware ### Phase 3: Persistence - [ ] Update InMemory repositories - [ ] Update TypeORM repositories - [ ] Add database migrations (if needed) - [ ] Implement rate limiting storage ### Phase 4: Website Integration - [ ] Update middleware for proper route protection - [ ] Create new auth pages (forgot password, reset) - [ ] Enhance existing pages with validation - [ ] Update dev tools overlay - [ ] Add client-side route protection HOCs ### Phase 5: Testing & Documentation - [ ] Write unit tests for new use cases - [ ] Write integration tests for new endpoints - [ ] Test both in-memory and TypeORM implementations - [ ] Update API documentation - [ ] Update architecture docs ## Key Requirements Checklist ### ✅ Must Work With - [ ] InMemory implementation - [ ] TypeORM implementation - [ ] Dev tools overlay - [ ] Existing session management ### ✅ Must Provide - [ ] Demo login for dev (not production) - [ ] Forgot password solution (modern approach) - [ ] Real name validation (no nicknames) - [ ] Proper website route protection ### ✅ Must Not Break - [ ] Existing signup/login flow - [ ] iRacing OAuth flow - [ ] Existing tests - [ ] Clean architecture principles ## Success Metrics 1. **Security**: All protected routes require authentication 2. **User Experience**: Clear validation messages, helpful error states 3. **Developer Experience**: Easy demo login, clear separation of concerns 4. **Maintainability**: Clean architecture, well-tested, documented 5. **Scalability**: Works with both in-memory and database persistence ## Notes - The demo login should be clearly marked as development-only - Magic links should have short expiration times (15-30 minutes) - Consider adding email verification as a future enhancement - Rate limiting should be configurable per environment - All new features should follow the existing clean architecture patterns