KemalIdentity::Sessions::Record
One row of auth_sessions: everything the shard knows about a live session.
The browser holds a high-entropy random secret and nothing else. This is the server-side
half, and it stores only the digest of that secret — so a leaked database backup
yields no usable session cookies (docs/02-security-model.md).
Constructors
Instance methods
The account's auth_version when this session was minted. A mismatch against the
account's current value fails the session.
Never prints the digest. It is not a password, but it is the value that resolves a
session, and docs/02-security-model.md forbids logging it.
Moved forward as the user stays active, but throttled: only written when
now - last_seen_at exceeds the configured touch_interval (60 s by default).
Without that throttle, every authenticated read becomes a write, which is the single
biggest performance trap in this design. The cost is that idle expiry is accurate only
to within one touch_interval, which is part of the contract rather than an
implementation accident (docs/02-security-model.md).
When the password behind this session was last actually typed, if it ever was.
Separate from authenticated_at because that one is restamped by every assurance
increase, and separate from assurance because a level says one factor was proved
without saying which: a federated login sits at Password too. nil for a session no
password produced — a remembered browser, a federated login, an adopted legacy session —
and a guard reads nil as "no", never as "unknown, allow it".
The account's tenant as it was when this session was minted, and the only authorization input this shard copies into a session.
Service#resolve rebuilds the principal from this row, not from the account, so a change
to the account's tenant is not felt by a session that already exists — for the rest of
that session's life, which is a longer window than anything else in the authorization
path has. Membership and roles are read on every decision; a grants Cache bounds itself
to a minute; this is bounded only by the session's absolute deadline.
It is a copy on purpose: Lookup deliberately does not carry the account's tenant, and
widening it would put another column of a join on every authenticated request for a value
that changes once in an account's lifetime, if ever. The consequence is that an
application that changes an account's tenant must revoke that account's sessions or bump
its auth_version — docs/02-security-model.md lists it with the other events that
require this, and Accounts::Repository#bump_auth_version is the one-row way to do it.
SHA-256 of the raw token, as raw bytes.
Bytes rather than a hex string: BYTEA is half the storage of a hex CHAR(64) and
there is no encoding for two adapters to disagree about (docs/03-data-model.md).