Skip to content

TOTP-Based Two-Factor Authentication (Additive Second Factor) #545

Description

@Abidoyesimze

Problem Statement

Authentication is entirely wallet-signature-based (src/controllers/auth-controller.ts: challenge/verify against a Stellar keypair). That's a strong primary factor, but it's a single factor — anyone who can produce a valid signature (a compromised device, a phished signing request) gets a full session with no second check. There's a formatOtpMessage/extractOtpCode pair in src/whatsapp/handler.ts, but that's a one-time code for linking a WhatsApp number to an account, not a login-time second factor. This issue adds optional TOTP-based two-factor authentication as an additive layer on top of the existing wallet-signature auth, for users who want it.

Current State

  • src/controllers/auth-controller.ts — challenge/verify/refresh/logout; session issuance on a valid signature alone.
  • src/routes/sessions.ts (Session & Device Management API (List, Name, Revoke) #376) — device/session management, the natural place to also manage 2FA enrollment.
  • src/whatsapp/handler.ts — OTP for account linking (a different, narrower mechanism — worth reusing the pattern of short-lived code generation/verification, not the code itself).
  • No TOTP secret field or verification step exists anywhere in the auth path (confirmed).

Proposed Solution

  1. TotpCredential: userId, secretEncrypted (encrypted at rest, reusing whatever encryption-key pattern src/keys/registry.ts already establishes for sensitive secrets), verifiedAt, recoveryCodesHashed[] (one-time backup codes, hashed, for when the authenticator device is unavailable).
  2. Enrollment: POST /api/v1/2fa/enroll generates a secret + QR-code payload (standard otpauth:// URI), POST /api/v1/2fa/verify-enrollment confirms with a code from the user's authenticator app before it becomes active — never enabled from just generating a secret.
  3. Login flow: when a user has an active TotpCredential, verify (post wallet-signature check) returns a requiresTotp challenge instead of a session; POST /api/v1/auth/2fa/verify with a valid code completes the login. Wallet signature remains factor one; TOTP is factor two, additive.
  4. Recovery: one-time backup codes issued at enrollment (shown once, hashed at rest), each usable exactly once; regenerating the set invalidates all previous codes.
  5. POST /api/v1/2fa/disable requires a fresh wallet-signature challenge (not just an active session) — disabling 2FA is a security downgrade and should require the same proof-of-control as enabling it.

Edge Cases & Failure Modes

  • User loses authenticator device and backup codes: falls to the guardian-based social recovery flow (Guardian-Based Social Recovery for Account Access #535) if that lands, or a manual, clearly-logged admin-assisted recovery path with strong identity re-verification — documented as a real, acknowledged friction point (2FA that can always be silently bypassed isn't 2FA).
  • Clock skew on TOTP verification: standard ±1 time-step tolerance window, not wider (wider windows weaken the guarantee).
  • Code replay: a used code (even if still within its time window) is rejected on reuse — track the last-accepted step per credential.
  • Enrollment abandoned mid-flow (secret generated, never verified): unverified secrets expire and are never treated as active — no zombie unverified-but-somehow-enforced 2FA.
  • Backup code reuse: rejected, same as TOTP replay.

Security & Privacy Considerations

  • This is a security-hardening feature by nature — the main risk to guard against is the feature itself becoming a lockout vector (hence the explicit recovery path requirements) or a downgrade vector (hence requiring fresh proof-of-control to disable, not just an active session which could itself be the compromised asset).
  • secretEncrypted and backup codes are never returned after initial issuance; no plaintext secret logging.
  • 2FA enrollment/disable events are audit-logged and trigger the same multi-channel security notification pattern as new-session alerts (Session & Device Management API (List, Name, Revoke) #376).

Out of Scope

  • Hardware security keys / WebAuthn (a distinct, separate issue — see the sibling passkey proposal).
  • Mandatory 2FA for all users (opt-in in v1; a mandate is a policy decision, not this issue's scope).
  • SMS-based 2FA (TOTP only — SMS is generally considered a weaker second factor and adds a telecom dependency).

Suggested Implementation Plan

  1. TotpCredential model (encrypted secret, hashed backup codes) + migration/rollback.
  2. Enrollment + verify-enrollment endpoints (QR/otpauth URI generation).
  3. Login-flow integration: requiresTotp challenge step after wallet-signature verification.
  4. Backup-code issuance/consumption/regeneration; replay protection (last-accepted-step tracking).
  5. Fresh-signature-required disable flow; audit logging + security notifications; docs/SESSIONS.md or a new docs/2FA.md + docs/openapi.yaml.

Acceptance Criteria

  • A user can enroll TOTP 2FA, confirmed by a verification code before activation; the secret is encrypted at rest and never re-exposed
  • An active TotpCredential requires a valid TOTP code as a second factor after wallet-signature verification, with standard ±1 step clock tolerance and replay protection
  • One-time hashed backup codes are issued at enrollment, each usable once, regenerable (invalidating the prior set)
  • Disabling 2FA requires a fresh wallet-signature challenge, not just an active session
  • Enrollment, verification, and disable events are audit-logged and trigger security notifications
  • An abandoned/unverified enrollment never becomes active or enforced
  • docs/2FA.md + docs/openapi.yaml added; unit + integration tests green

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

GrantFox OSSIssue tracked in GrantFox OSSMaybe RewardedIssue may be eligible for a GrantFox rewardOfficial Campaign | FWC26Campaign: Official Campaign | FWC26Stellar WaveIssues in the Stellar wave programThird CampaignCampaign: Third CampaignenhancementNew feature or request

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions