# Unified Authentication & Authorization Architecture

## Executive Summary

This document defines a **clean, predictable, and secure** authentication and authorization architecture that eliminates the current "fucking unpredictable mess" by establishing clear boundaries between server-side and client-side responsibilities.

## Current State Analysis

### What's Wrong

1. **Confusing Layers**: Middleware, RouteGuards, AuthGuards, Blockers, Gateways - unclear hierarchy
2. **Mixed Responsibilities**: Server and client both doing similar checks inconsistently  
3. **Inconsistent Patterns**: Some routes use middleware, some use guards, some use both
4. **Role Confusion**: Frontend has role logic that should be server-only
5. **Debugging Nightmare**: Multiple layers with unclear flow

### What's Actually Working

1. **API Guards**: Clean NestJS pattern with `@Public()`, `@RequireRoles()`
2. **Basic Middleware**: Route protection works at edge
3. **Auth Context**: Session management exists
4. **Permission Model**: Documented in AUTHORIZATION.md

## Core Principle: Server as Source of Truth

**Golden Rule**: The API server is the **single source of truth** for authentication and authorization. The client is a dumb terminal that displays what the server allows.

### Server-Side Responsibilities (API)

#### 1. Authentication
- ✅ **Session Validation**: Verify JWT/session cookies
- ✅ **Identity Resolution**: Who is this user?
- ✅ **Token Management**: Issue, refresh, revoke tokens
- ❌ **UI Redirects**: Never redirect, return 401/403

#### 2. Authorization  
- ✅ **Role Verification**: Check user roles against requirements
- ✅ **Permission Evaluation**: Check capabilities (view/mutate)
- ✅ **Scope Resolution**: Determine league/sponsor/team context
- ✅ **Access Denial**: Return 401/403 with clear messages
- ❌ **Client State**: Never trust client-provided identity

#### 3. Data Filtering
- ✅ **Filter sensitive data**: Remove fields based on permissions
- ✅ **Scope-based queries**: Only return data user can access
- ❌ **Client-side filtering**: Never rely on frontend to hide data

### Client-Side Responsibilities (Website)

#### 1. UX Enhancement
- ✅ **Loading States**: Show "Verifying authentication..."
- ✅ **Redirects**: Send unauthenticated users to login
- ✅ **UI Hiding**: Hide buttons/links user can't access
- ✅ **Feedback**: Show "Access denied" messages
- ❌ **Security**: Never trust client checks for security

#### 2. Session Management
- ✅ **Session Cache**: Store session in context
- ✅ **Auto-refresh**: Fetch session on app load
- ✅ **Logout Flow**: Clear local state, call API logout
- ❌ **Role Logic**: Don't make decisions based on roles

#### 3. Route Protection
- ✅ **Middleware**: Basic auth check at edge
- ✅ **Layout Guards**: Verify session before rendering
- ✅ **Page Guards**: Additional verification (defense in depth)
- ❌ **Authorization**: Don't check permissions, let API fail

## Clean Architecture Layers

