Skip to content
This repository was archived by the owner on Apr 10, 2026. It is now read-only.
This repository was archived by the owner on Apr 10, 2026. It is now read-only.

feat(frontend): 2FA — setup & management in Settings panel #54

@dajbelshaw

Description

@dajbelshaw

Part of #50.

Location

Add a "Two-factor authentication" section to SettingsPanel.tsx (Security section, below the password change area if one exists, or as a new Security group).

API additions (api.ts)

setupTotp(): Promise<{ otpauthUrl: string; secret: string }>
// POST /api/user/totp/setup

confirmTotp(code: string): Promise<{ backupCodes: string[] }>
// POST /api/user/totp/confirm

disableTotp(code: string): Promise<void>
// DELETE /api/user/totp

Also add totpEnabled: boolean to AuthUser interface (returned by /api/auth/me).

UI states

State A — 2FA disabled

Two-factor authentication          [Enable]
Not enabled

Clicking Enable → starts setup flow (State B).

State B — Setup flow (multi-step within settings)

Step 1: Scan QR code

  • Render QR code from otpauthUrl (use qrcode npm package in the frontend, or a <canvas>-based renderer — keep it client-side, no external services)
  • Show the base32 secret as a fallback for manual entry (monospace, copy button)
  • "Can't scan? Enter this code manually: XXXX XXXX XXXX XXXX"
  • [Continue] button → Step 2

Step 2: Verify setup

  • "Enter the 6-digit code from your authenticator app to confirm setup."
  • TOTP code input (same as login TOTP step)
  • [Confirm] / [Back]
  • On success → Step 3

Step 3: Save your backup codes

  • Display 8 backup codes in a 2×4 grid, monospace font
  • [Download codes] button — triggers download of a .txt file: taskdial-backup-codes.txt
  • Warning: "These codes will not be shown again. Store them somewhere safe."
  • [Done] → State C

State C — 2FA enabled

Two-factor authentication          [Disable]
Enabled ✓
Backup codes: 8 remaining          [Regenerate]

backupCodesCount returned from /api/auth/me or a separate endpoint.

Clicking Disable → prompt for current TOTP code (or backup code) → api.disableTotp(code) → State A.

Clicking Regenerate → same confirm step as setup (verify TOTP first) → new backup codes shown once.

Component structure

Keep the setup flow as local state within the settings panel — no new routes needed. Use the same card/section styling as existing settings groups.

AuthUser type change

export interface AuthUser {
  id: string;
  email: string;
  role: string;
  onboardingComplete?: boolean;
  totpEnabled: boolean;        // new
  backupCodesCount?: number;   // new — only meaningful when totpEnabled
}

Server /api/auth/me must return these fields.

Metadata

Metadata

Assignees

No one assigned

    Labels

    authAuthentication & sessionsenhancementNew feature or requestuxUser experience / interface

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions