@passflow/react 0.2.0 → 0.2.10

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 (39) hide show
  1. package/dist/index.cjs.js +4 -4
  2. package/dist/index.cjs.js.map +1 -1
  3. package/dist/index.es.js +467 -40
  4. package/dist/index.es.js.map +1 -1
  5. package/dist/src/components/flow/two-factor-verify/index.d.ts +1 -0
  6. package/dist/src/components/flow/two-factor-verify/index.d.ts.map +1 -1
  7. package/dist/src/components/form/forgot-password/forgot-password.d.ts.map +1 -1
  8. package/dist/src/components/form/index.d.ts +1 -0
  9. package/dist/src/components/form/index.d.ts.map +1 -1
  10. package/dist/src/components/form/invitation-join/index.d.ts.map +1 -1
  11. package/dist/src/components/form/reset-password/index.d.ts.map +1 -1
  12. package/dist/src/components/form/signin/index.d.ts.map +1 -1
  13. package/dist/src/components/form/signup/index.d.ts.map +1 -1
  14. package/dist/src/components/form/two-factor-challenge/index.d.ts +10 -0
  15. package/dist/src/components/form/two-factor-challenge/index.d.ts.map +1 -0
  16. package/dist/src/components/form/two-factor-challenge/method-selector.d.ts +12 -0
  17. package/dist/src/components/form/two-factor-challenge/method-selector.d.ts.map +1 -0
  18. package/dist/src/components/form/two-factor-challenge/otp-input.d.ts +12 -0
  19. package/dist/src/components/form/two-factor-challenge/otp-input.d.ts.map +1 -0
  20. package/dist/src/components/form/two-factor-verify/two-factor-recovery-form.d.ts.map +1 -1
  21. package/dist/src/components/form/verify-challenge/verify-challenge-otp-manual.d.ts.map +1 -1
  22. package/dist/src/components/provider/passflow-provider.d.ts.map +1 -1
  23. package/dist/src/context/auth-context.d.ts +10 -1
  24. package/dist/src/context/auth-context.d.ts.map +1 -1
  25. package/dist/src/hooks/index.d.ts +3 -0
  26. package/dist/src/hooks/index.d.ts.map +1 -1
  27. package/dist/src/hooks/use-app-settings.d.ts.map +1 -1
  28. package/dist/src/hooks/use-session-expired.d.ts +49 -0
  29. package/dist/src/hooks/use-session-expired.d.ts.map +1 -0
  30. package/dist/src/hooks/use-two-factor-challenge.d.ts +16 -0
  31. package/dist/src/hooks/use-two-factor-challenge.d.ts.map +1 -0
  32. package/dist/src/hooks/use-two-factor-methods.d.ts +14 -0
  33. package/dist/src/hooks/use-two-factor-methods.d.ts.map +1 -0
  34. package/dist/src/hooks/use-two-factor-setup-magic-link.d.ts.map +1 -1
  35. package/dist/src/types/index.d.ts +18 -0
  36. package/dist/src/types/index.d.ts.map +1 -1
  37. package/dist/src/utils/classify-two-factor-error.d.ts.map +1 -1
  38. package/dist/style.css +1 -1
  39. package/package.json +2 -2
package/dist/index.es.js CHANGED
@@ -6,6 +6,8 @@ import clsx from 'clsx';
6
6
  import { twMerge } from 'tailwind-merge';
7
7
  import * as React from 'react';
8
8
  import { useState, useEffect, forwardRef, createContext, useCallback, useContext, useRef, useLayoutEffect, useMemo, useReducer } from 'react';
9
+ import { PassflowEvent, parseToken, Passflow } from '@passflow/core';
10
+ export * from '@passflow/core';
9
11
  import queryString from 'query-string';
10
12
  import { getCountryForTimezone } from 'countries-and-timezones';
11
13
  import { usePhoneInput, defaultCountries, parseCountry, FlagImage } from 'react-international-phone';
@@ -16,11 +18,9 @@ import { HelmetProvider, Helmet } from 'react-helmet-async';
16
18
  import { ErrorBoundary } from 'react-error-boundary';
17
19
  import { phone as phone$1 } from 'phone';
18
20
  import { useForm, Controller } from 'react-hook-form';
19
- import { parseToken, Passflow } from '@passflow/core';
20
- export * from '@passflow/core';
21
21
  import 'react-dom';
22
22
 
23
- const version = "0.1.42";
23
+ const version = "0.2.10";
24
24
 