```
┌─────────────────────────────────────────────────────────────┐
│                    USER REQUEST                              │
└─────────────────────────────────────────────────────────────┘
                            │
                            ▼
┌─────────────────────────────────────────────────────────────┐
│  1. EDGE MIDDLEWARE (Next.js)                               │
│  • Check for session cookie                                 │
│  • Public routes: Allow through                             │
│  • Protected routes: Require auth cookie                   │
│  • Redirect to login if no cookie                           │
│  • NEVER check roles here                                   │
└─────────────────────────────────────────────────────────────┘
                            │
                            ▼
┌─────────────────────────────────────────────────────────────┐
│  2. API REQUEST (with session cookie)                       │
│  • NestJS AuthenticationGuard extracts user from session    │
│  • Attaches user identity to request                        │
└─────────────────────────────────────────────────────────────┘
                            │
                            ▼
┌─────────────────────────────────────────────────────────────┐
│  3. API AUTHORIZATION GUARD                                 │
│  • Check route metadata: @Public(), @RequireRoles()         │
│  • Evaluate permissions based on user identity              │
│  • Return 401 (unauthenticated) or 403 (forbidden)          │
│  • NEVER redirect, NEVER trust client identity              │
└─────────────────────────────────────────────────────────────┘
                            │
                            ▼
┌─────────────────────────────────────────────────────────────┐
│  4. API CONTROLLER                                          │
│  • Execute business logic                                  │
│  • Filter data based on permissions                        │
│  • Return appropriate response                             │
└─────────────────────────────────────────────────────────────┘
                            │
                            ▼
┌─────────────────────────────────────────────────────────────┐
│  5. CLIENT RESPONSE HANDLING                                │
│  • 200: Render data                                         │
│  • 401: Redirect to login with returnTo                     │
│  • 403: Show "Access denied" message                        │
│  • 404: Show "Not found" (for non-disclosure)               │
└─────────────────────────────────────────────────────────────┘
                            │
                            ▼
┌─────────────────────────────────────────────────────────────┐
│  6. COMPONENT RENDERING                                     │
│  • Layout guards: Verify session exists                     │
│  • Route guards: Show loading → content or redirect         │
│  • UI elements: Hide buttons user can't use                 │
└─────────────────────────────────────────────────────────────┘
```

## Implementation: Clean Route Protection

### Step 1: Simplify Middleware (Edge Layer)

**File**: `apps/website/middleware.ts`

```typescript
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

/**
 * Edge Middleware - Simple and Predictable
 * 
 * Responsibilities:
 * 1. Allow public routes (static assets, auth pages, discovery)
 * 2. Check for session cookie on protected routes
 * 3. Redirect to login if no cookie
 * 4. Let everything else through (API handles authorization)
 */
export function middleware(request: NextRequest) {
  const { pathname } = request.nextUrl;

  // 1. Always allow static assets and API routes
  if (
    pathname.startsWith('/_next/') ||
    pathname.startsWith('/api/') ||
    pathname.match(/\.(svg|png|jpg|jpeg|gif|webp|ico|css|js)$/)
  ) {
    return NextResponse.next();
  }

  // 2. Define public routes (no auth required)
  const publicRoutes = [
    '/',
    '/auth/login',
    '/auth/signup',
    '/auth/forgot-password',
    '/auth/reset-password',
    '/auth/iracing',
    '/auth/iracing/start',
    '/auth/iracing/callback',
    '/leagues',
    '/drivers',
    '/teams',
    '/leaderboards',
    '/races',
    '/sponsor/signup',
  ];

  // 3. Check if current route is public
  const isPublic = publicRoutes.includes(pathname) || 
    publicRoutes.some(route => pathname.startsWith(route + '/'));

  if (isPublic) {
    // Special handling: redirect authenticated users away from auth pages
    const hasAuthCookie = request.cookies.has('gp_session');
    const authRoutes = ['/auth/login', '/auth/signup', '/auth/forgot-password', '/auth/reset-password'];
    
    if (authRoutes.includes(pathname) && hasAuthCookie) {
      return NextResponse.redirect(new URL('/dashboard', request.url));
    }
    
    return NextResponse.next();
  }

  // 4. Protected routes: require session cookie
  const hasAuthCookie = request.cookies.has('gp_session');
  
  if (!hasAuthCookie) {
    const loginUrl = new URL('/auth/login', request.url);
    loginUrl.searchParams.set('returnTo', pathname);
    return NextResponse.redirect(loginUrl);
  }

  // 5. User has cookie, let them through
  // API will handle actual authorization
  return NextResponse.next();
}

export const config = {
  matcher: [
    '/((?!_next/static|_next/image|_next/data|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp|mp4|webm|mov|avi)$).*)',
  ],
};
```

### Step 2: Clean Layout Guards (Client Layer)

**File**: `apps/website/lib/guards/AuthLayout.tsx`

