diff --git a/server/middleware/01.api-auth.ts b/server/middleware/01.api-auth.ts new file mode 100644 index 0000000..e845366 --- /dev/null +++ b/server/middleware/01.api-auth.ts @@ -0,0 +1,96 @@ +/** + * Middleware de autenticación para API + * + * Asegura que TODAS las rutas /api/* estén protegidas: + * - Rutas con Authentik: Requieren header x-authentik-username + * - Rutas sin Authentik (expuestas): Requieren Bearer token válido + */ + +import { query } from '../utils/database' + +// Rutas que NO pasan por Authentik (expuestas con API Key) +const API_KEY_ROUTES = [ + '/api/messages/send', + '/api/mcp' +] + +// Rutas públicas (sin autenticación) +const PUBLIC_ROUTES = [ + '/api/health' +] + +export default defineEventHandler(async (event) => { + const path = getRequestURL(event).pathname + + // Solo aplicar a rutas /api/* + if (!path.startsWith('/api/')) { + return + } + + // Rutas públicas - sin autenticación + if (PUBLIC_ROUTES.some(route => path.startsWith(route))) { + return + } + + // Verificar si es ruta expuesta (sin Authentik) + const isExposedRoute = API_KEY_ROUTES.some(route => path.startsWith(route)) + + if (isExposedRoute) { + // Ruta expuesta: Requiere Bearer token + const authHeader = getHeader(event, 'authorization') + + if (!authHeader?.startsWith('Bearer ')) { + throw createError({ + statusCode: 401, + message: 'Authorization Bearer token required' + }) + } + + const token = authHeader.slice(7) + const config = useRuntimeConfig() + + // Validar contra Master API Key + if (config.masterApiKey && token === config.masterApiKey) { + // Token válido - continuar + return + } + + // Validar contra API Keys en DB + const crypto = await import('crypto') + const keyHash = crypto.createHash('sha256').update(token).digest('hex') + + const keyResult = await query( + `SELECT id FROM api_keys + WHERE key_hash = $1 AND is_active = TRUE + AND (expires_at IS NULL OR expires_at > NOW())`, + [keyHash] + ) + + if (keyResult.rows.length === 0) { + throw createError({ + statusCode: 401, + message: 'Invalid API Key' + }) + } + + // Token válido - actualizar last_used + await query( + 'UPDATE api_keys SET last_used_at = NOW() WHERE id = $1', + [keyResult.rows[0].id] + ) + + return + } + + // Ruta protegida por Authentik: Requiere header x-authentik-username + const authentikUser = getHeader(event, 'x-authentik-username') + + if (!authentikUser) { + throw createError({ + statusCode: 401, + message: 'Authentication required' + }) + } + + // Usuario autenticado - continuar +})