KemalIdentity::Testing::MemoryMfaRepository
Inherits KemalIdentity::MFA::Repository < Reference < Object
In-memory MFA::Repository, passing the same contract the database adapters must.
The two single-use operations hold the mutex across their check and their write, which is what "one statement" means with no statements available. A double that checked and then wrote outside the lock would pass every single-fiber spec and quietly disagree with a real adapter about the one property that matters.
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.
Marks enrolment finished, recording the counter that proved it.
Returns false if the factor does not exist or was already confirmed. Confirming twice
is not an error — a double-submitted form is a normal thing — but the caller still
learns nothing changed, and the first timestamp is the one an audit trail wants.
Records that counter has now been used, if and only if it is strictly greater than
whatever this factor was last used at.
Returns false when the counter has already been spent, which the caller must treat as
a failed verification even though the code itself was arithmetically correct. This is
the replay defence, and it must be one statement — see the note above.
Spends the code with this digest, if it exists for this account and is unused.
Returns false for an unknown or already-spent code. One statement, for the same reason
as #consume_counter: two requests carrying the same code must not both succeed.
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.
Marks a factor disabled, returning false if it does not exist or was already disabled.
at is stamped rather than defaulted so the caller's clock is the one on the row. A
disabled factor must stop authenticating — see Factor#usable? — while remaining
visible to a management listing, so this is a flag and not a delete.
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.
Records that a code offered for this factor was wrong, returning the new consecutive
failure count — or nil if the factor does not exist.
One statement (UPDATE ... SET consecutive_failures = consecutive_failures + 1 ... RETURNING consecutive_failures, or the dialect's equivalent), because the count is what
a lifetime bound is enforced against and two parallel wrong guesses must count as two.
A read followed by a write loses one of them, which is the direction that favours the
guesser.
Replaces an account's recovery codes with codes, atomically.
Atomic 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. Regenerating is exactly the operation somebody performs when they think the old codes leaked.