Plataforma técnica · Orchestrator
Sessions
Orchestrator Command Sessions
SessionService provee visibilidad en tiempo real de quién está conectado y un kill switch para terminar sesiones forzosamente. Es la herramienta de respuesta a incidentes: token comprometido, usuario olvidó cerrar sesión en un kiosko público, sospecha de actividad anormal.
Diferencia con autenticación
Section titled “Diferencia con autenticación”SessionService no autentica (eso es AuthService / routes/command/auth.ts). Solo lee y borra registros en auth.user_sessions. La creación de sesiones ocurre en el flujo de login (POST /api/auth/login emite la cookie sid y el INSERT de la sesión).
Modelo de Sesión Activa
Section titled “Modelo de Sesión Activa”ActiveSession (lo que devuelve GET /api/sessions):
| Campo | Origen SQL | Notas |
|---|---|---|
session_id | s.id | UUID, usado para el kill switch. |
user_id | s.user_id | |
username | u.username | LEFT JOIN — 'Unknown' si NULL. |
role | u.role | LEFT JOIN — 'USER' si NULL. |
tenant_id | t.id | Vía u.tenant_id. |
tenant_name | t.business_name | |
ip_address | s.ip_address | Capturada al login. |
user_agent | s.user_agent | |
login_time | s.created_at | |
last_activity | s.created_at (fallback) | No hay columna real de last_activity. |
duration_minutes | EXTRACT(EPOCH FROM (NOW() - s.created_at))/60 | Redondeado. |
Consulta de Sesiones Activas
Section titled “Consulta de Sesiones Activas”La query principal de SessionRepository.findActiveSessions:
SELECTs.id AS session_id,s.user_id,s.created_at AS login_time,s.expires_at,s.ip_address,s.user_agent,u.username,u.role,t.id AS tenant_id,t.business_name AS tenant_name,EXTRACT(EPOCH FROM (NOW() - s.created_at))/60 AS duration_minutesFROM auth.user_sessions sLEFT JOIN auth.users u ON s.user_id = u.idLEFT JOIN command.tenants t ON u.tenant_id = t.idWHERE s.expires_at > NOW()ORDER BY s.created_at DESC;Filtro de actividad: el WHERE s.expires_at > NOW() excluye sesiones ya vencidas. No hay limpieza activa de filas vencidas — quedan en la tabla y son ignoradas por esta query (y por authenticateToken).
Endpoints
Section titled “Endpoints”GET /api/sessions
Section titled “GET /api/sessions”Lista todas las sesiones activas. Cualquier rol autorizado por authorizeRoute puede consultar (típicamente ADMIN y SUPER_ADMIN).
curl -b "sid=$JWT" http://localhost:8000/api/sessionsRespuesta (array, ejemplo abreviado):
[{ "session_id": "550e8400-e29b-41d4-a716-446655440000", "user_id": "a1b2c3d4-...", "username": "chris", "role": "SUPER_ADMIN", "tenant_id": "...", "tenant_name": "Albornoz Contadores SpA", "ip_address": "192.168.1.10", "user_agent": "Mozilla/5.0 (...)", "login_time": "2026-05-24T10:00:00.000Z", "last_activity": "2026-05-24T10:00:00.000Z", "duration_minutes": 47}]DELETE /api/sessions/:id
Section titled “DELETE /api/sessions/:id”Termina la sesión forzosamente (kill switch). El usuario perderá acceso en su próxima request porque authenticateToken no encontrará el registro en auth.user_sessions.
curl -X DELETE -b "sid=$JWT" \ "http://localhost:8000/api/sessions/550e8400-e29b-41d4-a716-446655440000"Variantes aceptadas por el router (compatibilidad con clientes):
| Forma | Equivalencia |
|---|---|
DELETE /api/sessions/:id | Path param. |
DELETE /api/sessions?id=<uuid> | Query param (mismo handler). |
Códigos:
| Status | Significado |
|---|---|
| 200 | { "success": true } — sesión terminada. |
| 400 | Missing session id. |
| 404 | Sesión no existe o ya estaba terminada. |
Flujo de Kill Switch
Section titled “Flujo de Kill Switch”sequenceDiagram
autonumber
participant Admin as Super Admin
participant API as Orchestrator
participant DB as auth.user_sessions
participant User as Cliente del usuario
Admin->>API: DELETE /api/sessions/{id}
API->>DB: DELETE FROM auth.user_sessions WHERE id=$1
DB-->>API: rowCount
alt rowCount > 0
API-->>Admin: 200 { success: true }
else 0
API-->>Admin: 404 not found
end
Note over User: Usuario sigue navegando con la cookie sid
User->>API: GET /api/employees
API->>DB: SELECT * FROM auth.user_sessions WHERE id=$sid
DB-->>API: vacío
API-->>User: 401 Unauthorized Degradación segura
Section titled “Degradación segura”SessionRepository devuelve un array vacío en lugar de lanzar si la query falla, para que el dashboard no quede en 500:
catch (e) { console.error("Error fetching active sessions:", e); return [];}Esto significa que un fallo de DB se manifiesta como “0 sesiones activas” en el dashboard. Monitorear console.error (o el audit log) para distinguir cero real vs error.
Por qué no hay UPDATE
Section titled “Por qué no hay UPDATE”Las sesiones son inmutables en el modelo: o existen y son válidas, o se borran. No hay “extender expiración” como operación administrativa porque eso requeriría re-autenticar de todos modos. El logout normal del usuario también es un DELETE (desde routes/command/auth.ts), no un UPDATE expires_at = NOW().