Implementar autenticación Authentik completa
All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 25s

- Backend: Nuevos endpoints /api/auth/status y /api/auth/check-group
- Frontend: Composable useAuthentik para gestión de autenticación
- Frontend: Componentes UserDropdown, UserAvatar, SessionStatusButton, GroupCheckButton
- Frontend: Integración en topbar con dropdown de usuario
- Config: URLs de Authentik y configuración de avatares
- Lectura de headers x-authentik-* inyectados por Traefik
- Verificación de grupos RBAC (frontend y backend)
- Validación de sesión contra Authentik
This commit is contained in:
2025-10-17 04:47:30 -06:00
parent ad18d22c7e
commit 918ca465d6
9 changed files with 679 additions and 0 deletions

View File

@@ -4,6 +4,7 @@ import path from 'path';
import { fileURLToPath } from 'url';
import apiRouter from './routes/api.js';
import radiusRouter from './routes/radius.js';
import authRouter from './routes/auth.js';
export function createApp() {
const app = express();
@@ -20,6 +21,9 @@ export function createApp() {
// REST API
app.use('/api', apiRouter);
// Auth API
app.use('/api/auth', authRouter);
// Simple health endpoint for reverse proxies / checks
app.get('/healthz', (_req, res) => res.json({ ok: true }));

View File

@@ -0,0 +1,90 @@
import { Router } from 'express';
const router = Router();
/**
* Endpoint para verificar el estado de autenticación en tiempo real
* Consulta los headers inyectados por Authentik Proxy Outpost
*/
router.get('/status', (req, res) => {
// Establecer headers para prevenir caching
res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate');
res.setHeader('Pragma', 'no-cache');
res.setHeader('Expires', '0');
// Leer headers de Authentik en tiempo real
const headers = req.headers;
const username = headers['x-authentik-username'];
const email = headers['x-authentik-email'];
const name = headers['x-authentik-name'];
const groups = headers['x-authentik-groups'];
const uid = headers['x-authentik-uid'];
const appSlug = headers['x-authentik-meta-app'];
const outpostName = headers['x-authentik-meta-outpost'];
// Si no hay username, no hay sesión activa en Authentik
if (!username) {
return res.json({
authenticated: false,
user: null,
timestamp: new Date().toISOString()
});
}
// Sesión activa
res.json({
authenticated: true,
user: {
username,
email,
name,
groups: groups ? groups.split('|').filter(g => g.trim()) : [],
uid,
appSlug,
outpostName
},
timestamp: new Date().toISOString()
});
});
/**
* Endpoint para verificar membresía de grupo desde el backend
* Valida contra los headers de Authentik en el servidor
*/
router.post('/check-group', (req, res) => {
const { groupName } = req.body || {};
if (!groupName || typeof groupName !== 'string') {
return res.status(400).json({
ok: false,
error: 'Group name is required'
});
}
// Leer headers de Authentik
const headers = req.headers;
const authentikGroups = headers['x-authentik-groups'];
// Si no hay header de grupos, el usuario no está autenticado o no tiene grupos
if (!authentikGroups) {
return res.json({
hasGroup: false,
groups: []
});
}
// Parsear los grupos (separados por |)
const userGroups = authentikGroups.split('|').filter(g => g.trim());
// Verificar si el usuario tiene el grupo solicitado
const hasGroup = userGroups.includes(groupName);
res.json({
hasGroup,
groups: userGroups,
checkedGroup: groupName
});
});
export default router;