Resumen
Endpoint que permite al frontend web (usuario logueado) recuperar el token de autenticación local de un Agent perteneciente a su misma organización. El token se devuelve descifrado, listo para que el frontend lo use en requests a localhost:5050.
Aislamiento multi-tenant: solo usuarios de la misma organización que el Agent pueden acceder a su token.
Inerte en Fase 1: el frontend aún no lo llama.
Especificación
Ruta
GET /api/agent/local-token?agent_id=<agent_id>
Autenticación
- Sesión de navegador (portero global): solo usuarios logueados.
- Permiso requerido:
fleet:view(ver detalles de flotas/agents). - No admite Agent JWT: aunque un Agent tuviera sesión, este endpoint rechaza Agent JWTs.
Query Parameters
agent_id(string, requerido): ID del Agent cuyo token se solicita.
Response
200 OK (éxito):
{
"local_token": "<opaco>" // token descifrado (era local_token_enc en BD)
}
403 Forbidden (sin sesión, sin permiso, o no es tu organización):
{
"error": "Session authentication required" // o "No organization in context"
}
404 Not Found (Agent no existe en tu org, o no ha depositado token aún):
{
"error": "Agent not found" // o "Agent has not registered a local token yet"
}
Implementación
Fichero
terminal/api/auth.py (líneas ~240–260, commit 61b0447).
Sub-componentes
-
Portero global de sesión:
- Verifica
request.userlogueado. - Rechaza requests anónimos (status 403).
- Verifica
-
require_perm(request, "fleet", "view"):- Verifica que el usuario tenga permiso explícito.
- Lanza excepción si falta (capturada por Django Ninja → 403).
-
get_current_org(request):- Extrae la organización del usuario (o del contexto de la sesión).
- Devuelve
Nonesi no hay org (response 403).
-
Aislamiento por organización:
agent = AgentInstance.objects.get(agent_id=agent_id, organization=org)- QuerySet filtrado por org del usuario (RLS efectivo).
- Si el Agent pertenece a OTRA org,
DoesNotExist→ 404.
-
Desciframiento:
CredentialManager.decrypt_credential(agent.local_token_enc)- Usa la misma
CredentialManagerque cifró (Fernet simétrico). - Devuelve el token en claro (seguro: solo navegador web logueado lo recibe).
- Usa la misma
-
Validación de existencia:
- Si
agent.local_token_encestá vacío, devuelve 404 (Agent no ha depositado token).
- Si
Flujo
- Frontend web (usuario logueado en sesión) quiere acceder al Agent local.
- Frontend envía
GET /api/agent/local-token?agent_id=ag_xyz. - Servidor verifica sesión +
fleet:view+ queag_xyzsea del usuario. - Servidor descifra
AgentInstance.local_token_enc. - Servidor responde con el token en claro.
- Frontend guarda el token en sesión/memoria y lo usa en requests a
localhost:5050.
Seguridad
- Multi-tenancy: QuerySet filtrado por
organization(RLS). - Sesión requerida: Agent JWTs no autentican este endpoint.
- Permiso granular:
fleet:view(noadmin). - Desciframiento solo en respuesta: el token nunca viaja cifrado al cliente (es opacodentro de la BD, descifrado en la response).
- Logs sin exposición: no se registra el token en claro.
Testing
Cubierto en tests/api/test_terminal_local_token.py::TestGetLocalToken:
test_round_trip_register_then_fetch: Agent deposita, frontend recoge descifrado.test_cross_tenant_isolation: admin de org B no puede leer token de agent en org A (404).test_requires_session_not_agent_jwt: Agent JWT rechazado (403).test_readonly_forbidden: usuario con permisoview_onlyrechazado (403).test_token_not_yet_registered_404: Agent sin token registrado aún (404).
Fase 1 — Inerte
Este endpoint no es llamado por el frontend hasta la Fase 2, cuando se actualice la lógica de conexión local en la web.
Véase también
- [[decision—20260610—terminal-auth-local-cross-origin]]
- [[entity—terminal—endpoint—register-local-token]]
- [[entity—terminal—model—agentinstance]]
- [[concept—saas—multi-tenancy]]
- [[feature—terminal—auth-local-agent-fase1]]