Setu-TS
    Preparing search index...

    Class TotpService

    TOTP service for enrolment, verification, recovery codes, and sign-in completion.

    The application instantiates this service and calls its methods from route handlers. The completeSignIn and completeSignInWithRecoveryCode methods are the only sign-in path: they read the pending principal from the AUTH_SESSION capability, verify the code for that principal, and promote the pending record through the auth-session service.

    Index
    • 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.

      Parameters

      • principalId: string

        The principal to enrol

      • label: string

        The label shown in the authenticator app (usually the user's email)

      Returns Promise<{ secret: string; uri: string }>

      The base32 secret and the otpauth:// URI

    • 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.

      Parameters

      Returns Promise<TotpCompleteSignInResult>

      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.

      Parameters

      • principalId: string

        The principal whose enrolment is confirmed

      • code: string

        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

      Returns Promise<ConfirmEnrolmentResult>

      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.

      Parameters

      • principalId: string

        The principal to disable TOTP for

      • Optionalproof: string

        A code from the current factor or an unused recovery code

      Returns Promise<DisableResult>

      '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.

      Parameters

      • principalId: string

        The principal to regenerate codes for

      • Optionalproof: string

        A code from the current factor or an unused recovery code

      Returns Promise<RecoveryCodesResult>

      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.

      Parameters

      • principalId: string

        The principal to verify for

      • code: string

        The six-digit code

      Returns Promise<TotpVerifyResult>

      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.

      Parameters

      • principalId: string

        The principal to verify for

      • code: string

        The recovery code

      Returns Promise<RecoveryVerifyResult>

      The verification result