{
  "slug": "auth-2fa",
  "spec_file": "docs/specs/2026-05-31-auth-2fa.md",
  "findings": [
    {
      "id": "auth-2fa-001",
      "severity": "P1",
      "type": "missing",
      "summary": "Profile Security tab with Enable 2FA not wired into app routes",
      "spec_ref": "Screens — /profile Security tab",
      "code_ref": "apps/zync-app/src/features/security/SecurityTab.tsx:37",
      "evidence": "SecurityTab and Enroll2FAModal exist but grep SecurityTab imports across apps/zync-app only finds the component file itself; no /profile route mounts it.",
      "repro": ""
    },
    {
      "id": "auth-2fa-002",
      "severity": "P1",
      "type": "broken",
      "summary": "Tenant enforce_2fa PATCH client hits wrong path/method vs server",
      "spec_ref": "Tenant-wide 2FA Enforcement — toggle sets tenants.enforce_2fa",
      "code_ref": "apps/zync-app/src/features/settings/SecuritySettingsPage.tsx:56",
      "evidence": "UI PATCHes /api/settings/security; server enforce_2fa handler is PATCH /api/auth/settings/security (auth/security-settings.ts:27) while /api/settings/security only exposes GET/PUT for idle-timeout policy (security-settings.ts).",
      "repro": ""
    },
    {
      "id": "auth-2fa-003",
      "severity": "P2",
      "type": "divergent",
      "summary": "GET /api/auth/me reports twoFactorEnabled false always",
      "spec_ref": "Screens — Current 2FA status",
      "code_ref": "apps/zync-api/src/routes/auth/me.ts:44",
      "evidence": "me handler hardcodes twoFactorEnabled: false instead of reading users.two_factor_enabled.",
      "repro": ""
    },
    {
      "id": "auth-2fa-004",
      "severity": "P2",
      "type": "divergent",
      "summary": "Forced-setup stores session_token in sessionStorage not memory-only",
      "spec_ref": "Login Flow — session_token stored in memory (not localStorage)",
      "code_ref": "apps/zync-app/src/features/auth/LoginForm.tsx:73",
      "evidence": "requires_2fa_setup branch calls sessionStorage.setItem('pending_2fa_setup_token', ...); spec says client keeps session_token in memory only.",
      "repro": ""
    },
    {
      "id": "auth-2fa-005",
      "severity": "P2",
      "type": "divergent",
      "summary": "2FA settings page reads enforce_2fa from /api/auth/me which omits tenant flags",
      "spec_ref": "Screens — /settings/security enforce toggle",
      "code_ref": "apps/zync-app/src/features/settings/SecuritySettingsPage.tsx:37",
      "evidence": "fetchSecuritySettings calls GET /api/auth/me and expects tenant.enforce_2fa; me response has no enforce_2fa or disable_2fa_remember_device fields.",
      "repro": ""
    },
    {
      "id": "auth-2fa-006",
      "severity": "P3",
      "type": "cosmetic",
      "summary": "Duplicate SecuritySettingsPage components for session vs 2FA settings",
      "spec_ref": "Screens — /settings/security",
      "code_ref": "apps/zync-app/src/routes/settings/security.tsx:23",
      "evidence": "Two unrelated SecuritySettingsPage implementations exist (routes/settings/security.tsx for sessions, features/settings/SecuritySettingsPage.tsx for 2FA); only sessions variant is partially built and neither is routed.",
      "repro": ""
    }
  ],
  "summary": { "P0": 0, "P1": 2, "P2": 3, "P3": 1, "total": 6 }
}
