Builds the service over a store, a runtime, and the issuer name an authenticator app will display.
The enrolment store, the runtime services, and the issuer
Begins a TOTP enrolment for a principal.
Generates a 20-byte secret, encodes it as base32 (RFC 4648, no padding),
and builds the otpauth:// URI for the authenticator app.
A principal with NO confirmed factor stores the new secret as the
unconfirmed one. A principal who already has a confirmed factor gets the new
secret stored as a PENDING secret: the proven secret keeps verifying, and
lastClaimedStep keeps its value, until confirmEnrolment
presents a code from the new secret. Beginning an enrolment therefore cannot
silently replace a working factor with an unproven one, and cannot reopen
the replay of an already-claimed step.
The principal to enrol
The label shown in the authenticator app (usually the user's email)
The base32 secret and the otpauth:// URI
Completes a pending sign-in with a TOTP code.
Reads the pending principal from the AUTH_SESSION capability, verifies
the code for THAT principal (never a caller-supplied id), and asks the
auth-session service to promote the pending record to the signed-in key. An
unconfirmed enrolment answers 'not-enrolled': see verify.
The request context
The six-digit TOTP code
The completion result
Completes a pending sign-in with a recovery code.
Reads the pending principal from the AUTH_SESSION capability, verifies
and consumes the recovery code for THAT principal, and asks the
auth-session service to promote the pending record to the signed-in key.
A recovery code completes the second factor with method 'otp': RFC 8176
registers no recovery-code value, so a policy cannot tell the two apart.
The request context
The recovery code
The completion result
Confirms a TOTP enrolment with a valid code from the secret awaiting confirmation, and mints the principal's recovery codes.
A FIRST enrolment (no confirmed factor yet) needs only the new code. A
RE-enrolment — a confirmed factor exists and beginEnrolment
stored a pending secret — also needs proof of the CURRENT factor: a code
from it or an unused recovery code. Without that rule anyone able to call
this with a principal id could swap in their own authenticator and take the
account's second factor; with it, replacing a factor takes the factor.
On success the pending secret becomes the active one, lastClaimedStep
stays monotonic across the swap, and a fresh set of ten recovery codes
REPLACES any earlier set — so recovery codes only ever exist for a proven
factor. The plaintext codes are returned this once.
Called for a confirmed factor with NO re-enrolment pending, code is
checked against the confirmed secret, so it is itself the proof; that call
re-mints the recovery codes.
The principal whose enrolment is confirmed
A six-digit code from the secret awaiting confirmation
Optionalproof: string
For a re-enrolment, a code from the current factor or an unused recovery code
The recovery codes on success, otherwise why it was refused
Disables TOTP for a principal, deleting the enrolment, recovery codes, and attempt counts.
A confirmed factor needs proof of itself (a TOTP code or an unused
recovery code): disabling it and then enrolling a new authenticator would
otherwise be a takeover by anyone able to name the principal. An
unconfirmed enrolment is removed without proof, since it was never a
factor. An administrator reset that cannot obtain proof calls the store's
deleteEnrolment directly, behind its own authorization.
The principal to disable TOTP for
Optionalproof: string
A code from the current factor or an unused recovery code
'ok', or why the call was refused
Regenerates the ten recovery codes for a principal with a confirmed factor, replacing the earlier set.
Requires proof of the current factor (a TOTP code or an unused recovery
code): regenerating invalidates every code the user holds and hands the new
ones to the caller, so without proof it would let anyone able to name the
principal mint codes that complete its sign-in. The first set is minted by
confirmEnrolment, not here.
Each code is 10 random bytes encoded as 16 base32 characters (80 bits), stored as a SHA-256 digest; the plaintext is returned this once.
The principal to regenerate codes for
Optionalproof: string
A code from the current factor or an unused recovery code
The new codes, or why the regeneration was refused
Verifies a TOTP code for a principal.
Checks lockout first (5 attempts in 15 minutes), then verifies the code against the current step and ±TOTP_WINDOW. A valid code claims its step (preventing replay) and clears the attempt count.
Only a CONFIRMED factor verifies. An enrolment that was begun and never
confirmed answers 'not-enrolled': an unconfirmed secret has never been
shown to reach the user's authenticator, so it is not yet a factor the
framework will sign anyone in with. confirmEnrolment is the one
path that accepts an unconfirmed secret, and it is what confirms it.
The principal to verify for
The six-digit code
The verification result
Verifies and consumes a recovery code.
Checks lockout first, then computes the SHA-256 digest of the code and atomically consumes it through the store. A match clears the attempt count.
The accepted form is the generated one, in any letter case and with any
surrounding whitespace: the code is trimmed and upper-cased, then must be
exactly 16 characters of the base32 alphabet. Anything else — a shorter
string, a character outside A-Z2-7, or a valid code with extra characters
appended — answers 'invalid' without consulting the store. That strictness
matters: decodeBase32 drops leftover bits, so a 17-character string can
decode to the same bytes as the 16-character code inside it.
The principal to verify for
The recovery code
The verification result
TOTP service for enrolment, verification, recovery codes, and sign-in completion.
The application instantiates this service and calls its methods from route handlers. The
completeSignInandcompleteSignInWithRecoveryCodemethods are the only sign-in path: they read the pending principal from theAUTH_SESSIONcapability, verify the code for that principal, and promote the pending record through the auth-session service.