class

KemalIdentity::Postgres::SessionRepository

Inherits KemalIdentity::Sessions::Repository < Reference < Object

Sessions::Repository over auth_sessions.

The hot path is #find_by_digest, and it is the one query whose cost lands on every authenticated request. Everything else runs at login, at logout, or in a background sweep.

Constants

SESSION_COLUMNS = "s.id, s.account_id, s.tenant_id, s.token_digest, s.auth_version, s.assurance,\ns.created_at, s.authenticated_at, s.mfa_verified_at, s.password_verified_at,\ns.last_seen_at, s.idle_expires_at, s.absolute_expires_at, s.revoked_at"
UNIQUE_VIOLATION = "23505"

PostgreSQL's SQLSTATE for a unique violation.

Constructors

new(db : DB::Database, accounts_table : String = "auth_accounts")

accounts_table exists because auth_accounts is a reference implementation. An application authenticating against its own users table joins against that instead, and still satisfies the same contract — the contract spec asserts the result shape, never the SQL.

Source

Instance methods

create(record : Sessions::Record) : Nil

Stores a new session.

Raises KemalIdentity::InfrastructureError if record.token_digest is already present. This is not defensive noise: auth_sessions.token_digest carries a unique index precisely so that a digest collision is a loud database error rather than a silent security failure in which two accounts share a session (docs/03-data-model.md). An implementation that upserted here would convert that error into exactly the failure the index exists to prevent.

Source
delete_expired(before : Time) : Int32

Deletes rows whose absolute_expires_at is at or before before, returning the count.

Disk reclamation only. Correctness never depends on this having run: expiry is evaluated on every read, which is the direct lesson of kemal-session issue #116, where a timeout only marked a session for deletion at the next GC pass and a read could refresh its access time before any expiry check, reviving it (docs/02-security-model.md).

Source
delete_revoked_before(before : Time) : Int32

Deletes revoked rows whose revoked_at is at or before before, returning the count.

Separate from #delete_expired because a revoked session is not necessarily an expired one: logging out at nine in the morning revokes a row whose absolute deadline is still hours away, and that row is worth keeping for a while. It is the evidence behind "you were signed out of this device", and deleting it the instant it is revoked throws away the only record that the logout happened.

The retention window is the application's to choose. Disk reclamation either way — correctness never depends on this having run, because revocation is evaluated on read.

Source
find_by_digest(digest : Bytes) : Sessions::Lookup | Nil

One indexed lookup with a join, returning session state and account status together.

Decision D7. Fetching the session and then fetching the account is two round trips on every authenticated request, which roughly doubles the fixed cost of every page view for no benefit (docs/03-data-model.md).

An inner join, so a session pointing at an account that no longer exists resolves to nothing: the failure mode is closed rather than open.

Source
revoke(id : String, at : Time) : Bool

Marks one session revoked, returning false if it does not exist or was already revoked.

Already-revoked returns false rather than raising: logging out twice is not an error, and the caller learns whether it changed anything.

Source
revoke_all_for_account(account_id : String, at : Time, except_id : String | Nil = nil) : Int32

Revokes every live session for an account, optionally sparing one, and returns how many it revoked.

except_id is what makes "log out everywhere else" and "change password without logging myself out" possible. Already-revoked sessions are not counted and not re-stamped, so the count is the number of sessions actually ended.

Source
touch(id : String, last_seen_at : Time, idle_expires_at : Time) : Bool

Moves last_seen_at and idle_expires_at forward for one session.

Called only when the throttle in SessionService allows it, never on every request. Returns false if no such session exists.

Source