class

KemalIdentity::SQLite::ActionTokenRepository

Inherits KemalIdentity::Accounts::ActionTokenRepository < Reference < Object

Accounts::ActionTokenRepository over auth_action_tokens, SQLite dialect.

#consume is a single conditional UPDATE ... RETURNING, exactly as in PostgreSQL, and for the same reason: a read followed by a write lets two concurrent requests both spend one reset link. SQLite serialises writers on the database rather than on the row, which makes the race harder to lose, but the statement shape is what the contract requires and it is what the concurrency example checks.

Constants

COLUMNS = "id, account_id, purpose, token_digest, created_at, expires_at, used_at"

Constructors

Instance methods

consume(digest : Bytes, purpose : Accounts::ActionPurpose, at : Time) : Accounts::ActionToken | Nil

Spends the token with this digest, for this purpose, and returns it — or nil.

Atomic: exactly one of any number of concurrent callers gets the token back.

purpose is part of the condition, not a label checked afterwards. A token issued to confirm an email address must not be redeemable to reset a password, or anybody able to trigger a confirmation message gets an account takeover.

Returns nil for expired, already used, wrong purpose, and unknown alike.

Source
create(token : Accounts::ActionToken) : Nil

Stores a newly issued token.

Raises KemalIdentity::InfrastructureError if the digest is already present. As with sessions, the unique index exists so that a collision is a loud error rather than two grants sharing a secret.

Source
delete_expired(before : Time) : Int32

Deletes rows past their expiry, returning the count.

Disk reclamation only. Correctness never depends on it: expiry is evaluated inside #consume.

Source
revoke_all_for_account(account_id : String, purpose : Accounts::ActionPurpose, at : Time) : Int32

Marks every outstanding token of this purpose for this account as used, returning how many it spent.

Issuing a new reset link invalidates the previous ones, so a link sitting in an old email — or in an inbox somebody else now controls — stops working. Also the right response to a completed password change.

Source