From 62e1ae9ce5ffa4fa536fb0f12f6f77e02ab1428c Mon Sep 17 00:00:00 2001 From: Hendrik Belitz Date: Wed, 16 Jul 2025 15:58:22 +0200 Subject: [PATCH] #53 Auth handler updates --- API_ROUTES_AUTH.md | 102 +++++++++++++++++++++++++++++++++ src/server-hooks/authHandle.ts | 44 +++++++++++--- 2 files changed, 139 insertions(+), 7 deletions(-) create mode 100644 API_ROUTES_AUTH.md diff --git a/API_ROUTES_AUTH.md b/API_ROUTES_AUTH.md new file mode 100644 index 0000000..3d2bf9a --- /dev/null +++ b/API_ROUTES_AUTH.md @@ -0,0 +1,102 @@ +# API Routes Authentication Overview + +This document provides a comprehensive overview of all API routes and their authentication requirements. + +## 🔓 Public Routes (No Authentication Required) + +### Authentication Routes + +- `/api/auth/challenge` - Generate WebAuthn challenge +- `/api/auth/login` - User login (WebAuthn or passphrase) +- `/api/auth/register` - User registration +- `/api/auth/confirm` - Email confirmation +- `/api/auth/resend-confirmation` - Resend confirmation email + +### System Routes + +- `/api/health` - Health check +- `/api/health/services` - Service health status +- `/api/docs` - API documentation +- `/api/openapi.json` - OpenAPI specification +- `/api/env` - Environment information +- `/api/log` - Logging endpoint + +### Admin Setup Routes + +- `/api/admin/init` - Initialize first global admin +- `/api/admin/exists` - Check if global admin exists + +## 🔒 Protected Routes (Authentication Required) + +### Protected Auth Routes (Any Authenticated User) + +- `/api/auth/logout` - User logout +- `/api/auth/refresh` - Refresh authentication tokens +- `/api/auth/session` - Get current session info +- `/api/auth/sessions` - Manage user sessions +- `/api/auth/sessions/[sessionId]` - Manage specific session +- `/api/auth/passkeys` - Add additional WebAuthn keys + +### Global Admin Routes (GLOBAL_ADMIN Role Required) + +- `/api/tenants` - Create and list tenants +- `/api/tenants/[id]` - Manage specific tenant +- `/api/tenants/[id]/config` - Tenant configuration +- `/api/tenants/config/defaults` - Default tenant configuration + +### Admin Routes (ADMIN Role Required) + +- `/api/admin/tenant` - Tenant management for admins + +## 📋 Route Categories + +### By Authentication Level: + +1. **Public**: 12 routes - No authentication required +2. **Protected Auth**: 6 routes - Any authenticated user +3. **Global Admin**: 5 routes - GLOBAL_ADMIN role required + +### By Functionality: + +- **Authentication**: 11 routes (5 public, 6 protected) +- **Admin Management**: 2 routes (2 public) +- **Tenant Management**: 5 routes (5 global admin) +- **System/Utility**: 6 routes (6 public) + +## 🔧 Implementation Details + +### Authentication Flow: + +1. **Session Cookie**: `session` cookie for browser-based auth +2. **Bearer Token**: Authorization header for API access +3. **Role Checking**: `AuthorizationService.hasRole()` for role verification + +### Security Features: + +- JWT token verification +- Session validation +- Role-based access control (RBAC) +- Proper HTTP status codes (401 vs 403) +- Detailed logging for security events + +### Configuration Location: + +- Main config: `src/server-hooks/authHandle.ts` +- Authorization service: `src/lib/server/auth/authorization-service.ts` +- Session service: `src/lib/server/auth/session-service.ts` + +## 🚨 Security Considerations + +1. **Public Routes**: Should be carefully reviewed for security implications +2. **Protected Routes**: Require valid authentication but no specific roles +3. **Global Admin Routes**: High privilege routes requiring GLOBAL_ADMIN role +4. **Rate Limiting**: Applied to all routes via `rateLimitHandle` +5. **CORS**: Properly configured for API access +6. **HTTPS**: Security headers enforced in production + +## 📝 Notes + +- All routes are automatically covered by the authentication middleware +- New API routes should be explicitly added to the appropriate category +- Role requirements are enforced at the middleware level +- Session management is handled automatically for authenticated routes diff --git a/src/server-hooks/authHandle.ts b/src/server-hooks/authHandle.ts index 242be57..720530e 100644 --- a/src/server-hooks/authHandle.ts +++ b/src/server-hooks/authHandle.ts @@ -8,31 +8,58 @@ import { AuthorizationService } from "$lib/server/auth/authorization-service"; const logger = new UniversalLogger().setContext("AuthHandle"); const SESSION_COOKIE_NAME = "session"; -const PROTECTED_PATHS = ["/api/admin", "/api/tenant-admin"]; +const PROTECTED_PATHS = ["/api/admin", "/api/tenant-admin", "/api/tenants", "/api/auth/register"]; const PUBLIC_PATHS = [ - "/api/auth", + "/api/auth/challenge", + "/api/auth/login", + + "/api/auth/confirm", + "/api/auth/resend-confirmation", "/api/health", "/api/docs", + "/api/openapi.json", + "/api/env", + "/api/log", "/api/admin/init", - "/api/admin/confirm", "/api/admin/exists" ]; -const GLOBAL_ADMIN_PATHS = ["/api/admin"]; +const GLOBAL_ADMIN_PATHS = ["/api/admin", "/api/tenants"]; const ADMIN_PATHS = ["/api/tenant-admin"]; +const PROTECTED_AUTH_PATHS = [ + "/api/auth/logout", + "/api/auth/refresh", + "/api/auth/session", + "/api/auth/sessions", + "/api/auth/passkeys" +]; + export const authHandle: Handle = async ({ event, resolve }) => { const { url, request } = event; const path = url.pathname; const isProtectedPath = PROTECTED_PATHS.some((protectedPath) => path.startsWith(protectedPath)); const isPublicPath = PUBLIC_PATHS.some((publicPath) => path.startsWith(publicPath)); + const isProtectedAuthPath = PROTECTED_AUTH_PATHS.some((authPath) => path.startsWith(authPath)); const isGlobalAdminPath = GLOBAL_ADMIN_PATHS.some((gadPath) => path.startsWith(gadPath)); const isAdminPath = ADMIN_PATHS.some((gadPath) => path.startsWith(gadPath)); - if (!isProtectedPath || isPublicPath) { + // Allow public paths + if (isPublicPath) { return resolve(event); } + // Require authentication for protected paths and protected auth paths + if (!isProtectedPath && !isProtectedAuthPath) { + return new Response( + JSON.stringify({ error: `Path ${path} not handled by auth. This is an error` }), + { + status: 400, + headers: { "Content-Type": "application/json" } + } + ); + } + let sessionToken: string | null = null; let accessToken: string | null = null; @@ -87,9 +114,10 @@ export const authHandle: Handle = async ({ event, resolve }) => { event.locals.user = user; event.locals.sessionToken = sessionToken || undefined; + if (isGlobalAdminPath && !AuthorizationService.hasRole(user, "GLOBAL_ADMIN")) { return new Response(JSON.stringify({ error: "Authentication failed" }), { - status: 401, + status: 403, headers: { "Content-Type": "application/json" } }); } else if ( @@ -97,10 +125,12 @@ export const authHandle: Handle = async ({ event, resolve }) => { !AuthorizationService.hasAnyRole(user, ["GLOBAL_ADMIN", "TENANT_ADMIN"]) ) { return new Response(JSON.stringify({ error: "Authentication failed" }), { - status: 401, + status: 403, headers: { "Content-Type": "application/json" } }); } + // Protected auth paths require any authenticated user (no specific role required) + // The authentication check above is sufficient logger.debug(`User authenticated: ${user.email} for ${path}`);