25
25
  window.passflowReactAppVersion = () => {
26
26
  console.log(`App Version: ${version}`);
@@ -292,7 +292,6 @@ function getUserFriendlyErrorMessage(error) {
292
292
  return "Two-factor authentication is not enabled for your account. Please contact your administrator to enable 2FA.";
293
293
  case "invalid_code":
294
294
  return "Invalid code. Please check your authenticator app and try again.";
295
- case "generic":
296
295
  default:
297
296
  return error.message;
298
297
  }
@@ -788,9 +787,27 @@ const NavigationContext$1 = createContext({
788
787
  });
789
788
 
790
789
  const AuthContext = createContext(void 0);
791
- const AuthProvider = ({ children }) => {
790
+ const AuthProvider = ({ children, onSessionExpired }) => {
792
791
  const passflow = usePassflow();
793
792
  const [isLoading, setIsLoading] = useState(false);
793
+ const [isSessionExpired, setIsSessionExpired] = useState(false);
794
+ useEffect(() => {
795
+ const subscriber = {
796
+ onAuthChange: (eventType, payload) => {
797
+ if (eventType === PassflowEvent.SessionExpired) {
798
+ const reason = payload?.reason ?? "refresh_failed";
799
+ setIsSessionExpired(true);
800
+ onSessionExpired?.(reason);
801
+ } else if (eventType === PassflowEvent.SignIn || eventType === PassflowEvent.Register) {
802
+ setIsSessionExpired(false);
803
+ }
804
+ }
805
+ };
806
+ passflow.subscribe(subscriber, [PassflowEvent.SessionExpired, PassflowEvent.SignIn, PassflowEvent.Register]);
807
+ return () => {
808
+ passflow.unsubscribe(subscriber);
809
+ };
810
+ }, [passflow, onSessionExpired]);
794
811
  const isAuthenticated = useCallback(() => passflow.isAuthenticated(), [passflow]);
795
812
  const getTokens = useCallback(
796
813
  async (doRefresh) => {
@@ -820,6 +837,7 @@ const AuthProvider = ({ children }) => {
820
837
  isAuthenticated,
821
838
  logout,
822
839
  isLoading,
840
+ isSessionExpired,
823
841
  getTokens
824
842
  };
825
843
  return /* @__PURE__ */ jsx(AuthContext.Provider, { value, children });
@@ -1032,10 +1050,9 @@ const useAppSettings = () => {
1032
1050
  break;
1033
1051
  }
1034
1052
  } catch {
1035
- continue;
1036
1053
  }
1037
1054
  }
1038
- if (response && response.ok) {
1055
+ if (response?.ok) {
1039
1056
  const settings = await response.json();
1040
1057
  const discoveredAppId = settings.login_app?.app_id || settings.appId;
1041
1058
  if (discoveredAppId) {
@@ -1713,7 +1730,8 @@ const useTwoFactorSetupMagicLink = (token) => {
1713
1730
  }
1714
1731
  setError(null);
1715
1732
  try {
1716
- const response = await passflow.validateTwoFactorSetupMagicLink(token);
1733
+ const passflowWithMagicLink = passflow;
1734
+ const response = await passflowWithMagicLink.validateTwoFactorSetupMagicLink(token);
1717
1735
  if (response.success && response.sessionToken && response.userId) {
1718
1736
  setSessionToken(response.sessionToken);
1719
1737
  setUserId(response.userId);
@@ -1771,7 +1789,8 @@ const useTwoFactorSetupMagicLink = (token) => {
1771
1789
  clearInterval(countdownTimerRef.current);
1772
1790
  }
1773
1791
  if (shouldClearOnUnmount.current) {
1774
- passflow.clearMagicLinkSession?.();
1792
+ const passflowWithSession = passflow;
1793
+ passflowWithSession.clearMagicLinkSession?.();
1775
1794
  }
1776
1795
  };
1777
1796
  }, [passflow]);
@@ -1789,6 +1808,178 @@ const useTwoFactorSetupMagicLink = (token) => {
1789
1808
  };
1790
1809
  };
1791
1810
 
1811
+ const useTwoFactorChallenge = () => {
1812
+ const passflow = usePassflow();
1813
+ const [challenge, setChallenge] = useState(null);
1814
+ const [selectedMethod, setSelectedMethod] = useState(null);
1815
+ const [isLoading, setIsLoading] = useState(false);
1816
+ const [error, setError] = useState(null);
1817
+ const requestChallenge = useCallback(
1818
+ async (firstFactorMethod) => {
1819
+ setIsLoading(true);
1820
+ setError(null);
1821
+ try {
1822
+ const response = await passflow.twoFactor.requestChallenge({
1823
+ first_factor_method: firstFactorMethod
1824
+ });
1825
+ setChallenge(response);
1826
+ setSelectedMethod(response.method);
1827
+ } catch (e) {
1828
+ setError(e);
1829
+ setChallenge(null);
1830
+ } finally {
1831
+ setIsLoading(false);
1832
+ }
1833
+ },
1834
+ [passflow]
1835
+ );
1836
+ const verify = useCallback(
1837
+ async (response, trustDevice = false) => {
1838
+ if (!challenge?.challenge_id) {
1839
+ setError(new Error("No active challenge session"));
1840
+ return null;
1841
+ }
1842
+ setIsLoading(true);
1843
+ setError(null);
1844
+ try {
1845
+ const result = await passflow.twoFactor.verifyV2({
1846
+ challenge_id: challenge.challenge_id,
1847
+ method: selectedMethod || challenge.method,
1848
+ response,
1849
+ trust_device: trustDevice
1850
+ });
1851
+ return result;
1852
+ } catch (e) {
1853
+ setError(e);
1854
+ return null;
1855
+ } finally {
1856
+ setIsLoading(false);
1857
+ }
1858
+ },
1859
+ [passflow, challenge, selectedMethod]
1860
+ );
1861
+ const switchMethod = useCallback(
1862
+ async (method) => {
1863
+ if (!challenge?.challenge_id) {
1864
+ setError(new Error("No active challenge session"));
1865
+ return;
1866
+ }
1867
+ setIsLoading(true);
1868
+ setError(null);
1869
+ try {
1870
+ const response = await passflow.twoFactor.switchToAlternative({
1871
+ challenge_id: challenge.challenge_id,
1872
+ method
1873
+ });
1874
+ setChallenge(response);
1875
+ setSelectedMethod(method);
1876
+ } catch (e) {
1877
+ setError(e);
1878
+ } finally {
1879
+ setIsLoading(false);
1880
+ }
1881
+ },
1882
+ [passflow, challenge]
1883
+ );
1884
+ const reset = useCallback(() => {
1885
+ setChallenge(null);
1886
+ setSelectedMethod(null);
1887
+ setError(null);
1888
+ setIsLoading(false);
1889
+ }, []);
1890
+ return {
1891
+ challenge,
1892
+ isLoading,
1893
+ error,
1894
+ requestChallenge,
1895
+ verify,
1896
+ switchMethod,
1897
+ selectedMethod,
1898
+ reset
1899
+ };
1900
+ };
1901
+
1902
+ const useTwoFactorMethods = () => {
1903
+ const passflow = usePassflow();
1904
+ const [availableMethods, setAvailableMethods] = useState([]);
1905
+ const [registeredMethods, setRegisteredMethods] = useState([]);
1906
+ const [isLoading, setIsLoading] = useState(false);
1907
+ const [error, setError] = useState(null);
1908
+ const refresh = useCallback(async () => {
1909
+ setIsLoading(true);
1910
+ setError(null);
1911
+ try {
1912
+ const methods = await passflow.twoFactor.getRegisteredMethods();
1913
+ setRegisteredMethods(methods);
1914
+ const available = methods.map((m) => m.method);
1915
+ setAvailableMethods(available);
1916
+ } catch (e) {
1917
+ setError(e);
1918
+ setRegisteredMethods([]);
1919
+ setAvailableMethods([]);
1920
+ } finally {
1921
+ setIsLoading(false);
1922
+ }
1923
+ }, [passflow]);
1924
+ const removeMethod = useCallback(
1925
+ async (methodId) => {
1926
+ setIsLoading(true);
1927
+ setError(null);
1928
+ try {
1929
+ await passflow.twoFactor.removeMethod?.(methodId);
1930
+ await refresh();
1931
+ } catch (e) {
1932
+ setError(e);
1933
+ } finally {
1934
+ setIsLoading(false);
1935
+ }
1936
+ },
1937
+ [passflow, refresh]
1938
+ );
1939
+ return {
1940
+ availableMethods,
1941
+ registeredMethods,
1942
+ isLoading,
1943
+ error,
1944
+ refresh,
1945
+ removeMethod
1946
+ };
1947
+ };
1948
+
1949
+ function useSessionExpired(options) {
1950
+ const passflow = usePassflow();
1951
+ const [isSessionExpired, setIsSessionExpired] = useState(false);
1952
+ const [expiredReason, setExpiredReason] = useState(null);
1953
+ useEffect(() => {
1954
+ const subscriber = {
1955
+ onAuthChange: (eventType, payload) => {
1956
+ if (eventType === PassflowEvent.SessionExpired) {
1957
+ const reason = payload?.reason ?? "refresh_failed";
1958
+ setIsSessionExpired(true);
1959
+ setExpiredReason(reason);
1960
+ options?.onSessionExpired?.(reason);
1961
+ } else if (eventType === PassflowEvent.SignIn || eventType === PassflowEvent.Register) {
1962
+ setIsSessionExpired(false);
1963
+ setExpiredReason(null);
1964
+ }
1965
+ }
1966
+ };
1967
+ passflow.subscribe(subscriber, [PassflowEvent.SessionExpired, PassflowEvent.SignIn, PassflowEvent.Register]);
1968
+ return () => {
1969
+ passflow.unsubscribe(subscriber);
1970
+ };
1971
+ }, [passflow, options?.onSessionExpired]);
1972
+ const resetSessionExpired = () => {
1973
+ setIsSessionExpired(false);
1974
+ setExpiredReason(null);
1975
+ };
1976
+ return {
1977
+ isSessionExpired,
1978
+ expiredReason,
1979
+ resetSessionExpired
1980
+ };
1981
+ }
1982
+
1792
1983
  const FieldPhone = ({ id, onChange, isError = false, className = "" }) => {
1793
1984
  const [show, setShow] = useState(false);
1794
1985
  const [filterValue, setFilterValue] = useState("");
@@ -2146,7 +2337,7 @@ const TwoFactorSetupForm = ({ onComplete, onCancel, numInputs }) => {
2146
2337
  /* @__PURE__ */ jsx(Icon, { size: "small", id: "warning", type: "general", className: "icon-warning" }),
2147
2338
  /* @__PURE__ */ jsx("span", { children: "These codes will only be shown once. Make sure to save them!" })
2148
2339
  ] }),
2149
- /* @__PURE__ */ jsx("div", { className: "passflow-2fa-recovery-codes", children: recoveryCodes.map((recoveryCode, index) => /* @__PURE__ */ jsx("code", { className: "passflow-2fa-recovery-code", children: recoveryCode }, index)) }),
2340
+ /* @__PURE__ */ jsx("div", { className: "passflow-2fa-recovery-codes", children: recoveryCodes.map((recoveryCode) => /* @__PURE__ */ jsx("code", { className: "passflow-2fa-recovery-code", children: recoveryCode }, recoveryCode)) }),
2150
2341
  /* @__PURE__ */ jsx(Button, { size: "big", type: "button", variant: "secondary", onClick: handleCopyRecoveryCodes, children: "Copy to Clipboard" }),
2151
2342
  /* @__PURE__ */ jsx(Button, { size: "big", type: "button", variant: "primary", onClick: handleComplete, children: "I've Saved These Codes" })
2152
2343
  ] })
