# Clean Authentication & Authorization Solution

## Overview

This document describes the **clean, predictable, and maintainable** authentication and authorization architecture that replaces the previous "fucking unpredictable mess."

## The Problem

**Before:**
- Multiple overlapping protection layers (middleware, RouteGuard, AuthGuard, Blockers, Gateways)
- Hardcoded paths scattered throughout codebase
- Mixed responsibilities between server and client
- Inconsistent patterns across routes
- Role logic in both client and server
- Debugging nightmare with unclear flow

## The Solution

### Core Principle: **Single Source of Truth**

All routing decisions flow through **one centralized configuration system**:

```typescript
// apps/website/lib/routing/RouteConfig.ts
export const routes = {
  auth: {
    login: '/auth/login',
    signup: '/auth/signup',
    // ... all auth routes
  },
  public: {
    home: '/',
    leagues: '/leagues',
    // ... all public routes
  },
  protected: {
    dashboard: '/dashboard',
    // ... all protected routes
  },
  sponsor: {
    dashboard: '/sponsor/dashboard',
    // ... sponsor routes
  },
  admin: {
    root: '/admin',
    users: '/admin/users',
  },
  league: {
    detail: (id: string) => `/leagues/${id}`,
    // ... parameterized routes
  },
  // ... etc
};
```

### Architecture Layers

#### 1. **Edge Middleware** (Simple & Clean)
```typescript
// apps/website/middleware.ts
export function middleware(request: NextRequest) {
  const hasAuthCookie = request.cookies.has('gp_session');
  
  // Public routes from config
  const publicRoutes = [
    routes.public.home,
    routes.public.leagues,
    routes.auth.login,
    // ... etc
  ];
  
  if (publicRoutes.includes(pathname)) {
    // Handle auth route redirects
    return NextResponse.next();
  }
  
  // Protected routes
  if (!hasAuthCookie) {
    const loginUrl = new URL(routes.auth.login, request.url);
    loginUrl.searchParams.set('returnTo', pathname);
    return NextResponse.redirect(loginUrl);
  }
  
  return NextResponse.next();
}
```

**Responsibilities:**
- ✅ Check session cookie
- ✅ Allow public routes
- ✅ Redirect to login if no cookie
- ❌ No role checking
- ❌ No hardcoded paths

#### 2. **Client Guards** (UX Enhancement)
```typescript
// apps/website/lib/guards/AuthGuard.tsx
export function AuthGuard({ children, requireAuth = true }: AuthGuardProps) {
  const { session, loading } = useAuth();
  const router = useRouter();

  useEffect(() => {
    if (requireAuth && !loading && !session) {
      const url = new URL(routes.auth.login, window.location.origin);
      url.searchParams.set('returnTo', window.location.pathname);
      router.push(url.toString());
    }
  }, [session, loading]);

  if (loading) return <LoadingState />;
  if (!session && requireAuth) return null;
  return <>{children}</>;
}

// apps/website/lib/guards/RoleGuard.tsx
export function RoleGuard({ children, requiredRoles }: RoleGuardProps) {
  const { session, loading } = useAuth();
  const router = useRouter();

  useEffect(() => {
    if (!loading && session && !requiredRoles.includes(session.role)) {
      router.push(routes.protected.dashboard);
    }
  }, [session, loading]);

  if (loading) return <LoadingState />;
  if (!session || !requiredRoles.includes(session.role)) return null;
  return <>{children}</>;
}
```

**Responsibilities:**
- ✅ Verify session exists
- ✅ Show loading states
- ✅ Redirect if unauthorized
- ✅ Hide UI elements
- ❌ Make security decisions