```typescript
'use client';

import { ReactNode, useEffect } from 'react';
import { useRouter } from 'next/navigation';
import { useAuth } from '@/lib/auth/AuthContext';
import { LoadingState } from '@/components/shared/LoadingState';

interface AuthLayoutProps {
  children: ReactNode;
  requireAuth?: boolean;
  redirectTo?: string;
}

/**
 * AuthLayout - Client-side session verification
 * 
 * Responsibilities:
 * 1. Verify user session exists
 * 2. Show loading state while checking
 * 3. Redirect to login if no session
 * 4. Render children if authenticated
 * 
 * Does NOT check permissions - that's the API's job
 */
export function AuthLayout({ 
  children, 
  requireAuth = true,
  redirectTo = '/auth/login'
}: AuthLayoutProps) {
  const router = useRouter();
  const { session, loading } = useAuth();

  useEffect(() => {
    if (!requireAuth) return;
    
    // If done loading and no session, redirect
    if (!loading && !session) {
      const returnTo = window.location.pathname;
      router.push(`${redirectTo}?returnTo=${encodeURIComponent(returnTo)}`);
    }
  }, [loading, session, router, requireAuth, redirectTo]);

  // Show loading state
  if (loading) {
    return (
      <div className="min-h-screen flex items-center justify-center bg-deep-graphite">
        <LoadingState message="Verifying authentication..." />
      </div>
    );
  }

  // Show nothing while redirecting (or show error if not redirecting)
  if (requireAuth && !session) {
    return null;
  }

  // Render protected content
  return <>{children}</>;
}
```

### Step 3: Role-Based Layout (Client Layer)

**File**: `apps/website/lib/guards/RoleLayout.tsx`

```typescript
'use client';

import { ReactNode, useEffect } from 'react';
import { useRouter } from 'next/navigation';
import { useAuth } from '@/lib/auth/AuthContext';
import { LoadingState } from '@/components/shared/LoadingState';

interface RoleLayoutProps {
  children: ReactNode;
  requiredRoles: string[];
  redirectTo?: string;
}

/**
 * RoleLayout - Client-side role verification
 * 
 * Responsibilities:
 * 1. Verify user session exists
 * 2. Show loading state
 * 3. Redirect if no session OR insufficient role
 * 4. Render children if authorized
 * 
 * Note: This is UX enhancement. API is still source of truth.
 */
export function RoleLayout({ 
  children, 
  requiredRoles,
  redirectTo = '/auth/login'
}: RoleLayoutProps) {
  const router = useRouter();
  const { session, loading } = useAuth();

  useEffect(() => {
    if (loading) return;

    // No session? Redirect
    if (!session) {
      const returnTo = window.location.pathname;
      router.push(`${redirectTo}?returnTo=${encodeURIComponent(returnTo)}`);
      return;
    }

    // Has session but wrong role? Redirect
    if (requiredRoles.length > 0 && !requiredRoles.includes(session.role || '')) {
      // Could redirect to dashboard or show access denied
      router.push('/dashboard');
      return;
    }
  }, [loading, session, router, requiredRoles, redirectTo]);

  if (loading) {
    return (
      <div className="min-h-screen flex items-center justify-center bg-deep-graphite">
        <LoadingState message="Verifying access..." />
      </div>
    );
  }

  if (!session || (requiredRoles.length > 0 && !requiredRoles.includes(session.role || ''))) {
    return null;
  }

  return <>{children}</>;
}
```

### Step 4: Usage Examples

#### Public Route (No Protection)
```typescript
// app/leagues/page.tsx
export default function LeaguesPage() {
  return <LeaguesList />;
}
```

#### Authenticated Route
```typescript
// app/dashboard/layout.tsx
import { AuthLayout } from '@/lib/guards/AuthLayout';

export default function DashboardLayout({ children }: { children: ReactNode }) {
  return (
    <AuthLayout requireAuth={true}>
      <div className="min-h-screen bg-deep-graphite">
        {children}
      </div>
    </AuthLayout>
  );
}

// app/dashboard/page.tsx
export default function DashboardPage() {
  // No additional auth checks needed - layout handles it
  return <DashboardContent />;
}
```

