> ## 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.

# Sessions and Device Management

> After this ships, a user can see every device signed into their account and cut off the one they left at a client's office.

## Context

Proving who you are and staying proven are different problems. Authentication (FS-IDN-0003) is a
credential exchange that happens once; a session is a standing claim that has to be revocable,
inspectable and bounded — and it is what an attacker actually steals.

The tension is a familiar one: short sessions are safer and drive users to weaken their own security
out of irritation. The resolution here is a **long session with re-authentication on sensitive
actions**, so daily work is uninterrupted while the operations that matter ask again.

An organization-scoped detail follows from FS-IDN-0001: a user in three organizations has sessions
that can be revoked per organization. Deactivating someone from one account must not sign them out
of the other two.

## Scope *(normative)*

* `identity.sessions` with device, IP, user agent and expiry.
* Session listing and per-session revocation by the user.
* Bulk revocation on password change, MFA change and membership deactivation.
* Re-authentication prompts on sensitive actions.
* Organization-scoped revocation.
* Suspicious-session detection: a new device or a distant location notifies the user.

## Non-scope *(normative)*

* Machine credentials — FS-IDN-0006. An API key is not a session and has no device.
* Member sessions, which live in `core`.
* IP allowlisting, which is a per-tenant hardening option (DEC-D6) and belongs to a later security
  policy feature.

## Behaviour *(normative)*

1. A session records device, IP and user agent at creation and updates `last_seen_at`. The user can
   see all of it — a session list that does not say *where* is not actionable.
2. Sessions are **revocable individually, in bulk, and per organization**. Revocation takes effect
   on the next request, never on the next cache expiry.
3. A revoked session is **never deleted**: the row is retained with `revoked_at` and the reason,
   because "when did that access stop" is an incident question.
4. Sensitive actions **re-prompt for authentication** regardless of session age: changing a password,
   changing MFA, creating or rotating an API key, granting a sensitive permission, starting an
   impersonation. The list is in the code as a decorator, not in a document that drifts.
5. A password change, an MFA change or a membership deactivation revokes the relevant sessions
   automatically and notifies the user.
6. A sign-in from an unrecognised device notifies the user by email. Notification, not blocking:
   blocking on a new device makes travel a support ticket.
7. Session tokens are opaque, hashed at rest, and rotated on privilege change so a token captured
   before a role change cannot use the new role.
8. Expiry is absolute, not only idle. An idle-only session lives forever on a machine nobody uses.

## Data *(normative)*

| Table               | Key invariants                                                                                                                                                                                                                                                                     |
| ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `identity.sessions` | `token_hash` unique; FK `user_id`; `organization_id` nullable (null = not yet scoped); `device_label`, `ip`, `user_agent`; `created_at`, `last_seen_at`, `expires_at`, `revoked_at`, `revoked_reason`; retained after revocation; index on (`user_id`, `revoked_at`, `expires_at`) |

## API *(normative)*

| Endpoint                                | Class      | Permission     | Budget       |
| --------------------------------------- | ---------- | -------------- | ------------ |
| `GET /v1/identity/sessions`             | Management | session-owner  | p95 \<300 ms |
| `DELETE /v1/identity/sessions/{id}`     | Management | session-owner  | p95 \<300 ms |
| `POST /v1/identity/sessions/revoke-all` | Management | session-owner  | p95 \<1 s    |
| `POST /v1/identity/auth/reauthenticate` | Runtime    | active session | p95 \<300 ms |

## Events *(normative)*

None on the outbox. Session lifecycle is audit-logged; new-device notifications go through
`messaging` as a `product` category message, driven by the audit entry rather than a domain event.

## Acceptance criteria *(normative)*

1. A revoked session is rejected on its very next request.
2. Revoking a session for organization A leaves the same user's session for organization B valid.
3. A password change revokes every other session and leaves the current one alive.
4. Every sensitive action re-prompts, proven by a test that enumerates the decorated routes.
5. A token captured before a role change cannot exercise the new role.
6. A revoked session row is still readable a year later with its reason.
7. A sign-in from an unrecognised device produces exactly one notification.
8. **Negative:** no session survives its absolute expiry, however recently it was used.

## Execution

Single slice, synchronous command. Session storage is Better Auth's with our columns added;
validation is cached in Upstash with revocation invalidating the entry.

## Open questions

| # | Question                                                                                                     | Decides | By              |
| - | ------------------------------------------------------------------------------------------------------------ | ------- | --------------- |
| 1 | Absolute session lifetime — 12 hours, or 30 days with re-prompts on sensitive actions? (PRD open question 2) | Daniel  | before approval |
| 2 | Is the sensitive-action list per organization configurable, or fixed by us?                                  | Daniel  | before approval |

## Changelog

| Version | Date       | Change        | Why | Author                 |
| ------- | ---------- | ------------- | --- | ---------------------- |
| 0.1.0   | 2026-08-17 | Initial draft | —   | daniel + claude-opus-5 |

## Delivery record

*Not implemented yet.*
