@burdenoff/microfe-auth 2026.527.1 → 2026.527.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/dist/auth/AuthRoutes.js +1 -1
  2. package/dist/auth/AuthRoutes.js.map +1 -1
  3. package/dist/auth/components/AuthLayout.js +14 -14
  4. package/dist/auth/components/AuthLayout.js.map +1 -1
  5. package/dist/auth/components/CaptchaV2.js +1 -4
  6. package/dist/auth/components/CaptchaV2.js.map +1 -1
  7. package/dist/auth/components/ConfirmActionDialog.js +3 -3
  8. package/dist/auth/components/ConfirmActionDialog.js.map +1 -1
  9. package/dist/auth/components/profile/UserProfileMenu.js +10 -6
  10. package/dist/auth/components/profile/UserProfileMenu.js.map +1 -1
  11. package/dist/auth/components/workspace/WorkspaceSelector.js +5 -5
  12. package/dist/auth/components/workspace/WorkspaceSelector.js.map +1 -1
  13. package/dist/auth/hooks/index.js +2 -2
  14. package/dist/auth/index.js +4 -4
  15. package/dist/auth/pages/AuditLogPage.js +7 -7
  16. package/dist/auth/pages/AuditLogPage.js.map +1 -1
  17. package/dist/auth/pages/EmailVerifyPage.js +10 -8
  18. package/dist/auth/pages/EmailVerifyPage.js.map +1 -1
  19. package/dist/auth/pages/LoginPage.js +20 -19
  20. package/dist/auth/pages/LoginPage.js.map +1 -1
  21. package/dist/auth/pages/MagicLinkPage.js +10 -8
  22. package/dist/auth/pages/MagicLinkPage.js.map +1 -1
  23. package/dist/auth/pages/MfaSetupPage.js +26 -21
  24. package/dist/auth/pages/MfaSetupPage.js.map +1 -1
  25. package/dist/auth/pages/MfaVerifyPage.js +21 -21
  26. package/dist/auth/pages/MfaVerifyPage.js.map +1 -1
  27. package/dist/auth/pages/OAuthCallbackPage.js +7 -6
  28. package/dist/auth/pages/OAuthCallbackPage.js.map +1 -1
  29. package/dist/auth/pages/PasswordResetPage.js +25 -22
  30. package/dist/auth/pages/PasswordResetPage.js.map +1 -1
  31. package/dist/auth/pages/ReactivatePage.js +17 -15
  32. package/dist/auth/pages/ReactivatePage.js.map +1 -1
  33. package/dist/auth/pages/RegisterPage.js +198 -194
  34. package/dist/auth/pages/RegisterPage.js.map +1 -1
  35. package/dist/auth/pages/SSOClearPage.js +1 -1
  36. package/dist/auth/pages/SSOClearPage.js.map +1 -1
  37. package/dist/auth/pages/SSODepositPage.js +1 -1
  38. package/dist/auth/pages/SSODepositPage.js.map +1 -1
  39. package/dist/auth/pages/SSORelayCallbackPage.js +1 -1
  40. package/dist/auth/pages/SSORelayCallbackPage.js.map +1 -1
  41. package/dist/auth/pages/SSORelayPage.js +1 -1
  42. package/dist/auth/pages/SSORelayPage.js.map +1 -1
  43. package/dist/auth/pages/SecurityPage.js +15 -16
  44. package/dist/auth/pages/SecurityPage.js.map +1 -1
  45. package/dist/auth/pages/SessionsPage.js +19 -20
  46. package/dist/auth/pages/SessionsPage.js.map +1 -1
  47. package/dist/auth/pages/SsoCallbackPage.js +19 -15
  48. package/dist/auth/pages/SsoCallbackPage.js.map +1 -1
  49. package/dist/auth/pages/SsoConfigPage.js +41 -27
  50. package/dist/auth/pages/SsoConfigPage.js.map +1 -1
  51. package/dist/auth/pages/SsoLoginPage.js +16 -14
  52. package/dist/auth/pages/SsoLoginPage.js.map +1 -1
  53. package/dist/auth/pages/UserDetailsPage.js +16 -17
  54. package/dist/auth/pages/UserDetailsPage.js.map +1 -1
  55. package/dist/auth/pages/UserProfilePage.js +31 -25
  56. package/dist/auth/pages/UserProfilePage.js.map +1 -1
  57. package/dist/auth/pages/UsersPage.js +46 -42
  58. package/dist/auth/pages/UsersPage.js.map +1 -1
  59. package/dist/index.js +11 -11
  60. package/dist/microfe-auth.css +1 -1
  61. package/package.json +1 -1
@@ -1,7 +1,6 @@
1
1
  import { useAuthStore as e } from "../store/authStore.js";
2
- import { useMfaOperations as t } from "../hooks/useMfaOperations.js";
3
- import "../hooks/index.js";
4
- import { AlertIcon as n, SpinnerIcon as r } from "../components/AuthIcons.js";
2
+ import { AlertIcon as t, SpinnerIcon as n } from "../components/AuthIcons.js";
3
+ import { useMfaOperations as r } from "../hooks/useMfaOperations.js";
5
4
  import { QRCodeSVG as i } from "../../node_modules/qrcode.react/lib/esm/index.js";
6
5
  import { ConfirmActionDialog as a } from "../components/ConfirmActionDialog.js";
7
6
  import { nativeCopyText as o, nativeImpact as s, nativeNotify as c } from "../../utils/nativeBridge.js";