#### Role-Protected Route
```typescript
// app/admin/layout.tsx
import { RoleLayout } from '@/lib/guards/RoleLayout';

export default function AdminLayout({ children }: { children: ReactNode }) {
  return (
    <RoleLayout requiredRoles={['owner', 'admin']}>
      <div className="min-h-screen bg-deep-graphite">
        {children}
      </div>
    </RoleLayout>
  );
}

// app/admin/page.tsx
export default function AdminPage() {
  // No additional checks - layout handles role verification
  return <AdminDashboard />;
}
```

#### Scoped Route (League Admin)
```typescript
// app/leagues/[id]/settings/layout.tsx
import { AuthLayout } from '@/lib/guards/AuthLayout';
import { LeagueAccessGuard } from '@/components/leagues/LeagueAccessGuard';

export default function LeagueSettingsLayout({ 
  children,
  params 
}: { 
  children: ReactNode;
  params: { id: string };
}) {
  return (
    <AuthLayout requireAuth={true}>
      <LeagueAccessGuard leagueId={params.id}>
        <div className="min-h-screen bg-deep-graphite">
          {children}
        </div>
      </LeagueAccessGuard>
    </AuthLayout>
  );
}
```

### Step 5: API Guard Cleanup

**File**: `apps/api/src/domain/auth/AuthorizationGuard.ts`

```typescript
import { CanActivate, ExecutionContext, ForbiddenException, Injectable, UnauthorizedException } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { AuthorizationService } from './AuthorizationService';
import { PUBLIC_ROUTE_METADATA_KEY } from './Public';
import { REQUIRE_ROLES_METADATA_KEY, RequireRolesMetadata } from './RequireRoles';

type AuthenticatedRequest = {
  user?: { userId: string };
};

@Injectable()
export class AuthorizationGuard implements CanActivate {
  constructor(
    private readonly reflector: Reflector,
    private readonly authorizationService: AuthorizationService,
  ) {}

  canActivate(context: ExecutionContext): boolean {
    const handler = context.getHandler();
    const controllerClass = context.getClass();

    // 1. Check if route is public
    const isPublic = this.reflector.getAllAndOverride<{ public: true } | undefined>(
      PUBLIC_ROUTE_METADATA_KEY,
      [handler, controllerClass],
    )?.public ?? false;

    if (isPublic) {
      return true;
    }

    // 2. Get required roles
    const rolesMetadata = this.reflector.getAllAndOverride<RequireRolesMetadata | undefined>(
      REQUIRE_ROLES_METADATA_KEY,
      [handler, controllerClass],
    ) ?? null;

    // 3. Get user identity from request (set by AuthenticationGuard)
    const request = context.switchToHttp().getRequest<AuthenticatedRequest>();
    const userId = request.user?.userId;

    // 4. Deny if not authenticated
    if (!userId) {
      throw new UnauthorizedException('Authentication required');
    }

    // 5. If no roles required, allow
    if (!rolesMetadata || rolesMetadata.anyOf.length === 0) {
      return true;
    }

    // 6. Check if user has required role
    const userRoles = this.authorizationService.getRolesForUser(userId);
    const hasAnyRole = rolesMetadata.anyOf.some((r) => userRoles.includes(r));
    
    if (!hasAnyRole) {
      throw new ForbiddenException(`Access requires one of: ${rolesMetadata.anyOf.join(', ')}`);
    }

    return true;
  }
}
```

### Step 6: Client Error Handling

**File**: `apps/website/lib/api/client.ts`

