@passflow/react 0.2.8 → 0.3.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.
Files changed (37) 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 +655 -6
  4. package/dist/index.es.js.map +1 -1
  5. package/dist/src/components/flow/passflow/index.d.ts.map +1 -1
  6. package/dist/src/components/flow/two-factor-verify/index.d.ts +1 -0
  7. package/dist/src/components/flow/two-factor-verify/index.d.ts.map +1 -1
  8. package/dist/src/components/form/cli-browser-auth/index.d.ts +2 -0
  9. package/dist/src/components/form/cli-browser-auth/index.d.ts.map +1 -0
  10. package/dist/src/components/form/cli-qr-auth/index.d.ts +2 -0
  11. package/dist/src/components/form/cli-qr-auth/index.d.ts.map +1 -0
  12. package/dist/src/components/form/index.d.ts +3 -0
  13. package/dist/src/components/form/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/context/auth-context.d.ts +10 -1
  21. package/dist/src/context/auth-context.d.ts.map +1 -1
  22. package/dist/src/context/router-context.d.ts +6 -0
  23. package/dist/src/context/router-context.d.ts.map +1 -1
  24. package/dist/src/hooks/index.d.ts +4 -0
  25. package/dist/src/hooks/index.d.ts.map +1 -1
  26. package/dist/src/hooks/use-cli-auth.d.ts +9 -0
  27. package/dist/src/hooks/use-cli-auth.d.ts.map +1 -0
  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/types/index.d.ts +18 -0
  35. package/dist/src/types/index.d.ts.map +1 -1
  36. package/dist/style.css +1 -1
  37. package/package.json +3 -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.2.8";
23
+ const version = "0.3.0";
24
24
 
25
25
  window.passflowReactAppVersion = () => {
26
26
  console.log(`App Version: ${version}`);
@@ -769,6 +769,12 @@ const routes = {
769
769
  },
770
770
  two_factor_setup_magic_link: {
771
771
  path: "/two-factor-setup-magic-link/:token"
772
+ },
773
+ cli_browser: {
774
+ path: "/cli/browser/:sessionId"
775
+ },
776
+ cli_qr: {
777
+ path: "/cli/auth/:sessionId"
772
778
  }
773
779
  };
774
780
 
@@ -787,9 +793,27 @@ const NavigationContext$1 = createContext({
787
793
  });
788
794
 
789
795
  const AuthContext = createContext(void 0);