#### 3. **API Guards** (Source of Truth)
```typescript
// apps/api/src/domain/auth/AuthorizationGuard.ts
@Injectable()
export class AuthorizationGuard implements CanActivate {
  canActivate(context: ExecutionContext): boolean {
    const isPublic = this.reflector.getMetadata('public', handler);
    if (isPublic) return true;

    const request = context.switchToHttp().getRequest();
    const userId = request.user?.userId;

    if (!userId) {
      throw new UnauthorizedException('Authentication required');
    }

    const rolesMetadata = this.reflector.getMetadata('roles', handler);
    if (rolesMetadata) {
      const userRoles = this.authorizationService.getRolesForUser(userId);
      const hasRole = rolesMetadata.some(r => userRoles.includes(r));
      if (!hasRole) {
        throw new ForbiddenException('Access denied');
      }
    }

    return true;
  }
}
```

**Responsibilities:**
- ✅ Verify authentication
- ✅ Check permissions
- ✅ Return 401/403
- ❌ Redirect
- ❌ Trust client

### Usage Examples

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

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

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

// app/dashboard/page.tsx
export default function DashboardPage() {
  return <DashboardContent />;
}
```

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

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

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

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

#### API Endpoint
```typescript
// apps/api/src/domain/league/LeagueController.ts
@Controller('leagues')
export class LeagueController {
  @Get(':leagueId/admin')
  @RequireAuthenticatedUser()
  @RequireRoles('admin')
  getLeagueAdmin(@Param('leagueId') leagueId: string) {
    // Service verifies league-specific permissions
    return this.leagueService.getAdminData(leagueId);
  }
}
```

## Benefits

### 1. **Predictable Flow**
```
User Request → Middleware (check cookie) → API (auth + authz) → Controller → Response → Client (handle errors)
```

### 2. **Easy Debugging**
```bash
# Check middleware
curl -I http://localhost:3000/dashboard

# Check API auth
curl -I http://localhost:3000/api/admin/users \
  -H "Cookie: gp_session=token"

# Check client session
# Browser console: console.log(useAuth().session)
```

### 3. **i18n Ready**
```typescript
// Future: Switch locales by changing config
const routesDe = { ...routes, auth: { login: '/de/auth/login' } };
const routesEs = { ...routes, auth: { login: '/es/auth/login' } };

// All code uses routes.auth.login, so switching is trivial
```

### 4. **Type Safety**
```typescript
// Compile-time checking
routes.league.detail('123'); // ✅ Works
routes.league.detail();      // ❌ Error: requires string

// Parameter validation
const path = buildPath('league.detail', { id: '123' }); // ✅
const path = buildPath('league.detail', {});            // ❌ Error
```

### 5. **Maintainable**
- **One file** to change all routes
- **No hardcoded paths** anywhere else
- **Clear separation** of concerns
- **Easy to test** each layer independently

## Migration Checklist

### Phase 1: Foundation (1 day)
- [x] Create `RouteConfig.ts` with all routes
- [x] Update `middleware.ts` to use route config
- [x] Remove hardcoded paths from middleware

### Phase 2: Guards (2 days)
- [x] Create `AuthGuard.tsx` with route config
- [x] Create `RoleGuard.tsx` with route config
- [x] Remove old `RouteGuard` and `AuthGuard` files
- [x] Remove `AuthGateway` and `AuthorizationBlocker`

### Phase 3: Route Updates (2 days)
- [ ] Update all route layouts to use new guards
- [ ] Remove redundant page-level checks
- [ ] Test all redirect flows

### Phase 4: API Verification (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`
- [ ] `/admin` → Redirects to `/auth/login?returnTo=/admin`
- [ ] `/leagues` → Works (public)
- [ ] `/auth/login` → Works (public)

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

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

### 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 chaos by:

1. **One Source of Truth**: All routes in `RouteConfig.ts`
2. **Clear Layers**: Middleware → API → Guards → Controller
3. **No Hardcoded Paths**: Everything uses the config
4. **i18n Ready**: Easy to add localized routes
5. **Type Safe**: Compile-time route validation
6. **Easy to Debug**: Each layer has one job

**Result**: Clean, predictable, secure authentication that just works.