Feat: Agregar middleware global de autenticación API
All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 1m9s

- Valida Bearer token para rutas expuestas (/api/messages/send, /api/mcp)
- Valida x-authentik-username para rutas protegidas por Authentik
- Rutas públicas (/api/health) sin autenticación
- Defensa en profundidad: cualquier endpoint /api/* está protegido
This commit is contained in:
2025-12-04 14:32:18 -06:00
parent ae4c674a0f
commit f6a58a4d16

View File

@@ -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
})