790
- const AuthProvider = ({ children }) => {
796
+ const AuthProvider = ({ children, onSessionExpired }) => {
791
797
  const passflow = usePassflow();
792
798
  const [isLoading, setIsLoading] = useState(false);
799
+ const [isSessionExpired, setIsSessionExpired] = useState(false);
800
+ useEffect(() => {
801
+ const subscriber = {
802
+ onAuthChange: (eventType, payload) => {
803
+ if (eventType === PassflowEvent.SessionExpired) {
804
+ const reason = payload?.reason ?? "refresh_failed";
805
+ setIsSessionExpired(true);
806
+ onSessionExpired?.(reason);
807
+ } else if (eventType === PassflowEvent.SignIn || eventType === PassflowEvent.Register) {
808
+ setIsSessionExpired(false);
809
+ }
810
+ }
811
+ };
812
+ passflow.subscribe(subscriber, [PassflowEvent.SessionExpired, PassflowEvent.SignIn, PassflowEvent.Register]);
813
+ return () => {
814
+ passflow.unsubscribe(subscriber);
815
+ };
816
+ }, [passflow, onSessionExpired]);
793
817
  const isAuthenticated = useCallback(() => passflow.isAuthenticated(), [passflow]);
794
818
  const getTokens = useCallback(
795
819
  async (doRefresh) => {
@@ -819,6 +843,7 @@ const AuthProvider = ({ children }) => {
819
843
  isAuthenticated,
820
844
  logout,
821
845
  isLoading,
846
+ isSessionExpired,
822
847
  getTokens
823
848
  };
824
849
  return /* @__PURE__ */ jsx(AuthContext.Provider, { value, children });
@@ -1789,6 +1814,281 @@ const useTwoFactorSetupMagicLink = (token) => {
1789
1814
  };
1790
1815
  };
1791
1816
 
1817
+ const useTwoFactorChallenge = () => {
1818
+ const passflow = usePassflow();
1819
+ const [challenge, setChallenge] = useState(null);
1820
+ const [selectedMethod, setSelectedMethod] = useState(null);
1821
+ const [isLoading, setIsLoading] = useState(false);
1822
+ const [error, setError] = useState(null);
1823
+ const requestChallenge = useCallback(
1824
+ async (firstFactorMethod) => {
1825
+ setIsLoading(true);
1826
+ setError(null);
1827
+ try {
1828
+ const response = await passflow.twoFactor.requestChallenge({
1829
+ first_factor_method: firstFactorMethod
1830
+ });
1831
+ setChallenge(response);
1832
+ setSelectedMethod(response.method);
1833
+ } catch (e) {
1834
+ setError(e);
1835
+ setChallenge(null);
1836
+ } finally {
1837
+ setIsLoading(false);
1838
+ }
1839
+ },
1840
+ [passflow]
1841
+ );
1842
+ const verify = useCallback(
1843
+ async (response, trustDevice = false) => {
1844
+ if (!challenge?.challenge_id) {
1845
+ setError(new Error("No active challenge session"));
1846
+ return null;
1847
+ }
1848
+ setIsLoading(true);
1849
+ setError(null);
1850
+ try {
1851
+ const result = await passflow.twoFactor.verifyV2({
1852
+ challenge_id: challenge.challenge_id,
1853
+ method: selectedMethod || challenge.method,
1854
+ response,
1855
+ trust_device: trustDevice
1856
+ });
1857
+ return result;
1858
+ } catch (e) {
1859
+ setError(e);
1860
+ return null;
1861
+ } finally {
1862
+ setIsLoading(false);
1863
+ }
1864
+ },
1865
+ [passflow, challenge, selectedMethod]
1866
+ );
1867
+ const switchMethod = useCallback(
1868
+ async (method) => {
1869
+ if (!challenge?.challenge_id) {
1870
+ setError(new Error("No active challenge session"));
1871
+ return;
1872
+ }
1873
+ setIsLoading(true);
1874
+ setError(null);
1875
+ try {
1876
+ const response = await passflow.twoFactor.switchToAlternative({
1877
+ challenge_id: challenge.challenge_id,
1878
+ method
1879
+ });
1880
+ setChallenge(response);
1881
+ setSelectedMethod(method);
1882
+ } catch (e) {
1883
+ setError(e);
1884
+ } finally {
1885
+ setIsLoading(false);
1886
+ }
1887
+ },
1888
+ [passflow, challenge]
1889
+ );
1890
+ const reset = useCallback(() => {
1891
+ setChallenge(null);
1892
+ setSelectedMethod(null);
1893
+ setError(null);
1894
+ setIsLoading(false);
1895
+ }, []);
1896
+ return {
1897
+ challenge,
1898
+ isLoading,
1899
+ error,
1900
+ requestChallenge,
1901
+ verify,
1902
+ switchMethod,
1903
+ selectedMethod,
1904
+ reset
1905
+ };
1906
+ };
1907
+
1908
+ const useTwoFactorMethods = () => {
1909
+ const passflow = usePassflow();
1910
+ const [availableMethods, setAvailableMethods] = useState([]);
1911
+ const [registeredMethods, setRegisteredMethods] = useState([]);
1912
+ const [isLoading, setIsLoading] = useState(false);
1913
+ const [error, setError] = useState(null);
1914
+ const refresh = useCallback(async () => {
1915
+ setIsLoading(true);
1916
+ setError(null);
1917
+ try {
1918
+ const methods = await passflow.twoFactor.getRegisteredMethods();
1919
+ setRegisteredMethods(methods);
1920
+ const available = methods.map((m) => m.method);
1921
+ setAvailableMethods(available);
1922
+ } catch (e) {
1923
+ setError(e);
1924
+ setRegisteredMethods([]);
1925
+ setAvailableMethods([]);
1926
+ } finally {
1927
+ setIsLoading(false);
1928
+ }
1929
+ }, [passflow]);
1930
+ const removeMethod = useCallback(
1931
+ async (methodId) => {
1932
+ setIsLoading(true);
1933
+ setError(null);
1934
+ try {
1935
+ await passflow.twoFactor.removeMethod?.(methodId);
1936
+ await refresh();
1937
+ } catch (e) {
1938
+ setError(e);
1939
+ } finally {
1940
+ setIsLoading(false);
1941
+ }
1942
+ },
1943
+ [passflow, refresh]
1944
+ );
1945
+ return {
1946
+ availableMethods,
1947
+ registeredMethods,
1948
+ isLoading,
1949
+ error,
1950
+ refresh,
1951
+ removeMethod
1952
+ };
1953
+ };
1954
+
1955
+ function useSessionExpired(options) {
1956
+ const passflow = usePassflow();
1957
+ const [isSessionExpired, setIsSessionExpired] = useState(false);
1958
+ const [expiredReason, setExpiredReason] = useState(null);
1959
+ useEffect(() => {
1960
+ const subscriber = {
1961
+ onAuthChange: (eventType, payload) => {
1962
+ if (eventType === PassflowEvent.SessionExpired) {
1963
+ const reason = payload?.reason ?? "refresh_failed";
1964
+ setIsSessionExpired(true);
1965
+ setExpiredReason(reason);
1966
+ options?.onSessionExpired?.(reason);
1967
+ } else if (eventType === PassflowEvent.SignIn || eventType === PassflowEvent.Register) {
1968
+ setIsSessionExpired(false);
1969
+ setExpiredReason(null);
1970
+ }
1971
+ }
1972
+ };
1973
+ passflow.subscribe(subscriber, [PassflowEvent.SessionExpired, PassflowEvent.SignIn, PassflowEvent.Register]);
1974
+ return () => {
1975
+ passflow.unsubscribe(subscriber);
1976
+ };
1977
+ }, [passflow, options?.onSessionExpired]);
1978
+ const resetSessionExpired = () => {
1979
+ setIsSessionExpired(false);
1980
+ setExpiredReason(null);
1981
+ };
1982
+ return {
1983
+ isSessionExpired,
1984
+ expiredReason,
1985
+ resetSessionExpired
1986
+ };
1987
+ }
1988
+
1989
+ const useCLIAuth = (sessionId) => {
1990
+ const [state, setState] = useState("loading");
1991
+ const [error, setError] = useState(null);
1992
+ const [expiresAt, setExpiresAt] = useState(null);
1993
+ const [challenge, setChallenge] = useState(null);
1994
+ const serverUrl = window.location.origin;
1995
+ const fetchStatus = useCallback(async () => {
1996
+ try {
1997
+ const response = await fetch(`${serverUrl}/cli/auth/status/${sessionId}`);
1998
+ if (!response.ok) {
1999
+ throw new Error(`Failed to fetch CLI auth status: ${response.statusText}`);
2000
+ }
2001
+ const data = await response.json();
2002
+ setExpiresAt(data.expires_at || null);
2003
+ switch (data.status) {
2004
+ case "pending":
2005
+ setState("pending");
2006
+ setChallenge(data.challenge || null);
2007
+ break;
2008
+ case "completed":
2009
+ setState("completed");
2010
+ break;
2011
+ case "expired":
2012
+ setState("expired");
2013
+ setError("This authentication session has expired. Please start a new one from your terminal.");
2014
+ break;
2015
+ case "failed":
2016
+ setState("error");
2017
+ setError(data.error || "Authentication failed");
2018
+ break;
2019
+ }
2020
+ } catch (err) {
2021
+ setState("error");
2022
+ setError(err instanceof Error ? err.message : "Failed to load authentication status");
2023
+ }
2024
+ }, [sessionId, serverUrl]);
2025
+ const authenticate = useCallback(async () => {
2026
+ if (!challenge) {
2027
+ setError("No challenge available for authentication");
2028
+ return;
2029
+ }
2030
+ setState("authenticating");
2031
+ setError(null);
2032
+ try {
2033
+ const { startAuthentication } = await import('@simplewebauthn/browser');
2034
+ const optionsJSON = challenge.publicKey || challenge;
2035
+ const webauthnResponse = await startAuthentication({ optionsJSON });
2036
+ const completeResponse = await fetch(`${serverUrl}/auth/passkey/authenticate/complete`, {
2037
+ method: "POST",
2038
+ headers: {
2039
+ "Content-Type": "application/json"
2040
+ },
2041
+ body: JSON.stringify(webauthnResponse)
2042
+ });
2043
+ if (!completeResponse.ok) {
2044
+ throw new Error(`Passkey authentication failed: ${completeResponse.statusText}`);
2045
+ }
2046
+ const authData = await completeResponse.json();
2047
+ if (!authData.access_token || !authData.refresh_token) {
2048
+ throw new Error("Invalid authentication response: missing tokens");
2049
+ }
2050
+ const cliCompleteRequest = {
2051
+ session_id: sessionId,
2052
+ access_token: authData.access_token,
2053
+ refresh_token: authData.refresh_token,
2054
+ user_id: authData.user_id,
2055
+ expires_in: authData.expires_in
2056
+ };
2057
+ const cliCompleteResponse = await fetch(`${serverUrl}/cli/auth/complete`, {
2058
+ method: "POST",
2059
+ headers: {
2060
+ "Content-Type": "application/json"
2061
+ },
2062
+ body: JSON.stringify(cliCompleteRequest)
2063
+ });
2064
+ if (!cliCompleteResponse.ok) {
2065
+ throw new Error(`CLI authentication completion failed: ${cliCompleteResponse.statusText}`);
2066
+ }
2067
+ setState("completed");
2068
+ } catch (err) {
2069
+ setState("error");
2070
+ if (err instanceof Error) {
2071
+ if (err.name === "NotAllowedError") {
2072
+ setError("Authentication was cancelled or not allowed");
2073
+ } else {
2074
+ setError(err.message);
2075
+ }
2076
+ } else {
2077
+ setError("Authentication failed");
2078
+ }
2079
+ }
2080
+ }, [challenge, sessionId, serverUrl]);
2081
+ useEffect(() => {
2082
+ void fetchStatus();
2083
+ }, [fetchStatus]);
2084
+ return {
2085
+ authenticate,
2086
+ state,
2087
+ error,
2088
+ expiresAt
2089
+ };
2090
+ };
2091
+
1792
2092
  const FieldPhone = ({ id, onChange, isError = false, className = "" }) => {
1793
2093
  const [show, setShow] = useState(false);
1794
2094
  const [filterValue, setFilterValue] = useState("");
@@ -2190,6 +2490,245 @@ const TwoFactorSetupFlow = ({
2190
2490
  return /* @__PURE__ */ jsx(TwoFactorSetupForm, { onComplete: handleSetupComplete, onCancel: handleCancel });
2191
2491
  };
2192
2492
 
2493
+ const methodLabels = {
2494
+ totp: "Authenticator App",
2495
+ email_otp: "Email Code",
2496
+ sms_otp: "SMS Code",
2497
+ passkey: "Passkey",
2498
+ recovery_codes: "Recovery Code",
2499
+ push_fcm: "Push Notification (FCM)",
2500
+ push_webpush: "Push Notification (Web)"
2501
+ };
2502
+ const methodIcons = {
2503
+ totp: "📱",
2504
+ email_otp: "✉️",
2505
+ sms_otp: "💬",
2506
+ passkey: "🔑",
2507
+ recovery_codes: "🔒",
2508
+ push_fcm: "🔔",
2509
+ push_webpush: "🔔"
2510
+ };
2511
+ const MethodSelector = ({
2512
+ availableMethods,
2513
+ currentMethod,
2514
+ onSelectMethod,
2515
+ isOpen,
2516
+ onClose
2517
+ }) => {
2518
+ const handleSelect = (method) => {
2519
+ onSelectMethod(method);
2520
+ onClose();
2521
+ };
2522
+ return /* @__PURE__ */ jsx(DialogPrimitive.Root, { open: isOpen, onOpenChange: (open) => !open && onClose(), children: /* @__PURE__ */ jsxs(DialogPrimitive.Portal, { children: [
2523
+ /* @__PURE__ */ jsx(DialogPrimitive.Overlay, { className: "pf-dialog-overlay" }),
2524
+ /* @__PURE__ */ jsxs(DialogPrimitive.Content, { className: "pf-dialog-content pf-method-selector-dialog", children: [
2525
+ /* @__PURE__ */ jsx(DialogPrimitive.Title, { className: "pf-dialog-title", children: "Choose Verification Method" }),
2526
+ /* @__PURE__ */ jsx(DialogPrimitive.Description, { className: "pf-dialog-description", children: "Select an alternative method to verify your identity" }),
2527
+ /* @__PURE__ */ jsx("div", { className: "pf-method-selector-list", children: availableMethods.map((method) => /* @__PURE__ */ jsxs(
2528
+ "button",
2529
+ {
2530
+ type: "button",
2531
+ className: `pf-method-selector-item ${method === currentMethod ? "pf-method-selector-item-active" : ""}`,
2532
+ onClick: () => handleSelect(method),
2533
+ disabled: method === currentMethod,
2534
+ children: [
2535
+ /* @__PURE__ */ jsx("span", { className: "pf-method-icon", children: methodIcons[method] || "🔐" }),
2536
+ /* @__PURE__ */ jsx("span", { className: "pf-method-label", children: methodLabels[method] || method }),
2537
+ method === currentMethod && /* @__PURE__ */ jsx("span", { className: "pf-method-current-badge", children: "Current" })
2538
+ ]
2539
+ },
2540
+ method
2541
+ )) }),
2542
+ /* @__PURE__ */ jsx(DialogPrimitive.Close, { asChild: true, children: /* @__PURE__ */ jsx("button", { type: "button", className: "pf-dialog-close", "aria-label": "Close", children: "×" }) })
2543
+ ] })
2544
+ ] }) });
2545
+ };
2546
+
2547
+ const OtpInputComponent = ({
2548
+ value,
2549
+ onChange,
2550
+ numInputs = 6,
2551
+ error,
2552
+ disabled = false,
2553
+ autoFocus = true
2554
+ }) => {
2555
+ const containerRef = useRef(null);
2556
+ useEffect(() => {
2557
+ if (autoFocus && containerRef.current) {
2558
+ const firstInput = containerRef.current.querySelector("input");
2559
+ if (firstInput) {
2560
+ firstInput.focus();
2561
+ }
2562
+ }
2563
+ }, [autoFocus]);
2564
+ return /* @__PURE__ */ jsxs("div", { ref: containerRef, className: "pf-otp-input-container", children: [
2565
+ /* @__PURE__ */ jsx(
2566
+ OtpInput,
2567
+ {
2568
+ value,
2569
+ onChange,
2570
+ numInputs,
2571
+ renderSeparator: /* @__PURE__ */ jsx("span", { className: "pf-otp-separator" }),
2572
+ renderInput: (props) => /* @__PURE__ */ jsx(
2573
+ "input",
2574
+ {
2575
+ ...props,
2576
+ className: `pf-otp-input ${error ? "pf-otp-input-error" : ""} ${disabled ? "pf-otp-input-disabled" : ""}`,
2577
+ disabled
2578
+ }
2579
+ ),
2580
+ inputType: "tel",
2581
+ shouldAutoFocus: autoFocus
2582
+ }
2583
+ ),
2584
+ error && /* @__PURE__ */ jsx("div", { className: "pf-otp-error", children: error })
2585
+ ] });
2586
+ };
2587
+
2588
+ const TwoFactorChallenge = ({
2589
+ firstFactorMethod,
2590
+ onSuccess,
2591
+ onError,
2592
+ trustDevice = false
2593
+ }) => {
2594
+ const { challenge, isLoading, error, requestChallenge, verify, switchMethod, selectedMethod } = useTwoFactorChallenge();
2595
+ const [otpValue, setOtpValue] = useState("");
2596
+ const [isMethodSelectorOpen, setIsMethodSelectorOpen] = useState(false);
2597
+ useEffect(() => {
2598
+ requestChallenge(firstFactorMethod);
2599
+ }, [requestChallenge, firstFactorMethod]);
2600
+ useEffect(() => {
2601
+ if (error && onError) {
2602
+ onError(error);
2603
+ }
2604
+ }, [error, onError]);
2605
+ const handleVerify = async () => {
2606
+ if (!otpValue) {
2607
+ return;
2608
+ }
2609
+ const result = await verify(otpValue, trustDevice);
2610
+ if (result) {
2611
+ onSuccess?.();
2612
+ }
2613
+ };
2614
+ const handleSwitchMethod = async (method) => {
2615
+ setOtpValue("");
2616
+ await switchMethod(method);
2617
+ };
2618
+ const handleOtpChange = (value) => {
2619
+ setOtpValue(value);
2620
+ if (value.length === 6) {
2621
+ setTimeout(() => {
2622
+ verify(value, trustDevice).then((result) => {
2623
+ if (result) {
2624
+ onSuccess?.();
2625
+ }
2626
+ });
2627
+ }, 100);
2628
+ }
2629
+ };
2630
+ if (!challenge) {
2631
+ return /* @__PURE__ */ jsx("div", { className: "pf-two-factor-challenge pf-loading", children: /* @__PURE__ */ jsx("p", { children: "Loading challenge..." }) });
2632
+ }
2633
+ const currentMethod = selectedMethod || challenge.method;
2634
+ const availableMethods = [challenge.method, ...challenge.alternative_methods || []];
2635
+ const renderChallengeInput = () => {
2636
+ switch (currentMethod) {
2637
+ case "totp":
2638
+ case "email_otp":
2639
+ case "sms_otp":
2640
+ return /* @__PURE__ */ jsxs("div", { className: "pf-challenge-input-wrapper", children: [
2641
+ /* @__PURE__ */ jsx(
2642
+ OtpInputComponent,
2643
+ {
2644
+ value: otpValue,
2645
+ onChange: handleOtpChange,
2646
+ numInputs: 6,
2647
+ error: error?.message,
2648
+ disabled: isLoading,
2649
+ autoFocus: true
2650
+ }
2651
+ ),
2652
+ currentMethod === "email_otp" && challenge.code_sent_to && /* @__PURE__ */ jsxs("p", { className: "pf-challenge-hint", children: [
2653
+ "Code sent to ",
2654
+ challenge.code_sent_to
2655
+ ] }),
2656
+ currentMethod === "sms_otp" && challenge.code_sent_to && /* @__PURE__ */ jsxs("p", { className: "pf-challenge-hint", children: [
2657
+ "Code sent to ",
2658
+ challenge.code_sent_to
2659
+ ] }),
2660
+ /* @__PURE__ */ jsx(
2661
+ "button",
2662
+ {
2663
+ type: "button",
2664
+ onClick: handleVerify,
2665
+ disabled: isLoading || otpValue.length !== 6,
2666
+ className: "pf-button pf-button-primary",
2667
+ children: isLoading ? "Verifying..." : "Verify"
2668
+ }
2669
+ )
2670
+ ] });
2671
+ case "passkey":
2672
+ 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" }) });
2673
+ case "recovery_codes":
2674
+ return /* @__PURE__ */ jsxs("div", { className: "pf-challenge-input-wrapper", children: [
2675
+ /* @__PURE__ */ jsx(
2676
+ "input",
2677
+ {
2678
+ type: "text",
2679
+ value: otpValue,
2680
+ onChange: (e) => setOtpValue(e.target.value),
2681
+ placeholder: "Enter recovery code",
2682
+ disabled: isLoading,
2683
+ className: "pf-input"
2684
+ }
2685
+ ),
2686
+ /* @__PURE__ */ jsx(
2687
+ "button",
2688
+ {
2689
+ type: "button",
2690
+ onClick: handleVerify,
2691
+ disabled: isLoading || !otpValue,
2692
+ className: "pf-button pf-button-primary",
2693
+ children: isLoading ? "Verifying..." : "Verify"
2694
+ }
2695
+ )
2696
+ ] });
2697
+ default:
2698
+ return /* @__PURE__ */ jsxs("p", { children: [
2699
+ "Unsupported method: ",
2700
+ currentMethod
2701
+ ] });
2702
+ }
2703
+ };
2704
+ return /* @__PURE__ */ jsxs("div", { className: "pf-two-factor-challenge", children: [
2705
+ /* @__PURE__ */ jsxs("div", { className: "pf-challenge-method-info", children: [
2706
+ /* @__PURE__ */ jsx("h3", { children: "Two-Factor Authentication" }),
2707
+ /* @__PURE__ */ jsx("p", { children: "Enter the verification code" })
2708
+ ] }),
2709
+ renderChallengeInput(),
2710
+ availableMethods.length > 1 && /* @__PURE__ */ jsx(
2711
+ "button",
2712
+ {
2713
+ type: "button",
2714
+ onClick: () => setIsMethodSelectorOpen(true),
2715
+ className: "pf-button pf-button-secondary pf-button-switch-method",
2716
+ children: "Use different method"
2717
+ }
2718
+ ),
2719
+ /* @__PURE__ */ jsx(
2720
+ MethodSelector,
2721
+ {
2722
+ availableMethods,
2723
+ currentMethod,
2724
+ onSelectMethod: handleSwitchMethod,
2725
+ isOpen: isMethodSelectorOpen,
2726
+ onClose: () => setIsMethodSelectorOpen(false)
2727
+ }
2728
+ )
2729
+ ] });
2730
+ };
2731
+
2193
2732
  const TwoFactorVerifyForm = ({
2194
2733
  onSuccess,
2195
2734
  onUseRecovery,
@@ -2477,7 +3016,8 @@ const TwoFactorRecoveryForm = ({
2477
3016
  const TwoFactorVerifyFlow = ({
2478
3017
  successAuthRedirect,
2479
3018
  signInPath = routes.signin.path,
2480
- twoFactorSetupPath = routes.two_factor_setup?.path
3019
+ twoFactorSetupPath = routes.two_factor_setup?.path,
3020
+ useV2Flow = false
2481
3021
  }) => {
2482
3022
  const [mode, setMode] = useState("verify");
2483
3023
  const [shouldBlockRender, setShouldBlockRender] = useState(false);
@@ -2530,6 +3070,9 @@ const TwoFactorVerifyFlow = ({
2530
3070
  if (!passflow.isTwoFactorVerificationRequired() && TwoFactorLoopPrevention.canRedirect()) {
2531
3071
  return null;
2532
3072
  }
3073
+ if (useV2Flow) {
3074
+ return /* @__PURE__ */ jsx(TwoFactorChallenge, { onSuccess: handleSuccess });
3075
+ }
2533
3076
  return /* @__PURE__ */ jsx(Fragment, { children: mode === "verify" ? /* @__PURE__ */ jsx(
2534
3077
  TwoFactorVerifyForm,
2535
3078
  {
@@ -6031,6 +6574,110 @@ const index = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({
6031
6574
  useRoutes
6032
6575
  }, Symbol.toStringTag, { value: 'Module' }));
6033
6576
 
6577
+ const CLIBrowserAuthForm = () => {
6578
+ const { sessionId } = useParams();
6579
+ if (!sessionId) {
6580
+ throw new Error("Session ID is required");
6581
+ }
6582
+ const { authenticate, state, error } = useCLIAuth(sessionId);
6583
+ const handleAuthenticate = async () => {
6584
+ await authenticate();
6585
+ };
6586
+ return /* @__PURE__ */ jsx(Wrapper, { title: "CLI Authentication", subtitle: "Authenticate your CLI tool", className: "passflow-cli-auth-wrapper", children: /* @__PURE__ */ jsxs("div", { className: "passflow-form", children: [
6587
+ state === "loading" && /* @__PURE__ */ jsx("div", { className: "passflow-form-container", children: /* @__PURE__ */ jsx("p", { className: "passflow-form-text", children: "Loading authentication session..." }) }),
6588
+ state === "pending" && /* @__PURE__ */ jsxs(Fragment, { children: [
6589
+ /* @__PURE__ */ jsx("div", { className: "passflow-form-container", children: /* @__PURE__ */ jsx("p", { className: "passflow-form-text", children: "Click the button below to authenticate with your passkey." }) }),
6590
+ /* @__PURE__ */ jsxs(
6591
+ Button,
6592
+ {
6593
+ size: "big",
6594
+ variant: "dark",
6595
+ type: "button",
6596
+ className: "passflow-button-passkey",
6597
+ withIcon: true,
6598
+ onClick: handleAuthenticate,
6599
+ children: [
6600
+ /* @__PURE__ */ jsx(Icon, { id: "key", size: "small", type: "general", className: "icon-white passflow-button-passkey-icon" }),
6601
+ "Authenticate with Passkey"
6602
+ ]
6603
+ }
6604
+ )
6605
+ ] }),
6606
+ state === "authenticating" && /* @__PURE__ */ jsx("div", { className: "passflow-form-container", children: /* @__PURE__ */ jsx("p", { className: "passflow-form-text", children: "Authenticating..." }) }),
6607
+ state === "completed" && /* @__PURE__ */ jsxs("div", { className: "passflow-form-container", children: [
6608
+ /* @__PURE__ */ jsxs("div", { className: "passflow-form-success", children: [
6609
+ /* @__PURE__ */ jsx(Icon, { size: "small", id: "check", type: "general", className: "icon-success" }),
6610
+ /* @__PURE__ */ jsx("p", { className: "passflow-form-success-text", children: "Authentication successful!" })
6611
+ ] }),
6612
+ /* @__PURE__ */ jsx("p", { className: "passflow-form-text", children: "You can close this window and return to your terminal." })
6613
+ ] }),
6614
+ state === "expired" && /* @__PURE__ */ jsxs("div", { className: "passflow-form-container", children: [
6615
+ /* @__PURE__ */ jsxs("div", { className: "passflow-form-error", children: [
6616
+ /* @__PURE__ */ jsx(Icon, { size: "small", id: "warning", type: "general", className: "icon-warning" }),
6617
+ /* @__PURE__ */ jsx("span", { className: "passflow-form-error-text", children: "Session Expired" })
6618
+ ] }),
6619
+ /* @__PURE__ */ jsx("p", { className: "passflow-form-text", children: "This authentication session has expired. Please start a new one from your terminal." })
6620
+ ] }),
6621
+ state === "error" && error && /* @__PURE__ */ jsx("div", { className: "passflow-form-container", children: /* @__PURE__ */ jsxs("div", { className: "passflow-form-error", children: [
6622
+ /* @__PURE__ */ jsx(Icon, { size: "small", id: "warning", type: "general", className: "icon-warning" }),
6623
+ /* @__PURE__ */ jsx("span", { className: "passflow-form-error-text", children: error })
6624
+ ] }) })
6625
+ ] }) });
6626
+ };
6627
+ const CLIBrowserAuth = withError(CLIBrowserAuthForm, ErrorComponent);
6628
+
6629
+ const CLIQRAuthForm = () => {
6630
+ const { sessionId } = useParams();
6631
+ if (!sessionId) {
6632
+ throw new Error("Session ID is required");
6633
+ }
6634
+ const { authenticate, state, error } = useCLIAuth(sessionId);
6635
+ const handleAuthenticate = async () => {
6636
+ await authenticate();
6637
+ };
6638
+ return /* @__PURE__ */ jsx(Wrapper, { title: "CLI Authentication", subtitle: "Authenticate for your CLI application", className: "passflow-cli-auth-wrapper", children: /* @__PURE__ */ jsxs("div", { className: "passflow-form", children: [
6639
+ state === "loading" && /* @__PURE__ */ jsx("div", { className: "passflow-form-container", children: /* @__PURE__ */ jsx("p", { className: "passflow-form-text", children: "Loading authentication session..." }) }),
6640
+ state === "pending" && /* @__PURE__ */ jsxs(Fragment, { children: [
6641
+ /* @__PURE__ */ jsx("div", { className: "passflow-form-container", children: /* @__PURE__ */ jsx("p", { className: "passflow-form-text", children: "Tap the button below to authenticate with your passkey." }) }),
6642
+ /* @__PURE__ */ jsxs(
6643
+ Button,
6644
+ {
6645
+ size: "big",
6646
+ variant: "dark",
6647
+ type: "button",
6648
+ className: "passflow-button-passkey",
6649
+ withIcon: true,
6650
+ onClick: handleAuthenticate,
6651
+ children: [
6652
+ /* @__PURE__ */ jsx(Icon, { id: "key", size: "small", type: "general", className: "icon-white passflow-button-passkey-icon" }),
6653
+ "Authenticate with Passkey"
6654
+ ]
6655
+ }
6656
+ )
6657
+ ] }),
6658
+ state === "authenticating" && /* @__PURE__ */ jsx("div", { className: "passflow-form-container", children: /* @__PURE__ */ jsx("p", { className: "passflow-form-text", children: "Authenticating..." }) }),
6659
+ state === "completed" && /* @__PURE__ */ jsxs("div", { className: "passflow-form-container", children: [
6660
+ /* @__PURE__ */ jsxs("div", { className: "passflow-form-success", children: [
6661
+ /* @__PURE__ */ jsx(Icon, { size: "small", id: "check", type: "general", className: "icon-success" }),
6662
+ /* @__PURE__ */ jsx("p", { className: "passflow-form-success-text", children: "Authentication successful!" })
6663
+ ] }),
6664
+ /* @__PURE__ */ jsx("p", { className: "passflow-form-text", children: "You can close this app and return to your terminal." })
6665
+ ] }),
6666
+ state === "expired" && /* @__PURE__ */ jsxs("div", { className: "passflow-form-container", children: [
6667
+ /* @__PURE__ */ jsxs("div", { className: "passflow-form-error", children: [
6668
+ /* @__PURE__ */ jsx(Icon, { size: "small", id: "warning", type: "general", className: "icon-warning" }),
6669
+ /* @__PURE__ */ jsx("span", { className: "passflow-form-error-text", children: "Session Expired" })
6670
+ ] }),
6671
+ /* @__PURE__ */ jsx("p", { className: "passflow-form-text", children: "This authentication session has expired. Please start a new one from your terminal." })
6672
+ ] }),
6673
+ state === "error" && error && /* @__PURE__ */ jsx("div", { className: "passflow-form-container", children: /* @__PURE__ */ jsxs("div", { className: "passflow-form-error", children: [
6674
+ /* @__PURE__ */ jsx(Icon, { size: "small", id: "warning", type: "general", className: "icon-warning" }),
6675
+ /* @__PURE__ */ jsx("span", { className: "passflow-form-error-text", children: error })
6676
+ ] }) })
6677
+ ] }) });
6678
+ };
6679
+ const CLIQRAuth = withError(CLIQRAuthForm, ErrorComponent);
6680
+
6034
6681
  const normalizePathPrefix = (path) => {
6035
6682
  if (!path) return "";
6036
6683
  const pathReplace = path.replace(/^\/+|\/+$/g, "");
@@ -6211,6 +6858,8 @@ const PassflowWrapper = ({
6211
6858
  )
6212
6859
  }
6213
6860
  ),
6861
+ /* @__PURE__ */ jsx(Route, { path: routes.cli_browser.path, element: /* @__PURE__ */ jsx(CLIBrowserAuth, {}) }),
6862
+ /* @__PURE__ */ jsx(Route, { path: routes.cli_qr.path, element: /* @__PURE__ */ jsx(CLIQRAuth, {}) }),
6214
6863
  /* @__PURE__ */ jsx(
6215
6864
  Route,
6216
6865
  {
@@ -6408,5 +7057,5 @@ const PassflowProvider = ({
6408
7057
  return /* @__PURE__ */ jsx(PassflowContext.Provider, { value: passflowValue, children: /* @__PURE__ */ jsx(NavigationContext$1.Provider, { value: navigationValue, children: /* @__PURE__ */ jsx(AuthProvider, { children }) }) });
6409
7058
  };
6410
7059
 
6411
- 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 };
7060
+ export { Button, CLIBrowserAuth, CLIQRAuth, 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, useCLIAuth, useForgotPassword, useJoinInvite, useLogout, useNavigation, useOutsideClick, usePassflow, usePasswordlessComplete, useProvider, useResetPassword, useSessionExpired, useSignIn, useSignUp, useTwoFactorChallenge, useTwoFactorManage, useTwoFactorMethods, useTwoFactorSetup, useTwoFactorSetupMagicLink, useTwoFactorStatus, useTwoFactorVerify, useUserPasskeys };
6412
7061
  //# sourceMappingURL=index.es.js.map