KemalIdentity::SQLite::MfaRepository
Inherits KemalIdentity::MFA::Repository < Reference < Object
MFA::Repository over auth_mfa_factors and auth_mfa_recovery_codes.
The two single-use operations are each one statement, for the reason
blueprints/0011-action-token-atomicity.md gives: a read followed by a write passes every
spec written against one fiber and fails against two, and here failing means a replayed
TOTP code or a recovery code spent twice.
Constants
Constructors
Instance methods
Zeroes the consecutive failure count. What a successful verification calls.
Returns false if the factor does not exist. Idempotent: a factor already at zero is
not an error, since every success calls this and most successes follow a success.
AND confirmed_at IS NULL reports whether anything changed and stops a second
confirmation overwriting the first timestamp, which is the one an audit trail wants.
The replay defence, as one statement. last_used_counter IS NULL OR ... < ? is what makes
"check that this counter is new" and "record that it is now used" the same operation, so
two requests carrying the same intercepted code cannot both succeed.
Single use, as one statement, for the same reason as #consume_counter.
Stores a newly enrolled, unconfirmed factor.
Raises InfrastructureError if the id already exists, rather than overwriting: a
collision means the id source is broken, and silently replacing an enrolled factor is
how somebody loses access to their account.
Removes every factor for an account, returning how many. What "disable MFA" calls.
AND disabled_at IS NULL reports whether anything changed and keeps the first timestamp,
which is the one an audit trail wants.
Every factor for an account, confirmed or not, oldest first. Unconfirmed ones are included because the enrolment screen has to show what is half-finished.
One statement, and the count comes back from the same one: two parallel wrong guesses must count as two, and a read followed by a write loses one of them — in the direction that favours whoever is guessing.
SQLite has had RETURNING since 3.35 (2021). query_one? rather than query_one
answers nil for a factor that is not there instead of raising.
One transaction, because the two halves are a security hole apart: an account left briefly with no codes cannot recover, and one left briefly with both sets has old codes that were supposed to be void.