```typescript
/**
 * API Client with unified error handling
 */
export async function apiFetch(url: string, options: RequestInit = {}) {
  const response = await fetch(url, {
    ...options,
    credentials: 'include',
    headers: {
      'Content-Type': 'application/json',
      ...options.headers,
    },
  });

  // Handle authentication errors
  if (response.status === 401) {
    // Session expired or invalid
    window.location.href = '/auth/login?returnTo=' + encodeURIComponent(window.location.pathname);
    throw new Error('Authentication required');
  }

  // Handle authorization errors
  if (response.status === 403) {
    const error = await response.json().catch(() => ({ message: 'Access denied' }));
    throw new Error(error.message || 'You do not have permission to access this resource');
  }

  // Handle not found
  if (response.status === 404) {
    throw new Error('Resource not found');
  }

  // Handle server errors
  if (response.status >= 500) {
    throw new Error('Server error. Please try again later.');
  }

  return response;
}
```

## Benefits of This Architecture

### 1. **Clear Responsibilities**
- Server: Security and authorization
- Client: UX and user experience

### 2. **Predictable Flow**
```
User → Middleware → API → Guard → Controller → Response → Client
```

### 3. **Easy Debugging**
- Check middleware logs
- Check API guard logs
- Check client session state

### 4. **Secure by Default**
- API never trusts client
- Client never makes security decisions
- Defense in depth without confusion

### 5. **Scalable**
- Easy to add new routes
- Easy to add new roles
- Easy to add new scopes

## Migration Plan

### Phase 1: Clean Up Middleware (1 day)
- [ ] Simplify `middleware.ts` to only check session cookie
- [ ] Remove role logic from middleware
- [ ] Define clear public routes list

### Phase 2: Create Clean Guards (2 days)
- [ ] Create `AuthLayout` component
- [ ] Create `RoleLayout` component  
- [ ] Create `ScopedLayout` component
- [ ] Remove old RouteGuard/AuthGuard complexity

### Phase 3: Update Route Layouts (2 days)
- [ ] Update all protected route layouts
- [ ] Remove redundant page-level checks
- [ ] Test all redirect flows

### Phase 4: API Guard Enhancement (1 day)
- [ ] Ensure all endpoints have proper decorators
- [ ] Add missing `@Public()` or `@RequireRoles()`
- [ ] Test 401/403 responses

### Phase 5: Documentation & Testing (1 day)
- [ ] Update all route protection docs
- [ ] Create testing checklist
- [ ] Verify all scenarios work

## Testing Checklist

### Unauthenticated User
- [ ] `/dashboard` → Redirects to `/auth/login?returnTo=/dashboard`
- [ ] `/profile` → Redirects to `/auth/login?returnTo=/profile`
- [ ] `/admin` → Redirects to `/auth/login?returnTo=/admin`
- [ ] `/leagues` → Works (public)
- [ ] `/auth/login` → Works (public)

### Authenticated User (Regular)
- [ ] `/dashboard` → Works
- [ ] `/profile` → Works
- [ ] `/admin` → Redirects to `/dashboard` (no role)
- [ ] `/leagues` → Works (public)
- [ ] `/auth/login` → Redirects to `/dashboard`

### Authenticated User (Admin)
- [ ] `/dashboard` → Works
- [ ] `/profile` → Works
- [ ] `/admin` → Works
- [ ] `/admin/users` → Works
- [ ] `/leagues` → Works (public)

### Session Expiry
- [ ] Navigate to protected route with expired session → Redirect to login
- [ ] Return to original route after login → Works

### API Direct Calls
- [ ] Call protected endpoint without auth → 401
- [ ] Call admin endpoint without role → 403
- [ ] Call public endpoint → 200

## Summary

This architecture eliminates the "fucking unpredictable mess" by:

1. **One Source of Truth**: API server handles all security
2. **Clear Layers**: Middleware → API → Guards → Controller
3. **Simple Client**: UX enhancement only, no security decisions
4. **Predictable Flow**: Always the same path for every request
5. **Easy to Debug**: Each layer has one job

The result: **Clean, predictable, secure authentication and authorization that just works.**