@@ -2190,6 +2381,245 @@ const TwoFactorSetupFlow = ({
2190
2381
  return /* @__PURE__ */ jsx(TwoFactorSetupForm, { onComplete: handleSetupComplete, onCancel: handleCancel });
2191
2382
  };
2192
2383
 
2384
+ const methodLabels = {
2385
+ totp: "Authenticator App",
2386
+ email_otp: "Email Code",
2387
+ sms_otp: "SMS Code",
2388
+ passkey: "Passkey",
2389
+ recovery_codes: "Recovery Code",
2390
+ push_fcm: "Push Notification (FCM)",
2391
+ push_webpush: "Push Notification (Web)"
2392
+ };
2393
+ const methodIcons = {
2394
+ totp: "📱",
2395
+ email_otp: "✉️",
2396
+ sms_otp: "💬",
2397
+ passkey: "🔑",
2398
+ recovery_codes: "🔒",
2399
+ push_fcm: "🔔",
2400
+ push_webpush: "🔔"
2401
+ };
2402
+ const MethodSelector = ({
2403
+ availableMethods,
2404
+ currentMethod,
2405
+ onSelectMethod,
2406
+ isOpen,
2407
+ onClose
2408
+ }) => {
2409
+ const handleSelect = (method) => {
2410
+ onSelectMethod(method);
2411
+ onClose();
2412
+ };
2413
+ return /* @__PURE__ */ jsx(DialogPrimitive.Root, { open: isOpen, onOpenChange: (open) => !open && onClose(), children: /* @__PURE__ */ jsxs(DialogPrimitive.Portal, { children: [
2414
+ /* @__PURE__ */ jsx(DialogPrimitive.Overlay, { className: "pf-dialog-overlay" }),
2415
+ /* @__PURE__ */ jsxs(DialogPrimitive.Content, { className: "pf-dialog-content pf-method-selector-dialog", children: [
2416
+ /* @__PURE__ */ jsx(DialogPrimitive.Title, { className: "pf-dialog-title", children: "Choose Verification Method" }),
2417
+ /* @__PURE__ */ jsx(DialogPrimitive.Description, { className: "pf-dialog-description", children: "Select an alternative method to verify your identity" }),
2418
+ /* @__PURE__ */ jsx("div", { className: "pf-method-selector-list", children: availableMethods.map((method) => /* @__PURE__ */ jsxs(
2419
+ "button",
2420
+ {
2421
+ type: "button",
2422
+ className: `pf-method-selector-item ${method === currentMethod ? "pf-method-selector-item-active" : ""}`,
2423
+ onClick: () => handleSelect(method),
2424
+ disabled: method === currentMethod,
2425
+ children: [
2426
+ /* @__PURE__ */ jsx("span", { className: "pf-method-icon", children: methodIcons[method] || "🔐" }),
2427
+ /* @__PURE__ */ jsx("span", { className: "pf-method-label", children: methodLabels[method] || method }),
2428
+ method === currentMethod && /* @__PURE__ */ jsx("span", { className: "pf-method-current-badge", children: "Current" })
2429
+ ]
2430
+ },
2431
+ method
2432
+ )) }),
2433
+ /* @__PURE__ */ jsx(DialogPrimitive.Close, { asChild: true, children: /* @__PURE__ */ jsx("button", { type: "button", className: "pf-dialog-close", "aria-label": "Close", children: "×" }) })
2434
+ ] })
2435
+ ] }) });
2436
+ };
2437
+
2438
+ const OtpInputComponent = ({
2439
+ value,
2440
+ onChange,
2441
+ numInputs = 6,
2442
+ error,
2443
+ disabled = false,
2444
+ autoFocus = true
2445
+ }) => {
2446
+ const containerRef = useRef(null);
2447
+ useEffect(() => {
2448
+ if (autoFocus && containerRef.current) {
2449
+ const firstInput = containerRef.current.querySelector("input");
2450
+ if (firstInput) {
2451
+ firstInput.focus();
2452
+ }
2453
+ }
2454
+ }, [autoFocus]);
2455
+ return /* @__PURE__ */ jsxs("div", { ref: containerRef, className: "pf-otp-input-container", children: [
2456
+ /* @__PURE__ */ jsx(
2457
+ OtpInput,
2458
+ {
2459
+ value,
2460
+ onChange,
2461
+ numInputs,
2462
+ renderSeparator: /* @__PURE__ */ jsx("span", { className: "pf-otp-separator" }),
2463
+ renderInput: (props) => /* @__PURE__ */ jsx(
2464
+ "input",
2465
+ {
2466
+ ...props,
2467
+ className: `pf-otp-input ${error ? "pf-otp-input-error" : ""} ${disabled ? "pf-otp-input-disabled" : ""}`,
2468
+ disabled
2469
+ }
2470
+ ),
2471
+ inputType: "tel",
2472
+ shouldAutoFocus: autoFocus
2473
+ }
2474
+ ),
2475
+ error && /* @__PURE__ */ jsx("div", { className: "pf-otp-error", children: error })
2476
+ ] });
2477
+ };
2478
+
2479
+ const TwoFactorChallenge = ({
2480
+ firstFactorMethod,
2481
+ onSuccess,
2482
+ onError,
2483
+ trustDevice = false
2484
+ }) => {
2485
+ const { challenge, isLoading, error, requestChallenge, verify, switchMethod, selectedMethod } = useTwoFactorChallenge();
2486
+ const [otpValue, setOtpValue] = useState("");
2487
+ const [isMethodSelectorOpen, setIsMethodSelectorOpen] = useState(false);
2488
+ useEffect(() => {
2489
+ requestChallenge(firstFactorMethod);
2490
+ }, [requestChallenge, firstFactorMethod]);
2491
+ useEffect(() => {
2492
+ if (error && onError) {
2493
+ onError(error);
2494
+ }
2495
+ }, [error, onError]);
2496
+ const handleVerify = async () => {
2497
+ if (!otpValue) {
2498
+ return;
2499
+ }
2500
+ const result = await verify(otpValue, trustDevice);
2501
+ if (result) {
2502
+ onSuccess?.();
2503
+ }
2504
+ };
2505
+ const handleSwitchMethod = async (method) => {
2506
+ setOtpValue("");
2507
+ await switchMethod(method);
2508
+ };
2509
+ const handleOtpChange = (value) => {
2510
+ setOtpValue(value);
2511
+ if (value.length === 6) {
2512
+ setTimeout(() => {
2513
+ verify(value, trustDevice).then((result) => {
2514
+ if (result) {
2515
+ onSuccess?.();
2516
+ }
2517
+ });
2518
+ }, 100);
2519
+ }
2520
+ };
2521
+ if (!challenge) {
2522
+ return /* @__PURE__ */ jsx("div", { className: "pf-two-factor-challenge pf-loading", children: /* @__PURE__ */ jsx("p", { children: "Loading challenge..." }) });
2523
+ }
2524
+ const currentMethod = selectedMethod || challenge.method;
2525
+ const availableMethods = [challenge.method, ...challenge.alternative_methods || []];
2526
+ const renderChallengeInput = () => {
2527
+ switch (currentMethod) {
2528
+ case "totp":
2529
+ case "email_otp":
2530
+ case "sms_otp":
2531
+ return /* @__PURE__ */ jsxs("div", { className: "pf-challenge-input-wrapper", children: [
2532
+ /* @__PURE__ */ jsx(
2533
+ OtpInputComponent,
2534
+ {
2535
+ value: otpValue,
2536
+ onChange: handleOtpChange,
2537
+ numInputs: 6,
2538
+ error: error?.message,
2539
+ disabled: isLoading,
2540
+ autoFocus: true
2541
+ }
2542
+ ),
2543
+ currentMethod === "email_otp" && challenge.code_sent_to && /* @__PURE__ */ jsxs("p", { className: "pf-challenge-hint", children: [
2544
+ "Code sent to ",
2545
+ challenge.code_sent_to
2546
+ ] }),
2547
+ currentMethod === "sms_otp" && challenge.code_sent_to && /* @__PURE__ */ jsxs("p", { className: "pf-challenge-hint", children: [
2548
+ "Code sent to ",
2549
+ challenge.code_sent_to
2550
+ ] }),
2551
+ /* @__PURE__ */ jsx(
2552
+ "button",
2553
+ {
2554
+ type: "button",
2555
+ onClick: handleVerify,
2556
+ disabled: isLoading || otpValue.length !== 6,
2557
+ className: "pf-button pf-button-primary",
2558
+ children: isLoading ? "Verifying..." : "Verify"
2559
+ }
2560
+ )
2561
+ ] });
2562
+ case "passkey":
2563
+ return /* @__PURE__ */ jsx("div", { className: "pf-challenge-input-wrapper", children: /* @__PURE__ */ jsx("button", { type: "button", onClick: handleVerify, disabled: isLoading, className: "pf-button pf-button-primary", children: isLoading ? "Verifying..." : "Use Passkey" }) });
2564
+ case "recovery_codes":
2565
+ return /* @__PURE__ */ jsxs("div", { className: "pf-challenge-input-wrapper", children: [
2566
+ /* @__PURE__ */ jsx(
2567
+ "input",
2568
+ {
2569
+ type: "text",
2570
+ value: otpValue,
2571
+ onChange: (e) => setOtpValue(e.target.value),
2572
+ placeholder: "Enter recovery code",
2573
+ disabled: isLoading,
2574
+ className: "pf-input"
2575
+ }
2576
+ ),
2577
+ /* @__PURE__ */ jsx(
2578
+ "button",
2579
+ {
2580
+ type: "button",
2581
+ onClick: handleVerify,
2582
+ disabled: isLoading || !otpValue,
2583
+ className: "pf-button pf-button-primary",
2584
+ children: isLoading ? "Verifying..." : "Verify"
2585
+ }
2586
+ )
2587
+ ] });
2588
+ default:
2589
+ return /* @__PURE__ */ jsxs("p", { children: [
2590
+ "Unsupported method: ",
2591
+ currentMethod
2592
+ ] });
2593
+ }
2594
+ };
2595
+ return /* @__PURE__ */ jsxs("div", { className: "pf-two-factor-challenge", children: [
2596
+ /* @__PURE__ */ jsxs("div", { className: "pf-challenge-method-info", children: [
2597
+ /* @__PURE__ */ jsx("h3", { children: "Two-Factor Authentication" }),
2598
+ /* @__PURE__ */ jsx("p", { children: "Enter the verification code" })
2599
+ ] }),
2600
+ renderChallengeInput(),
2601
+ availableMethods.length > 1 && /* @__PURE__ */ jsx(
2602
+ "button",
2603
+ {
2604
+ type: "button",
2605
+ onClick: () => setIsMethodSelectorOpen(true),
2606
+ className: "pf-button pf-button-secondary pf-button-switch-method",
2607
+ children: "Use different method"
2608
+ }
2609
+ ),
2610
+ /* @__PURE__ */ jsx(
2611
+ MethodSelector,
2612
+ {
2613
+ availableMethods,
2614
+ currentMethod,
2615
+ onSelectMethod: handleSwitchMethod,
2616
+ isOpen: isMethodSelectorOpen,
2617
+ onClose: () => setIsMethodSelectorOpen(false)
2618
+ }
2619
+ )
2620
+ ] });
2621
+ };
2622
+
2193
2623
  const TwoFactorVerifyForm = ({
2194
2624
  onSuccess,
2195
2625
  onUseRecovery,
@@ -2440,7 +2870,6 @@ const TwoFactorRecoveryForm = ({
2440
2870
  isError && "passflow-field--error",
2441
2871
  isInputDisabled && "passflow-field--disabled"
2442
2872
  ),
2443
- autoFocus: !isInputDisabled,
2444
2873
  autoComplete: "off",
2445
2874
  disabled: isInputDisabled
2446
2875
  }
@@ -2478,7 +2907,8 @@ const TwoFactorRecoveryForm = ({
2478
2907
  const TwoFactorVerifyFlow = ({
2479
2908
  successAuthRedirect,
2480
2909
  signInPath = routes.signin.path,
2481
- twoFactorSetupPath = routes.two_factor_setup?.path
2910
+ twoFactorSetupPath = routes.two_factor_setup?.path,
2911
+ useV2Flow = false
2482
2912
  }) => {
2483
2913
  const [mode, setMode] = useState("verify");
2484
2914
  const [shouldBlockRender, setShouldBlockRender] = useState(false);
@@ -2531,6 +2961,9 @@ const TwoFactorVerifyFlow = ({
2531
2961
  if (!passflow.isTwoFactorVerificationRequired() && TwoFactorLoopPrevention.canRedirect()) {
2532
2962
  return null;
2533
2963
  }
2964
+ if (useV2Flow) {
2965
+ return /* @__PURE__ */ jsx(TwoFactorChallenge, { onSuccess: handleSuccess });
2966
+ }
2534
2967
  return /* @__PURE__ */ jsx(Fragment, { children: mode === "verify" ? /* @__PURE__ */ jsx(
2535
2968
  TwoFactorVerifyForm,
2536
2969
  {
@@ -2692,9 +3125,9 @@ const SignInForm = ({
2692
3125
  navigate({ to: twoFactorVerifyPath ?? routes.two_factor_verify.path });
2693
3126
  return;
2694
3127
  }
2695
- if (!isValidUrl(successAuthRedirect ?? appSettings.defaults.redirect))
2696
- navigate({ to: successAuthRedirect ?? appSettings.defaults.redirect });
2697
- else window.location.href = await getUrlWithTokens(passflow, successAuthRedirect ?? appSettings.defaults.redirect);
3128
+ const redirectUrl = successAuthRedirect ?? appSettings?.defaults.redirect ?? "";
3129
+ if (!isValidUrl(redirectUrl)) navigate({ to: redirectUrl });
3130
+ else window.location.href = await getUrlWithTokens(passflow, redirectUrl);
2698
3131
  }
2699
3132
  };
2700
3133
  const onSubmitPasskeyHandler = async (passkeyPayload) => {
@@ -2708,9 +3141,9 @@ const SignInForm = ({
2708
3141
  navigate({ to: twoFactorVerifyPath ?? routes.two_factor_verify.path });
2709
3142
  return;
2710
3143
  }
2711
- if (!isValidUrl(successAuthRedirect ?? appSettings.defaults.redirect))
2712
- navigate({ to: successAuthRedirect ?? appSettings.defaults.redirect });
2713
- else window.location.href = await getUrlWithTokens(passflow, successAuthRedirect ?? appSettings.defaults.redirect);
3144
+ const redirectUrl = successAuthRedirect ?? appSettings?.defaults.redirect ?? "";
3145
+ if (!isValidUrl(redirectUrl)) navigate({ to: redirectUrl });
3146
+ else window.location.href = await getUrlWithTokens(passflow, redirectUrl);
2714
3147
  }
2715
3148
  };
2716
3149
  const onSubmitPasswordlessHandler = async (userPayload) => {
@@ -2719,8 +3152,7 @@ const SignInForm = ({
2719
3152
  ...userPayload,
2720
3153
  challenge_type: getPasswordlessData(authMethods, defaultMethod)?.challengeType,
2721
3154
  create_tenant: createTenantForNewUser,
2722
- // biome-ignore lint/style/noNonNullAssertion: <explanation>
2723
- redirect_url: successAuthRedirect ?? appSettings.defaults.redirect,
3155
+ redirect_url: successAuthRedirect ?? appSettings?.defaults.redirect,
2724
3156
  ...!isEmpty(inviteToken) && { invite_token: inviteToken }
2725
3157
  };
2726
3158
  const response = await fetch(payload, "passwordless");
@@ -3151,25 +3583,24 @@ const SignUpForm = ({
3151
3583
  };
3152
3584
  const status = await fetch(payload, "password");
3153
3585
  if (status) {
3154
- if (!isValidUrl(successAuthRedirect ?? appSettings.defaults.redirect))
3155
- navigate({ to: successAuthRedirect ?? appSettings.defaults.redirect });
3156
- else window.location.href = await getUrlWithTokens(passflow, successAuthRedirect ?? appSettings.defaults.redirect);
3586
+ const redirectUrl = successAuthRedirect ?? appSettings?.defaults.redirect ?? "";
3587
+ if (!isValidUrl(redirectUrl)) navigate({ to: redirectUrl });
3588
+ else window.location.href = await getUrlWithTokens(passflow, redirectUrl);
3157
3589
  }
3158
3590
  };
3159
3591
  const onSubmitPasskeyHandler = async () => {
3592
+ const redirectUrl = successAuthRedirect ?? appSettings?.defaults.redirect ?? "";
3160
3593
  const payload = {
3161
3594
  relying_party_id: relyingPartyId,
3162
3595
  create_tenant: createTenantForNewUser,
3163
- // biome-ignore lint/style/noNonNullAssertion: <explanation>
3164
- redirect_url: successAuthRedirect ?? appSettings.defaults.redirect,
3596
+ redirect_url: redirectUrl,
3165
3597
  ...!isEmpty(inviteToken) && { invite_token: inviteToken },
3166
3598
  scopes
3167
3599
  };
3168
3600
  const response = await fetch(payload, "passkey");
3169
3601
  if (response) {
3170
- if (!isValidUrl(successAuthRedirect ?? appSettings.defaults.redirect))
3171
- navigate({ to: successAuthRedirect ?? appSettings.defaults.redirect });
3172
- else window.location.href = await getUrlWithTokens(passflow, successAuthRedirect ?? appSettings.defaults.redirect);
3602
+ if (!isValidUrl(redirectUrl)) navigate({ to: redirectUrl });
3603
+ else window.location.href = await getUrlWithTokens(passflow, redirectUrl);
3173
3604
  }
3174
3605
  };
3175
3606
  const onSubmitPasswordlessHandler = async (userPayload) => {
@@ -3178,8 +3609,7 @@ const SignUpForm = ({
3178
3609
  ...userPayload,
3179
3610
  challenge_type: currentChallegeType,
3180
3611
  create_tenant: createTenantForNewUser,
3181
- // biome-ignore lint/style/noNonNullAssertion: <explanation>
3182
- redirect_url: successAuthRedirect ?? appSettings.defaults.redirect,
3612
+ redirect_url: successAuthRedirect ?? appSettings?.defaults.redirect,
3183
3613
  ...!isEmpty(inviteToken) && { invite_token: inviteToken }
3184
3614
  };
3185
3615
  const response = await fetch(payload, "passwordless");
@@ -3665,8 +4095,7 @@ const VerifyChallengeOTPManual = ({
3665
4095
  const payload = {
3666
4096
  create_tenant: createTenantForNewUser,
3667
4097
  challenge_type: challengeType,
3668
- // biome-ignore lint/style/noNonNullAssertion: <explanation>
3669
- redirect_url: successAuthRedirect ?? appSettings.defaults.redirect,
4098
+ redirect_url: successAuthRedirect ?? appSettings?.defaults.redirect ?? "",
3670
4099
  ...identity === "email" ? { email: identityValue } : { phone: identityValue }
3671
4100
  };
3672
4101
  const refetchResponse = await refetch(payload, type);
@@ -3919,8 +4348,7 @@ const ForgotPassword = ({
3919
4348
  ...isEmail && { email: values.email_or_username },
3920
4349
  ...isUsername && { username: values.email_or_username },
3921
4350
  ...isPhone && { phone: validatedPhone.phoneNumber },
3922
- // biome-ignore lint/style/noNonNullAssertion: <explanation>
3923
- redirect_url: successResetRedirect ?? appSettings.defaults.redirect
4351
+ redirect_url: successResetRedirect ?? appSettings?.defaults.redirect
3924
4352
  };
3925
4353
  const status = await fetch(payload);
3926
4354
  if (status) {
@@ -4138,12 +4566,12 @@ const ResetPassword = ({ successAuthRedirect }) => {
4138
4566
  const resetTokenType = resetTokenData;
4139
4567
  const status = await fetch(values.password);
4140
4568
  if (status) {
4141
- if (!isValidUrl(resetTokenType?.redirect_url ?? successAuthRedirect ?? appSettings.defaults.redirect))
4142
- navigate({ to: resetTokenType?.redirect_url ?? successAuthRedirect ?? appSettings.defaults.redirect });
4569
+ if (!isValidUrl(resetTokenType?.redirect_url ?? successAuthRedirect ?? appSettings?.defaults.redirect))
4570
+ navigate({ to: resetTokenType?.redirect_url ?? successAuthRedirect ?? appSettings?.defaults.redirect });
4143
4571
  else
4144
4572
  window.location.href = await getUrlWithTokens(
4145
4573
  passflow,
4146
- resetTokenType?.redirect_url ?? successAuthRedirect ?? appSettings.defaults.redirect
4574
+ resetTokenType?.redirect_url ?? successAuthRedirect ?? appSettings?.defaults.redirect
4147
4575
  );
4148
4576
  }
4149
4577
  };
@@ -4306,7 +4734,7 @@ const InvitationJoinFlow = ({
4306
4734
  type: "button",
4307
4735
  variant: "primary",
4308
4736
  className: "passflow-button-invitation-join",
4309
- onClick: () => void onClickAcceptInvitationHandler(redirectUrl ?? successAuthRedirect ?? appSettings.defaults.redirect),
4737
+ onClick: () => void onClickAcceptInvitationHandler(redirectUrl ?? successAuthRedirect ?? appSettings?.defaults.redirect),
4310
4738
  disabled: isInvitationJoinLoading,
4311
4739
  children: "Accept invitation"
4312
4740
  }
@@ -6385,7 +6813,6 @@ const PassflowProvider = ({
6385
6813
  }
6386
6814
  }
6387
6815
  } catch (error) {
6388
- continue;
6389
6816
  }
6390
6817
  }
6391
6818
  console.warn("Failed to discover appId from /settings");
@@ -6415,5 +6842,5 @@ const PassflowProvider = ({
6415
6842
  return /* @__PURE__ */ jsx(PassflowContext.Provider, { value: passflowValue, children: /* @__PURE__ */ jsx(NavigationContext$1.Provider, { value: navigationValue, children: /* @__PURE__ */ jsx(AuthProvider, { children }) }) });
6416
6843
  };
6417
6844
 
6418
- export { Button, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, FieldPassword, FieldPhone, FieldText, ForgotPassword, ForgotPasswordSuccess, Icon, InvitationJoin, Link, PassflowFlow, PassflowProvider, Popover, PopoverAnchor, PopoverContent, PopoverTrigger, ProvidersBox, ResetPassword, SignIn, SignInForm, SignUp, SignUpForm, Switch, TwoFactorRecoveryForm, TwoFactorSetupForm, TwoFactorSetupMagicLinkFlow, TwoFactorVerifyForm, VerifyChallengeMagicLink, VerifyChallengeOTP, Wrapper, useAppSettings, useAuth, useAuthCloudRedirect, useForgotPassword, useJoinInvite, useLogout, useNavigation, useOutsideClick, usePassflow, usePasswordlessComplete, useProvider, useResetPassword, useSignIn, useSignUp, useTwoFactorManage, useTwoFactorSetup, useTwoFactorSetupMagicLink, useTwoFactorStatus, useTwoFactorVerify, useUserPasskeys };
6845
+ export { Button, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, FieldPassword, FieldPhone, FieldText, ForgotPassword, ForgotPasswordSuccess, Icon, InvitationJoin, Link, PassflowFlow, PassflowProvider, Popover, PopoverAnchor, PopoverContent, PopoverTrigger, ProvidersBox, ResetPassword, SignIn, SignInForm, SignUp, SignUpForm, Switch, TwoFactorChallenge, TwoFactorRecoveryForm, TwoFactorSetupForm, TwoFactorSetupMagicLinkFlow, TwoFactorVerifyForm, VerifyChallengeMagicLink, VerifyChallengeOTP, Wrapper, useAppSettings, useAuth, useAuthCloudRedirect, useForgotPassword, useJoinInvite, useLogout, useNavigation, useOutsideClick, usePassflow, usePasswordlessComplete, useProvider, useResetPassword, useSessionExpired, useSignIn, useSignUp, useTwoFactorChallenge, useTwoFactorManage, useTwoFactorMethods, useTwoFactorSetup, useTwoFactorSetupMagicLink, useTwoFactorStatus, useTwoFactorVerify, useUserPasskeys };
6419
6846
  //# sourceMappingURL=index.es.js.map