@@ -13,7 +12,7 @@ function m() {
13
12
  let { t: m } = u(), h = (e, t) => {
14
13
  let n = m(e);
15
14
  return n === e ? t : n;
16
- }, { setupMFA: g, enableMFA: _, disableMFA: v, isLoading: y } = t(), b = e((e) => e.user), x = e((e) => e.isAuthenticated), [S, C] = l(null), [w, T] = l(""), [E, D] = l(null), [O, k] = l(null), [A, j] = l(!1), [M, N] = l(!1), P = async (e) => {
15
+ }, { setupMFA: g, enableMFA: _, disableMFA: v, isLoading: y } = r(), b = e((e) => e.user), x = e((e) => e.isAuthenticated), [S, C] = l(null), [w, T] = l(""), [E, D] = l(null), [O, k] = l(null), [A, j] = l(!1), [M, N] = l(!1), P = async (e) => {
17
16
  try {
18
17
  D(null), k(null), C(await g(e));
19
18
  } catch (e) {
@@ -54,7 +53,7 @@ function m() {
54
53
  children: /* @__PURE__ */ p("div", {
55
54
  className: "flex items-center gap-3",
56
55
  children: [/* @__PURE__ */ f("svg", {
57
- className: "w-5 h-5 text-[var(--color-status-success-text)]",
56
+ className: "size-5 text-[var(--color-status-success-text)]",
58
57
  fill: "none",
59
58
  viewBox: "0 0 24 24",
60
59
  stroke: "currentColor",
@@ -72,12 +71,13 @@ function m() {
72
71
  }),
73
72
  E && /* @__PURE__ */ p("div", {
74
73
  className: "mb-6 flex items-start gap-3 p-4 rounded-xl bg-[var(--color-status-error-bg-subtle)] border border-[var(--color-status-error-border)]",
75
- children: [/* @__PURE__ */ f(n, { className: "w-5 h-5 text-[var(--color-status-error-text)] flex-shrink-0 mt-0.5" }), /* @__PURE__ */ p("div", {
74
+ children: [/* @__PURE__ */ f(t, { className: "size-5 text-[var(--color-status-error-text)] flex-shrink-0 mt-0.5" }), /* @__PURE__ */ p("div", {
76
75
  className: "flex-1",
77
76
  children: [/* @__PURE__ */ f("p", {
78
77
  className: "text-sm text-[var(--color-status-error-text)]",
79
78
  children: E
80
79
  }), /* @__PURE__ */ f("button", {
80
+ type: "button",
81
81
  onClick: () => D(null),
82
82
  className: "mt-2 text-sm text-[var(--color-status-error-text)] hover:underline transition-colors duration-200",
83
83
  children: h("auth.mfaSetup.dismiss", "Dismiss")
@@ -89,9 +89,9 @@ function m() {
89
89
  children: [/* @__PURE__ */ p("div", {
90
90
  className: "flex items-center gap-3 mb-6",
91
91
  children: [/* @__PURE__ */ f("div", {
92
- className: "w-10 h-10 rounded-full bg-[var(--color-bg-accent)] flex items-center justify-center",
92
+ className: "size-10 rounded-full bg-[var(--color-bg-accent)] flex items-center justify-center",
93
93
  children: /* @__PURE__ */ f("svg", {
94
- className: "w-5 h-5 text-[var(--color-text-link)]",
94
+ className: "size-5 text-[var(--color-text-link)]",
95
95
  fill: "none",
96
96
  viewBox: "0 0 24 24",
97
97
  stroke: "currentColor",
@@ -121,7 +121,7 @@ function m() {
121
121
  size: 192,
122
122
  level: "M",
123
123
  includeMargin: !1,
124
- className: "w-48 h-48"
124
+ className: "size-48"
125
125
  })
126
126
  })
127
127
  }),
@@ -138,7 +138,7 @@ function m() {
138
138
  /* @__PURE__ */ p("div", {
139
139
  className: "flex items-start gap-3 mb-3",
140
140
  children: [/* @__PURE__ */ f("svg", {
141
- className: "w-5 h-5 text-[var(--color-status-warning-text)] flex-shrink-0 mt-0.5",
141
+ className: "size-5 text-[var(--color-status-warning-text)] flex-shrink-0 mt-0.5",
142
142
  fill: "none",
143
143
  viewBox: "0 0 24 24",
144
144
  stroke: "currentColor",
@@ -165,9 +165,10 @@ function m() {
165
165
  ". ",
166
166
  e
167
167
  ]
168
- }, t))
168
+ }, e))
169
169
  }),
170
170
  /* @__PURE__ */ f("button", {
171
+ type: "button",
171
172
  onClick: async () => {
172
173
  S?.backupCodes && (s("light"), await o(S.backupCodes.join("\n")) ? (j(!0), c("success"), setTimeout(() => j(!1), 2e3)) : c("error"));
173
174
  },
@@ -196,12 +197,14 @@ function m() {
196
197
  /* @__PURE__ */ p("div", {
197
198
  className: "flex gap-3",
198
199
  children: [/* @__PURE__ */ f("button", {
200
+ type: "button",
199
201
  onClick: () => {
200
202
  C(null), T(""), D(null);
201
203
  },
202
204
  className: "flex-1 py-3 px-4 rounded-xl border border-[var(--color-border-default)] text-[var(--color-text-secondary)] font-medium hover:bg-[var(--color-bg-muted)] transition-colors duration-200",
203
205
  children: h("auth.mfaSetup.cancel", "Cancel")
204
206
  }), /* @__PURE__ */ f("button", {
207
+ type: "button",
205
208
  onClick: async () => {
206
209
  if (w.length !== 6) {
207
210
  D(h("auth.mfaSetup.enter6Digit", "Please enter a 6-digit code"));
@@ -215,7 +218,7 @@ function m() {
215
218
  },
216
219
  disabled: y || w.length !== 6,
217
220
  className: "auth-primary-button flex flex-1 items-center justify-center gap-2 px-4 py-3 font-medium",
218
- children: y ? /* @__PURE__ */ p(d, { children: [/* @__PURE__ */ f(r, { className: "w-5 h-5 animate-spin" }), /* @__PURE__ */ f("span", { children: h("auth.mfa.verifying", "Verifying...") })] }) : h("auth.mfaSetup.enable2fa", "Enable 2FA")
221
+ children: y ? /* @__PURE__ */ p(d, { children: [/* @__PURE__ */ f(n, { className: "size-5 animate-spin" }), /* @__PURE__ */ f("span", { children: h("auth.mfa.verifying", "Verifying...") })] }) : h("auth.mfaSetup.enable2fa", "Enable 2FA")
219
222
  })]
220
223
  })
221
224
  ]
@@ -226,9 +229,9 @@ function m() {
226
229
  /* @__PURE__ */ p("div", {
227
230
  className: "flex items-center gap-4 mb-6",
228
231
  children: [/* @__PURE__ */ f("div", {
229
- className: "w-12 h-12 rounded-full bg-[var(--color-status-success-bg-subtle)] flex items-center justify-center",
232
+ className: "size-12 rounded-full bg-[var(--color-status-success-bg-subtle)] flex items-center justify-center",
230
233
  children: /* @__PURE__ */ f("svg", {
231
- className: "w-6 h-6 text-[var(--color-status-success-text)]",
234
+ className: "size-6 text-[var(--color-status-success-text)]",
232
235
  fill: "none",
233
236
  viewBox: "0 0 24 24",
234
237
  stroke: "currentColor",
@@ -255,18 +258,19 @@ function m() {
255
258
  })
256
259
  }),
257
260
  /* @__PURE__ */ f("button", {
261
+ type: "button",
258
262
  onClick: () => N(!0),
259
263
  disabled: y,
260
264
  className: "w-full py-3 px-4 rounded-xl border border-[var(--color-status-error-border)] text-[var(--color-status-error-text)] font-medium bg-[var(--color-bg-surface)] hover:bg-[var(--color-status-error-bg-subtle)] disabled:opacity-50 disabled:cursor-not-allowed transition-colors duration-200 flex items-center justify-center gap-2",
261
- children: y ? /* @__PURE__ */ p(d, { children: [/* @__PURE__ */ f(r, { className: "w-5 h-5 animate-spin" }), /* @__PURE__ */ f("span", { children: h("auth.mfa.disabling", "Disabling...") })] }) : h("auth.mfaSetup.disableTwoFactor", "Disable Two-Factor Authentication")
265
+ children: y ? /* @__PURE__ */ p(d, { children: [/* @__PURE__ */ f(n, { className: "size-5 animate-spin" }), /* @__PURE__ */ f("span", { children: h("auth.mfa.disabling", "Disabling...") })] }) : h("auth.mfaSetup.disableTwoFactor", "Disable Two-Factor Authentication")
262
266
  })
263
267
  ] }) : /* @__PURE__ */ p(d, { children: [
264
268
  /* @__PURE__ */ p("div", {
265
269
  className: "flex items-center gap-4 mb-6",
266
270
  children: [/* @__PURE__ */ f("div", {
267
- className: "w-12 h-12 rounded-full bg-[var(--color-bg-muted)] flex items-center justify-center",
271
+ className: "size-12 rounded-full bg-[var(--color-bg-muted)] flex items-center justify-center",
268
272
  children: /* @__PURE__ */ f("svg", {
269
- className: "w-6 h-6 text-[var(--color-text-tertiary)]",
273
+ className: "size-6 text-[var(--color-text-tertiary)]",
270
274
  fill: "none",
271
275
  viewBox: "0 0 24 24",
272
276
  stroke: "currentColor",
@@ -300,11 +304,12 @@ function m() {
300
304
  })]
301
305
  }),
302
306
  /* @__PURE__ */ f("button", {
307
+ type: "button",
303
308
  onClick: () => P("TOTP"),
304
309
  disabled: y,
305
310
  className: "auth-primary-button flex w-full items-center justify-center gap-2 px-4 py-3 font-medium",
306
- children: y ? /* @__PURE__ */ p(d, { children: [/* @__PURE__ */ f(r, { className: "w-5 h-5 animate-spin" }), /* @__PURE__ */ f("span", { children: h("auth.mfa.settingUp", "Setting up...") })] }) : /* @__PURE__ */ p(d, { children: [/* @__PURE__ */ f("svg", {
307
- className: "w-5 h-5",
311
+ children: y ? /* @__PURE__ */ p(d, { children: [/* @__PURE__ */ f(n, { className: "size-5 animate-spin" }), /* @__PURE__ */ f("span", { children: h("auth.mfa.settingUp", "Setting up...") })] }) : /* @__PURE__ */ p(d, { children: [/* @__PURE__ */ f("svg", {
312
+ className: "size-5",
308
313
  fill: "none",
309
314
  viewBox: "0 0 24 24",
310
315
  stroke: "currentColor",
@@ -325,8 +330,8 @@ function m() {
325
330
  className: "text-center py-16 border-2 border-dashed border-border-default rounded-xl",
326
331
  children: [
327
332
  /* @__PURE__ */ f("div", {
328
- className: "w-16 h-16 mx-auto mb-4 rounded-full bg-status-warning-bg-subtle flex items-center justify-center",
329
- children: /* @__PURE__ */ f(n, { className: "w-8 h-8 text-status-warning-text" })
333
+ className: "size-16 mx-auto mb-4 rounded-full bg-status-warning-bg-subtle flex items-center justify-center",
334
+ children: /* @__PURE__ */ f(t, { className: "size-8 text-status-warning-text" })
330
335
  }),
331
336
  /* @__PURE__ */ f("h2", {
332
337
  className: "text-xl font-semibold text-text-primary mb-2",
@@ -1 +1 @@
1
- {"version":3,"file":"MfaSetupPage.js","names":[],"sources":["../../../src/auth/pages/MfaSetupPage.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell\";\nimport { QRCodeSVG } from \"qrcode.react\";\nimport { useMfaOperations } from \"../hooks\";\nimport { useAuthStore } from \"../store/authStore\";\nimport { ConfirmActionDialog } from \"../components/ConfirmActionDialog\";\nimport { SpinnerIcon, AlertIcon } from \"../components/AuthIcons\";\nimport type { MFAMethod, MFASetupResult } from \"../types\";\nimport {\n nativeCopyText,\n nativeImpact,\n nativeNotify,\n} from \"../../utils/nativeBridge\";\n\n/**\n * MFA setup and management page with professional UI\n */\nexport function MfaSetupPage() {\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n };\n\n const { setupMFA, enableMFA, disableMFA, isLoading } = useMfaOperations();\n const user = useAuthStore((state) => state.user);\n const isAuthenticated = useAuthStore((state) => state.isAuthenticated);\n const [setupData, setSetupData] = useState<MFASetupResult | null>(null);\n const [verificationCode, setVerificationCode] = useState(\"\");\n const [error, setError] = useState<string | null>(null);\n const [successMessage, setSuccessMessage] = useState<string | null>(null);\n const [backupCodesCopied, setBackupCodesCopied] = useState(false);\n const [showDisableDialog, setShowDisableDialog] = useState(false);\n\n const handleSetupMfa = async (method: MFAMethod) => {\n try {\n setError(null);\n setSuccessMessage(null);\n const data = await setupMFA(method);\n setSetupData(data);\n } catch (err) {\n console.error(\"Failed to setup MFA:\", err);\n setError(\n err instanceof Error\n ? err.message\n : \"Failed to setup MFA. Please try again.\",\n );\n }\n };\n\n const handleVerifyAndEnable = async () => {\n if (verificationCode.length !== 6) {\n setError(tr(\"auth.mfaSetup.enter6Digit\", \"Please enter a 6-digit code\"));\n return;\n }\n\n try {\n setError(null);\n const success = await enableMFA(verificationCode);\n if (success) {\n setSetupData(null);\n setVerificationCode(\"\");\n setSuccessMessage(\n tr(\n \"auth.mfaSetup.enableSuccess\",\n \"Two-factor authentication has been enabled successfully!\",\n ),\n );\n }\n } catch (err) {\n console.error(\"Failed to verify MFA code:\", err);\n setError(\n err instanceof Error\n ? err.message\n : \"Invalid verification code. Please try again.\",\n );\n }\n };\n\n const handleDisableMfa = async () => {\n try {\n setError(null);\n setSuccessMessage(null);\n await disableMFA();\n setSuccessMessage(\n tr(\n \"auth.mfaSetup.disableSuccess\",\n \"Two-factor authentication has been disabled.\",\n ),\n );\n setShowDisableDialog(false);\n } catch (err) {\n console.error(\"Failed to disable MFA:\", err);\n setError(\n err instanceof Error\n ? err.message\n : \"Failed to disable MFA. Please try again.\",\n );\n }\n };\n\n const copyBackupCodes = async () => {\n if (setupData?.backupCodes) {\n void nativeImpact(\"light\");\n const ok = await nativeCopyText(setupData.backupCodes.join(\"\\n\"));\n if (ok) {\n setBackupCodesCopied(true);\n void nativeNotify(\"success\");\n setTimeout(() => setBackupCodesCopied(false), 2000);\n } else {\n void nativeNotify(\"error\");\n }\n }\n };\n\n const cancelSetup = () => {\n setSetupData(null);\n setVerificationCode(\"\");\n setError(null);\n };\n\n // Authentication check\n if (!isAuthenticated) {\n return (\n <div className=\"max-w-2xl mx-auto p-6\">\n <div className=\"text-center py-16 border-2 border-dashed border-border-default rounded-xl\">\n <div className=\"w-16 h-16 mx-auto mb-4 rounded-full bg-status-warning-bg-subtle flex items-center justify-center\">\n <AlertIcon className=\"w-8 h-8 text-status-warning-text\" />\n </div>\n <h2 className=\"text-xl font-semibold text-text-primary mb-2\">\n {tr(\"auth.mfaSetup.authRequired\", \"Authentication Required\")}\n </h2>\n <p className=\"text-text-secondary\">\n {tr(\n \"auth.mfaSetup.signInToManage\",\n \"Please sign in to manage MFA settings\",\n )}\n </p>\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"max-w-2xl mx-auto p-6\">\n <ConfirmActionDialog\n open={showDisableDialog}\n onOpenChange={setShowDisableDialog}\n title={tr(\n \"auth.mfaSetup.disableTitle\",\n \"Disable two-factor authentication?\",\n )}\n description={tr(\n \"auth.mfaSetup.disableDesc\",\n \"Your account will become less secure and future sign-ins will only require your password until MFA is re-enabled.\",\n )}\n confirmText={tr(\"auth.mfaSetup.disableConfirm\", \"Disable MFA\")}\n variant=\"warning\"\n isPending={isLoading}\n onConfirm={handleDisableMfa}\n />\n {/* Header */}\n <div className=\"mb-8\">\n <h1 className=\"text-3xl font-semibold text-[var(--color-text-primary)]\">\n {tr(\"auth.mfaSetup.title\", \"Two-Factor Authentication\")}\n </h1>\n <p className=\"text-[var(--color-text-secondary)] mt-2\">\n {tr(\n \"auth.mfaSetup.subtitle\",\n \"Add an extra layer of security to your account\",\n )}\n </p>\n </div>\n\n {/* Success Message */}\n {successMessage && (\n <div className=\"mb-6 p-4 rounded-xl bg-[var(--color-status-success-bg-subtle)] border border-[var(--color-status-success-border)]\">\n <div className=\"flex items-center gap-3\">\n <svg\n className=\"w-5 h-5 text-[var(--color-status-success-text)]\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z\"\n />\n </svg>\n <p className=\"text-[var(--color-status-success-text)]\">\n {successMessage}\n </p>\n </div>\n </div>\n )}\n\n {/* Error Message */}\n {error && (\n <div className=\"mb-6 flex items-start gap-3 p-4 rounded-xl bg-[var(--color-status-error-bg-subtle)] border border-[var(--color-status-error-border)]\">\n <AlertIcon className=\"w-5 h-5 text-[var(--color-status-error-text)] flex-shrink-0 mt-0.5\" />\n <div className=\"flex-1\">\n <p className=\"text-sm text-[var(--color-status-error-text)]\">\n {error}\n </p>\n <button\n onClick={() => setError(null)}\n className=\"mt-2 text-sm text-[var(--color-status-error-text)] hover:underline transition-colors duration-200\"\n >\n {tr(\"auth.mfaSetup.dismiss\", \"Dismiss\")}\n </button>\n </div>\n </div>\n )}\n\n {setupData ? (\n /* Setup Flow */\n <div className=\"p-6 rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] shadow-sm\">\n <div className=\"flex items-center gap-3 mb-6\">\n <div className=\"w-10 h-10 rounded-full bg-[var(--color-bg-accent)] flex items-center justify-center\">\n <svg\n className=\"w-5 h-5 text-[var(--color-text-link)]\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z\"\n />\n </svg>\n </div>\n <div>\n <h2 className=\"text-xl font-semibold text-[var(--color-text-primary)]\">\n {tr(\"auth.mfaSetup.setupAuthApp\", \"Setup Authenticator App\")}\n </h2>\n <p className=\"text-sm text-[var(--color-text-secondary)]\">\n {tr(\n \"auth.mfaSetup.scanQr\",\n \"Scan the QR code with your authenticator app\",\n )}\n </p>\n </div>\n </div>\n\n <div className=\"space-y-6\">\n {/* QR Code */}\n {setupData.qrCode && (\n <div className=\"flex justify-center\">\n <div className=\"p-4 bg-[var(--color-bg-surface)] rounded-xl border border-[var(--color-border-default)]\">\n <QRCodeSVG\n value={setupData.qrCode}\n size={192}\n level=\"M\"\n includeMargin={false}\n className=\"w-48 h-48\"\n />\n </div>\n </div>\n )}\n\n {/* Manual Secret */}\n {setupData.secret && (\n <div>\n <p className=\"text-sm text-[var(--color-text-secondary)] mb-2\">\n {tr(\n \"auth.mfaSetup.cantScan\",\n \"Can't scan the QR code? Enter this key manually:\",\n )}\n </p>\n <code className=\"block p-3 rounded-lg bg-[var(--color-bg-muted)] text-[var(--color-text-primary)] font-mono text-sm break-all select-all\">\n {setupData.secret}\n </code>\n </div>\n )}\n\n {/* Backup Codes */}\n {setupData.backupCodes && setupData.backupCodes.length > 0 && (\n <div className=\"p-4 rounded-xl bg-[var(--color-status-warning-bg-subtle)] border border-[var(--color-status-warning-border)]\">\n <div className=\"flex items-start gap-3 mb-3\">\n <svg\n className=\"w-5 h-5 text-[var(--color-status-warning-text)] flex-shrink-0 mt-0.5\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z\"\n />\n </svg>\n <div>\n <h3 className=\"font-medium text-[var(--color-status-warning-text)]\">\n {tr(\n \"auth.mfaSetup.saveBackupCodes\",\n \"Save your backup codes\",\n )}\n </h3>\n <p className=\"text-sm text-[var(--color-status-warning-text)]\">\n {tr(\n \"auth.mfaSetup.storeCodesSafely\",\n \"Store these codes safely. You can use them to access your account if you lose your authenticator device.\",\n )}\n </p>\n </div>\n </div>\n <div className=\"p-3 rounded-lg bg-[var(--color-bg-surface)] grid grid-cols-2 gap-2 mb-3\">\n {setupData.backupCodes.map((code, i) => (\n <code\n key={i}\n className=\"text-sm text-[var(--color-text-primary)] font-mono\"\n >\n {i + 1}. {code}\n </code>\n ))}\n </div>\n <button\n onClick={copyBackupCodes}\n className=\"text-sm font-medium text-[var(--color-status-warning-text)] hover:underline transition-colors duration-200\"\n >\n {backupCodesCopied\n ? tr(\"auth.mfaSetup.copied\", \"Copied!\")\n : tr(\"auth.mfaSetup.copyBackupCodes\", \"Copy backup codes\")}\n </button>\n </div>\n )}\n\n {/* Verification Code Input */}\n <div>\n <label\n htmlFor=\"mfa-verification-code\"\n className=\"block text-sm font-medium text-[var(--color-text-primary)] mb-2\"\n >\n {tr(\n \"auth.mfaSetup.enterCode\",\n \"Enter the 6-digit code from your app\",\n )}\n </label>\n <input\n id=\"mfa-verification-code\"\n type=\"text\"\n inputMode=\"numeric\"\n maxLength={6}\n value={verificationCode}\n onChange={(e) =>\n setVerificationCode(e.target.value.replace(/\\D/g, \"\"))\n }\n placeholder={tr(\n \"auth.mfa.verificationCodePlaceholder\",\n \"000000\",\n )}\n aria-label={tr(\n \"auth.mfaSetup.enterCode\",\n \"Enter the 6-digit code from your app\",\n )}\n aria-required=\"true\"\n autoComplete=\"one-time-code\"\n className=\"block w-full px-4 py-3 rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] text-[var(--color-text-primary)] text-center text-2xl tracking-widest font-mono focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg transition-all duration-300\"\n />\n </div>\n\n {/* Action Buttons */}\n <div className=\"flex gap-3\">\n <button\n onClick={cancelSetup}\n className=\"flex-1 py-3 px-4 rounded-xl border border-[var(--color-border-default)] text-[var(--color-text-secondary)] font-medium hover:bg-[var(--color-bg-muted)] transition-colors duration-200\"\n >\n {tr(\"auth.mfaSetup.cancel\", \"Cancel\")}\n </button>\n <button\n onClick={handleVerifyAndEnable}\n disabled={isLoading || verificationCode.length !== 6}\n className=\"auth-primary-button flex flex-1 items-center justify-center gap-2 px-4 py-3 font-medium\"\n >\n {isLoading ? (\n <>\n <SpinnerIcon className=\"w-5 h-5 animate-spin\" />\n <span>{tr(\"auth.mfa.verifying\", \"Verifying...\")}</span>\n </>\n ) : (\n tr(\"auth.mfaSetup.enable2fa\", \"Enable 2FA\")\n )}\n </button>\n </div>\n </div>\n </div>\n ) : (\n /* Status Display */\n <div className=\"p-6 rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] shadow-sm\">\n {!user?.mfaEnabled ? (\n /* MFA Not Enabled */\n <>\n <div className=\"flex items-center gap-4 mb-6\">\n <div className=\"w-12 h-12 rounded-full bg-[var(--color-bg-muted)] flex items-center justify-center\">\n <svg\n className=\"w-6 h-6 text-[var(--color-text-tertiary)]\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z\"\n />\n </svg>\n </div>\n <div>\n <h2 className=\"text-xl font-semibold text-[var(--color-text-primary)]\">\n {tr(\n \"auth.mfaSetup.twoFactorOff\",\n \"Two-Factor Authentication is Off\",\n )}\n </h2>\n <p className=\"text-[var(--color-text-secondary)]\">\n {tr(\n \"auth.mfaSetup.notProtected\",\n \"Your account is not protected with 2FA\",\n )}\n </p>\n </div>\n </div>\n\n <div className=\"mb-6 p-4 rounded-lg bg-[var(--color-bg-accent)] border border-[var(--color-border-default)]\">\n <h3 className=\"font-medium text-[var(--color-text-link)] mb-2\">\n {tr(\"auth.mfaSetup.whyEnable\", \"Why enable 2FA?\")}\n </h3>\n <ul className=\"text-sm text-[var(--color-text-link)] space-y-1\">\n <li>\n • Protects your account even if your password is compromised\n </li>\n <li>• Adds a second verification step during sign-in</li>\n <li>\n • Works with popular authenticator apps (Google, Authy,\n etc.)\n </li>\n </ul>\n </div>\n\n <button\n onClick={() => handleSetupMfa(\"TOTP\")}\n disabled={isLoading}\n className=\"auth-primary-button flex w-full items-center justify-center gap-2 px-4 py-3 font-medium\"\n >\n {isLoading ? (\n <>\n <SpinnerIcon className=\"w-5 h-5 animate-spin\" />\n <span>{tr(\"auth.mfa.settingUp\", \"Setting up...\")}</span>\n </>\n ) : (\n <>\n <svg\n className=\"w-5 h-5\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z\"\n />\n </svg>\n <span>\n {tr(\n \"auth.mfaSetup.enableTwoFactor\",\n \"Enable Two-Factor Authentication\",\n )}\n </span>\n </>\n )}\n </button>\n </>\n ) : (\n /* MFA Enabled */\n <>\n <div className=\"flex items-center gap-4 mb-6\">\n <div className=\"w-12 h-12 rounded-full bg-[var(--color-status-success-bg-subtle)] flex items-center justify-center\">\n <svg\n className=\"w-6 h-6 text-[var(--color-status-success-text)]\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z\"\n />\n </svg>\n </div>\n <div>\n <h2 className=\"text-xl font-semibold text-[var(--color-text-primary)]\">\n {tr(\n \"auth.mfaSetup.twoFactorOn\",\n \"Two-Factor Authentication is On\",\n )}\n </h2>\n <p className=\"text-[var(--color-text-secondary)]\">\n {tr(\n \"auth.mfaSetup.protected\",\n \"Your account is protected with an authenticator app\",\n )}\n </p>\n </div>\n </div>\n\n <div className=\"mb-6 p-4 rounded-lg bg-[var(--color-status-success-bg-subtle)] border border-[var(--color-status-success-border)]\">\n <p className=\"text-sm text-[var(--color-status-success-text)]\">\n {tr(\n \"auth.mfaSetup.verificationOnSignIn\",\n \"You'll be asked for a verification code from your authenticator app when signing in.\",\n )}\n </p>\n </div>\n\n <button\n onClick={() => setShowDisableDialog(true)}\n disabled={isLoading}\n className=\"w-full py-3 px-4 rounded-xl border border-[var(--color-status-error-border)] text-[var(--color-status-error-text)] font-medium bg-[var(--color-bg-surface)] hover:bg-[var(--color-status-error-bg-subtle)] disabled:opacity-50 disabled:cursor-not-allowed transition-colors duration-200 flex items-center justify-center gap-2\"\n >\n {isLoading ? (\n <>\n <SpinnerIcon className=\"w-5 h-5 animate-spin\" />\n <span>{tr(\"auth.mfa.disabling\", \"Disabling...\")}</span>\n </>\n ) : (\n tr(\n \"auth.mfaSetup.disableTwoFactor\",\n \"Disable Two-Factor Authentication\",\n )\n )}\n </button>\n </>\n )}\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"MfaSetupPage.js","names":[],"sources":["../../../src/auth/pages/MfaSetupPage.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell\";\nimport { QRCodeSVG } from \"qrcode.react\";\nimport { useMfaOperations } from \"../hooks/useMfaOperations\";\nimport { useAuthStore } from \"../store/authStore\";\nimport { ConfirmActionDialog } from \"../components/ConfirmActionDialog\";\nimport { SpinnerIcon, AlertIcon } from \"../components/AuthIcons\";\nimport type { MFAMethod, MFASetupResult } from \"../types\";\nimport {\n nativeCopyText,\n nativeImpact,\n nativeNotify,\n} from \"../../utils/nativeBridge\";\n\n/**\n * MFA setup and management page with professional UI\n */\nexport function MfaSetupPage() {\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n };\n\n const { setupMFA, enableMFA, disableMFA, isLoading } = useMfaOperations();\n const user = useAuthStore((state) => state.user);\n const isAuthenticated = useAuthStore((state) => state.isAuthenticated);\n const [setupData, setSetupData] = useState<MFASetupResult | null>(null);\n const [verificationCode, setVerificationCode] = useState(\"\");\n const [error, setError] = useState<string | null>(null);\n const [successMessage, setSuccessMessage] = useState<string | null>(null);\n const [backupCodesCopied, setBackupCodesCopied] = useState(false);\n const [showDisableDialog, setShowDisableDialog] = useState(false);\n\n const handleSetupMfa = async (method: MFAMethod) => {\n try {\n setError(null);\n setSuccessMessage(null);\n const data = await setupMFA(method);\n setSetupData(data);\n } catch (err) {\n console.error(\"Failed to setup MFA:\", err);\n setError(\n err instanceof Error\n ? err.message\n : \"Failed to setup MFA. Please try again.\",\n );\n }\n };\n\n const handleVerifyAndEnable = async () => {\n if (verificationCode.length !== 6) {\n setError(tr(\"auth.mfaSetup.enter6Digit\", \"Please enter a 6-digit code\"));\n return;\n }\n\n try {\n setError(null);\n const success = await enableMFA(verificationCode);\n if (success) {\n setSetupData(null);\n setVerificationCode(\"\");\n setSuccessMessage(\n tr(\n \"auth.mfaSetup.enableSuccess\",\n \"Two-factor authentication has been enabled successfully!\",\n ),\n );\n }\n } catch (err) {\n console.error(\"Failed to verify MFA code:\", err);\n setError(\n err instanceof Error\n ? err.message\n : \"Invalid verification code. Please try again.\",\n );\n }\n };\n\n const handleDisableMfa = async () => {\n try {\n setError(null);\n setSuccessMessage(null);\n await disableMFA();\n setSuccessMessage(\n tr(\n \"auth.mfaSetup.disableSuccess\",\n \"Two-factor authentication has been disabled.\",\n ),\n );\n setShowDisableDialog(false);\n } catch (err) {\n console.error(\"Failed to disable MFA:\", err);\n setError(\n err instanceof Error\n ? err.message\n : \"Failed to disable MFA. Please try again.\",\n );\n }\n };\n\n const copyBackupCodes = async () => {\n if (setupData?.backupCodes) {\n void nativeImpact(\"light\");\n const ok = await nativeCopyText(setupData.backupCodes.join(\"\\n\"));\n if (ok) {\n setBackupCodesCopied(true);\n void nativeNotify(\"success\");\n setTimeout(() => setBackupCodesCopied(false), 2000);\n } else {\n void nativeNotify(\"error\");\n }\n }\n };\n\n const cancelSetup = () => {\n setSetupData(null);\n setVerificationCode(\"\");\n setError(null);\n };\n\n // Authentication check\n if (!isAuthenticated) {\n return (\n <div className=\"max-w-2xl mx-auto p-6\">\n <div className=\"text-center py-16 border-2 border-dashed border-border-default rounded-xl\">\n <div className=\"size-16 mx-auto mb-4 rounded-full bg-status-warning-bg-subtle flex items-center justify-center\">\n <AlertIcon className=\"size-8 text-status-warning-text\" />\n </div>\n <h2 className=\"text-xl font-semibold text-text-primary mb-2\">\n {tr(\"auth.mfaSetup.authRequired\", \"Authentication Required\")}\n </h2>\n <p className=\"text-text-secondary\">\n {tr(\n \"auth.mfaSetup.signInToManage\",\n \"Please sign in to manage MFA settings\",\n )}\n </p>\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"max-w-2xl mx-auto p-6\">\n <ConfirmActionDialog\n open={showDisableDialog}\n onOpenChange={setShowDisableDialog}\n title={tr(\n \"auth.mfaSetup.disableTitle\",\n \"Disable two-factor authentication?\",\n )}\n description={tr(\n \"auth.mfaSetup.disableDesc\",\n \"Your account will become less secure and future sign-ins will only require your password until MFA is re-enabled.\",\n )}\n confirmText={tr(\"auth.mfaSetup.disableConfirm\", \"Disable MFA\")}\n variant=\"warning\"\n isPending={isLoading}\n onConfirm={handleDisableMfa}\n />\n {/* Header */}\n <div className=\"mb-8\">\n <h1 className=\"text-3xl font-semibold text-[var(--color-text-primary)]\">\n {tr(\"auth.mfaSetup.title\", \"Two-Factor Authentication\")}\n </h1>\n <p className=\"text-[var(--color-text-secondary)] mt-2\">\n {tr(\n \"auth.mfaSetup.subtitle\",\n \"Add an extra layer of security to your account\",\n )}\n </p>\n </div>\n\n {/* Success Message */}\n {successMessage && (\n <div className=\"mb-6 p-4 rounded-xl bg-[var(--color-status-success-bg-subtle)] border border-[var(--color-status-success-border)]\">\n <div className=\"flex items-center gap-3\">\n <svg\n className=\"size-5 text-[var(--color-status-success-text)]\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z\"\n />\n </svg>\n <p className=\"text-[var(--color-status-success-text)]\">\n {successMessage}\n </p>\n </div>\n </div>\n )}\n\n {/* Error Message */}\n {error && (\n <div className=\"mb-6 flex items-start gap-3 p-4 rounded-xl bg-[var(--color-status-error-bg-subtle)] border border-[var(--color-status-error-border)]\">\n <AlertIcon className=\"size-5 text-[var(--color-status-error-text)] flex-shrink-0 mt-0.5\" />\n <div className=\"flex-1\">\n <p className=\"text-sm text-[var(--color-status-error-text)]\">\n {error}\n </p>\n <button type=\"button\"\n onClick={() => setError(null)}\n className=\"mt-2 text-sm text-[var(--color-status-error-text)] hover:underline transition-colors duration-200\"\n >\n {tr(\"auth.mfaSetup.dismiss\", \"Dismiss\")}\n </button>\n </div>\n </div>\n )}\n\n {setupData ? (\n /* Setup Flow */\n <div className=\"p-6 rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] shadow-sm\">\n <div className=\"flex items-center gap-3 mb-6\">\n <div className=\"size-10 rounded-full bg-[var(--color-bg-accent)] flex items-center justify-center\">\n <svg\n className=\"size-5 text-[var(--color-text-link)]\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z\"\n />\n </svg>\n </div>\n <div>\n <h2 className=\"text-xl font-semibold text-[var(--color-text-primary)]\">\n {tr(\"auth.mfaSetup.setupAuthApp\", \"Setup Authenticator App\")}\n </h2>\n <p className=\"text-sm text-[var(--color-text-secondary)]\">\n {tr(\n \"auth.mfaSetup.scanQr\",\n \"Scan the QR code with your authenticator app\",\n )}\n </p>\n </div>\n </div>\n\n <div className=\"space-y-6\">\n {/* QR Code */}\n {setupData.qrCode && (\n <div className=\"flex justify-center\">\n <div className=\"p-4 bg-[var(--color-bg-surface)] rounded-xl border border-[var(--color-border-default)]\">\n <QRCodeSVG\n value={setupData.qrCode}\n size={192}\n level=\"M\"\n includeMargin={false}\n className=\"size-48\"\n />\n </div>\n </div>\n )}\n\n {/* Manual Secret */}\n {setupData.secret && (\n <div>\n <p className=\"text-sm text-[var(--color-text-secondary)] mb-2\">\n {tr(\n \"auth.mfaSetup.cantScan\",\n \"Can't scan the QR code? Enter this key manually:\",\n )}\n </p>\n <code className=\"block p-3 rounded-lg bg-[var(--color-bg-muted)] text-[var(--color-text-primary)] font-mono text-sm break-all select-all\">\n {setupData.secret}\n </code>\n </div>\n )}\n\n {/* Backup Codes */}\n {setupData.backupCodes && setupData.backupCodes.length > 0 && (\n <div className=\"p-4 rounded-xl bg-[var(--color-status-warning-bg-subtle)] border border-[var(--color-status-warning-border)]\">\n <div className=\"flex items-start gap-3 mb-3\">\n <svg\n className=\"size-5 text-[var(--color-status-warning-text)] flex-shrink-0 mt-0.5\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z\"\n />\n </svg>\n <div>\n <h3 className=\"font-medium text-[var(--color-status-warning-text)]\">\n {tr(\n \"auth.mfaSetup.saveBackupCodes\",\n \"Save your backup codes\",\n )}\n </h3>\n <p className=\"text-sm text-[var(--color-status-warning-text)]\">\n {tr(\n \"auth.mfaSetup.storeCodesSafely\",\n \"Store these codes safely. You can use them to access your account if you lose your authenticator device.\",\n )}\n </p>\n </div>\n </div>\n <div className=\"p-3 rounded-lg bg-[var(--color-bg-surface)] grid grid-cols-2 gap-2 mb-3\">\n {setupData.backupCodes.map((code, i) => (\n <code\n key={code}\n className=\"text-sm text-[var(--color-text-primary)] font-mono\"\n >\n {i + 1}. {code}\n </code>\n ))}\n </div>\n <button type=\"button\"\n onClick={copyBackupCodes}\n className=\"text-sm font-medium text-[var(--color-status-warning-text)] hover:underline transition-colors duration-200\"\n >\n {backupCodesCopied\n ? tr(\"auth.mfaSetup.copied\", \"Copied!\")\n : tr(\"auth.mfaSetup.copyBackupCodes\", \"Copy backup codes\")}\n </button>\n </div>\n )}\n\n {/* Verification Code Input */}\n <div>\n <label\n htmlFor=\"mfa-verification-code\"\n className=\"block text-sm font-medium text-[var(--color-text-primary)] mb-2\"\n >\n {tr(\n \"auth.mfaSetup.enterCode\",\n \"Enter the 6-digit code from your app\",\n )}\n </label>\n <input\n id=\"mfa-verification-code\"\n type=\"text\"\n inputMode=\"numeric\"\n maxLength={6}\n value={verificationCode}\n onChange={(e) =>\n setVerificationCode(e.target.value.replace(/\\D/g, \"\"))\n }\n placeholder={tr(\n \"auth.mfa.verificationCodePlaceholder\",\n \"000000\",\n )}\n aria-label={tr(\n \"auth.mfaSetup.enterCode\",\n \"Enter the 6-digit code from your app\",\n )}\n aria-required=\"true\"\n autoComplete=\"one-time-code\"\n className=\"block w-full px-4 py-3 rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] text-[var(--color-text-primary)] text-center text-2xl tracking-widest font-mono focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg transition-all duration-300\"\n />\n </div>\n\n {/* Action Buttons */}\n <div className=\"flex gap-3\">\n <button type=\"button\"\n onClick={cancelSetup}\n className=\"flex-1 py-3 px-4 rounded-xl border border-[var(--color-border-default)] text-[var(--color-text-secondary)] font-medium hover:bg-[var(--color-bg-muted)] transition-colors duration-200\"\n >\n {tr(\"auth.mfaSetup.cancel\", \"Cancel\")}\n </button>\n <button type=\"button\"\n onClick={handleVerifyAndEnable}\n disabled={isLoading || verificationCode.length !== 6}\n className=\"auth-primary-button flex flex-1 items-center justify-center gap-2 px-4 py-3 font-medium\"\n >\n {isLoading ? (\n <>\n <SpinnerIcon className=\"size-5 animate-spin\" />\n <span>{tr(\"auth.mfa.verifying\", \"Verifying...\")}</span>\n </>\n ) : (\n tr(\"auth.mfaSetup.enable2fa\", \"Enable 2FA\")\n )}\n </button>\n </div>\n </div>\n </div>\n ) : (\n /* Status Display */\n <div className=\"p-6 rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] shadow-sm\">\n {!user?.mfaEnabled ? (\n /* MFA Not Enabled */\n <>\n <div className=\"flex items-center gap-4 mb-6\">\n <div className=\"size-12 rounded-full bg-[var(--color-bg-muted)] flex items-center justify-center\">\n <svg\n className=\"size-6 text-[var(--color-text-tertiary)]\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z\"\n />\n </svg>\n </div>\n <div>\n <h2 className=\"text-xl font-semibold text-[var(--color-text-primary)]\">\n {tr(\n \"auth.mfaSetup.twoFactorOff\",\n \"Two-Factor Authentication is Off\",\n )}\n </h2>\n <p className=\"text-[var(--color-text-secondary)]\">\n {tr(\n \"auth.mfaSetup.notProtected\",\n \"Your account is not protected with 2FA\",\n )}\n </p>\n </div>\n </div>\n\n <div className=\"mb-6 p-4 rounded-lg bg-[var(--color-bg-accent)] border border-[var(--color-border-default)]\">\n <h3 className=\"font-medium text-[var(--color-text-link)] mb-2\">\n {tr(\"auth.mfaSetup.whyEnable\", \"Why enable 2FA?\")}\n </h3>\n <ul className=\"text-sm text-[var(--color-text-link)] space-y-1\">\n <li>\n • Protects your account even if your password is compromised\n </li>\n <li>• Adds a second verification step during sign-in</li>\n <li>\n • Works with popular authenticator apps (Google, Authy,\n etc.)\n </li>\n </ul>\n </div>\n\n <button type=\"button\"\n onClick={() => handleSetupMfa(\"TOTP\")}\n disabled={isLoading}\n className=\"auth-primary-button flex w-full items-center justify-center gap-2 px-4 py-3 font-medium\"\n >\n {isLoading ? (\n <>\n <SpinnerIcon className=\"size-5 animate-spin\" />\n <span>{tr(\"auth.mfa.settingUp\", \"Setting up...\")}</span>\n </>\n ) : (\n <>\n <svg\n className=\"size-5\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z\"\n />\n </svg>\n <span>\n {tr(\n \"auth.mfaSetup.enableTwoFactor\",\n \"Enable Two-Factor Authentication\",\n )}\n </span>\n </>\n )}\n </button>\n </>\n ) : (\n /* MFA Enabled */\n <>\n <div className=\"flex items-center gap-4 mb-6\">\n <div className=\"size-12 rounded-full bg-[var(--color-status-success-bg-subtle)] flex items-center justify-center\">\n <svg\n className=\"size-6 text-[var(--color-status-success-text)]\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z\"\n />\n </svg>\n </div>\n <div>\n <h2 className=\"text-xl font-semibold text-[var(--color-text-primary)]\">\n {tr(\n \"auth.mfaSetup.twoFactorOn\",\n \"Two-Factor Authentication is On\",\n )}\n </h2>\n <p className=\"text-[var(--color-text-secondary)]\">\n {tr(\n \"auth.mfaSetup.protected\",\n \"Your account is protected with an authenticator app\",\n )}\n </p>\n </div>\n </div>\n\n <div className=\"mb-6 p-4 rounded-lg bg-[var(--color-status-success-bg-subtle)] border border-[var(--color-status-success-border)]\">\n <p className=\"text-sm text-[var(--color-status-success-text)]\">\n {tr(\n \"auth.mfaSetup.verificationOnSignIn\",\n \"You'll be asked for a verification code from your authenticator app when signing in.\",\n )}\n </p>\n </div>\n\n <button type=\"button\"\n onClick={() => setShowDisableDialog(true)}\n disabled={isLoading}\n className=\"w-full py-3 px-4 rounded-xl border border-[var(--color-status-error-border)] text-[var(--color-status-error-text)] font-medium bg-[var(--color-bg-surface)] hover:bg-[var(--color-status-error-bg-subtle)] disabled:opacity-50 disabled:cursor-not-allowed transition-colors duration-200 flex items-center justify-center gap-2\"\n >\n {isLoading ? (\n <>\n <SpinnerIcon className=\"size-5 animate-spin\" />\n <span>{tr(\"auth.mfa.disabling\", \"Disabling...\")}</span>\n </>\n ) : (\n tr(\n \"auth.mfaSetup.disableTwoFactor\",\n \"Disable Two-Factor Authentication\",\n )\n )}\n </button>\n </>\n )}\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
@@ -1,9 +1,8 @@
1
1
  import { useAuthStore as e } from "../store/authStore.js";
2
- import { useMfaOperations as t } from "../hooks/useMfaOperations.js";
3
- import { MFA_CHALLENGE_ID as n, MFA_PENDING_REFRESH as r, MFA_PENDING_TOKEN as i, MFA_REDIRECT as a } from "../constants/sessionKeys.js";
4
- import "../hooks/index.js";
5
- import { AuthLayout as o } from "../components/AuthLayout.js";
6
- import { AlertIcon as s, SpinnerIcon as c } from "../components/AuthIcons.js";
2
+ import { AuthLayout as t } from "../components/AuthLayout.js";
3
+ import { AlertIcon as n, SpinnerIcon as r } from "../components/AuthIcons.js";
4
+ import { useMfaOperations as i } from "../hooks/useMfaOperations.js";
5
+ import { MFA_CHALLENGE_ID as a, MFA_PENDING_REFRESH as o, MFA_PENDING_TOKEN as s, MFA_REDIRECT as c } from "../constants/sessionKeys.js";
7
6
  import { useEffect as l, useRef as u, useState as d } from "react";
8
7
  import { useNavigate as f } from "react-router-dom";
9
8
  import { useI18n as p } from "@burdenoff/fe-libs/shared/providers/shell";
@@ -13,16 +12,16 @@ function _() {
13
12
  let { t: _ } = p(), v = (e, t) => {
14
13
  let n = _(e);
15
14
  return n === e ? t : n;
16
- }, y = f(), { verifyMFA: b, mfaChallengeId: x, isLoading: S } = t(), [C, w] = d([
15
+ }, y = f(), { verifyMFA: b, mfaChallengeId: x, isLoading: S } = i(), [C, w] = d([
17
16
  "",
18
17
  "",
19
18
  "",
20
19
  "",
21
20
  "",
22
21
  ""
23
- ]), [T, E] = d(null), D = u([]), O = typeof window < "u" ? sessionStorage.getItem(n) : null, k = x || O;
22
+ ]), [T, E] = d(null), D = u([]), O = typeof window < "u" ? sessionStorage.getItem(a) : null, k = x || O;
24
23
  l(() => {
25
- D.current[0]?.focus(), sessionStorage.getItem(i), sessionStorage.getItem(a);
24
+ D.current[0]?.focus(), sessionStorage.getItem(s), sessionStorage.getItem(c);
26
25
  }, [O]);
27
26
  let A = (e, t) => {
28
27
  let n = t.replace(/\D/g, "").slice(-1), r = [...C];
@@ -41,8 +40,8 @@ function _() {
41
40
  let r = Math.min(t.length - 1, 5);
42
41
  D.current[r]?.focus(), t.length === 6 && N(t);
43
42
  }, N = async (t) => {
44
- let o = t || C.join("");
45
- if (o.length === 6) {
43
+ let n = t || C.join("");
44
+ if (n.length === 6) {
46
45
  if (E(null), !k) {
47
46
  E(v("auth.mfaVerify.sessionExpired", "MFA session expired. Please log in again."));
48
47
  return;
@@ -50,15 +49,15 @@ function _() {
50
49
  try {
51
50
  let t = await b({
52
51
  challengeId: k,
53
- code: o,
52
+ code: n,
54
53
  method: "TOTP"
55
54
  });
56
55
  if (t.success) {
57
- let e = sessionStorage.getItem(a);
58
- sessionStorage.removeItem(n), sessionStorage.removeItem(i), sessionStorage.removeItem(r), sessionStorage.removeItem(a), y(e || "/", { replace: !0 });
56
+ let e = sessionStorage.getItem(c);
57
+ sessionStorage.removeItem(a), sessionStorage.removeItem(s), sessionStorage.removeItem(o), sessionStorage.removeItem(c), y(e || "/", { replace: !0 });
59
58
  } else {
60
59
  if (t.error === "mfa_challenge_expired") {
61
- sessionStorage.removeItem(n), sessionStorage.removeItem(i), sessionStorage.removeItem(r), sessionStorage.removeItem(a), e.getState().setMfaRequired(!1), y("/auth/login", {
60
+ sessionStorage.removeItem(a), sessionStorage.removeItem(s), sessionStorage.removeItem(o), sessionStorage.removeItem(c), e.getState().setMfaRequired(!1), y("/auth/login", {
62
61
  replace: !0,
63
62
  state: { message: "Your verification session expired. Please sign in again." }
64
63
  });
@@ -75,7 +74,7 @@ function _() {
75
74
  }
76
75
  } catch (t) {
77
76
  if (console.error("MFA verification failed:", t), (t instanceof Error ? t.message : String(t)).includes("mfa_challenge_expired")) {
78
- sessionStorage.removeItem(n), sessionStorage.removeItem(i), sessionStorage.removeItem(r), sessionStorage.removeItem(a), e.getState().setMfaRequired(!1), y("/auth/login", {
77
+ sessionStorage.removeItem(a), sessionStorage.removeItem(s), sessionStorage.removeItem(o), sessionStorage.removeItem(c), e.getState().setMfaRequired(!1), y("/auth/login", {
79
78
  replace: !0,
80
79
  state: { message: "Your verification session expired. Please sign in again." }
81
80
  });
@@ -92,16 +91,16 @@ function _() {
92
91
  }
93
92
  }
94
93
  };
95
- return /* @__PURE__ */ g(o, {
94
+ return /* @__PURE__ */ g(t, {
96
95
  title: v("auth.mfaVerify.title", "Two-factor authentication"),
97
96
  subtitle: v("auth.mfaVerify.subtitle", "Enter the 6-digit code from your authenticator app"),
98
97
  children: [
99
98
  /* @__PURE__ */ h("div", {
100
99
  className: "flex justify-center mb-6",
101
100
  children: /* @__PURE__ */ h("div", {
102
- className: "w-16 h-16 rounded-full bg-[var(--color-bg-accent)] flex items-center justify-center",
101
+ className: "size-16 rounded-full bg-[var(--color-bg-accent)] flex items-center justify-center",
103
102
  children: /* @__PURE__ */ h("svg", {
104
- className: "w-8 h-8 text-[var(--color-text-link)]",
103
+ className: "size-8 text-[var(--color-text-link)]",
105
104
  fill: "none",
106
105
  viewBox: "0 0 24 24",
107
106
  stroke: "currentColor",
@@ -122,7 +121,7 @@ function _() {
122
121
  children: [
123
122
  T && /* @__PURE__ */ g("div", {
124
123
  className: "flex items-start gap-3 p-4 rounded-xl bg-[var(--color-status-error-bg-subtle)] border border-[var(--color-status-error-border)]",
125
- children: [/* @__PURE__ */ h(s, { className: "w-5 h-5 text-[var(--color-status-error-text)] flex-shrink-0 mt-0.5" }), /* @__PURE__ */ h("p", {
124
+ children: [/* @__PURE__ */ h(n, { className: "size-5 text-[var(--color-status-error-text)] flex-shrink-0 mt-0.5" }), /* @__PURE__ */ h("p", {
126
125
  className: "text-sm text-[var(--color-status-error-text)]",
127
126
  children: T
128
127
  })]
@@ -154,7 +153,7 @@ function _() {
154
153
  type: "submit",
155
154
  disabled: S || C.join("").length !== 6,
156
155
  className: "auth-primary-button flex w-full items-center justify-center gap-2 px-4 py-3 font-semibold",
157
- children: S ? /* @__PURE__ */ g(m, { children: [/* @__PURE__ */ h(c, { className: "w-5 h-5 animate-spin" }), /* @__PURE__ */ h("span", { children: "Verifying..." })] }) : v("auth.mfaVerify.verify", "Verify")
156
+ children: S ? /* @__PURE__ */ g(m, { children: [/* @__PURE__ */ h(r, { className: "size-5 animate-spin" }), /* @__PURE__ */ h("span", { children: "Verifying…" })] }) : v("auth.mfaVerify.verify", "Verify")
158
157
  }),
159
158
  /* @__PURE__ */ g("div", {
160
159
  className: "text-center space-y-3",
@@ -173,8 +172,9 @@ function _() {
173
172
  /* @__PURE__ */ h("p", {
174
173
  className: "mt-8 text-center text-sm text-[var(--color-text-secondary)]",
175
174
  children: /* @__PURE__ */ h("button", {
175
+ type: "button",
176
176
  onClick: () => {
177
- sessionStorage.removeItem(n), sessionStorage.removeItem(i), sessionStorage.removeItem(r), sessionStorage.removeItem(a), e.getState().setMfaRequired(!1), y("/auth/login");
177
+ sessionStorage.removeItem(a), sessionStorage.removeItem(s), sessionStorage.removeItem(o), sessionStorage.removeItem(c), e.getState().setMfaRequired(!1), y("/auth/login");
178
178
  },
179
179
  className: "font-semibold text-text-link hover:text-text-linkHover transition-colors",
180
180
  children: v("auth.mfaVerify.backToSignIn", "Back to sign in")
@@ -1 +1 @@
1
- {"version":3,"file":"MfaVerifyPage.js","names":[],"sources":["../../../src/auth/pages/MfaVerifyPage.tsx"],"sourcesContent":["import { useState, useRef, useEffect } from \"react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell\";\nimport { useNavigate } from \"react-router-dom\";\nimport { useMfaOperations } from \"../hooks\";\nimport { useAuthStore } from \"../store/authStore\";\nimport { AuthLayout } from \"../components/AuthLayout\";\nimport { AlertIcon, SpinnerIcon } from \"../components/AuthIcons\";\nimport {\n MFA_CHALLENGE_ID,\n MFA_PENDING_TOKEN,\n MFA_PENDING_REFRESH,\n MFA_REDIRECT,\n} from \"../constants/sessionKeys\";\n\n/**\n * MFA verification page with professional design\n * Shown when user needs to verify MFA code during login\n * Handles both regular login MFA and SSO MFA flows\n */\nexport function MfaVerifyPage() {\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n };\n\n const navigate = useNavigate();\n const {\n verifyMFA,\n mfaChallengeId: storeChallengeId,\n isLoading,\n } = useMfaOperations();\n const [code, setCode] = useState([\"\", \"\", \"\", \"\", \"\", \"\"]);\n const [error, setError] = useState<string | null>(null);\n const inputRefs = useRef<(HTMLInputElement | null)[]>([]);\n\n // Get challenge ID from store or sessionStorage (SSO flow)\n const sessionChallengeId =\n typeof window !== \"undefined\"\n ? sessionStorage.getItem(MFA_CHALLENGE_ID)\n : null;\n const mfaChallengeId = storeChallengeId || sessionChallengeId;\n\n // Focus first input on mount and detect SSO flow\n useEffect(() => {\n inputRefs.current[0]?.focus();\n\n // Check if this is an SSO flow (has pending tokens in sessionStorage)\n const hasPendingToken = sessionStorage.getItem(MFA_PENDING_TOKEN);\n const hasRedirect = sessionStorage.getItem(MFA_REDIRECT);\n if (hasPendingToken || hasRedirect || sessionChallengeId) {\n }\n }, [sessionChallengeId]);\n\n const handleChange = (index: number, value: string) => {\n // Only allow digits\n const digit = value.replace(/\\D/g, \"\").slice(-1);\n\n const newCode = [...code];\n newCode[index] = digit;\n setCode(newCode);\n\n // Auto-focus next input\n if (digit && index < 5) {\n inputRefs.current[index + 1]?.focus();\n }\n\n // Auto-submit when all digits are entered\n if (digit && index === 5) {\n const fullCode = newCode.join(\"\");\n if (fullCode.length === 6) {\n handleSubmit(fullCode);\n }\n }\n };\n\n const handleKeyDown = (index: number, e: React.KeyboardEvent) => {\n if (e.key === \"Backspace\" && !code[index] && index > 0) {\n inputRefs.current[index - 1]?.focus();\n }\n };\n\n const handlePaste = (e: React.ClipboardEvent) => {\n e.preventDefault();\n const pastedData = e.clipboardData\n .getData(\"text\")\n .replace(/\\D/g, \"\")\n .slice(0, 6);\n const newCode = [...code];\n pastedData.split(\"\").forEach((char, i) => {\n if (i < 6) newCode[i] = char;\n });\n setCode(newCode);\n\n // Focus last filled input or first empty\n const lastIndex = Math.min(pastedData.length - 1, 5);\n inputRefs.current[lastIndex]?.focus();\n\n // Auto-submit if full code pasted\n if (pastedData.length === 6) {\n handleSubmit(pastedData);\n }\n };\n\n const handleSubmit = async (fullCode?: string) => {\n const codeToSubmit = fullCode || code.join(\"\");\n if (codeToSubmit.length !== 6) return;\n\n setError(null);\n\n if (!mfaChallengeId) {\n setError(\n tr(\n \"auth.mfaVerify.sessionExpired\",\n \"MFA session expired. Please log in again.\",\n ),\n );\n return;\n }\n\n try {\n const result = await verifyMFA({\n challengeId: mfaChallengeId,\n code: codeToSubmit,\n method: \"TOTP\",\n });\n\n if (result.success) {\n // Clean up SSO session data\n const ssoRedirect = sessionStorage.getItem(MFA_REDIRECT);\n sessionStorage.removeItem(MFA_CHALLENGE_ID);\n sessionStorage.removeItem(MFA_PENDING_TOKEN);\n sessionStorage.removeItem(MFA_PENDING_REFRESH);\n sessionStorage.removeItem(MFA_REDIRECT);\n\n // Redirect to SSO redirect URL or default to dashboard\n const redirectUrl = ssoRedirect || \"/\";\n navigate(redirectUrl, { replace: true });\n } else {\n // Handle expired challenge - redirect to login\n if (result.error === \"mfa_challenge_expired\") {\n // Clear all MFA state\n sessionStorage.removeItem(MFA_CHALLENGE_ID);\n sessionStorage.removeItem(MFA_PENDING_TOKEN);\n sessionStorage.removeItem(MFA_PENDING_REFRESH);\n sessionStorage.removeItem(MFA_REDIRECT);\n useAuthStore.getState().setMfaRequired(false);\n\n // Redirect to login with message\n navigate(\"/auth/login\", {\n replace: true,\n state: {\n message:\n \"Your verification session expired. Please sign in again.\",\n },\n });\n return;\n }\n\n setError(\n result.error ||\n tr(\"auth.mfaVerify.invalidCode\", \"Invalid verification code\"),\n );\n setCode([\"\", \"\", \"\", \"\", \"\", \"\"]);\n inputRefs.current[0]?.focus();\n }\n } catch (err) {\n console.error(\"MFA verification failed:\", err);\n\n // Check if the error is about expired challenge\n const errorMessage = err instanceof Error ? err.message : String(err);\n if (errorMessage.includes(\"mfa_challenge_expired\")) {\n // Clear all MFA state\n sessionStorage.removeItem(MFA_CHALLENGE_ID);\n sessionStorage.removeItem(MFA_PENDING_TOKEN);\n sessionStorage.removeItem(MFA_PENDING_REFRESH);\n sessionStorage.removeItem(MFA_REDIRECT);\n useAuthStore.getState().setMfaRequired(false);\n\n // Redirect to login with message\n navigate(\"/auth/login\", {\n replace: true,\n state: {\n message: \"Your verification session expired. Please sign in again.\",\n },\n });\n return;\n }\n\n setError(\n tr(\n \"auth.mfaVerify.verificationFailed\",\n \"Verification failed. Please try again.\",\n ),\n );\n setCode([\"\", \"\", \"\", \"\", \"\", \"\"]);\n inputRefs.current[0]?.focus();\n }\n };\n\n return (\n <AuthLayout\n title={tr(\"auth.mfaVerify.title\", \"Two-factor authentication\")}\n subtitle={tr(\n \"auth.mfaVerify.subtitle\",\n \"Enter the 6-digit code from your authenticator app\",\n )}\n >\n {/* Shield Icon */}\n <div className=\"flex justify-center mb-6\">\n <div className=\"w-16 h-16 rounded-full bg-[var(--color-bg-accent)] flex items-center justify-center\">\n <svg\n className=\"w-8 h-8 text-[var(--color-text-link)]\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z\"\n />\n </svg>\n </div>\n </div>\n\n <form\n onSubmit={(e) => {\n e.preventDefault();\n handleSubmit();\n }}\n className=\"space-y-6\"\n >\n {/* Error Alert */}\n {error && (\n <div className=\"flex items-start gap-3 p-4 rounded-xl bg-[var(--color-status-error-bg-subtle)] border border-[var(--color-status-error-border)]\">\n <AlertIcon className=\"w-5 h-5 text-[var(--color-status-error-text)] flex-shrink-0 mt-0.5\" />\n <p className=\"text-sm text-[var(--color-status-error-text)]\">\n {error}\n </p>\n </div>\n )}\n\n {/* Code Input */}\n <div className=\"space-y-2\">\n <label className=\"block text-sm font-medium text-[var(--color-text-primary)] text-center\">\n {tr(\"auth.mfaVerify.verificationCode\", \"Verification Code\")}\n </label>\n <div className=\"flex justify-center gap-2 sm:gap-3\">\n {code.map((digit, index) => (\n <input\n key={index}\n ref={(el) => {\n inputRefs.current[index] = el;\n }}\n type=\"text\"\n inputMode=\"numeric\"\n autoComplete=\"one-time-code\"\n maxLength={1}\n value={digit}\n onChange={(e) => handleChange(index, e.target.value)}\n onKeyDown={(e) => handleKeyDown(index, e)}\n onPaste={handlePaste}\n className=\"w-10 h-12 sm:w-12 sm:h-14 text-center text-xl sm:text-2xl font-semibold rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] text-[var(--color-text-primary)] focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg transition-all\"\n />\n ))}\n </div>\n </div>\n\n {/* Submit Button */}\n <button\n type=\"submit\"\n disabled={isLoading || code.join(\"\").length !== 6}\n className=\"auth-primary-button flex w-full items-center justify-center gap-2 px-4 py-3 font-semibold\"\n >\n {isLoading ? (\n <>\n <SpinnerIcon className=\"w-5 h-5 animate-spin\" />\n <span>Verifying...</span>\n </>\n ) : (\n tr(\"auth.mfaVerify.verify\", \"Verify\")\n )}\n </button>\n\n {/* Recovery Options */}\n <div className=\"text-center space-y-3\">\n <p className=\"text-sm text-[var(--color-text-secondary)]\">\n {tr(\n \"auth.mfaVerify.cantAccess\",\n \"Can't access your authenticator app?\",\n )}\n </p>\n <button\n type=\"button\"\n onClick={() => navigate(\"/auth/mfa-recovery\")}\n className=\"text-sm font-medium text-text-link hover:text-text-linkHover transition-colors\"\n >\n {tr(\"auth.mfaVerify.useRecoveryCode\", \"Use a recovery code\")}\n </button>\n </div>\n </form>\n\n {/* Back to Login */}\n <p className=\"mt-8 text-center text-sm text-[var(--color-text-secondary)]\">\n <button\n onClick={() => {\n // Clear MFA state so LoginPage doesn't immediately redirect back here\n sessionStorage.removeItem(MFA_CHALLENGE_ID);\n sessionStorage.removeItem(MFA_PENDING_TOKEN);\n sessionStorage.removeItem(MFA_PENDING_REFRESH);\n sessionStorage.removeItem(MFA_REDIRECT);\n useAuthStore.getState().setMfaRequired(false);\n navigate(\"/auth/login\");\n }}\n className=\"font-semibold text-text-link hover:text-text-linkHover transition-colors\"\n >\n {tr(\"auth.mfaVerify.backToSignIn\", \"Back to sign in\")}\n </button>\n </p>\n </AuthLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"MfaVerifyPage.js","names":[],"sources":["../../../src/auth/pages/MfaVerifyPage.tsx"],"sourcesContent":["import { useState, useRef, useEffect } from \"react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell\";\nimport { useNavigate } from \"react-router-dom\";\nimport { useMfaOperations } from \"../hooks/useMfaOperations\";\nimport { useAuthStore } from \"../store/authStore\";\nimport { AuthLayout } from \"../components/AuthLayout\";\nimport { AlertIcon, SpinnerIcon } from \"../components/AuthIcons\";\nimport {\n MFA_CHALLENGE_ID,\n MFA_PENDING_TOKEN,\n MFA_PENDING_REFRESH,\n MFA_REDIRECT,\n} from \"../constants/sessionKeys\";\n\n/**\n * MFA verification page with professional design\n * Shown when user needs to verify MFA code during login\n * Handles both regular login MFA and SSO MFA flows\n */\nexport function MfaVerifyPage() {\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n };\n\n const navigate = useNavigate();\n const {\n verifyMFA,\n mfaChallengeId: storeChallengeId,\n isLoading,\n } = useMfaOperations();\n const [code, setCode] = useState([\"\", \"\", \"\", \"\", \"\", \"\"]);\n const [error, setError] = useState<string | null>(null);\n const inputRefs = useRef<(HTMLInputElement | null)[]>([]);\n\n // Get challenge ID from store or sessionStorage (SSO flow)\n const sessionChallengeId =\n typeof window !== \"undefined\"\n ? sessionStorage.getItem(MFA_CHALLENGE_ID)\n : null;\n const mfaChallengeId = storeChallengeId || sessionChallengeId;\n\n // Focus first input on mount and detect SSO flow\n useEffect(() => {\n inputRefs.current[0]?.focus();\n\n // Check if this is an SSO flow (has pending tokens in sessionStorage)\n const hasPendingToken = sessionStorage.getItem(MFA_PENDING_TOKEN);\n const hasRedirect = sessionStorage.getItem(MFA_REDIRECT);\n if (hasPendingToken || hasRedirect || sessionChallengeId) {\n }\n }, [sessionChallengeId]);\n\n const handleChange = (index: number, value: string) => {\n // Only allow digits\n const digit = value.replace(/\\D/g, \"\").slice(-1);\n\n const newCode = [...code];\n newCode[index] = digit;\n setCode(newCode);\n\n // Auto-focus next input\n if (digit && index < 5) {\n inputRefs.current[index + 1]?.focus();\n }\n\n // Auto-submit when all digits are entered\n if (digit && index === 5) {\n const fullCode = newCode.join(\"\");\n if (fullCode.length === 6) {\n handleSubmit(fullCode);\n }\n }\n };\n\n const handleKeyDown = (index: number, e: React.KeyboardEvent) => {\n if (e.key === \"Backspace\" && !code[index] && index > 0) {\n inputRefs.current[index - 1]?.focus();\n }\n };\n\n const handlePaste = (e: React.ClipboardEvent) => {\n e.preventDefault();\n const pastedData = e.clipboardData\n .getData(\"text\")\n .replace(/\\D/g, \"\")\n .slice(0, 6);\n const newCode = [...code];\n pastedData.split(\"\").forEach((char, i) => {\n if (i < 6) newCode[i] = char;\n });\n setCode(newCode);\n\n // Focus last filled input or first empty\n const lastIndex = Math.min(pastedData.length - 1, 5);\n inputRefs.current[lastIndex]?.focus();\n\n // Auto-submit if full code pasted\n if (pastedData.length === 6) {\n handleSubmit(pastedData);\n }\n };\n\n const handleSubmit = async (fullCode?: string) => {\n const codeToSubmit = fullCode || code.join(\"\");\n if (codeToSubmit.length !== 6) return;\n\n setError(null);\n\n if (!mfaChallengeId) {\n setError(\n tr(\n \"auth.mfaVerify.sessionExpired\",\n \"MFA session expired. Please log in again.\",\n ),\n );\n return;\n }\n\n try {\n const result = await verifyMFA({\n challengeId: mfaChallengeId,\n code: codeToSubmit,\n method: \"TOTP\",\n });\n\n if (result.success) {\n // Clean up SSO session data\n const ssoRedirect = sessionStorage.getItem(MFA_REDIRECT);\n sessionStorage.removeItem(MFA_CHALLENGE_ID);\n sessionStorage.removeItem(MFA_PENDING_TOKEN);\n sessionStorage.removeItem(MFA_PENDING_REFRESH);\n sessionStorage.removeItem(MFA_REDIRECT);\n\n // Redirect to SSO redirect URL or default to dashboard\n const redirectUrl = ssoRedirect || \"/\";\n navigate(redirectUrl, { replace: true });\n } else {\n // Handle expired challenge - redirect to login\n if (result.error === \"mfa_challenge_expired\") {\n // Clear all MFA state\n sessionStorage.removeItem(MFA_CHALLENGE_ID);\n sessionStorage.removeItem(MFA_PENDING_TOKEN);\n sessionStorage.removeItem(MFA_PENDING_REFRESH);\n sessionStorage.removeItem(MFA_REDIRECT);\n useAuthStore.getState().setMfaRequired(false);\n\n // Redirect to login with message\n navigate(\"/auth/login\", {\n replace: true,\n state: {\n message:\n \"Your verification session expired. Please sign in again.\",\n },\n });\n return;\n }\n\n setError(\n result.error ||\n tr(\"auth.mfaVerify.invalidCode\", \"Invalid verification code\"),\n );\n setCode([\"\", \"\", \"\", \"\", \"\", \"\"]);\n inputRefs.current[0]?.focus();\n }\n } catch (err) {\n console.error(\"MFA verification failed:\", err);\n\n // Check if the error is about expired challenge\n const errorMessage = err instanceof Error ? err.message : String(err);\n if (errorMessage.includes(\"mfa_challenge_expired\")) {\n // Clear all MFA state\n sessionStorage.removeItem(MFA_CHALLENGE_ID);\n sessionStorage.removeItem(MFA_PENDING_TOKEN);\n sessionStorage.removeItem(MFA_PENDING_REFRESH);\n sessionStorage.removeItem(MFA_REDIRECT);\n useAuthStore.getState().setMfaRequired(false);\n\n // Redirect to login with message\n navigate(\"/auth/login\", {\n replace: true,\n state: {\n message: \"Your verification session expired. Please sign in again.\",\n },\n });\n return;\n }\n\n setError(\n tr(\n \"auth.mfaVerify.verificationFailed\",\n \"Verification failed. Please try again.\",\n ),\n );\n setCode([\"\", \"\", \"\", \"\", \"\", \"\"]);\n inputRefs.current[0]?.focus();\n }\n };\n\n return (\n <AuthLayout\n title={tr(\"auth.mfaVerify.title\", \"Two-factor authentication\")}\n subtitle={tr(\n \"auth.mfaVerify.subtitle\",\n \"Enter the 6-digit code from your authenticator app\",\n )}\n >\n {/* Shield Icon */}\n <div className=\"flex justify-center mb-6\">\n <div className=\"size-16 rounded-full bg-[var(--color-bg-accent)] flex items-center justify-center\">\n <svg\n className=\"size-8 text-[var(--color-text-link)]\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z\"\n />\n </svg>\n </div>\n </div>\n\n <form\n onSubmit={(e) => {\n e.preventDefault();\n handleSubmit();\n }}\n className=\"space-y-6\"\n >\n {/* Error Alert */}\n {error && (\n <div className=\"flex items-start gap-3 p-4 rounded-xl bg-[var(--color-status-error-bg-subtle)] border border-[var(--color-status-error-border)]\">\n <AlertIcon className=\"size-5 text-[var(--color-status-error-text)] flex-shrink-0 mt-0.5\" />\n <p className=\"text-sm text-[var(--color-status-error-text)]\">\n {error}\n </p>\n </div>\n )}\n\n {/* Code Input */}\n <div className=\"space-y-2\">\n <label className=\"block text-sm font-medium text-[var(--color-text-primary)] text-center\">\n {tr(\"auth.mfaVerify.verificationCode\", \"Verification Code\")}\n </label>\n <div className=\"flex justify-center gap-2 sm:gap-3\">\n {code.map((digit, index) => (\n <input\n key={index}\n ref={(el) => {\n inputRefs.current[index] = el;\n }}\n type=\"text\"\n inputMode=\"numeric\"\n autoComplete=\"one-time-code\"\n maxLength={1}\n value={digit}\n onChange={(e) => handleChange(index, e.target.value)}\n onKeyDown={(e) => handleKeyDown(index, e)}\n onPaste={handlePaste}\n className=\"w-10 h-12 sm:w-12 sm:h-14 text-center text-xl sm:text-2xl font-semibold rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] text-[var(--color-text-primary)] focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg transition-all\"\n />\n ))}\n </div>\n </div>\n\n {/* Submit Button */}\n <button\n type=\"submit\"\n disabled={isLoading || code.join(\"\").length !== 6}\n className=\"auth-primary-button flex w-full items-center justify-center gap-2 px-4 py-3 font-semibold\"\n >\n {isLoading ? (\n <>\n <SpinnerIcon className=\"size-5 animate-spin\" />\n <span>Verifying…</span>\n </>\n ) : (\n tr(\"auth.mfaVerify.verify\", \"Verify\")\n )}\n </button>\n\n {/* Recovery Options */}\n <div className=\"text-center space-y-3\">\n <p className=\"text-sm text-[var(--color-text-secondary)]\">\n {tr(\n \"auth.mfaVerify.cantAccess\",\n \"Can't access your authenticator app?\",\n )}\n </p>\n <button\n type=\"button\"\n onClick={() => navigate(\"/auth/mfa-recovery\")}\n className=\"text-sm font-medium text-text-link hover:text-text-linkHover transition-colors\"\n >\n {tr(\"auth.mfaVerify.useRecoveryCode\", \"Use a recovery code\")}\n </button>\n </div>\n </form>\n\n {/* Back to Login */}\n <p className=\"mt-8 text-center text-sm text-[var(--color-text-secondary)]\">\n <button type=\"button\"\n onClick={() => {\n // Clear MFA state so LoginPage doesn't immediately redirect back here\n sessionStorage.removeItem(MFA_CHALLENGE_ID);\n sessionStorage.removeItem(MFA_PENDING_TOKEN);\n sessionStorage.removeItem(MFA_PENDING_REFRESH);\n sessionStorage.removeItem(MFA_REDIRECT);\n useAuthStore.getState().setMfaRequired(false);\n navigate(\"/auth/login\");\n }}\n className=\"font-semibold text-text-link hover:text-text-linkHover transition-colors\"\n >\n {tr(\"auth.mfaVerify.backToSignIn\", \"Back to sign in\")}\n </button>\n </p>\n </AuthLayout>\n );\n}\n"],"mappings":"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"}
@@ -49,8 +49,8 @@ function m() {
49
49
  y === "loading" && /* @__PURE__ */ p(d, { children: [/* @__PURE__ */ f("div", {
50
50
  className: "relative",
51
51
  children: /* @__PURE__ */ f("div", {
52
- className: "w-16 h-16 rounded-full bg-status-info-bg-subtle flex items-center justify-center",
53
- children: /* @__PURE__ */ f(i, { className: "w-8 h-8 text-text-link animate-spin" })
52
+ className: "size-16 rounded-full bg-status-info-bg-subtle flex items-center justify-center",
53
+ children: /* @__PURE__ */ f(i, { className: "size-8 text-text-link animate-spin" })
54
54
  })
55
55
  }), /* @__PURE__ */ f("p", {
56
56
  className: "text-sm text-[var(--color-text-secondary)]",
@@ -59,8 +59,8 @@ function m() {
59
59
  y === "success" && /* @__PURE__ */ p(d, { children: [/* @__PURE__ */ f("div", {
60
60
  className: "relative",
61
61
  children: /* @__PURE__ */ f("div", {
62
- className: "w-16 h-16 rounded-full bg-status-success-bg-subtle flex items-center justify-center",
63
- children: /* @__PURE__ */ f(r, { className: "w-8 h-8 text-status-success-text" })
62
+ className: "size-16 rounded-full bg-status-success-bg-subtle flex items-center justify-center",
63
+ children: /* @__PURE__ */ f(r, { className: "size-8 text-status-success-text" })
64
64
  })
65
65
  }), /* @__PURE__ */ f("p", {
66
66
  className: "text-sm text-[var(--color-text-secondary)]",
@@ -70,8 +70,8 @@ function m() {
70
70
  /* @__PURE__ */ f("div", {
71
71
  className: "relative",
72
72
  children: /* @__PURE__ */ f("div", {
73
- className: "w-16 h-16 rounded-full bg-status-error-bg-subtle flex items-center justify-center",
74
- children: /* @__PURE__ */ f(n, { className: "w-8 h-8 text-status-error-text" })
73
+ className: "size-16 rounded-full bg-status-error-bg-subtle flex items-center justify-center",
74
+ children: /* @__PURE__ */ f(n, { className: "size-8 text-status-error-text" })
75
75
  })
76
76
  }),
77
77
  /* @__PURE__ */ p("div", {
@@ -85,6 +85,7 @@ function m() {
85
85
  })]
86
86
  }),
87
87
  /* @__PURE__ */ f("button", {
88
+ type: "button",
88
89
  onClick: () => g("/auth/login", { replace: !0 }),
89
90
  className: "px-6 py-2.5 rounded-xl bg-[var(--color-action-primary-bg)] text-white font-medium hover:bg-[var(--color-action-primary-bgHover)] transition-colors",
90
91
  children: h("auth.oauthCallback.backToLogin", "Back to Login")