> ## Documentation Index
> Fetch the complete documentation index at: https://internal.softcrum.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Sesiones y gestión de dispositivos

> Cuando esto se entregue, un usuario puede ver todos los dispositivos conectados a su cuenta y cortar el que dejó en la oficina de un cliente.

> Traducción. Autoritativo: [`fs-idn-0004-sessions-and-devices.md`](/modules/identity/features/fs-idn-0004-sessions-and-devices).

## Contexto

Probar quién eres y seguir probado son problemas distintos. La autenticación (FS-IDN-0003) es un
intercambio de credenciales que ocurre una vez; una sesión es una afirmación permanente que tiene que
ser revocable, inspeccionable y acotada — y es lo que un atacante efectivamente roba.

La tensión es conocida: las sesiones cortas son más seguras y empujan a los usuarios a debilitar su
propia seguridad por fastidio. La resolución acá es una **sesión larga con reautenticación en
acciones sensibles**, para que el trabajo diario no se interrumpa mientras las operaciones que
importan vuelven a preguntar.

Un detalle que se desprende de FS-IDN-0001: las sesiones se revocan por organización. Desactivar a
alguien de una cuenta no debe sacarlo de las otras.

## Alcance *(normativo)*

* `identity.sessions` con dispositivo, IP, user agent y expiración.
* Listado y revocación por sesión desde el propio usuario.
* Revocación masiva ante cambio de contraseña, cambio de MFA y desactivación de membresía.
* Re-prompt de autenticación en acciones sensibles.
* Revocación acotada por organización.
* Detección de sesión sospechosa: dispositivo nuevo o ubicación lejana notifica al usuario.

## Fuera de alcance *(normativo)*

* Credenciales de máquina — FS-IDN-0006. Una API key no es una sesión y no tiene dispositivo.
* Sesiones de member, que viven en `core`.
* Allowlist de IP, que es endurecimiento opcional por tenant (DEC-D6) y pertenece a una feature
  posterior de política de seguridad.

## Comportamiento *(normativo)*

1. Una sesión registra dispositivo, IP y user agent al crearse y actualiza `last_seen_at`. El usuario
   ve todo eso — una lista de sesiones que no dice *dónde* no es accionable.
2. Las sesiones son **revocables individualmente, en masa y por organización**. La revocación surte
   efecto en el siguiente request, nunca al expirar la caché.
3. Una sesión revocada **nunca se borra**: la fila se conserva con `revoked_at` y la razón, porque
   "cuándo se cortó ese acceso" es una pregunta de incidente.
4. Las acciones sensibles **vuelven a pedir autenticación** sin importar la antigüedad de la sesión:
   cambiar contraseña, cambiar MFA, crear o rotar una API key, otorgar un permiso sensible, iniciar
   una impersonación. La lista vive en el código como decorador, no en un documento que se desactualiza.
5. Un cambio de contraseña, de MFA o una desactivación de membresía revocan las sesiones
   correspondientes automáticamente y notifican.
6. Un ingreso desde un dispositivo no reconocido notifica por correo. Notificación, no bloqueo:
   bloquear ante un dispositivo nuevo convierte un viaje en un ticket de soporte.
7. Los tokens de sesión son opacos, hasheados en reposo, y rotan al cambiar privilegios, de modo que
   un token capturado antes de un cambio de rol no puede usar el nuevo rol.
8. La expiración es absoluta, no solo por inactividad. Una sesión solo-inactividad vive para siempre
   en una máquina que nadie usa.

## Datos *(normativo)*

| Tabla               | Invariantes clave                                                                                                                                                                                                                                       |
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `identity.sessions` | `token_hash` único; FK `user_id`; `organization_id` nullable; `device_label`, `ip`, `user_agent`; `created_at`, `last_seen_at`, `expires_at`, `revoked_at`, `revoked_reason`; conservada tras revocarse; índice (`user_id`, `revoked_at`, `expires_at`) |

## API *(normativo)*

| Endpoint                                | Clase      | Permiso            | Presupuesto  |
| --------------------------------------- | ---------- | ------------------ | ------------ |
| `GET /v1/identity/sessions`             | Management | dueño de la sesión | p95 \<300 ms |
| `DELETE /v1/identity/sessions/{id}`     | Management | dueño de la sesión | p95 \<300 ms |
| `POST /v1/identity/sessions/revoke-all` | Management | dueño de la sesión | p95 \<1 s    |
| `POST /v1/identity/auth/reauthenticate` | Runtime    | sesión activa      | p95 \<300 ms |

## Eventos *(normativo)*

Ninguno en el outbox. El ciclo de vida de sesión queda auditado; las notificaciones de dispositivo
nuevo van por `messaging` como categoría `product`.

## Criterios de aceptación *(normativo)*

1. Una sesión revocada se rechaza en su siguiente request.
2. Revocar una sesión de la organización A deja válida la del mismo usuario en la B.
3. Un cambio de contraseña revoca todas las demás sesiones y deja viva la actual.
4. Toda acción sensible vuelve a pedir autenticación, probado enumerando las rutas decoradas.
5. Un token capturado antes de un cambio de rol no puede ejercer el nuevo rol.
6. Una fila de sesión revocada sigue legible un año después con su razón.
7. Un ingreso desde dispositivo no reconocido produce exactamente una notificación.
8. **Negativo:** ninguna sesión sobrevive su expiración absoluta, por reciente que sea su uso.

## Ejecución

Un solo slice, comando síncrono. El almacenamiento es el de Better Auth con nuestras columnas; la
validación se cachea en Upstash y la revocación invalida la entrada.

## Preguntas abiertas

| # | Pregunta                                                                                                      | Decide | Para             |
| - | ------------------------------------------------------------------------------------------------------------- | ------ | ---------------- |
| 1 | Duración absoluta de sesión — ¿12 horas, o 30 días con re-prompts en acciones sensibles? (pregunta 2 del PRD) | Daniel | antes de aprobar |
| 2 | ¿La lista de acciones sensibles es configurable por organización, o fija?                                     | Daniel | antes de aprobar |

## Changelog

| Versión | Fecha      | Cambio           | Por qué | Autor                  |
| ------- | ---------- | ---------------- | ------- | ---------------------- |
| 0.1.0   | 2026-08-17 | Borrador inicial | —       | daniel + claude-opus-5 |

## Registro de entrega

*Aún no implementado.*
