@authowl/react 0.21.2 → 0.23.0

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.
package/dist/index.js CHANGED
@@ -20,6 +20,7 @@ import {
20
20
  useConsent,
21
21
  useEmailVerification,
22
22
  useInvitationRecipientHint,
23
+ useLastUsedSignInMethod,
23
24
  useLocale,
24
25
  useMFA,
25
26
  useOrganization,
@@ -31,13 +32,14 @@ import {
31
32
  useServerError,
32
33
  useSession,
33
34
  useSignIn,
35
+ useSignInMethodRecorder,
34
36
  useSignOut,
35
37
  useSignUp,
36
38
  useSubmitAction,
37
39
  useT,
38
40
  useUser,
39
41
  useWaitlist
40
- } from "./chunk-PUJV7S6G.js";
42
+ } from "./chunk-E5ZOQHXY.js";
41
43
 
42
44
  // src/project-capabilities.ts
43
45
  import {
@@ -276,6 +278,7 @@ function SocialButtons({ providers, callbackURL }) {
276
278
  const t = useT();
277
279
  const toServerError = useServerError();
278
280
  const { signInSocial } = useSignIn();
281
+ const recordSignInMethod = useSignInMethodRecorder();
279
282
  const [pending, setPending] = React2.useState(null);
280
283
  const [error, setError] = React2.useState(null);
281
284
  React2.useEffect(() => {
@@ -308,6 +311,8 @@ function SocialButtons({ providers, callbackURL }) {
308
311
  if (res?.error) {
309
312
  setError(toServerError(res.error, t("social.error.startFailed")));
310
313
  setPending(null);
314
+ } else {
315
+ recordSignInMethod(`social:${provider}`, true);
311
316
  }
312
317
  } catch {
313
318
  setError(t("social.error.startFailed"));
@@ -356,104 +361,19 @@ function PasskeyButton({ redirectTo, onSignedIn }) {
356
361
  }
357
362
 
358
363
  // src/components/ForgotPassword.tsx
359
- import * as React5 from "react";
360
-
361
- // src/components/AuthChallenge.tsx
362
364
  import * as React4 from "react";
363
365
 
364
- // src/components/Turnstile.tsx
366
+ // src/components/AuthChallenge.tsx
365
367
  import * as React3 from "react";
366
- import { jsx as jsx4 } from "react/jsx-runtime";
367
- var SCRIPT_URL = "https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit";
368
- var SCRIPT_LOAD_TIMEOUT_MS = 1e4;
369
- var LOCAL_TURNSTILE_TEST_TOKEN = "XXXX.DUMMY.TOKEN.XXXX";
370
- var loader = null;
371
- function loadTurnstile() {
372
- if (window.turnstile) return Promise.resolve(window.turnstile);
373
- if (loader) return loader;
374
- loader = new Promise((resolve, reject) => {
375
- const existing = document.querySelector(`script[src="${SCRIPT_URL}"]`);
376
- const script = existing ?? document.createElement("script");
377
- const cleanup = () => {
378
- clearTimeout(timeout);
379
- script.removeEventListener("load", loaded);
380
- script.removeEventListener("error", failed);
381
- };
382
- const loaded = () => {
383
- cleanup();
384
- if (window.turnstile) {
385
- resolve(window.turnstile);
386
- } else {
387
- reject(new Error("Turnstile unavailable"));
388
- }
389
- };
390
- const failed = () => {
391
- cleanup();
392
- if (!existing) script.remove();
393
- reject(new Error("Turnstile failed to load"));
394
- };
395
- const timeout = setTimeout(failed, SCRIPT_LOAD_TIMEOUT_MS);
396
- script.addEventListener("load", loaded, { once: true });
397
- script.addEventListener("error", failed, { once: true });
398
- if (!existing) {
399
- script.src = SCRIPT_URL;
400
- script.async = true;
401
- script.defer = true;
402
- const nonce = document.querySelector("script[nonce]")?.nonce;
403
- if (nonce) script.nonce = nonce;
404
- document.head.appendChild(script);
405
- }
406
- }).catch((error) => {
407
- loader = null;
408
- throw error;
409
- });
410
- return loader;
411
- }
412
- function Turnstile({
413
- siteKey,
414
- theme,
415
- onToken,
416
- onUnavailable
417
- }) {
418
- const container = React3.useRef(null);
419
- const callbacks = React3.useRef({ onToken, onUnavailable });
420
- callbacks.current = { onToken, onUnavailable };
421
- React3.useEffect(() => {
422
- callbacks.current.onToken(null);
423
- if (!siteKey) {
424
- callbacks.current.onToken(LOCAL_TURNSTILE_TEST_TOKEN);
425
- return;
426
- }
427
- let active = true;
428
- let widget = null;
429
- void loadTurnstile().then((api) => {
430
- if (!active || !container.current) return;
431
- const id = api.render(container.current, {
432
- sitekey: siteKey,
433
- theme: theme === "system" ? "auto" : theme,
434
- size: "flexible",
435
- callback: (token) => callbacks.current.onToken(token),
436
- "expired-callback": () => callbacks.current.onToken(null),
437
- "error-callback": () => {
438
- callbacks.current.onToken(null);
439
- callbacks.current.onUnavailable();
440
- }
441
- });
442
- widget = { api, id };
443
- }).catch(() => {
444
- if (active) callbacks.current.onUnavailable();
445
- });
446
- return () => {
447
- active = false;
448
- if (widget) widget.api.remove(widget.id);
449
- };
450
- }, [siteKey, theme]);
451
- if (!siteKey) return null;
452
- return /* @__PURE__ */ jsx4("div", { className: "ba-turnstile", ref: container, "data-testid": "phoneotp-turnstile" });
368
+
369
+ // src/components/captcha-provider-ids.ts
370
+ var CAPTCHA_PROVIDER_IDS = ["turnstile", "hcaptcha", "recaptcha-v2"];
371
+ function isSupportedCaptchaProvider(provider) {
372
+ return CAPTCHA_PROVIDER_IDS.includes(provider);
453
373
  }
454
374
 
455
375
  // src/components/AuthChallenge.tsx
456
- import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
376
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
457
377
  var AUTH_CHALLENGE_ACTIONS = {
458
378
  signUp: "auth_signup",
459
379
  signIn: "auth_signin",
@@ -468,38 +388,53 @@ var challengeError = {
468
388
  code: "BOT_CHALLENGE_FAILED",
469
389
  message: "Human verification failed."
470
390
  };
471
- function turnstileTheme(root, fallback) {
391
+ function captchaTheme(root, fallback) {
472
392
  const rendered = root?.dataset.authowlTheme;
473
393
  if (rendered === "light" || rendered === "dark") return rendered;
474
394
  if (fallback === "light" || fallback === "dark") return fallback;
475
395
  return "auto";
476
396
  }
477
397
  function useAuthChallenge() {
478
- const { config, isLoading } = usePublicConfig();
398
+ const { config, isLoading, retry } = usePublicConfig();
479
399
  const t = useT();
480
- const container = React4.useRef(null);
481
- const active = React4.useRef(null);
482
- const [status, setStatus] = React4.useState("idle");
483
- const siteKey = config?.authTurnstileSiteKey ?? null;
484
- const removeActive = React4.useCallback((reason) => {
400
+ const container = React3.useRef(null);
401
+ const active = React3.useRef(null);
402
+ const [status, setStatus] = React3.useState("idle");
403
+ const captcha = config?.captcha ?? null;
404
+ const providerSupported = captcha ? isSupportedCaptchaProvider(captcha.provider) : false;
405
+ const unavailableProvider = captcha && !providerSupported ? captcha.provider : null;
406
+ const refetched = React3.useRef(null);
407
+ const refetchIfConfigMayBeStale = React3.useCallback(() => {
408
+ if (!config || refetched.current === config) return;
409
+ refetched.current = config;
410
+ retry();
411
+ }, [config, retry]);
412
+ const removeActive = React3.useCallback((reason) => {
485
413
  const current = active.current;
486
414
  active.current = null;
487
415
  if (!current) return;
488
- current.api.remove(current.id);
416
+ current.adapter.teardown(current.api, current.id);
489
417
  if (reason) current.reject(reason);
490
418
  }, []);
491
- React4.useEffect(
492
- () => () => removeActive(new Error("Turnstile challenge was cancelled")),
419
+ React3.useEffect(
420
+ () => () => removeActive(new Error("Captcha challenge was cancelled")),
493
421
  [removeActive]
494
422
  );
495
- const tokenFor = React4.useCallback(
423
+ const tokenFor = React3.useCallback(
496
424
  async (action) => {
497
- if (!siteKey) return null;
425
+ if (!captcha) return null;
426
+ if (!providerSupported) throw new Error(`Unsupported captcha provider: ${captcha.provider}`);
498
427
  setStatus("checking");
499
- removeActive(new Error("Turnstile challenge was replaced"));
428
+ removeActive(new Error("Captcha challenge was replaced"));
500
429
  try {
501
- const api = await loadTurnstile();
502
- if (!container.current) throw new Error("Turnstile container unavailable");
430
+ const [{ loadCaptcha }, { captchaAdapterFor }] = await Promise.all([
431
+ import("./captcha-loader-VNULARCZ.js"),
432
+ import("./captcha-providers-STYFL25P.js")
433
+ ]);
434
+ const adapter = captchaAdapterFor(captcha.provider);
435
+ if (!adapter) throw new Error(`Unsupported captcha provider: ${captcha.provider}`);
436
+ const api = await loadCaptcha(adapter);
437
+ if (!container.current) throw new Error("Captcha container unavailable");
503
438
  return await new Promise((resolve, reject) => {
504
439
  let settled = false;
505
440
  const finish = (outcome) => {
@@ -507,27 +442,29 @@ function useAuthChallenge() {
507
442
  settled = true;
508
443
  const current = active.current;
509
444
  active.current = null;
510
- if (current) current.api.remove(current.id);
445
+ if (current) current.adapter.teardown(current.api, current.id);
511
446
  if ("token" in outcome) resolve(outcome.token);
512
447
  else reject(outcome.error);
513
448
  };
514
- const fail = () => finish({ error: new Error("Turnstile challenge failed") });
449
+ const fail = () => finish({ error: new Error("Captcha challenge failed") });
515
450
  const root = container.current.closest(".authowl-root");
451
+ const optionalFailureCallbacks = captcha.provider === "turnstile" ? {
452
+ "timeout-callback": fail,
453
+ "unsupported-callback": fail
454
+ } : {};
516
455
  const id = api.render(container.current, {
517
- sitekey: siteKey,
518
- theme: turnstileTheme(root, config?.branding?.theme),
519
- action,
520
- execution: "execute",
521
- appearance: "interaction-only",
522
- size: "flexible",
523
- language: root?.dataset.authowlLocale ?? config?.locale,
456
+ ...adapter.invisibleRenderOptions({
457
+ siteKey: captcha.siteKey,
458
+ theme: captchaTheme(root, config?.branding?.theme),
459
+ action,
460
+ language: root?.dataset.authowlLocale ?? config?.locale
461
+ }),
524
462
  callback: (token) => finish({ token }),
525
463
  "expired-callback": fail,
526
464
  "error-callback": fail,
527
- "timeout-callback": fail,
528
- "unsupported-callback": fail
465
+ ...optionalFailureCallbacks
529
466
  });
530
- active.current = { api, id, reject };
467
+ active.current = { adapter, api, id, reject };
531
468
  try {
532
469
  api.execute(id);
533
470
  } catch {
@@ -538,9 +475,9 @@ function useAuthChallenge() {
538
475
  setStatus("idle");
539
476
  }
540
477
  },
541
- [config?.branding?.theme, config?.locale, removeActive, siteKey]
478
+ [captcha, config?.branding?.theme, config?.locale, providerSupported, removeActive]
542
479
  );
543
- const run = React4.useCallback(
480
+ const run = React3.useCallback(
544
481
  async (action, request) => {
545
482
  if (isLoading) {
546
483
  return {
@@ -550,37 +487,40 @@ function useAuthChallenge() {
550
487
  }
551
488
  try {
552
489
  const token = await tokenFor(action);
553
- return await request(token ? { authChallengeToken: token } : void 0);
490
+ const result = await request(token ? { authChallengeToken: token } : void 0);
491
+ if (result?.error?.code === challengeError.code) refetchIfConfigMayBeStale();
492
+ return result;
554
493
  } catch {
555
494
  setStatus("failed");
495
+ refetchIfConfigMayBeStale();
556
496
  return {
557
497
  data: null,
558
498
  error: challengeError
559
499
  };
560
500
  }
561
501
  },
562
- [isLoading, tokenFor]
502
+ [isLoading, refetchIfConfigMayBeStale, tokenFor]
563
503
  );
564
- const control = siteKey ? /* @__PURE__ */ jsxs4("div", { className: "ba-auth-challenge", "data-testid": "auth-challenge", children: [
565
- /* @__PURE__ */ jsx5("div", { className: "ba-turnstile", ref: container }),
566
- /* @__PURE__ */ jsx5("p", { className: "ba-sr-only", role: "status", "aria-live": "polite", children: status === "checking" ? t("authChallenge.checking") : status === "failed" ? t("authChallenge.error.failed") : "" })
504
+ const control = captcha ? /* @__PURE__ */ jsxs4("div", { className: "ba-auth-challenge", "data-testid": "auth-challenge", children: [
505
+ providerSupported ? /* @__PURE__ */ jsx4("div", { className: "ba-turnstile", ref: container }) : null,
506
+ unavailableProvider ? /* @__PURE__ */ jsx4(FormError, { "data-testid": "auth-challenge-unsupported", children: t("authChallenge.error.unsupportedProvider", { provider: unavailableProvider }) }) : /* @__PURE__ */ jsx4("p", { className: "ba-sr-only", role: "status", "aria-live": "polite", children: status === "checking" ? t("authChallenge.checking") : status === "failed" ? t("authChallenge.error.failed") : "" })
567
507
  ] }) : null;
568
508
  return { run, control, configPending: isLoading };
569
509
  }
570
510
 
571
511
  // src/components/ForgotPassword.tsx
572
- import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
512
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
573
513
  function ForgotPassword({ resetPasswordUrl, onBack }) {
574
514
  const t = useT();
575
515
  const { requestPasswordReset } = usePasswordReset();
576
516
  const { pending, error, run } = useSubmitAction();
577
517
  const authChallenge = useAuthChallenge();
578
- const [email, setEmail] = React5.useState("");
579
- const [sent, setSent] = React5.useState(false);
518
+ const [email, setEmail] = React4.useState("");
519
+ const [sent, setSent] = React4.useState(false);
580
520
  if (sent) {
581
521
  return /* @__PURE__ */ jsxs5("div", { className: "ba-fields", "data-testid": "forgot-sent", children: [
582
- /* @__PURE__ */ jsx6("p", { className: "ba-muted", children: richMessage(t("forgotPassword.sent"), { email: /* @__PURE__ */ jsx6(Bidi, { children: email }) }) }),
583
- onBack && /* @__PURE__ */ jsx6("button", { type: "button", className: "ba-link-button", onClick: onBack, children: t("forgotPassword.backToSignIn") })
522
+ /* @__PURE__ */ jsx5("p", { className: "ba-muted", children: richMessage(t("forgotPassword.sent"), { email: /* @__PURE__ */ jsx5(Bidi, { children: email }) }) }),
523
+ onBack && /* @__PURE__ */ jsx5("button", { type: "button", className: "ba-link-button", onClick: onBack, children: t("forgotPassword.backToSignIn") })
584
524
  ] });
585
525
  }
586
526
  return /* @__PURE__ */ jsxs5(
@@ -599,7 +539,7 @@ function ForgotPassword({ resetPasswordUrl, onBack }) {
599
539
  children: [
600
540
  /* @__PURE__ */ jsxs5("label", { className: "ba-label", children: [
601
541
  t("common.emailLabel"),
602
- /* @__PURE__ */ jsx6(
542
+ /* @__PURE__ */ jsx5(
603
543
  "input",
604
544
  {
605
545
  className: "ba-input",
@@ -612,25 +552,25 @@ function ForgotPassword({ resetPasswordUrl, onBack }) {
612
552
  )
613
553
  ] }),
614
554
  authChallenge.control,
615
- /* @__PURE__ */ jsx6(FormError, { children: error }),
616
- /* @__PURE__ */ jsx6(
555
+ /* @__PURE__ */ jsx5(FormError, { children: error }),
556
+ /* @__PURE__ */ jsx5(
617
557
  "button",
618
558
  {
619
559
  className: "ba-button",
620
560
  type: "submit",
621
561
  disabled: pending || authChallenge.configPending,
622
562
  "aria-busy": pending || void 0,
623
- children: /* @__PURE__ */ jsx6(Busy, { busy: pending, label: t("common.sending"), children: t("forgotPassword.submit") })
563
+ children: /* @__PURE__ */ jsx5(Busy, { busy: pending, label: t("common.sending"), children: t("forgotPassword.submit") })
624
564
  }
625
565
  ),
626
- onBack && /* @__PURE__ */ jsx6("button", { type: "button", className: "ba-link-button", onClick: onBack, children: t("forgotPassword.backToSignIn") })
566
+ onBack && /* @__PURE__ */ jsx5("button", { type: "button", className: "ba-link-button", onClick: onBack, children: t("forgotPassword.backToSignIn") })
627
567
  ]
628
568
  }
629
569
  );
630
570
  }
631
571
 
632
572
  // src/components/OtpCodeForm.tsx
633
- import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
573
+ import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
634
574
  function OtpCodeForm({
635
575
  email,
636
576
  code,
@@ -653,8 +593,8 @@ function OtpCodeForm({
653
593
  },
654
594
  children: [
655
595
  /* @__PURE__ */ jsxs6("label", { className: "ba-label", children: [
656
- richMessage(t("emailOtp.codeLabel"), { email: /* @__PURE__ */ jsx7(Bidi, { children: email }) }),
657
- /* @__PURE__ */ jsx7(
596
+ richMessage(t("emailOtp.codeLabel"), { email: /* @__PURE__ */ jsx6(Bidi, { children: email }) }),
597
+ /* @__PURE__ */ jsx6(
658
598
  "input",
659
599
  {
660
600
  className: "ba-input",
@@ -667,24 +607,24 @@ function OtpCodeForm({
667
607
  }
668
608
  )
669
609
  ] }),
670
- /* @__PURE__ */ jsx7(FormError, { children: error }),
671
- /* @__PURE__ */ jsx7("button", { className: "ba-button", type: "submit", disabled: pending, "aria-busy": pending || void 0, children: /* @__PURE__ */ jsx7(Busy, { busy: pending, label: t("common.verifying"), children: t("emailOtp.verifySubmit") }) }),
672
- /* @__PURE__ */ jsx7("button", { type: "button", className: "ba-link-button", onClick: onChangeEmail, children: t("emailOtp.changeEmail") })
610
+ /* @__PURE__ */ jsx6(FormError, { children: error }),
611
+ /* @__PURE__ */ jsx6("button", { className: "ba-button", type: "submit", disabled: pending, "aria-busy": pending || void 0, children: /* @__PURE__ */ jsx6(Busy, { busy: pending, label: t("common.verifying"), children: t("emailOtp.verifySubmit") }) }),
612
+ /* @__PURE__ */ jsx6("button", { type: "button", className: "ba-link-button", onClick: onChangeEmail, children: t("emailOtp.changeEmail") })
673
613
  ]
674
614
  }
675
615
  );
676
616
  }
677
617
 
678
618
  // src/components/MFAChallenge.tsx
679
- import * as React6 from "react";
680
- import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
619
+ import * as React5 from "react";
620
+ import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
681
621
  function MFAChallenge({ onVerified, allowTrustDevice = true }) {
682
622
  const t = useT();
683
623
  const { verifyTotp, verifyBackupCode, sendOtp, verifyOtp } = useMFA();
684
624
  const { pending, error, setError, run } = useSubmitAction();
685
- const [mode, setMode] = React6.useState("totp");
686
- const [code, setCode] = React6.useState("");
687
- const [trustDevice, setTrustDevice] = React6.useState(false);
625
+ const [mode, setMode] = React5.useState("totp");
626
+ const [code, setCode] = React5.useState("");
627
+ const [trustDevice, setTrustDevice] = React5.useState(false);
688
628
  const switchMode = (next) => {
689
629
  setMode(next);
690
630
  setCode("");
@@ -708,11 +648,11 @@ function MFAChallenge({ onVerified, allowTrustDevice = true }) {
708
648
  const hint = mode === "totp" ? t("mfa.challenge.totpHint") : mode === "backup" ? t("mfa.challenge.backupHint") : t("mfa.challenge.otpHint");
709
649
  const label2 = mode === "totp" ? t("mfa.challenge.totpLabel") : mode === "backup" ? t("mfa.challenge.backupLabel") : t("mfa.challenge.otpLabel");
710
650
  return /* @__PURE__ */ jsxs7("form", { method: "post", className: "ba-fields", "data-testid": "mfa-challenge", onSubmit: submit, children: [
711
- /* @__PURE__ */ jsx8("h2", { className: "ba-title", children: t("mfa.challenge.title") }),
712
- /* @__PURE__ */ jsx8("p", { className: "ba-muted", children: hint }),
651
+ /* @__PURE__ */ jsx7("h2", { className: "ba-title", children: t("mfa.challenge.title") }),
652
+ /* @__PURE__ */ jsx7("p", { className: "ba-muted", children: hint }),
713
653
  /* @__PURE__ */ jsxs7("label", { className: "ba-label", children: [
714
654
  label2,
715
- /* @__PURE__ */ jsx8(
655
+ /* @__PURE__ */ jsx7(
716
656
  "input",
717
657
  {
718
658
  className: "ba-input",
@@ -730,24 +670,24 @@ function MFAChallenge({ onVerified, allowTrustDevice = true }) {
730
670
  ] }),
731
671
  mode !== "backup" && allowTrustDevice && /* @__PURE__ */ jsxs7("label", { className: "ba-consent ba-consent-centered", children: [
732
672
  /* @__PURE__ */ jsxs7("span", { className: "ba-checkbox-control", children: [
733
- /* @__PURE__ */ jsx8("input", { className: "ba-checkbox", type: "checkbox", checked: trustDevice, onChange: (e) => setTrustDevice(e.target.checked) }),
734
- /* @__PURE__ */ jsx8("span", { className: "ba-checkbox-visual", "aria-hidden": "true", children: /* @__PURE__ */ jsx8("span", { className: "ba-checkbox-check" }) })
673
+ /* @__PURE__ */ jsx7("input", { className: "ba-checkbox", type: "checkbox", checked: trustDevice, onChange: (e) => setTrustDevice(e.target.checked) }),
674
+ /* @__PURE__ */ jsx7("span", { className: "ba-checkbox-visual", "aria-hidden": "true", children: /* @__PURE__ */ jsx7("span", { className: "ba-checkbox-check" }) })
735
675
  ] }),
736
- /* @__PURE__ */ jsx8("span", { children: t("mfa.challenge.trustDevice", { days: 30 }) })
676
+ /* @__PURE__ */ jsx7("span", { children: t("mfa.challenge.trustDevice", { days: 30 }) })
737
677
  ] }),
738
- /* @__PURE__ */ jsx8(FormError, { children: error }),
739
- /* @__PURE__ */ jsx8("button", { className: "ba-button", type: "submit", disabled: pending || !code, "aria-busy": pending || void 0, children: /* @__PURE__ */ jsx8(Busy, { busy: pending, label: t("common.verifying"), children: t("mfa.challenge.submit") }) }),
740
- mode !== "totp" && /* @__PURE__ */ jsx8("button", { type: "button", className: "ba-link-button", onClick: () => switchMode("totp"), children: t("mfa.challenge.useTotp") }),
741
- mode !== "backup" && /* @__PURE__ */ jsx8("button", { type: "button", className: "ba-link-button", onClick: () => switchMode("backup"), children: t("mfa.challenge.useBackup") }),
742
- /* @__PURE__ */ jsx8("button", { type: "button", className: "ba-link-button", disabled: pending, onClick: requestOtp, children: t(mode === "otp" ? "mfa.challenge.resendOtp" : "mfa.challenge.useEmailOtp") })
678
+ /* @__PURE__ */ jsx7(FormError, { children: error }),
679
+ /* @__PURE__ */ jsx7("button", { className: "ba-button", type: "submit", disabled: pending || !code, "aria-busy": pending || void 0, children: /* @__PURE__ */ jsx7(Busy, { busy: pending, label: t("common.verifying"), children: t("mfa.challenge.submit") }) }),
680
+ mode !== "totp" && /* @__PURE__ */ jsx7("button", { type: "button", className: "ba-link-button", onClick: () => switchMode("totp"), children: t("mfa.challenge.useTotp") }),
681
+ mode !== "backup" && /* @__PURE__ */ jsx7("button", { type: "button", className: "ba-link-button", onClick: () => switchMode("backup"), children: t("mfa.challenge.useBackup") }),
682
+ /* @__PURE__ */ jsx7("button", { type: "button", className: "ba-link-button", disabled: pending, onClick: requestOtp, children: t(mode === "otp" ? "mfa.challenge.resendOtp" : "mfa.challenge.useEmailOtp") })
743
683
  ] });
744
684
  }
745
685
 
746
686
  // src/components/mfa-enrollment-step.tsx
747
- import * as React9 from "react";
687
+ import * as React8 from "react";
748
688
 
749
689
  // src/components/MFAEnrollment.tsx
750
- import * as React8 from "react";
690
+ import * as React7 from "react";
751
691
 
752
692
  // src/components/mfa-enrollment.ts
753
693
  function shouldDiscardSetup(args) {
@@ -756,12 +696,12 @@ function shouldDiscardSetup(args) {
756
696
  }
757
697
 
758
698
  // src/components/QrCode.tsx
759
- import * as React7 from "react";
760
- import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
699
+ import * as React6 from "react";
700
+ import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
761
701
  function QrCode({ value, size = 200 }) {
762
702
  const t = useT();
763
- const [path, setPath] = React7.useState(null);
764
- React7.useEffect(() => {
703
+ const [path, setPath] = React6.useState(null);
704
+ React6.useEffect(() => {
765
705
  let active = true;
766
706
  setPath(null);
767
707
  void import("./qr-ZNG4MAV5.js").then(
@@ -790,15 +730,15 @@ function QrCode({ value, size = 200 }) {
790
730
  shapeRendering: "crispEdges",
791
731
  className: "ba-qr",
792
732
  children: [
793
- /* @__PURE__ */ jsx9("rect", { width: dim, height: dim, fill: "#ffffff" }),
794
- /* @__PURE__ */ jsx9("g", { transform: `translate(${margin} ${margin})`, children: /* @__PURE__ */ jsx9("path", { d: path.d, fill: "#000000" }) })
733
+ /* @__PURE__ */ jsx8("rect", { width: dim, height: dim, fill: "#ffffff" }),
734
+ /* @__PURE__ */ jsx8("g", { transform: `translate(${margin} ${margin})`, children: /* @__PURE__ */ jsx8("path", { d: path.d, fill: "#000000" }) })
795
735
  ]
796
736
  }
797
737
  );
798
738
  }
799
739
 
800
740
  // src/components/MFAEnrollment.tsx
801
- import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
741
+ import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
802
742
  function secretFromUri(uri) {
803
743
  try {
804
744
  return new URL(uri).searchParams.get("secret");
@@ -811,11 +751,11 @@ function MFAEnrollment({ onEnrolled, title }) {
811
751
  const { enable, verifyTotp } = useMFA();
812
752
  const { user, isLoaded } = useUser();
813
753
  const { pending, error, run } = useSubmitAction();
814
- const [password, setPassword] = React8.useState("");
815
- const [setup, setSetup] = React8.useState(null);
816
- const [code, setCode] = React8.useState("");
817
- const [done, setDone] = React8.useState(false);
818
- const setupOwnerId = React8.useRef(null);
754
+ const [password, setPassword] = React7.useState("");
755
+ const [setup, setSetup] = React7.useState(null);
756
+ const [code, setCode] = React7.useState("");
757
+ const [done, setDone] = React7.useState(false);
758
+ const setupOwnerId = React7.useRef(null);
819
759
  const userId = user?.id ?? null;
820
760
  const stale = shouldDiscardSetup({
821
761
  isLoaded,
@@ -824,7 +764,7 @@ function MFAEnrollment({ onEnrolled, title }) {
824
764
  currentUserId: userId
825
765
  });
826
766
  const activeSetup = stale ? null : setup;
827
- React8.useEffect(() => {
767
+ React7.useEffect(() => {
828
768
  if (stale) {
829
769
  setSetup(null);
830
770
  setPassword("");
@@ -833,18 +773,18 @@ function MFAEnrollment({ onEnrolled, title }) {
833
773
  }, [stale]);
834
774
  if (done) {
835
775
  return /* @__PURE__ */ jsxs9("div", { className: "ba-fields", "data-testid": "mfa-enroll-done", children: [
836
- /* @__PURE__ */ jsx10("h2", { className: "ba-title", children: t("mfa.enroll.doneTitle") }),
837
- /* @__PURE__ */ jsx10("p", { className: "ba-muted", children: t("mfa.enroll.doneBody") })
776
+ /* @__PURE__ */ jsx9("h2", { className: "ba-title", children: t("mfa.enroll.doneTitle") }),
777
+ /* @__PURE__ */ jsx9("p", { className: "ba-muted", children: t("mfa.enroll.doneBody") })
838
778
  ] });
839
779
  }
840
780
  if (!activeSetup) {
841
781
  if (!isLoaded) {
842
- return /* @__PURE__ */ jsx10("div", { className: "ba-fields", "data-testid": "mfa-enroll-loading", "aria-busy": "true", children: /* @__PURE__ */ jsx10("div", { className: "ba-skeleton" }) });
782
+ return /* @__PURE__ */ jsx9("div", { className: "ba-fields", "data-testid": "mfa-enroll-loading", "aria-busy": "true", children: /* @__PURE__ */ jsx9("div", { className: "ba-skeleton" }) });
843
783
  }
844
784
  if (user?.twoFactorEnabled) {
845
785
  return /* @__PURE__ */ jsxs9("div", { className: "ba-fields", "data-testid": "mfa-enroll-active", children: [
846
- /* @__PURE__ */ jsx10("h2", { className: "ba-title", children: t("mfa.enroll.activeTitle") }),
847
- /* @__PURE__ */ jsx10("p", { className: "ba-muted", children: t("mfa.enroll.activeBody") })
786
+ /* @__PURE__ */ jsx9("h2", { className: "ba-title", children: t("mfa.enroll.activeTitle") }),
787
+ /* @__PURE__ */ jsx9("p", { className: "ba-muted", children: t("mfa.enroll.activeBody") })
848
788
  ] });
849
789
  }
850
790
  return /* @__PURE__ */ jsxs9(
@@ -864,11 +804,11 @@ function MFAEnrollment({ onEnrolled, title }) {
864
804
  });
865
805
  },
866
806
  children: [
867
- title !== null && /* @__PURE__ */ jsx10("h2", { className: "ba-title", children: title ?? t("mfa.enroll.title") }),
868
- /* @__PURE__ */ jsx10("p", { className: "ba-muted", children: t("mfa.enroll.passwordHint") }),
807
+ title !== null && /* @__PURE__ */ jsx9("h2", { className: "ba-title", children: title ?? t("mfa.enroll.title") }),
808
+ /* @__PURE__ */ jsx9("p", { className: "ba-muted", children: t("mfa.enroll.passwordHint") }),
869
809
  /* @__PURE__ */ jsxs9("label", { className: "ba-label", children: [
870
810
  t("common.passwordLabel"),
871
- /* @__PURE__ */ jsx10(
811
+ /* @__PURE__ */ jsx9(
872
812
  "input",
873
813
  {
874
814
  className: "ba-input",
@@ -880,27 +820,27 @@ function MFAEnrollment({ onEnrolled, title }) {
880
820
  }
881
821
  )
882
822
  ] }),
883
- /* @__PURE__ */ jsx10(FormError, { children: error }),
884
- /* @__PURE__ */ jsx10("button", { className: "ba-button", type: "submit", disabled: pending || !password, "aria-busy": pending || void 0, children: /* @__PURE__ */ jsx10(Busy, { busy: pending, label: t("mfa.enroll.startPending"), children: t("common.continue") }) })
823
+ /* @__PURE__ */ jsx9(FormError, { children: error }),
824
+ /* @__PURE__ */ jsx9("button", { className: "ba-button", type: "submit", disabled: pending || !password, "aria-busy": pending || void 0, children: /* @__PURE__ */ jsx9(Busy, { busy: pending, label: t("mfa.enroll.startPending"), children: t("common.continue") }) })
885
825
  ]
886
826
  }
887
827
  );
888
828
  }
889
829
  const secret = secretFromUri(activeSetup.totpURI);
890
830
  return /* @__PURE__ */ jsxs9("div", { className: "ba-fields", "data-testid": "mfa-enroll-verify", children: [
891
- /* @__PURE__ */ jsx10("h2", { className: "ba-title", children: t("mfa.enroll.scanQrTitle") }),
892
- /* @__PURE__ */ jsx10("p", { className: "ba-muted", children: t("mfa.enroll.scanQrHint") }),
893
- /* @__PURE__ */ jsx10(QrCode, { value: activeSetup.totpURI }),
894
- secret && /* @__PURE__ */ jsx10("p", { className: "ba-muted", children: richMessage(t("mfa.enroll.manualKey"), {
895
- secret: /* @__PURE__ */ jsx10("code", { className: "ba-code", children: /* @__PURE__ */ jsx10(Bidi, { children: secret }) })
831
+ /* @__PURE__ */ jsx9("h2", { className: "ba-title", children: t("mfa.enroll.scanQrTitle") }),
832
+ /* @__PURE__ */ jsx9("p", { className: "ba-muted", children: t("mfa.enroll.scanQrHint") }),
833
+ /* @__PURE__ */ jsx9(QrCode, { value: activeSetup.totpURI }),
834
+ secret && /* @__PURE__ */ jsx9("p", { className: "ba-muted", children: richMessage(t("mfa.enroll.manualKey"), {
835
+ secret: /* @__PURE__ */ jsx9("code", { className: "ba-code", children: /* @__PURE__ */ jsx9(Bidi, { children: secret }) })
896
836
  }) }),
897
837
  /* @__PURE__ */ jsxs9("div", { children: [
898
838
  /* @__PURE__ */ jsxs9("p", { className: "ba-muted", children: [
899
- /* @__PURE__ */ jsx10("strong", { children: t("mfa.enroll.backupCodesWarningStrong") }),
839
+ /* @__PURE__ */ jsx9("strong", { children: t("mfa.enroll.backupCodesWarningStrong") }),
900
840
  " ",
901
841
  t("mfa.enroll.backupCodesWarningRest")
902
842
  ] }),
903
- /* @__PURE__ */ jsx10("ul", { className: "ba-backup-codes", "data-testid": "mfa-backup-codes", children: activeSetup.backupCodes.map((c) => /* @__PURE__ */ jsx10("li", { children: /* @__PURE__ */ jsx10("code", { className: "ba-code", children: c }) }, c)) })
843
+ /* @__PURE__ */ jsx9("ul", { className: "ba-backup-codes", "data-testid": "mfa-backup-codes", children: activeSetup.backupCodes.map((c) => /* @__PURE__ */ jsx9("li", { children: /* @__PURE__ */ jsx9("code", { className: "ba-code", children: c }) }, c)) })
904
844
  ] }),
905
845
  /* @__PURE__ */ jsxs9(
906
846
  "form",
@@ -922,7 +862,7 @@ function MFAEnrollment({ onEnrolled, title }) {
922
862
  children: [
923
863
  /* @__PURE__ */ jsxs9("label", { className: "ba-label", children: [
924
864
  t("mfa.enroll.codeLabel"),
925
- /* @__PURE__ */ jsx10(
865
+ /* @__PURE__ */ jsx9(
926
866
  "input",
927
867
  {
928
868
  className: "ba-input",
@@ -934,8 +874,8 @@ function MFAEnrollment({ onEnrolled, title }) {
934
874
  }
935
875
  )
936
876
  ] }),
937
- /* @__PURE__ */ jsx10(FormError, { children: error }),
938
- /* @__PURE__ */ jsx10("button", { className: "ba-button", type: "submit", disabled: pending || !code, "aria-busy": pending || void 0, children: /* @__PURE__ */ jsx10(Busy, { busy: pending, label: t("common.verifying"), children: t("mfa.enroll.activateSubmit") }) })
877
+ /* @__PURE__ */ jsx9(FormError, { children: error }),
878
+ /* @__PURE__ */ jsx9("button", { className: "ba-button", type: "submit", disabled: pending || !code, "aria-busy": pending || void 0, children: /* @__PURE__ */ jsx9(Busy, { busy: pending, label: t("common.verifying"), children: t("mfa.enroll.activateSubmit") }) })
939
879
  ]
940
880
  }
941
881
  )
@@ -943,13 +883,13 @@ function MFAEnrollment({ onEnrolled, title }) {
943
883
  }
944
884
 
945
885
  // src/components/mfa-enrollment-step.tsx
946
- import { Fragment as Fragment2, jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
886
+ import { Fragment as Fragment2, jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
947
887
  function useConfirmedMfaPending(onUnknown = "pending") {
948
888
  const client = useAuthClient();
949
889
  const { needsMfaEnrollment } = useUser();
950
890
  const { refetch } = useSession();
951
- const [confirmed, setConfirmed] = React9.useState(null);
952
- React9.useEffect(() => {
891
+ const [confirmed, setConfirmed] = React8.useState(null);
892
+ React8.useEffect(() => {
953
893
  if (!needsMfaEnrollment) {
954
894
  setConfirmed(null);
955
895
  return;
@@ -974,22 +914,22 @@ function MfaEnrollmentStep({ title }) {
974
914
  const t = useT();
975
915
  const { refetch } = useSession();
976
916
  return /* @__PURE__ */ jsxs10(Fragment2, { children: [
977
- /* @__PURE__ */ jsx11("h2", { className: "ba-title", children: title ?? t("mfaGate.title") }),
978
- /* @__PURE__ */ jsx11("p", { className: "ba-muted", children: t("mfaGate.body") }),
979
- /* @__PURE__ */ jsx11(MFAEnrollment, { onEnrolled: () => refetch({ query: { disableCookieCache: true } }) })
917
+ /* @__PURE__ */ jsx10("h2", { className: "ba-title", children: title ?? t("mfaGate.title") }),
918
+ /* @__PURE__ */ jsx10("p", { className: "ba-muted", children: t("mfaGate.body") }),
919
+ /* @__PURE__ */ jsx10(MFAEnrollment, { onEnrolled: () => refetch({ query: { disableCookieCache: true } }) })
980
920
  ] });
981
921
  }
982
922
 
983
923
  // src/components/AuthOwlBadge.tsx
984
- import { jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
924
+ import { jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
985
925
  function OwlMark() {
986
926
  return /* @__PURE__ */ jsxs11("svg", { className: "ba-badge-mark", viewBox: "0 0 24 24", "aria-hidden": "true", focusable: "false", children: [
987
- /* @__PURE__ */ jsx12("circle", { cx: "12", cy: "12", r: "11", fill: "#F5B84C" }),
988
- /* @__PURE__ */ jsx12("circle", { cx: "8.4", cy: "10", r: "3", fill: "#fff" }),
989
- /* @__PURE__ */ jsx12("circle", { cx: "15.6", cy: "10", r: "3", fill: "#fff" }),
990
- /* @__PURE__ */ jsx12("circle", { cx: "8.4", cy: "10", r: "1.4", fill: "#1f1300" }),
991
- /* @__PURE__ */ jsx12("circle", { cx: "15.6", cy: "10", r: "1.4", fill: "#1f1300" }),
992
- /* @__PURE__ */ jsx12("path", { d: "M12 13.1l1.5 2.1h-3z", fill: "#1f1300" })
927
+ /* @__PURE__ */ jsx11("circle", { cx: "12", cy: "12", r: "11", fill: "#F5B84C" }),
928
+ /* @__PURE__ */ jsx11("circle", { cx: "8.4", cy: "10", r: "3", fill: "#fff" }),
929
+ /* @__PURE__ */ jsx11("circle", { cx: "15.6", cy: "10", r: "3", fill: "#fff" }),
930
+ /* @__PURE__ */ jsx11("circle", { cx: "8.4", cy: "10", r: "1.4", fill: "#1f1300" }),
931
+ /* @__PURE__ */ jsx11("circle", { cx: "15.6", cy: "10", r: "1.4", fill: "#1f1300" }),
932
+ /* @__PURE__ */ jsx11("path", { d: "M12 13.1l1.5 2.1h-3z", fill: "#1f1300" })
993
933
  ] });
994
934
  }
995
935
  function AuthOwlBadge({ href = "https://authowl.dev", force } = {}) {
@@ -1005,9 +945,9 @@ function AuthOwlBadge({ href = "https://authowl.dev", force } = {}) {
1005
945
  rel: "noopener noreferrer",
1006
946
  "data-testid": "authowl-badge",
1007
947
  children: [
1008
- /* @__PURE__ */ jsx12(OwlMark, {}),
1009
- /* @__PURE__ */ jsx12("span", { children: richMessage(t("badge.securedBy"), {
1010
- brand: /* @__PURE__ */ jsx12("span", { className: "ba-badge-brand", children: "AuthOwl" })
948
+ /* @__PURE__ */ jsx11(OwlMark, {}),
949
+ /* @__PURE__ */ jsx11("span", { children: richMessage(t("badge.securedBy"), {
950
+ brand: /* @__PURE__ */ jsx11("span", { className: "ba-badge-brand", children: "AuthOwl" })
1011
951
  }) })
1012
952
  ]
1013
953
  }
@@ -1022,19 +962,19 @@ import {
1022
962
  } from "@authowl/core";
1023
963
 
1024
964
  // src/components/ConsentDocLinks.tsx
1025
- import { Fragment as Fragment3, jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
965
+ import { Fragment as Fragment3, jsx as jsx12, jsxs as jsxs12 } from "react/jsx-runtime";
1026
966
  function ConsentDocLinks({ termsUrl, privacyUrl }) {
1027
967
  const t = useT();
1028
968
  if (!termsUrl && !privacyUrl) return null;
1029
969
  return /* @__PURE__ */ jsxs12(Fragment3, { children: [
1030
- termsUrl && /* @__PURE__ */ jsx13("a", { href: termsUrl, target: "_blank", rel: "noopener noreferrer", children: t("consent.termsOfService") }),
970
+ termsUrl && /* @__PURE__ */ jsx12("a", { href: termsUrl, target: "_blank", rel: "noopener noreferrer", children: t("consent.termsOfService") }),
1031
971
  termsUrl && privacyUrl && t("consent.docJoiner"),
1032
- privacyUrl && /* @__PURE__ */ jsx13("a", { href: privacyUrl, target: "_blank", rel: "noopener noreferrer", children: t("consent.privacyPolicy") })
972
+ privacyUrl && /* @__PURE__ */ jsx12("a", { href: privacyUrl, target: "_blank", rel: "noopener noreferrer", children: t("consent.privacyPolicy") })
1033
973
  ] });
1034
974
  }
1035
975
 
1036
976
  // src/components/LegalConsentCheckbox.tsx
1037
- import { jsx as jsx14, jsxs as jsxs13 } from "react/jsx-runtime";
977
+ import { jsx as jsx13, jsxs as jsxs13 } from "react/jsx-runtime";
1038
978
  function LegalConsentCheckbox({
1039
979
  legal,
1040
980
  accepted,
@@ -1045,7 +985,7 @@ function LegalConsentCheckbox({
1045
985
  if (!legal.required) return null;
1046
986
  return /* @__PURE__ */ jsxs13("label", { className: "ba-consent", "data-testid": testId, children: [
1047
987
  /* @__PURE__ */ jsxs13("span", { className: "ba-checkbox-control", children: [
1048
- /* @__PURE__ */ jsx14(
988
+ /* @__PURE__ */ jsx13(
1049
989
  "input",
1050
990
  {
1051
991
  className: "ba-checkbox",
@@ -1054,14 +994,69 @@ function LegalConsentCheckbox({
1054
994
  onChange: (event) => onAcceptedChange(event.target.checked)
1055
995
  }
1056
996
  ),
1057
- /* @__PURE__ */ jsx14("span", { className: "ba-checkbox-visual", "aria-hidden": "true", children: /* @__PURE__ */ jsx14("span", { className: "ba-checkbox-check" }) })
997
+ /* @__PURE__ */ jsx13("span", { className: "ba-checkbox-visual", "aria-hidden": "true", children: /* @__PURE__ */ jsx13("span", { className: "ba-checkbox-check" }) })
1058
998
  ] }),
1059
- /* @__PURE__ */ jsx14("span", { children: richMessage(t("signUp.consentLabel"), {
1060
- links: /* @__PURE__ */ jsx14(ConsentDocLinks, { termsUrl: legal.termsUrl, privacyUrl: legal.privacyUrl })
999
+ /* @__PURE__ */ jsx13("span", { children: richMessage(t("signUp.consentLabel"), {
1000
+ links: /* @__PURE__ */ jsx13(ConsentDocLinks, { termsUrl: legal.termsUrl, privacyUrl: legal.privacyUrl })
1061
1001
  }) })
1062
1002
  ] });
1063
1003
  }
1064
1004
 
1005
+ // src/components/Turnstile.tsx
1006
+ import * as React9 from "react";
1007
+ import { jsx as jsx14 } from "react/jsx-runtime";
1008
+ var LOCAL_TURNSTILE_TEST_TOKEN = "XXXX.DUMMY.TOKEN.XXXX";
1009
+ async function loadTurnstile() {
1010
+ const [{ loadCaptcha }, { CAPTCHA_ADAPTERS }] = await Promise.all([
1011
+ import("./captcha-loader-VNULARCZ.js"),
1012
+ import("./captcha-providers-STYFL25P.js")
1013
+ ]);
1014
+ const adapter = CAPTCHA_ADAPTERS.turnstile;
1015
+ return { api: await loadCaptcha(adapter), adapter };
1016
+ }
1017
+ function Turnstile({
1018
+ siteKey,
1019
+ theme,
1020
+ onToken,
1021
+ onUnavailable
1022
+ }) {
1023
+ const container = React9.useRef(null);
1024
+ const callbacks = React9.useRef({ onToken, onUnavailable });
1025
+ callbacks.current = { onToken, onUnavailable };
1026
+ React9.useEffect(() => {
1027
+ callbacks.current.onToken(null);
1028
+ if (!siteKey) {
1029
+ callbacks.current.onToken(LOCAL_TURNSTILE_TEST_TOKEN);
1030
+ return;
1031
+ }
1032
+ let active = true;
1033
+ let widget = null;
1034
+ void loadTurnstile().then(({ api, adapter }) => {
1035
+ if (!active || !container.current) return;
1036
+ const id = api.render(container.current, {
1037
+ sitekey: siteKey,
1038
+ theme: theme === "system" ? "auto" : theme,
1039
+ size: "flexible",
1040
+ callback: (token) => callbacks.current.onToken(token),
1041
+ "expired-callback": () => callbacks.current.onToken(null),
1042
+ "error-callback": () => {
1043
+ callbacks.current.onToken(null);
1044
+ callbacks.current.onUnavailable();
1045
+ }
1046
+ });
1047
+ widget = { api, adapter, id };
1048
+ }).catch(() => {
1049
+ if (active) callbacks.current.onUnavailable();
1050
+ });
1051
+ return () => {
1052
+ active = false;
1053
+ if (widget) widget.adapter.teardown(widget.api, widget.id);
1054
+ };
1055
+ }, [siteKey, theme]);
1056
+ if (!siteKey) return null;
1057
+ return /* @__PURE__ */ jsx14("div", { className: "ba-turnstile", ref: container, "data-testid": "phoneotp-turnstile" });
1058
+ }
1059
+
1065
1060
  // src/components/PhoneOTP.tsx
1066
1061
  import { jsx as jsx15, jsxs as jsxs14 } from "react/jsx-runtime";
1067
1062
  function PhoneOTP({
@@ -1425,6 +1420,15 @@ function SignIn({
1425
1420
  const [challenge, setChallenge] = React13.useState(false);
1426
1421
  const mfaEnrolment = useConfirmedMfaPending("clear");
1427
1422
  const emailRef = React13.useRef(null);
1423
+ const recordSignInMethod = useSignInMethodRecorder();
1424
+ const onPasskeySignedIn = React13.useCallback(() => {
1425
+ recordSignInMethod("passkey");
1426
+ onSignedIn?.();
1427
+ }, [onSignedIn, recordSignInMethod]);
1428
+ const onPhoneOtpSignedIn = React13.useCallback(() => {
1429
+ recordSignInMethod("phone-otp");
1430
+ onSignedIn?.();
1431
+ }, [onSignedIn, recordSignInMethod]);
1428
1432
  const plan = resolveSignInMethods(
1429
1433
  config,
1430
1434
  typeof window === "undefined" ? void 0 : window.location.hostname
@@ -1432,7 +1436,7 @@ function SignIn({
1432
1436
  usePasskeyAutofill({
1433
1437
  enabled: credentialMode === "email" && plan.autofillHost !== null,
1434
1438
  redirectTo,
1435
- onSignedIn
1439
+ onSignedIn: onPasskeySignedIn
1436
1440
  });
1437
1441
  const badge = /* @__PURE__ */ jsx19(AuthOwlBadge, { force: showBadge });
1438
1442
  const branding = showBranding ? /* @__PURE__ */ jsx19(AuthOwlBranding, {}) : null;
@@ -1484,7 +1488,10 @@ function SignIn({
1484
1488
  error,
1485
1489
  onSubmit: () => void run(() => signInEmailOtp({ email, otp }), {
1486
1490
  failure: t("emailOtp.error.invalidCode"),
1487
- onSuccess: () => finishSignIn({ sessionStore, redirectTo, onSignedIn })
1491
+ onSuccess: () => {
1492
+ recordSignInMethod("email-otp");
1493
+ finishSignIn({ sessionStore, redirectTo, onSignedIn });
1494
+ }
1488
1495
  }),
1489
1496
  onChangeEmail: () => {
1490
1497
  setOtp("");
@@ -1512,7 +1519,7 @@ function SignIn({
1512
1519
  PhoneOTP,
1513
1520
  {
1514
1521
  redirectTo,
1515
- onSignedIn,
1522
+ onSignedIn: onPhoneOtpSignedIn,
1516
1523
  onBack: () => setView("sign-in"),
1517
1524
  onMfaPasswordRequired: () => {
1518
1525
  setView("sign-in");
@@ -1540,6 +1547,7 @@ function SignIn({
1540
1547
  failure: t("signIn.error.failed"),
1541
1548
  onSuccess: (res) => {
1542
1549
  const data = res.data;
1550
+ recordSignInMethod(credentialMode === "username" ? "username" : "password");
1543
1551
  if (data && "twoFactorRedirect" in data && data.twoFactorRedirect) {
1544
1552
  setChallenge(true);
1545
1553
  } else {
@@ -1552,7 +1560,10 @@ function SignIn({
1552
1560
  "magic",
1553
1561
  () => void run(() => authChallenge.run(AUTH_CHALLENGE_ACTIONS.passwordless, (options) => signInMagicLink({ email, callbackURL: redirectTo }, options)), {
1554
1562
  failure: t("magicLink.error.sendFailed"),
1555
- onSuccess: () => setView("magic-sent")
1563
+ onSuccess: () => {
1564
+ recordSignInMethod("magic-link", true);
1565
+ setView("magic-sent");
1566
+ }
1556
1567
  })
1557
1568
  );
1558
1569
  const doRequestOtp = () => start(
@@ -1569,6 +1580,7 @@ function SignIn({
1569
1580
  {
1570
1581
  failure: t("sso.error.startFailed"),
1571
1582
  mapError: (error2) => error2.status === 404 ? t("sso.error.notFound") : null,
1583
+ onSuccess: () => recordSignInMethod("sso", true),
1572
1584
  // SSO always redirects the browser to the IdP; keep the spinner up
1573
1585
  // through that navigation instead of flashing back to idle.
1574
1586
  keepPendingOnSuccess: true
@@ -1707,7 +1719,7 @@ function SignIn({
1707
1719
  ]
1708
1720
  }
1709
1721
  ),
1710
- plan.passkey && /* @__PURE__ */ jsx19(PasskeyButton, { redirectTo, onSignedIn }),
1722
+ plan.passkey && /* @__PURE__ */ jsx19(PasskeyButton, { redirectTo, onSignedIn: onPasskeySignedIn }),
1711
1723
  plan.phoneOtp && /* @__PURE__ */ jsx19(
1712
1724
  "button",
1713
1725
  {
@@ -3449,9 +3461,9 @@ function formatSessionTime(date, locale) {
3449
3461
 
3450
3462
  // src/components/user-profile/use-account-resource.ts
3451
3463
  import * as React32 from "react";
3452
- function useAccountResource(loader2, failure) {
3453
- const loaderRef = React32.useRef(loader2);
3454
- loaderRef.current = loader2;
3464
+ function useAccountResource(loader, failure) {
3465
+ const loaderRef = React32.useRef(loader);
3466
+ loaderRef.current = loader;
3455
3467
  const failureRef = React32.useRef(failure);
3456
3468
  failureRef.current = failure;
3457
3469
  const toServerError = useServerError();
@@ -4443,7 +4455,7 @@ var SECTION_KEYS2 = {
4443
4455
  invitations: "organization.profile.nav.invitations",
4444
4456
  danger: "organization.profile.nav.danger"
4445
4457
  };
4446
- var TeamsSection = React44.lazy(() => import("./TeamsSection-ZQDNMWV3.js"));
4458
+ var TeamsSection = React44.lazy(() => import("./TeamsSection-NV64O5I4.js"));
4447
4459
  function OrganizationProfile({ organizationId, defaultSection = "general", onDeleted, onLeft } = {}) {
4448
4460
  const t = useT();
4449
4461
  const toServerError = useServerError();
@@ -5010,6 +5022,14 @@ function normalizeDismissReason(reason) {
5010
5022
  }
5011
5023
  return "unknown";
5012
5024
  }
5025
+ var warnedMissingNonce = /* @__PURE__ */ new Set();
5026
+ function warnOneTapWithoutNonce(clientId) {
5027
+ if (warnedMissingNonce.has(clientId)) return;
5028
+ warnedMissingNonce.add(clientId);
5029
+ console.warn(
5030
+ "[AuthOwl] <GoogleOneTap/> is running without a `nonce`. Google\u2019s ID token is then not bound to a value chosen for this prompt. Generate a fresh random value per prompt and pass it as `nonce`; AuthOwl forwards it to Google and verifies the match. The direct browser exchange does not keep separate one-time server state, so do not treat this prop by itself as replay protection. (This warning is dev-only.)"
5031
+ );
5032
+ }
5013
5033
  function GoogleOneTap({
5014
5034
  disabled = false,
5015
5035
  nonce,
@@ -5033,6 +5053,11 @@ function GoogleOneTap({
5033
5053
  callbacks.current = { onSignedIn, onSkipped, onDismissed, onError };
5034
5054
  const googleEnabled = config?.socialProviders.includes("google") === true;
5035
5055
  const clientId = config?.socialProviderClientIds?.google;
5056
+ React47.useEffect(() => {
5057
+ if (process.env.NODE_ENV === "production") return;
5058
+ if (disabled || !googleEnabled || !clientId || nonce) return;
5059
+ warnOneTapWithoutNonce(clientId);
5060
+ }, [clientId, disabled, googleEnabled, nonce]);
5036
5061
  React47.useEffect(() => {
5037
5062
  if (configLoading || !sessionLoaded) return;
5038
5063
  if (disabled) {
@@ -5208,6 +5233,7 @@ export {
5208
5233
  useConsent,
5209
5234
  useEmailVerification,
5210
5235
  useInvitationRecipientHint,
5236
+ useLastUsedSignInMethod,
5211
5237
  useLocale,
5212
5238
  useMFA,
5213
5239
  useOrganization,