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
97 lines
2.4 KiB
TypeScript
97 lines
2.4 KiB
TypeScript
/**
|
|
* 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
|
|
})
|