class

KemalIdentity::Postgres::AccountRepository

Inherits KemalIdentity::Accounts::Repository < Reference < Object

Accounts::Repository over the reference auth_accounts table.

A reference implementation, not a requirement. An application that already has users.email and users.password_digest implements the contract over that table and never creates auth_accounts at all — that distinction is the difference between "adoptable incrementally" and "rewrite your user model first" (docs/03-data-model.md). This class exists so there is something to point at, and so the contract has a second implementation keeping the in-memory double honest.

Constants

COLUMNS = "id, tenant_id, normalized_login, email_verified_at, disabled_at,\nauth_version, password_digest, password_scheme, created_at, updated_at"

Every column the contract needs, named explicitly rather than SELECT *: a column added to the table later must not silently change what this reads.

Constructors

Instance methods

bump_auth_version(id : String) : Int32 | Nil

Increments auth_version and returns the new value, or nil if no such account exists.

Invalidates every session for the account without enumerating rows: each session stores the auth_version it was minted under, and a mismatch fails the session on its next read. Used on password change and MFA recovery, alongside explicit revocation rather than instead of it (docs/02-security-model.md).

Also the answer for a change to the account's tenant, which is the one authorization input a session copies: without a bump or an explicit revocation, sessions that already exist keep the tenant they were minted with until they expire (Sessions::Record#tenant_id).

Source
find_by_id(id : String) : Accounts::Account | Nil

The account with this id, or nil.

Source
find_by_login(normalized_login : String, tenant_id : String | Nil = nil) : Accounts::Account | Nil

The tenant_id IS NULL case is split out rather than folded into one clever predicate.

tenant_id = NULL matches nothing in SQL — it is not false, it is unknown — so the single-tenant lookup has to say IS NULL explicitly. Writing the obvious parameterised query instead returns no rows for every single-tenant application, and the contract spec has a named example for it.

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

Records that this account's address has been proved, and returns false if no such account exists.

Idempotent: confirming twice is not an error, and the second call moves the timestamp forward rather than refusing. A user who clicks a link twice has not done anything wrong.

Source
update_password_digest(id : String, digest : String, scheme : String, at : Time) : Bool

Replaces the stored digest and scheme, and moves updated_at to at.

This is the lazy-rehash write: a successful login against a digest at an outdated cost rehashes at the current one, so old digests disappear as people sign in and nobody is forced through a password reset (docs/06-roadmap.md).

Returns false if no such account exists. Does not revoke sessions — a rehash of the same password is not a credential change, and revoking here would log everyone out of the application that just upgraded its cost.

Source