@passflow/react 0.0.1 → 0.2.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.cjs.js +4 -4
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.es.js +1440 -119
- package/dist/index.es.js.map +1 -1
- package/dist/src/components/flow/index.d.ts +1 -0
- package/dist/src/components/flow/index.d.ts.map +1 -1
- package/dist/src/components/flow/passflow/index.d.ts +1 -1
- package/dist/src/components/flow/passflow/index.d.ts.map +1 -1
- package/dist/src/components/flow/two-factor-setup/index.d.ts +10 -0
- package/dist/src/components/flow/two-factor-setup/index.d.ts.map +1 -0
- package/dist/src/components/flow/two-factor-setup-magic-link/index.d.ts +35 -0
- package/dist/src/components/flow/two-factor-setup-magic-link/index.d.ts.map +1 -0
- package/dist/src/components/flow/two-factor-verify/index.d.ts +10 -0
- package/dist/src/components/flow/two-factor-verify/index.d.ts.map +1 -0
- package/dist/src/components/form/index.d.ts +2 -0
- package/dist/src/components/form/index.d.ts.map +1 -1
- package/dist/src/components/form/invitation-join/index.d.ts +1 -1
- package/dist/src/components/form/invitation-join/index.d.ts.map +1 -1
- package/dist/src/components/form/reset-password/index.d.ts.map +1 -1
- package/dist/src/components/form/signin/index.d.ts +2 -1
- package/dist/src/components/form/signin/index.d.ts.map +1 -1
- package/dist/src/components/form/signup/index.d.ts +1 -1
- package/dist/src/components/form/signup/index.d.ts.map +1 -1
- package/dist/src/components/form/two-factor-setup/index.d.ts +2 -0
- package/dist/src/components/form/two-factor-setup/index.d.ts.map +1 -0
- package/dist/src/components/form/two-factor-setup/two-factor-setup-form.d.ts +9 -0
- package/dist/src/components/form/two-factor-setup/two-factor-setup-form.d.ts.map +1 -0
- package/dist/src/components/form/two-factor-verify/index.d.ts +3 -0
- package/dist/src/components/form/two-factor-verify/index.d.ts.map +1 -0
- package/dist/src/components/form/two-factor-verify/two-factor-recovery-form.d.ts +11 -0
- package/dist/src/components/form/two-factor-verify/two-factor-recovery-form.d.ts.map +1 -0
- package/dist/src/components/form/two-factor-verify/two-factor-verify-form.d.ts +14 -0
- package/dist/src/components/form/two-factor-verify/two-factor-verify-form.d.ts.map +1 -0
- package/dist/src/components/form/verify-challenge/{varify-challenge-otp-redirect.d.ts → verify-challenge-otp-redirect.d.ts} +1 -1
- package/dist/src/components/form/verify-challenge/{varify-challenge-otp-redirect.d.ts.map → verify-challenge-otp-redirect.d.ts.map} +1 -1
- package/dist/src/components/form/verify-challenge/{varify-challenge-success.d.ts → verify-challenge-success.d.ts} +1 -1
- package/dist/src/components/form/verify-challenge/verify-challenge-success.d.ts.map +1 -0
- package/dist/src/components/provider/passflow-provider.d.ts.map +1 -1
- package/dist/src/context/passflow-context.d.ts +2 -0
- package/dist/src/context/passflow-context.d.ts.map +1 -1
- package/dist/src/context/router-context.d.ts +12 -0
- package/dist/src/context/router-context.d.ts.map +1 -1
- package/dist/src/hocs/with-error.d.ts +1 -1
- package/dist/src/hocs/with-error.d.ts.map +1 -1
- package/dist/src/hooks/index.d.ts +5 -0
- package/dist/src/hooks/index.d.ts.map +1 -1
- package/dist/src/hooks/use-app-settings.d.ts.map +1 -1
- package/dist/src/hooks/use-passflow-store.d.ts.map +1 -1
- package/dist/src/hooks/use-signin.d.ts.map +1 -1
- package/dist/src/hooks/use-two-factor-manage.d.ts +15 -0
- package/dist/src/hooks/use-two-factor-manage.d.ts.map +1 -0
- package/dist/src/hooks/use-two-factor-setup-magic-link.d.ts +55 -0
- package/dist/src/hooks/use-two-factor-setup-magic-link.d.ts.map +1 -0
- package/dist/src/hooks/use-two-factor-setup.d.ts +18 -0
- package/dist/src/hooks/use-two-factor-setup.d.ts.map +1 -0
- package/dist/src/hooks/use-two-factor-status.d.ts +13 -0
- package/dist/src/hooks/use-two-factor-status.d.ts.map +1 -0
- package/dist/src/hooks/use-two-factor-verify.d.ts +18 -0
- package/dist/src/hooks/use-two-factor-verify.d.ts.map +1 -0
- package/dist/src/test/setup.d.ts +1 -0
- package/dist/src/test/setup.d.ts.map +1 -0
- package/dist/src/test/utils/render.d.ts +4 -0
- package/dist/src/test/utils/render.d.ts.map +1 -0
- package/dist/src/test/utils/test-passflow.d.ts +6 -0
- package/dist/src/test/utils/test-passflow.d.ts.map +1 -0
- package/dist/src/types/index.d.ts +1 -0
- package/dist/src/types/index.d.ts.map +1 -1
- package/dist/src/types/two-factor-errors.d.ts +14 -0
- package/dist/src/types/two-factor-errors.d.ts.map +1 -0
- package/dist/src/utils/classify-two-factor-error.d.ts +13 -0
- package/dist/src/utils/classify-two-factor-error.d.ts.map +1 -0
- package/dist/src/utils/{cn/index.d.ts → cn.d.ts} +1 -1
- package/dist/src/utils/cn.d.ts.map +1 -0
- package/dist/src/utils/{get-app-version/index.d.ts → get-app-version.d.ts} +1 -1
- package/dist/src/utils/get-app-version.d.ts.map +1 -0
- package/dist/src/utils/{get-auth-methods/index.d.ts → get-auth-methods.d.ts} +1 -1
- package/dist/src/utils/get-auth-methods.d.ts.map +1 -0
- package/dist/src/utils/{get-form-labels/index.d.ts → get-form-labels.d.ts} +3 -3
- package/dist/src/utils/get-form-labels.d.ts.map +1 -0
- package/dist/src/utils/{get-url-errors/index.d.ts → get-url-errors.d.ts} +1 -1
- package/dist/src/utils/get-url-errors.d.ts.map +1 -0
- package/dist/src/utils/get-url-with-tokens.d.ts +4 -0
- package/dist/src/utils/get-url-with-tokens.d.ts.map +1 -0
- package/dist/src/utils/index.d.ts +3 -1
- package/dist/src/utils/index.d.ts.map +1 -1
- package/dist/src/utils/two-factor-loop-prevention.d.ts +40 -0
- package/dist/src/utils/two-factor-loop-prevention.d.ts.map +1 -0
- package/dist/src/utils/{undefined-on-catch/index.d.ts → undefined-on-catch.d.ts} +1 -1
- package/dist/src/utils/undefined-on-catch.d.ts.map +1 -0
- package/dist/src/utils/{url-params/index.d.ts → url-params.d.ts} +1 -1
- package/dist/src/utils/url-params.d.ts.map +1 -0
- package/dist/src/utils/{validate-url/index.d.ts → validate-url.d.ts} +1 -1
- package/dist/src/utils/validate-url.d.ts.map +1 -0
- package/dist/style.css +1 -1
- package/package.json +31 -31
- package/dist/src/components/form/verify-challenge/varify-challenge-success.d.ts.map +0 -1
- package/dist/src/utils/cn/index.d.ts.map +0 -1
- package/dist/src/utils/get-app-version/index.d.ts.map +0 -1
- package/dist/src/utils/get-auth-methods/index.d.ts.map +0 -1
- package/dist/src/utils/get-form-labels/index.d.ts.map +0 -1
- package/dist/src/utils/get-url-errors/index.d.ts.map +0 -1
- package/dist/src/utils/get-url-with-tokens/index.d.ts +0 -3
- package/dist/src/utils/get-url-with-tokens/index.d.ts.map +0 -1
- package/dist/src/utils/undefined-on-catch/index.d.ts.map +0 -1
- package/dist/src/utils/url-params/index.d.ts.map +0 -1
- package/dist/src/utils/validate-url/index.d.ts.map +0 -1
package/dist/index.es.js
CHANGED
|
@@ -5,22 +5,22 @@ import 'dayjs';
|
|
|
5
5
|
import clsx from 'clsx';
|
|
6
6
|
import { twMerge } from 'tailwind-merge';
|
|
7
7
|
import * as React from 'react';
|
|
8
|
-
import { useState, useEffect, forwardRef, createContext, useCallback, useContext,
|
|
8
|
+
import { useState, useEffect, forwardRef, createContext, useCallback, useContext, useRef, useLayoutEffect, useMemo, useReducer } from 'react';
|
|
9
9
|
import queryString from 'query-string';
|
|
10
10
|
import { getCountryForTimezone } from 'countries-and-timezones';
|
|
11
11
|
import { usePhoneInput, defaultCountries, parseCountry, FlagImage } from 'react-international-phone';
|
|
12
12
|
import * as PopoverPrimitive from '@radix-ui/react-popover';
|
|
13
13
|
import * as DialogPrimitive from '@radix-ui/react-dialog';
|
|
14
|
+
import OtpInput from 'react-otp-input';
|
|
14
15
|
import { HelmetProvider, Helmet } from 'react-helmet-async';
|
|
15
16
|
import { ErrorBoundary } from 'react-error-boundary';
|
|
16
17
|
import { phone as phone$1 } from 'phone';
|
|
17
18
|
import { useForm, Controller } from 'react-hook-form';
|
|
18
|
-
import OtpInput from 'react-otp-input';
|
|
19
19
|
import { parseToken, Passflow } from '@passflow/core';
|
|
20
20
|
export * from '@passflow/core';
|
|
21
21
|
import 'react-dom';
|
|
22
22
|
|
|
23
|
-
const version = "0.
|
|
23
|
+
const version = "0.1.42";
|
|
24
24
|
|
|
25
25
|
window.passflowReactAppVersion = () => {
|
|
26
26
|
console.log(`App Version: ${version}`);
|
|
@@ -69,11 +69,14 @@ const isValidUrl = (url) => {
|
|
|
69
69
|
}
|
|
70
70
|
};
|
|
71
71
|
|
|
72
|
-
const getUrlWithTokens = async (passflow, url) => {
|
|
72
|
+
const getUrlWithTokens = async (passflow, url, format = "hash") => {
|
|
73
73
|
const tokens = await passflow.getTokens(false);
|
|
74
74
|
if (tokens) {
|
|
75
75
|
tokens.scopes = void 0;
|
|
76
76
|
const tokenParams = Object.entries(tokens).filter(([_, value]) => value).map(([key, value]) => `${key}=${encodeURIComponent(value)}`).join("&");
|
|
77
|
+
if (format === "hash") {
|
|
78
|
+
return `${url}#${tokenParams}`;
|
|
79
|
+
}
|
|
77
80
|
return `${url}?${tokenParams}`;
|
|
78
81
|
}
|
|
79
82
|
return url;
|
|
@@ -243,6 +246,140 @@ const getUrlErrors = (subUrl) => {
|
|
|
243
246
|
return { error, message: message ? decodeURIComponent(message) : null };
|
|
244
247
|
};
|
|
245
248
|
|
|
249
|
+
const ERROR_PATTERNS = {
|
|
250
|
+
expired: ["2FA verification expired or not required", "verification expired", "session expired", "verification not required"],
|
|
251
|
+
not_enabled: ["Two-factor authentication is not enabled for this user", "2FA is not enabled", "two-factor not enabled"],
|
|
252
|
+
invalid_code: ["invalid code", "incorrect code", "code mismatch", "wrong code", "invalid 2fa code"]
|
|
253
|
+
};
|
|
254
|
+
function classifyTwoFactorError(errorMessage) {
|
|
255
|
+
const normalizedMessage = errorMessage.toLowerCase();
|
|
256
|
+
if (ERROR_PATTERNS.expired.some((pattern) => normalizedMessage.includes(pattern.toLowerCase()))) {
|
|
257
|
+
return {
|
|
258
|
+
type: "expired",
|
|
259
|
+
message: errorMessage,
|
|
260
|
+
isRecoverable: true,
|
|
261
|
+
shouldAutoRedirect: true
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
if (ERROR_PATTERNS.not_enabled.some((pattern) => normalizedMessage.includes(pattern.toLowerCase()))) {
|
|
265
|
+
return {
|
|
266
|
+
type: "not_enabled",
|
|
267
|
+
message: errorMessage,
|
|
268
|
+
isRecoverable: false,
|
|
269
|
+
shouldAutoRedirect: false
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
if (ERROR_PATTERNS.invalid_code.some((pattern) => normalizedMessage.includes(pattern.toLowerCase()))) {
|
|
273
|
+
return {
|
|
274
|
+
type: "invalid_code",
|
|
275
|
+
message: errorMessage,
|
|
276
|
+
isRecoverable: true,
|
|
277
|
+
shouldAutoRedirect: false
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
return {
|
|
281
|
+
type: "generic",
|
|
282
|
+
message: errorMessage,
|
|
283
|
+
isRecoverable: true,
|
|
284
|
+
shouldAutoRedirect: false
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
function getUserFriendlyErrorMessage(error) {
|
|
288
|
+
switch (error.type) {
|
|
289
|
+
case "expired":
|
|
290
|
+
return "Your session has expired. Redirecting to sign in...";
|
|
291
|
+
case "not_enabled":
|
|
292
|
+
return "Two-factor authentication is not enabled for your account. Please contact your administrator to enable 2FA.";
|
|
293
|
+
case "invalid_code":
|
|
294
|
+
return "Invalid code. Please check your authenticator app and try again.";
|
|
295
|
+
case "generic":
|
|
296
|
+
default:
|
|
297
|
+
return error.message;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const STORAGE_KEY_PREFIX = "passflow_2fa";
|
|
302
|
+
const REDIRECT_COUNT_KEY = `${STORAGE_KEY_PREFIX}_redirect_count`;
|
|
303
|
+
const LAST_ERROR_KEY = `${STORAGE_KEY_PREFIX}_last_error_type`;
|
|
304
|
+
const MAX_REDIRECTS = 3;
|
|
305
|
+
const TwoFactorLoopPrevention = {
|
|
306
|
+
/**
|
|
307
|
+
* Check if redirect should be allowed
|
|
308
|
+
* @returns true if redirect is safe, false if loop detected
|
|
309
|
+
*/
|
|
310
|
+
canRedirect() {
|
|
311
|
+
try {
|
|
312
|
+
const count = this.getRedirectCount();
|
|
313
|
+
return count < MAX_REDIRECTS;
|
|
314
|
+
} catch {
|
|
315
|
+
return true;
|
|
316
|
+
}
|
|
317
|
+
},
|
|
318
|
+
/**
|
|
319
|
+
* Increment redirect counter
|
|
320
|
+
*/
|
|
321
|
+
incrementRedirect() {
|
|
322
|
+
try {
|
|
323
|
+
const count = this.getRedirectCount();
|
|
324
|
+
sessionStorage.setItem(REDIRECT_COUNT_KEY, String(count + 1));
|
|
325
|
+
} catch {
|
|
326
|
+
}
|
|
327
|
+
},
|
|
328
|
+
/**
|
|
329
|
+
* Get current redirect count
|
|
330
|
+
*/
|
|
331
|
+
getRedirectCount() {
|
|
332
|
+
try {
|
|
333
|
+
const value = sessionStorage.getItem(REDIRECT_COUNT_KEY);
|
|
334
|
+
return value ? Number.parseInt(value, 10) : 0;
|
|
335
|
+
} catch {
|
|
336
|
+
return 0;
|
|
337
|
+
}
|
|
338
|
+
},
|
|
339
|
+
/**
|
|
340
|
+
* Reset redirect counter (call on successful auth)
|
|
341
|
+
*/
|
|
342
|
+
reset() {
|
|
343
|
+
try {
|
|
344
|
+
sessionStorage.removeItem(REDIRECT_COUNT_KEY);
|
|
345
|
+
sessionStorage.removeItem(LAST_ERROR_KEY);
|
|
346
|
+
} catch {
|
|
347
|
+
}
|
|
348
|
+
},
|
|
349
|
+
/**
|
|
350
|
+
* Track last error type to detect repeated errors
|
|
351
|
+
*/
|
|
352
|
+
setLastErrorType(errorType) {
|
|
353
|
+
try {
|
|
354
|
+
sessionStorage.setItem(LAST_ERROR_KEY, errorType);
|
|
355
|
+
} catch {
|
|
356
|
+
}
|
|
357
|
+
},
|
|
358
|
+
/**
|
|
359
|
+
* Get last error type
|
|
360
|
+
*/
|
|
361
|
+
getLastErrorType() {
|
|
362
|
+
try {
|
|
363
|
+
return sessionStorage.getItem(LAST_ERROR_KEY);
|
|
364
|
+
} catch {
|
|
365
|
+
return null;
|
|
366
|
+
}
|
|
367
|
+
},
|
|
368
|
+
/**
|
|
369
|
+
* Check if same error is repeating
|
|
370
|
+
*/
|
|
371
|
+
isRepeatingError(errorType) {
|
|
372
|
+
const lastError = this.getLastErrorType();
|
|
373
|
+
return lastError === errorType && this.getRedirectCount() > 0;
|
|
374
|
+
},
|
|
375
|
+
/**
|
|
376
|
+
* Get user-friendly message when loop is detected
|
|
377
|
+
*/
|
|
378
|
+
getLoopDetectedMessage() {
|
|
379
|
+
return "Unable to complete authentication. This may be a configuration issue. Please contact support.";
|
|
380
|
+
}
|
|
381
|
+
};
|
|
382
|
+
|
|
246
383
|
const flags = "data:image/png;base64,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";
|
|
247
384
|
|
|
248
385
|
const apple = "data:image/svg+xml,%3csvg%20width='16'%20height='16'%20viewBox='0%200%2016%2016'%20fill='none'%20xmlns='http://www.w3.org/2000/svg'%3e%3cpath%20d='M13.6167%204.77308C13.5312%204.83608%2012.0218%205.64397%2012.0218%207.44037C12.0218%209.51818%2013.9425%2010.2533%2014%2010.2715C13.9912%2010.3162%2013.6948%2011.2781%2012.9873%2012.2583C12.3564%2013.1207%2011.6976%2013.9818%2010.6952%2013.9818C9.69291%2013.9818%209.43498%2013.4287%208.27784%2013.4287C7.15021%2013.4287%206.74928%2014%205.83245%2014C4.9156%2014%204.27588%2013.2019%203.54034%2012.2218C2.68836%2011.0709%202%209.28291%202%207.58597C2%204.86409%203.86316%203.42054%205.69684%203.42054C6.67116%203.42054%207.48335%204.02821%208.09508%204.02821C8.67726%204.02821%209.58531%203.38414%2010.6937%203.38414C11.1139%203.38414%2012.6233%203.42054%2013.6167%204.77308ZM10.1676%202.23182C10.6259%201.71517%2010.9503%200.998298%2010.9503%200.281428C10.9503%200.182018%2010.9414%200.0812084%2010.9223%200C10.1764%200.0266027%209.28899%200.471848%208.75398%201.0613C8.33385%201.51495%207.94176%202.23182%207.94176%202.95849C7.94176%203.06771%207.96092%203.17691%207.9698%203.21192C8.01697%203.22032%208.09361%203.23012%208.17025%203.23012C8.83946%203.23012%209.68112%202.80448%2010.1676%202.23182Z'%20fill='black'/%3e%3c/svg%3e";
|
|
@@ -580,7 +717,9 @@ const initialState = {
|
|
|
580
717
|
appId: void 0,
|
|
581
718
|
scopes: void 0,
|
|
582
719
|
createTenantForNewUser: void 0,
|
|
583
|
-
parseQueryParams: true
|
|
720
|
+
parseQueryParams: true,
|
|
721
|
+
isDiscoveringAppId: false,
|
|
722
|
+
hasSettingsError: false
|
|
584
723
|
};
|
|
585
724
|
const PassflowContext = createContext(void 0);
|
|
586
725
|
const passflowReducer = (state, action) => {
|
|
@@ -619,6 +758,18 @@ const routes = {
|
|
|
619
758
|
},
|
|
620
759
|
passkey: {
|
|
621
760
|
path: "/passkeysss"
|
|
761
|
+
},
|
|
762
|
+
two_factor_verify: {
|
|
763
|
+
path: "/two-factor-verify"
|
|
764
|
+
},
|
|
765
|
+
two_factor_recovery: {
|
|
766
|
+
path: "/two-factor-recovery"
|
|
767
|
+
},
|
|
768
|
+
two_factor_setup: {
|
|
769
|
+
path: "/two-factor-setup"
|
|
770
|
+
},
|
|
771
|
+
two_factor_setup_magic_link: {
|
|
772
|
+
path: "/two-factor-setup-magic-link/:token"
|
|
622
773
|
}
|
|
623
774
|
};
|
|
624
775
|
|
|
@@ -646,7 +797,7 @@ const AuthProvider = ({ children }) => {
|
|
|
646
797
|
setIsLoading(true);
|
|
647
798
|
try {
|
|
648
799
|
const tokens = await passflow.getTokens(doRefresh);
|
|
649
|
-
const parsedTokens = tokens ? passflow.
|
|
800
|
+
const parsedTokens = tokens ? passflow.getParsedTokens() : void 0;
|
|
650
801
|
return {
|
|
651
802
|
tokens,
|
|
652
803
|
parsedTokens
|
|
@@ -697,17 +848,13 @@ const useSignIn = () => {
|
|
|
697
848
|
else if (type === "passkey") {
|
|
698
849
|
await passflow.passkeyAuthenticate(payload);
|
|
699
850
|
} else {
|
|
700
|
-
|
|
701
|
-
cleanup();
|
|
702
|
-
return passwordlessResponse;
|
|
851
|
+
return await passflow.passwordlessSignIn(payload);
|
|
703
852
|
}
|
|
704
|
-
cleanup();
|
|
705
853
|
return true;
|
|
706
854
|
} catch (e) {
|
|
707
855
|
setIsError(true);
|
|
708
856
|
const error = e;
|
|
709
857
|
setErrorMessage(error.message);
|
|
710
|
-
cleanup();
|
|
711
858
|
return false;
|
|
712
859
|
} finally {
|
|
713
860
|
cleanup();
|
|
@@ -852,48 +999,143 @@ const useAppSettings = () => {
|
|
|
852
999
|
const [isError, setIsError] = useState(false);
|
|
853
1000
|
const [errorMessage, setErrorMessage] = useState("");
|
|
854
1001
|
const [isLoading, setIsLoading] = useState(false);
|
|
1002
|
+
const isFetchingRef = useRef(false);
|
|
855
1003
|
if (!context) {
|
|
856
1004
|
throw new Error("useAppSetting must be used within an PassflowProvider");
|
|
857
1005
|
}
|
|
858
1006
|
const { state, dispatch } = context;
|
|
859
1007
|
useLayoutEffect(() => {
|
|
860
|
-
if (!state.appSettings) {
|
|
1008
|
+
if (!state.appSettings && !state.hasSettingsError && !isFetchingRef.current) {
|
|
861
1009
|
const fetchAllSettings = async () => {
|
|
1010
|
+
isFetchingRef.current = true;
|
|
862
1011
|
setIsLoading(true);
|
|
863
1012
|
try {
|
|
1013
|
+
if (!passflow.appId && !state.isDiscoveringAppId) {
|
|
1014
|
+
dispatch({
|
|
1015
|
+
type: "SET_PASSFLOW_STATE",
|
|
1016
|
+
payload: {
|
|
1017
|
+
...state,
|
|
1018
|
+
isDiscoveringAppId: true
|
|
1019
|
+
}
|
|
1020
|
+
});
|
|
1021
|
+
try {
|
|
1022
|
+
const urlsToTry = ["/settings"];
|
|
1023
|
+
if (passflow.url) {
|
|
1024
|
+
urlsToTry.push(`${passflow.url}/settings`);
|
|
1025
|
+
}
|
|
1026
|
+
let response = null;
|
|
1027
|
+
for (const url of urlsToTry) {
|
|
1028
|
+
try {
|
|
1029
|
+
const r = await fetch(url);
|
|
1030
|
+
if (r.ok) {
|
|
1031
|
+
response = r;
|
|
1032
|
+
break;
|
|
1033
|
+
}
|
|
1034
|
+
} catch {
|
|
1035
|
+
continue;
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
if (response && response.ok) {
|
|
1039
|
+
const settings = await response.json();
|
|
1040
|
+
const discoveredAppId = settings.login_app?.app_id || settings.appId;
|
|
1041
|
+
if (discoveredAppId) {
|
|
1042
|
+
passflow.setAppId(discoveredAppId);
|
|
1043
|
+
const stateUpdates = {
|
|
1044
|
+
appId: discoveredAppId
|
|
1045
|
+
};
|
|
1046
|
+
if (!state.scopes) {
|
|
1047
|
+
const discoveredScopes = settings.login_app?.scopes || settings.scopes;
|
|
1048
|
+
if (discoveredScopes) {
|
|
1049
|
+
stateUpdates.scopes = discoveredScopes;
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
if (isUndefined(state.createTenantForNewUser)) {
|
|
1053
|
+
const createTenant = settings.login_app?.create_tenant_for_new_user ?? settings.createTenantForNewUser;
|
|
1054
|
+
if (createTenant !== void 0) {
|
|
1055
|
+
stateUpdates.createTenantForNewUser = createTenant;
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
dispatch({
|
|
1059
|
+
type: "SET_PASSFLOW_STATE",
|
|
1060
|
+
payload: {
|
|
1061
|
+
...state,
|
|
1062
|
+
...stateUpdates,
|
|
1063
|
+
isDiscoveringAppId: false
|
|
1064
|
+
}
|
|
1065
|
+
});
|
|
1066
|
+
} else {
|
|
1067
|
+
dispatch({
|
|
1068
|
+
type: "SET_PASSFLOW_STATE",
|
|
1069
|
+
payload: {
|
|
1070
|
+
...state,
|
|
1071
|
+
isDiscoveringAppId: false
|
|
1072
|
+
}
|
|
1073
|
+
});
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
} catch (discoveryError) {
|
|
1077
|
+
console.warn("Failed to discover appId from /settings:", discoveryError);
|
|
1078
|
+
dispatch({
|
|
1079
|
+
type: "SET_PASSFLOW_STATE",
|
|
1080
|
+
payload: {
|
|
1081
|
+
...state,
|
|
1082
|
+
isDiscoveringAppId: false
|
|
1083
|
+
}
|
|
1084
|
+
});
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
864
1087
|
let appSettings = {};
|
|
865
1088
|
if (passflow.appId) {
|
|
866
1089
|
appSettings = await passflow.getAppSettings();
|
|
867
1090
|
}
|
|
868
|
-
|
|
869
|
-
|
|
1091
|
+
const finalStateUpdates = {
|
|
1092
|
+
appSettings
|
|
1093
|
+
};
|
|
1094
|
+
if (!state.scopes) finalStateUpdates.scopes = appSettings.defaults?.scopes;
|
|
1095
|
+
if (isUndefined(state.createTenantForNewUser))
|
|
1096
|
+
finalStateUpdates.createTenantForNewUser = appSettings.defaults?.create_tenant_for_new_user;
|
|
870
1097
|
let passwordPolicy = null;
|
|
871
1098
|
if (appSettings.auth_strategies && hasPasswordStrategy(appSettings.auth_strategies)) {
|
|
872
1099
|
passwordPolicy = await passflow.getPasswordPolicySettings();
|
|
873
1100
|
}
|
|
1101
|
+
finalStateUpdates.passwordPolicy = passwordPolicy;
|
|
874
1102
|
dispatch({
|
|
875
1103
|
type: "SET_PASSFLOW_STATE",
|
|
876
1104
|
payload: {
|
|
877
1105
|
...state,
|
|
878
|
-
|
|
879
|
-
passwordPolicy
|
|
1106
|
+
...finalStateUpdates
|
|
880
1107
|
}
|
|
881
1108
|
});
|
|
882
1109
|
} catch (e) {
|
|
883
1110
|
setIsError(true);
|
|
884
1111
|
const error = e;
|
|
885
1112
|
setErrorMessage(error.message);
|
|
1113
|
+
dispatch({
|
|
1114
|
+
type: "SET_PASSFLOW_STATE",
|
|
1115
|
+
payload: {
|
|
1116
|
+
...state,
|
|
1117
|
+
hasSettingsError: true
|
|
1118
|
+
}
|
|
1119
|
+
});
|
|
886
1120
|
} finally {
|
|
887
1121
|
setIsLoading(false);
|
|
1122
|
+
isFetchingRef.current = false;
|
|
888
1123
|
}
|
|
889
1124
|
};
|
|
890
1125
|
void fetchAllSettings();
|
|
891
1126
|
}
|
|
892
|
-
}, [dispatch, state.appSettings,
|
|
1127
|
+
}, [dispatch, state.appSettings, state.hasSettingsError, state.isDiscoveringAppId, passflow]);
|
|
893
1128
|
const reset = () => {
|
|
894
1129
|
setIsError(false);
|
|
895
1130
|
setErrorMessage("");
|
|
896
1131
|
setIsLoading(false);
|
|
1132
|
+
dispatch({
|
|
1133
|
+
type: "SET_PASSFLOW_STATE",
|
|
1134
|
+
payload: {
|
|
1135
|
+
...state,
|
|
1136
|
+
hasSettingsError: false
|
|
1137
|
+
}
|
|
1138
|
+
});
|
|
897
1139
|
};
|
|
898
1140
|
const applyThemeStyles = (style) => {
|
|
899
1141
|
const root = document.documentElement;
|
|
@@ -1146,7 +1388,7 @@ const useNavigation = () => {
|
|
|
1146
1388
|
setNavigate(null);
|
|
1147
1389
|
return;
|
|
1148
1390
|
}
|
|
1149
|
-
const wrappedNavigate = (options) => {
|
|
1391
|
+
const wrappedNavigate = ((options) => {
|
|
1150
1392
|
const { to, search = "", replace = false } = options;
|
|
1151
1393
|
switch (router) {
|
|
1152
1394
|
case "react-router":
|
|
@@ -1177,7 +1419,7 @@ const useNavigation = () => {
|
|
|
1177
1419
|
}
|
|
1178
1420
|
}
|
|
1179
1421
|
}
|
|
1180
|
-
};
|
|
1422
|
+
});
|
|
1181
1423
|
setNavigate(wrappedNavigate);
|
|
1182
1424
|
},
|
|
1183
1425
|
[setNavigate, router]
|
|
@@ -1189,6 +1431,364 @@ const useNavigation = () => {
|
|
|
1189
1431
|
};
|
|
1190
1432
|
};
|
|
1191
1433
|
|
|
1434
|
+
const useTwoFactorStatus = () => {
|
|
1435
|
+
const passflow = usePassflow();
|
|
1436
|
+
const [data, setData] = useState(null);
|
|
1437
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
1438
|
+
const [isError, setIsError] = useState(false);
|
|
1439
|
+
const [errorMessage, setErrorMessage] = useState("");
|
|
1440
|
+
const fetch = useCallback(async () => {
|
|
1441
|
+
setIsLoading(true);
|
|
1442
|
+
setIsError(false);
|
|
1443
|
+
setErrorMessage("");
|
|
1444
|
+
try {
|
|
1445
|
+
const response = await passflow.getTwoFactorStatus();
|
|
1446
|
+
setData(response);
|
|
1447
|
+
return response;
|
|
1448
|
+
} catch (e) {
|
|
1449
|
+
setIsError(true);
|
|
1450
|
+
const error = e;
|
|
1451
|
+
setErrorMessage(error.message);
|
|
1452
|
+
return null;
|
|
1453
|
+
} finally {
|
|
1454
|
+
setIsLoading(false);
|
|
1455
|
+
}
|
|
1456
|
+
}, [passflow]);
|
|
1457
|
+
useLayoutEffect(() => {
|
|
1458
|
+
void fetch();
|
|
1459
|
+
}, [fetch]);
|
|
1460
|
+
return {
|
|
1461
|
+
data,
|
|
1462
|
+
refetch: fetch,
|
|
1463
|
+
isLoading,
|
|
1464
|
+
isError,
|
|
1465
|
+
errorMessage
|
|
1466
|
+
};
|
|
1467
|
+
};
|
|
1468
|
+
|
|
1469
|
+
const useTwoFactorSetup = () => {
|
|
1470
|
+
const passflow = usePassflow();
|
|
1471
|
+
const [setupData, setSetupData] = useState(null);
|
|
1472
|
+
const [recoveryCodes, setRecoveryCodes] = useState([]);
|
|
1473
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
1474
|
+
const [isError, setIsError] = useState(false);
|
|
1475
|
+
const [error, setError] = useState("");
|
|
1476
|
+
const [step, setStep] = useState("idle");
|
|
1477
|
+
const beginSetup = useCallback(async () => {
|
|
1478
|
+
setIsLoading(true);
|
|
1479
|
+
setIsError(false);
|
|
1480
|
+
setError("");
|
|
1481
|
+
try {
|
|
1482
|
+
const response = await passflow.beginTwoFactorSetup();
|
|
1483
|
+
setSetupData(response);
|
|
1484
|
+
setStep("setup");
|
|
1485
|
+
return response;
|
|
1486
|
+
} catch (e) {
|
|
1487
|
+
setIsError(true);
|
|
1488
|
+
const err = e;
|
|
1489
|
+
setError(err.message);
|
|
1490
|
+
return null;
|
|
1491
|
+
} finally {
|
|
1492
|
+
setIsLoading(false);
|
|
1493
|
+
}
|
|
1494
|
+
}, [passflow]);
|
|
1495
|
+
const confirmSetup = useCallback(
|
|
1496
|
+
async (code) => {
|
|
1497
|
+
setIsLoading(true);
|
|
1498
|
+
setIsError(false);
|
|
1499
|
+
setError("");
|
|
1500
|
+
try {
|
|
1501
|
+
const response = await passflow.confirmTwoFactorSetup(code);
|
|
1502
|
+
setRecoveryCodes(response.recovery_codes);
|
|
1503
|
+
setStep("complete");
|
|
1504
|
+
return response;
|
|
1505
|
+
} catch (e) {
|
|
1506
|
+
setIsError(true);
|
|
1507
|
+
const err = e;
|
|
1508
|
+
setError(err.message);
|
|
1509
|
+
return null;
|
|
1510
|
+
} finally {
|
|
1511
|
+
setIsLoading(false);
|
|
1512
|
+
}
|
|
1513
|
+
},
|
|
1514
|
+
[passflow]
|
|
1515
|
+
);
|
|
1516
|
+
const reset = useCallback(() => {
|
|
1517
|
+
setSetupData(null);
|
|
1518
|
+
setRecoveryCodes([]);
|
|
1519
|
+
setIsError(false);
|
|
1520
|
+
setError("");
|
|
1521
|
+
setStep("idle");
|
|
1522
|
+
}, []);
|
|
1523
|
+
return {
|
|
1524
|
+
setupData,
|
|
1525
|
+
recoveryCodes,
|
|
1526
|
+
step,
|
|
1527
|
+
beginSetup,
|
|
1528
|
+
confirmSetup,
|
|
1529
|
+
reset,
|
|
1530
|
+
isLoading,
|
|
1531
|
+
isError,
|
|
1532
|
+
error
|
|
1533
|
+
};
|
|
1534
|
+
};
|
|
1535
|
+
|
|
1536
|
+
const useTwoFactorVerify = () => {
|
|
1537
|
+
const passflow = usePassflow();
|
|
1538
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
1539
|
+
const [isError, setIsError] = useState(false);
|
|
1540
|
+
const [error, setError] = useState("");
|
|
1541
|
+
const [errorType, setErrorType] = useState(null);
|
|
1542
|
+
const [errorDetails, setErrorDetails] = useState(null);
|
|
1543
|
+
const isVerificationRequired = useCallback(() => {
|
|
1544
|
+
return passflow.isTwoFactorVerificationRequired();
|
|
1545
|
+
}, [passflow]);
|
|
1546
|
+
const handleError = useCallback((e) => {
|
|
1547
|
+
const err = e;
|
|
1548
|
+
const classified = classifyTwoFactorError(err.message);
|
|
1549
|
+
setIsError(true);
|
|
1550
|
+
setError(err.message);
|
|
1551
|
+
setErrorType(classified.type);
|
|
1552
|
+
setErrorDetails(classified);
|
|
1553
|
+
}, []);
|
|
1554
|
+
const verify = useCallback(
|
|
1555
|
+
async (code) => {
|
|
1556
|
+
setIsLoading(true);
|
|
1557
|
+
setIsError(false);
|
|
1558
|
+
setError("");
|
|
1559
|
+
setErrorType(null);
|
|
1560
|
+
setErrorDetails(null);
|
|
1561
|
+
try {
|
|
1562
|
+
const response = await passflow.verifyTwoFactor(code);
|
|
1563
|
+
return response;
|
|
1564
|
+
} catch (e) {
|
|
1565
|
+
handleError(e);
|
|
1566
|
+
return null;
|
|
1567
|
+
} finally {
|
|
1568
|
+
setIsLoading(false);
|
|
1569
|
+
}
|
|
1570
|
+
},
|
|
1571
|
+
[passflow, handleError]
|
|
1572
|
+
);
|
|
1573
|
+
const useRecoveryCode = useCallback(
|
|
1574
|
+
async (code) => {
|
|
1575
|
+
setIsLoading(true);
|
|
1576
|
+
setIsError(false);
|
|
1577
|
+
setError("");
|
|
1578
|
+
setErrorType(null);
|
|
1579
|
+
setErrorDetails(null);
|
|
1580
|
+
try {
|
|
1581
|
+
const response = await passflow.useTwoFactorRecoveryCode(code);
|
|
1582
|
+
return response;
|
|
1583
|
+
} catch (e) {
|
|
1584
|
+
handleError(e);
|
|
1585
|
+
return null;
|
|
1586
|
+
} finally {
|
|
1587
|
+
setIsLoading(false);
|
|
1588
|
+
}
|
|
1589
|
+
},
|
|
1590
|
+
[passflow, handleError]
|
|
1591
|
+
);
|
|
1592
|
+
const reset = useCallback(() => {
|
|
1593
|
+
setIsError(false);
|
|
1594
|
+
setError("");
|
|
1595
|
+
setErrorType(null);
|
|
1596
|
+
setErrorDetails(null);
|
|
1597
|
+
}, []);
|
|
1598
|
+
return {
|
|
1599
|
+
isVerificationRequired,
|
|
1600
|
+
verify,
|
|
1601
|
+
useRecoveryCode,
|
|
1602
|
+
reset,
|
|
1603
|
+
isLoading,
|
|
1604
|
+
isError,
|
|
1605
|
+
error,
|
|
1606
|
+
errorType,
|
|
1607
|
+
errorDetails
|
|
1608
|
+
};
|
|
1609
|
+
};
|
|
1610
|
+
|
|
1611
|
+
const useTwoFactorManage = () => {
|
|
1612
|
+
const passflow = usePassflow();
|
|
1613
|
+
const [recoveryCodes, setRecoveryCodes] = useState([]);
|
|
1614
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
1615
|
+
const [isError, setIsError] = useState(false);
|
|
1616
|
+
const [error, setError] = useState("");
|
|
1617
|
+
const disable = useCallback(
|
|
1618
|
+
async (code) => {
|
|
1619
|
+
setIsLoading(true);
|
|
1620
|
+
setIsError(false);
|
|
1621
|
+
setError("");
|
|
1622
|
+
try {
|
|
1623
|
+
await passflow.disableTwoFactor(code);
|
|
1624
|
+
return true;
|
|
1625
|
+
} catch (e) {
|
|
1626
|
+
setIsError(true);
|
|
1627
|
+
const err = e;
|
|
1628
|
+
setError(err.message);
|
|
1629
|
+
return false;
|
|
1630
|
+
} finally {
|
|
1631
|
+
setIsLoading(false);
|
|
1632
|
+
}
|
|
1633
|
+
},
|
|
1634
|
+
[passflow]
|
|
1635
|
+
);
|
|
1636
|
+
const regenerateCodes = useCallback(
|
|
1637
|
+
async (code) => {
|
|
1638
|
+
setIsLoading(true);
|
|
1639
|
+
setIsError(false);
|
|
1640
|
+
setError("");
|
|
1641
|
+
try {
|
|
1642
|
+
const response = await passflow.regenerateTwoFactorRecoveryCodes(code);
|
|
1643
|
+
setRecoveryCodes(response.recovery_codes);
|
|
1644
|
+
return response;
|
|
1645
|
+
} catch (e) {
|
|
1646
|
+
setIsError(true);
|
|
1647
|
+
const err = e;
|
|
1648
|
+
setError(err.message);
|
|
1649
|
+
return null;
|
|
1650
|
+
} finally {
|
|
1651
|
+
setIsLoading(false);
|
|
1652
|
+
}
|
|
1653
|
+
},
|
|
1654
|
+
[passflow]
|
|
1655
|
+
);
|
|
1656
|
+
const reset = useCallback(() => {
|
|
1657
|
+
setRecoveryCodes([]);
|
|
1658
|
+
setIsError(false);
|
|
1659
|
+
setError("");
|
|
1660
|
+
}, []);
|
|
1661
|
+
return {
|
|
1662
|
+
recoveryCodes,
|
|
1663
|
+
disable,
|
|
1664
|
+
regenerateCodes,
|
|
1665
|
+
reset,
|
|
1666
|
+
isLoading,
|
|
1667
|
+
isError,
|
|
1668
|
+
error
|
|
1669
|
+
};
|
|
1670
|
+
};
|
|
1671
|
+
|
|
1672
|
+
const useTwoFactorSetupMagicLink = (token) => {
|
|
1673
|
+
const passflow = usePassflow();
|
|
1674
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
1675
|
+
const [isRetrying, setIsRetrying] = useState(false);
|
|
1676
|
+
const [isValidated, setIsValidated] = useState(false);
|
|
1677
|
+
const [error, setError] = useState(null);
|
|
1678
|
+
const [sessionToken, setSessionToken] = useState(null);
|
|
1679
|
+
const [userId, setUserId] = useState(null);
|
|
1680
|
+
const [appId, setAppId] = useState(null);
|
|
1681
|
+
const [expiresIn, setExpiresIn] = useState(null);
|
|
1682
|
+
const [retryCountdown, setRetryCountdown] = useState(null);
|
|
1683
|
+
const [validatedToken, setValidatedToken] = useState(null);
|
|
1684
|
+
const shouldClearOnUnmount = useRef(true);
|
|
1685
|
+
const countdownTimerRef = useRef(null);
|
|
1686
|
+
const startCountdown = useCallback((seconds) => {
|
|
1687
|
+
if (countdownTimerRef.current) {
|
|
1688
|
+
clearInterval(countdownTimerRef.current);
|
|
1689
|
+
}
|
|
1690
|
+
setRetryCountdown(seconds);
|
|
1691
|
+
countdownTimerRef.current = setInterval(() => {
|
|
1692
|
+
setRetryCountdown((prev) => {
|
|
1693
|
+
if (prev === null || prev <= 1) {
|
|
1694
|
+
if (countdownTimerRef.current) {
|
|
1695
|
+
clearInterval(countdownTimerRef.current);
|
|
1696
|
+
countdownTimerRef.current = null;
|
|
1697
|
+
}
|
|
1698
|
+
return null;
|
|
1699
|
+
}
|
|
1700
|
+
return prev - 1;
|
|
1701
|
+
});
|
|
1702
|
+
}, 1e3);
|
|
1703
|
+
}, []);
|
|
1704
|
+
const validateToken = useCallback(
|
|
1705
|
+
async (isRetry = false) => {
|
|
1706
|
+
if (!isRetry && validatedToken === token && isValidated) {
|
|
1707
|
+
return;
|
|
1708
|
+
}
|
|
1709
|
+
if (isRetry) {
|
|
1710
|
+
setIsRetrying(true);
|
|
1711
|
+
} else {
|
|
1712
|
+
setIsLoading(true);
|
|
1713
|
+
}
|
|
1714
|
+
setError(null);
|
|
1715
|
+
try {
|
|
1716
|
+
const response = await passflow.validateTwoFactorSetupMagicLink(token);
|
|
1717
|
+
if (response.success && response.sessionToken && response.userId) {
|
|
1718
|
+
setSessionToken(response.sessionToken);
|
|
1719
|
+
setUserId(response.userId);
|
|
1720
|
+
setAppId(response.appId ?? null);
|
|
1721
|
+
setExpiresIn(response.expiresIn ?? null);
|
|
1722
|
+
setIsValidated(true);
|
|
1723
|
+
setValidatedToken(token);
|
|
1724
|
+
setRetryCountdown(null);
|
|
1725
|
+
shouldClearOnUnmount.current = false;
|
|
1726
|
+
} else if (response.error) {
|
|
1727
|
+
setError(response.error);
|
|
1728
|
+
setIsValidated(false);
|
|
1729
|
+
shouldClearOnUnmount.current = true;
|
|
1730
|
+
if (response.error.code === "RATE_LIMITED" && response.error.retryAfter) {
|
|
1731
|
+
startCountdown(response.error.retryAfter);
|
|
1732
|
+
}
|
|
1733
|
+
}
|
|
1734
|
+
} catch (err) {
|
|
1735
|
+
setError({
|
|
1736
|
+
code: "SERVER_ERROR",
|
|
1737
|
+
message: err instanceof Error ? err.message : "An unexpected error occurred"
|
|
1738
|
+
});
|
|
1739
|
+
setIsValidated(false);
|
|
1740
|
+
shouldClearOnUnmount.current = true;
|
|
1741
|
+
} finally {
|
|
1742
|
+
setIsLoading(false);
|
|
1743
|
+
setIsRetrying(false);
|
|
1744
|
+
}
|
|
1745
|
+
},
|
|
1746
|
+
[token, validatedToken, isValidated, passflow, startCountdown]
|
|
1747
|
+
);
|
|
1748
|
+
const retry = useCallback(async () => {
|
|
1749
|
+
if (retryCountdown !== null && retryCountdown > 0) {
|
|
1750
|
+
return;
|
|
1751
|
+
}
|
|
1752
|
+
setValidatedToken(null);
|
|
1753
|
+
await validateToken(true);
|
|
1754
|
+
}, [validateToken, retryCountdown]);
|
|
1755
|
+
useEffect(() => {
|
|
1756
|
+
if (!token) {
|
|
1757
|
+
setError({
|
|
1758
|
+
code: "INVALID_TOKEN",
|
|
1759
|
+
message: "No token provided"
|
|
1760
|
+
});
|
|
1761
|
+
setIsLoading(false);
|
|
1762
|
+
return;
|
|
1763
|
+
}
|
|
1764
|
+
if (token !== validatedToken) {
|
|
1765
|
+
validateToken();
|
|
1766
|
+
}
|
|
1767
|
+
}, [token, validatedToken, validateToken]);
|
|
1768
|
+
useEffect(() => {
|
|
1769
|
+
return () => {
|
|
1770
|
+
if (countdownTimerRef.current) {
|
|
1771
|
+
clearInterval(countdownTimerRef.current);
|
|
1772
|
+
}
|
|
1773
|
+
if (shouldClearOnUnmount.current) {
|
|
1774
|
+
passflow.clearMagicLinkSession?.();
|
|
1775
|
+
}
|
|
1776
|
+
};
|
|
1777
|
+
}, [passflow]);
|
|
1778
|
+
return {
|
|
1779
|
+
isLoading,
|
|
1780
|
+
isRetrying,
|
|
1781
|
+
isValidated,
|
|
1782
|
+
error,
|
|
1783
|
+
sessionToken,
|
|
1784
|
+
userId,
|
|
1785
|
+
appId,
|
|
1786
|
+
expiresIn,
|
|
1787
|
+
retryCountdown,
|
|
1788
|
+
retry
|
|
1789
|
+
};
|
|
1790
|
+
};
|
|
1791
|
+
|
|
1192
1792
|
const FieldPhone = ({ id, onChange, isError = false, className = "" }) => {
|
|
1193
1793
|
const [show, setShow] = useState(false);
|
|
1194
1794
|
const [filterValue, setFilterValue] = useState("");
|
|
@@ -1431,25 +2031,515 @@ const Wrapper = ({
|
|
|
1431
2031
|
customLogo,
|
|
1432
2032
|
removeBranding = false
|
|
1433
2033
|
}) => {
|
|
1434
|
-
return /* @__PURE__ */ jsxs(HelmetProvider, { children: [
|
|
1435
|
-
/* @__PURE__ */ jsx(Helmet, { children: /* @__PURE__ */ jsx("style", { type: "text/css", children: customCss }) }),
|
|
1436
|
-
/* @__PURE__ */ jsxs("div", { id: "passflow-wrapper", className: "passflow-wrapper", children: [
|
|
1437
|
-
/* @__PURE__ */ jsxs("div", { className: cn("passflow-form-main-wrapper", className), children: [
|
|
1438
|
-
/* @__PURE__ */ jsxs("div", { className: "passflow-form-main-container", children: [
|
|
1439
|
-
customLogo ? /* @__PURE__ */ jsx("img", { src: customLogo, alt: "custom logo", className: "passflow-form-main-container-logo" }) : /* @__PURE__ */ jsx(Icon, { id: iconId, size: "large", type: "general" }),
|
|
1440
|
-
title && /* @__PURE__ */ jsxs("div", { className: "passflow-form-header", children: [
|
|
1441
|
-
/* @__PURE__ */ jsx("h2", { className: "passflow-form-title", children: title }),
|
|
1442
|
-
subtitle && /* @__PURE__ */ jsx("span", { className: "passflow-form-subtitle", children: subtitle })
|
|
1443
|
-
] })
|
|
1444
|
-
] }),
|
|
1445
|
-
children
|
|
1446
|
-
] }),
|
|
1447
|
-
!removeBranding && /* @__PURE__ */ jsx("div", { className: "passflow-branding", children: /* @__PURE__ */ jsxs("p", { className: "passflow-branding-text", children: [
|
|
1448
|
-
"Secured by ",
|
|
1449
|
-
/* @__PURE__ */ jsx("span", { className: "passflow-branding-text-secondary passflow-secondary-font", children: "PASSFLOW" })
|
|
1450
|
-
] }) })
|
|
1451
|
-
] })
|
|
1452
|
-
] });
|
|
2034
|
+
return /* @__PURE__ */ jsxs(HelmetProvider, { children: [
|
|
2035
|
+
/* @__PURE__ */ jsx(Helmet, { children: /* @__PURE__ */ jsx("style", { type: "text/css", children: customCss }) }),
|
|
2036
|
+
/* @__PURE__ */ jsxs("div", { id: "passflow-wrapper", className: "passflow-wrapper", children: [
|
|
2037
|
+
/* @__PURE__ */ jsxs("div", { className: cn("passflow-form-main-wrapper", className), children: [
|
|
2038
|
+
/* @__PURE__ */ jsxs("div", { className: "passflow-form-main-container", children: [
|
|
2039
|
+
customLogo ? /* @__PURE__ */ jsx("img", { src: customLogo, alt: "custom logo", className: "passflow-form-main-container-logo" }) : /* @__PURE__ */ jsx(Icon, { id: iconId, size: "large", type: "general" }),
|
|
2040
|
+
title && /* @__PURE__ */ jsxs("div", { className: "passflow-form-header", children: [
|
|
2041
|
+
/* @__PURE__ */ jsx("h2", { className: "passflow-form-title", children: title }),
|
|
2042
|
+
subtitle && /* @__PURE__ */ jsx("span", { className: "passflow-form-subtitle", children: subtitle })
|
|
2043
|
+
] })
|
|
2044
|
+
] }),
|
|
2045
|
+
children
|
|
2046
|
+
] }),
|
|
2047
|
+
!removeBranding && /* @__PURE__ */ jsx("div", { className: "passflow-branding", children: /* @__PURE__ */ jsxs("p", { className: "passflow-branding-text", children: [
|
|
2048
|
+
"Secured by ",
|
|
2049
|
+
/* @__PURE__ */ jsx("span", { className: "passflow-branding-text-secondary passflow-secondary-font", children: "PASSFLOW" })
|
|
2050
|
+
] }) })
|
|
2051
|
+
] })
|
|
2052
|
+
] });
|
|
2053
|
+
};
|
|
2054
|
+
|
|
2055
|
+
const TwoFactorSetupForm = ({ onComplete, onCancel, numInputs }) => {
|
|
2056
|
+
const [code, setCode] = useState("");
|
|
2057
|
+
const [showSecret, setShowSecret] = useState(false);
|
|
2058
|
+
const { setupData, recoveryCodes, step, beginSetup, confirmSetup, reset, isLoading, isError, error } = useTwoFactorSetup();
|
|
2059
|
+
const totpDigits = numInputs ?? 6;
|
|
2060
|
+
const handleCodeChange = (value) => {
|
|
2061
|
+
setCode(value);
|
|
2062
|
+
};
|
|
2063
|
+
const handleConfirm = async (e) => {
|
|
2064
|
+
e.preventDefault();
|
|
2065
|
+
await confirmSetup(code);
|
|
2066
|
+
};
|
|
2067
|
+
const handleCopyRecoveryCodes = () => {
|
|
2068
|
+
navigator.clipboard.writeText(recoveryCodes.join("\n"));
|
|
2069
|
+
};
|
|
2070
|
+
const handleComplete = () => {
|
|
2071
|
+
onComplete?.(recoveryCodes);
|
|
2072
|
+
reset();
|
|
2073
|
+
};
|
|
2074
|
+
if (step === "idle") {
|
|
2075
|
+
return /* @__PURE__ */ jsx(
|
|
2076
|
+
Wrapper,
|
|
2077
|
+
{
|
|
2078
|
+
title: "Enable Two-Factor Authentication",
|
|
2079
|
+
subtitle: "Add an extra layer of security to your account",
|
|
2080
|
+
className: "passflow-2fa-setup",
|
|
2081
|
+
children: /* @__PURE__ */ jsxs("div", { className: "passflow-2fa-setup-start", children: [
|
|
2082
|
+
/* @__PURE__ */ jsx(Button, { size: "big", type: "button", variant: "primary", onClick: () => beginSetup(), disabled: isLoading, children: isLoading ? "Loading..." : "Get Started" }),
|
|
2083
|
+
onCancel && /* @__PURE__ */ jsx("button", { type: "button", className: "passflow-link-button", onClick: onCancel, children: "Cancel" }),
|
|
2084
|
+
isError && /* @__PURE__ */ jsxs("div", { className: "passflow-field-error", children: [
|
|
2085
|
+
/* @__PURE__ */ jsx(Icon, { size: "small", id: "warning", type: "general", className: "icon-warning" }),
|
|
2086
|
+
/* @__PURE__ */ jsx("span", { className: "passflow-field-error-text", children: error })
|
|
2087
|
+
] })
|
|
2088
|
+
] })
|
|
2089
|
+
}
|
|
2090
|
+
);
|
|
2091
|
+
}
|
|
2092
|
+
if (step === "setup" && setupData) {
|
|
2093
|
+
return /* @__PURE__ */ jsx(
|
|
2094
|
+
Wrapper,
|
|
2095
|
+
{
|
|
2096
|
+
title: "Scan QR Code",
|
|
2097
|
+
subtitle: "Scan this code with your authenticator app (Google Authenticator, Authy, etc.)",
|
|
2098
|
+
className: "passflow-2fa-setup",
|
|
2099
|
+
children: /* @__PURE__ */ jsxs("form", { onSubmit: handleConfirm, className: "passflow-2fa-setup-qr", children: [
|
|
2100
|
+
/* @__PURE__ */ jsx("div", { className: "passflow-2fa-qr-container", children: /* @__PURE__ */ jsx("img", { src: `data:image/png;base64,${setupData.qr_code}`, alt: "2FA QR Code", className: "passflow-2fa-qr-image" }) }),
|
|
2101
|
+
/* @__PURE__ */ jsxs("div", { className: "passflow-2fa-secret-toggle", children: [
|
|
2102
|
+
/* @__PURE__ */ jsx("button", { type: "button", className: "passflow-link-button", onClick: () => setShowSecret(!showSecret), children: showSecret ? "Hide manual code" : "Can't scan? Enter code manually" }),
|
|
2103
|
+
showSecret && /* @__PURE__ */ jsx("code", { className: "passflow-2fa-secret-code", children: setupData.secret })
|
|
2104
|
+
] }),
|
|
2105
|
+
/* @__PURE__ */ jsxs("div", { className: "passflow-2fa-confirm-section", children: [
|
|
2106
|
+
/* @__PURE__ */ jsxs("p", { className: "passflow-2fa-confirm-label", children: [
|
|
2107
|
+
"Enter the ",
|
|
2108
|
+
totpDigits,
|
|
2109
|
+
"-digit code from your app to confirm:"
|
|
2110
|
+
] }),
|
|
2111
|
+
/* @__PURE__ */ jsxs("div", { id: "otp-wrapper", className: "passflow-verify-otp-wrapper", children: [
|
|
2112
|
+
/* @__PURE__ */ jsx(
|
|
2113
|
+
OtpInput,
|
|
2114
|
+
{
|
|
2115
|
+
value: code,
|
|
2116
|
+
onChange: handleCodeChange,
|
|
2117
|
+
numInputs: totpDigits,
|
|
2118
|
+
shouldAutoFocus: true,
|
|
2119
|
+
skipDefaultStyles: true,
|
|
2120
|
+
containerStyle: "passflow-verify-otp-inputs",
|
|
2121
|
+
inputStyle: cn("passflow-field-otp passflow-field-otp--focused", isError && "passflow-field-otp--error"),
|
|
2122
|
+
inputType: "text",
|
|
2123
|
+
renderInput: (props) => /* @__PURE__ */ jsx("input", { ...props })
|
|
2124
|
+
}
|
|
2125
|
+
),
|
|
2126
|
+
isError && /* @__PURE__ */ jsxs("div", { className: "passflow-verify-otp-error", children: [
|
|
2127
|
+
/* @__PURE__ */ jsx(Icon, { size: "small", id: "warning", type: "general", className: "icon-warning" }),
|
|
2128
|
+
/* @__PURE__ */ jsx("span", { className: "passflow-verify-otp-error-text", children: error })
|
|
2129
|
+
] })
|
|
2130
|
+
] })
|
|
2131
|
+
] }),
|
|
2132
|
+
/* @__PURE__ */ jsx(Button, { size: "big", type: "submit", variant: "primary", disabled: code.length !== totpDigits || isLoading, children: isLoading ? "Confirming..." : "Confirm & Enable" })
|
|
2133
|
+
] })
|
|
2134
|
+
}
|
|
2135
|
+
);
|
|
2136
|
+
}
|
|
2137
|
+
if (step === "complete" && recoveryCodes.length > 0) {
|
|
2138
|
+
return /* @__PURE__ */ jsx(
|
|
2139
|
+
Wrapper,
|
|
2140
|
+
{
|
|
2141
|
+
title: "Save Your Recovery Codes",
|
|
2142
|
+
subtitle: "Store these codes in a safe place. You'll need them if you lose access to your authenticator.",
|
|
2143
|
+
className: "passflow-2fa-setup",
|
|
2144
|
+
children: /* @__PURE__ */ jsxs("div", { className: "passflow-2fa-setup-complete", children: [
|
|
2145
|
+
/* @__PURE__ */ jsxs("div", { className: "passflow-2fa-warning", children: [
|
|
2146
|
+
/* @__PURE__ */ jsx(Icon, { size: "small", id: "warning", type: "general", className: "icon-warning" }),
|
|
2147
|
+
/* @__PURE__ */ jsx("span", { children: "These codes will only be shown once. Make sure to save them!" })
|
|
2148
|
+
] }),
|
|
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)) }),
|
|
2150
|
+
/* @__PURE__ */ jsx(Button, { size: "big", type: "button", variant: "secondary", onClick: handleCopyRecoveryCodes, children: "Copy to Clipboard" }),
|
|
2151
|
+
/* @__PURE__ */ jsx(Button, { size: "big", type: "button", variant: "primary", onClick: handleComplete, children: "I've Saved These Codes" })
|
|
2152
|
+
] })
|
|
2153
|
+
}
|
|
2154
|
+
);
|
|
2155
|
+
}
|
|
2156
|
+
return null;
|
|
2157
|
+
};
|
|
2158
|
+
|
|
2159
|
+
const TwoFactorSetupFlow = ({
|
|
2160
|
+
twoFactorVerifyPath = routes.two_factor_verify.path,
|
|
2161
|
+
signInPath = routes.signin.path
|
|
2162
|
+
}) => {
|
|
2163
|
+
const [shouldBlockRender, setShouldBlockRender] = useState(false);
|
|
2164
|
+
const { navigate } = useNavigation();
|
|
2165
|
+
const passflow = usePassflow();
|
|
2166
|
+
useEffect(() => {
|
|
2167
|
+
if (!passflow.isTwoFactorVerificationRequired()) {
|
|
2168
|
+
if (TwoFactorLoopPrevention.canRedirect()) {
|
|
2169
|
+
TwoFactorLoopPrevention.incrementRedirect();
|
|
2170
|
+
navigate({ to: signInPath });
|
|
2171
|
+
setShouldBlockRender(true);
|
|
2172
|
+
} else {
|
|
2173
|
+
setShouldBlockRender(false);
|
|
2174
|
+
}
|
|
2175
|
+
}
|
|
2176
|
+
}, [passflow, navigate, signInPath]);
|
|
2177
|
+
const handleSetupComplete = () => {
|
|
2178
|
+
navigate({ to: twoFactorVerifyPath });
|
|
2179
|
+
};
|
|
2180
|
+
const handleCancel = () => {
|
|
2181
|
+
TwoFactorLoopPrevention.reset();
|
|
2182
|
+
navigate({ to: signInPath });
|
|
2183
|
+
};
|
|
2184
|
+
if (shouldBlockRender) {
|
|
2185
|
+
return null;
|
|
2186
|
+
}
|
|
2187
|
+
if (!passflow.isTwoFactorVerificationRequired() && TwoFactorLoopPrevention.canRedirect()) {
|
|
2188
|
+
return null;
|
|
2189
|
+
}
|
|
2190
|
+
return /* @__PURE__ */ jsx(TwoFactorSetupForm, { onComplete: handleSetupComplete, onCancel: handleCancel });
|
|
2191
|
+
};
|
|
2192
|
+
|
|
2193
|
+
const TwoFactorVerifyForm = ({
|
|
2194
|
+
onSuccess,
|
|
2195
|
+
onUseRecovery,
|
|
2196
|
+
title = "Two-Factor Authentication",
|
|
2197
|
+
subtitle,
|
|
2198
|
+
numInputs,
|
|
2199
|
+
shouldAutoFocus = true,
|
|
2200
|
+
signInPath,
|
|
2201
|
+
twoFactorSetupPath
|
|
2202
|
+
}) => {
|
|
2203
|
+
const [code, setCode] = useState("");
|
|
2204
|
+
const { verify, isLoading, isError, errorDetails, reset } = useTwoFactorVerify();
|
|
2205
|
+
const totpDigits = numInputs ?? 6;
|
|
2206
|
+
const computedSubtitle = subtitle ?? `Enter the ${totpDigits}-digit code from your authenticator app`;
|
|
2207
|
+
const handleChange = (value) => {
|
|
2208
|
+
reset();
|
|
2209
|
+
setCode(value);
|
|
2210
|
+
};
|
|
2211
|
+
const handleSubmit = async (e) => {
|
|
2212
|
+
e.preventDefault();
|
|
2213
|
+
const result = await verify(code);
|
|
2214
|
+
if (result) {
|
|
2215
|
+
onSuccess?.();
|
|
2216
|
+
}
|
|
2217
|
+
};
|
|
2218
|
+
const handleOtpChange = async (value) => {
|
|
2219
|
+
handleChange(value);
|
|
2220
|
+
if (value.length === totpDigits) {
|
|
2221
|
+
const result = await verify(value);
|
|
2222
|
+
if (result) {
|
|
2223
|
+
onSuccess?.();
|
|
2224
|
+
}
|
|
2225
|
+
}
|
|
2226
|
+
};
|
|
2227
|
+
const handleBackToSignIn = () => {
|
|
2228
|
+
TwoFactorLoopPrevention.reset();
|
|
2229
|
+
window.location.href = signInPath || "/signin";
|
|
2230
|
+
};
|
|
2231
|
+
const handleSetup2FA = () => {
|
|
2232
|
+
window.location.href = twoFactorSetupPath || "/two-factor-setup";
|
|
2233
|
+
};
|
|
2234
|
+
const isLoopDetected = !TwoFactorLoopPrevention.canRedirect();
|
|
2235
|
+
const renderErrorContent = () => {
|
|
2236
|
+
if (!isError || !errorDetails) return null;
|
|
2237
|
+
if (isLoopDetected) {
|
|
2238
|
+
return /* @__PURE__ */ jsxs("div", { className: "passflow-form-error passflow-form-error--critical", children: [
|
|
2239
|
+
/* @__PURE__ */ jsx(Icon, { size: "medium", id: "warning", type: "general", className: "icon-warning" }),
|
|
2240
|
+
/* @__PURE__ */ jsxs("div", { className: "passflow-form-error-content", children: [
|
|
2241
|
+
/* @__PURE__ */ jsx("span", { className: "passflow-form-error-text", children: TwoFactorLoopPrevention.getLoopDetectedMessage() }),
|
|
2242
|
+
signInPath && /* @__PURE__ */ jsx(
|
|
2243
|
+
Button,
|
|
2244
|
+
{
|
|
2245
|
+
type: "button",
|
|
2246
|
+
size: "medium",
|
|
2247
|
+
variant: "secondary",
|
|
2248
|
+
onClick: handleBackToSignIn,
|
|
2249
|
+
className: "passflow-button-back-to-signin",
|
|
2250
|
+
children: "Back to Sign In"
|
|
2251
|
+
}
|
|
2252
|
+
)
|
|
2253
|
+
] })
|
|
2254
|
+
] });
|
|
2255
|
+
}
|
|
2256
|
+
if (errorDetails.type === "not_enabled") {
|
|
2257
|
+
return /* @__PURE__ */ jsxs("div", { className: "passflow-form-error passflow-form-error--persistent", children: [
|
|
2258
|
+
/* @__PURE__ */ jsx(Icon, { size: "medium", id: "warning", type: "general", className: "icon-warning" }),
|
|
2259
|
+
/* @__PURE__ */ jsxs("div", { className: "passflow-form-error-content", children: [
|
|
2260
|
+
/* @__PURE__ */ jsx("span", { className: "passflow-form-error-text", children: "You need to set up two-factor authentication first." }),
|
|
2261
|
+
/* @__PURE__ */ jsxs("div", { className: "passflow-form-error-actions", children: [
|
|
2262
|
+
twoFactorSetupPath && /* @__PURE__ */ jsx(
|
|
2263
|
+
Button,
|
|
2264
|
+
{
|
|
2265
|
+
type: "button",
|
|
2266
|
+
size: "medium",
|
|
2267
|
+
variant: "primary",
|
|
2268
|
+
onClick: handleSetup2FA,
|
|
2269
|
+
className: "passflow-button-setup-2fa",
|
|
2270
|
+
children: "Set Up 2FA"
|
|
2271
|
+
}
|
|
2272
|
+
),
|
|
2273
|
+
signInPath && /* @__PURE__ */ jsx(
|
|
2274
|
+
Button,
|
|
2275
|
+
{
|
|
2276
|
+
type: "button",
|
|
2277
|
+
size: "medium",
|
|
2278
|
+
variant: "secondary",
|
|
2279
|
+
onClick: handleBackToSignIn,
|
|
2280
|
+
className: "passflow-button-back-to-signin",
|
|
2281
|
+
children: "Back to Sign In"
|
|
2282
|
+
}
|
|
2283
|
+
)
|
|
2284
|
+
] })
|
|
2285
|
+
] })
|
|
2286
|
+
] });
|
|
2287
|
+
}
|
|
2288
|
+
if (errorDetails.type === "expired") {
|
|
2289
|
+
return /* @__PURE__ */ jsxs("div", { className: "passflow-form-info", children: [
|
|
2290
|
+
/* @__PURE__ */ jsx(Icon, { size: "small", id: "info", type: "general", className: "icon-info" }),
|
|
2291
|
+
/* @__PURE__ */ jsx("span", { className: "passflow-form-info-text", children: getUserFriendlyErrorMessage(errorDetails) })
|
|
2292
|
+
] });
|
|
2293
|
+
}
|
|
2294
|
+
return /* @__PURE__ */ jsxs("div", { className: "passflow-form-error", children: [
|
|
2295
|
+
/* @__PURE__ */ jsx(Icon, { size: "small", id: "warning", type: "general", className: "icon-warning" }),
|
|
2296
|
+
/* @__PURE__ */ jsx("span", { className: "passflow-form-error-text", children: getUserFriendlyErrorMessage(errorDetails) })
|
|
2297
|
+
] });
|
|
2298
|
+
};
|
|
2299
|
+
const isInputDisabled = isLoading || errorDetails?.type === "not_enabled" || errorDetails?.type === "expired" || isLoopDetected;
|
|
2300
|
+
return /* @__PURE__ */ jsx(Wrapper, { title, subtitle: computedSubtitle, className: "passflow-2fa-verify", children: /* @__PURE__ */ jsxs("form", { onSubmit: handleSubmit, className: "passflow-form", children: [
|
|
2301
|
+
/* @__PURE__ */ jsxs("div", { className: "passflow-form-container", children: [
|
|
2302
|
+
/* @__PURE__ */ jsx("div", { className: "passflow-verify-otp-wrapper", children: /* @__PURE__ */ jsx(
|
|
2303
|
+
OtpInput,
|
|
2304
|
+
{
|
|
2305
|
+
value: code,
|
|
2306
|
+
onChange: handleOtpChange,
|
|
2307
|
+
numInputs: totpDigits,
|
|
2308
|
+
shouldAutoFocus: shouldAutoFocus && !isInputDisabled,
|
|
2309
|
+
skipDefaultStyles: true,
|
|
2310
|
+
containerStyle: "passflow-verify-otp-inputs",
|
|
2311
|
+
inputStyle: cn(
|
|
2312
|
+
"passflow-field-otp passflow-field-otp--focused",
|
|
2313
|
+
isError && "passflow-field-otp--error",
|
|
2314
|
+
isInputDisabled && "passflow-field-otp--disabled"
|
|
2315
|
+
),
|
|
2316
|
+
inputType: "text",
|
|
2317
|
+
renderInput: (props) => /* @__PURE__ */ jsx("input", { ...props, disabled: isInputDisabled })
|
|
2318
|
+
}
|
|
2319
|
+
) }),
|
|
2320
|
+
renderErrorContent()
|
|
2321
|
+
] }),
|
|
2322
|
+
!isInputDisabled && /* @__PURE__ */ jsx(
|
|
2323
|
+
Button,
|
|
2324
|
+
{
|
|
2325
|
+
size: "big",
|
|
2326
|
+
type: "submit",
|
|
2327
|
+
variant: "primary",
|
|
2328
|
+
disabled: code.length !== totpDigits || isLoading,
|
|
2329
|
+
className: "passflow-button-signin",
|
|
2330
|
+
children: isLoading ? "Verifying..." : "Verify"
|
|
2331
|
+
}
|
|
2332
|
+
),
|
|
2333
|
+
onUseRecovery && !isInputDisabled && /* @__PURE__ */ jsx("div", { className: "passflow-form-actions", children: /* @__PURE__ */ jsxs("p", { className: "passflow-dont-have-account", children: [
|
|
2334
|
+
"Lost your device?",
|
|
2335
|
+
" ",
|
|
2336
|
+
/* @__PURE__ */ jsx(
|
|
2337
|
+
"button",
|
|
2338
|
+
{
|
|
2339
|
+
type: "button",
|
|
2340
|
+
onClick: onUseRecovery,
|
|
2341
|
+
className: "passflow-link",
|
|
2342
|
+
style: { background: "none", border: "none", cursor: "pointer" },
|
|
2343
|
+
children: "Use recovery code"
|
|
2344
|
+
}
|
|
2345
|
+
)
|
|
2346
|
+
] }) })
|
|
2347
|
+
] }) });
|
|
2348
|
+
};
|
|
2349
|
+
|
|
2350
|
+
const TwoFactorRecoveryForm = ({
|
|
2351
|
+
onSuccess,
|
|
2352
|
+
onBack,
|
|
2353
|
+
title = "Use Recovery Code",
|
|
2354
|
+
subtitle = "Enter one of your recovery codes",
|
|
2355
|
+
signInPath
|
|
2356
|
+
}) => {
|
|
2357
|
+
const [code, setCode] = useState("");
|
|
2358
|
+
const { useRecoveryCode, isLoading, isError, errorDetails, reset } = useTwoFactorVerify();
|
|
2359
|
+
const handleChange = (e) => {
|
|
2360
|
+
reset();
|
|
2361
|
+
setCode(e.target.value.toUpperCase().replace(/\s/g, ""));
|
|
2362
|
+
};
|
|
2363
|
+
const handleSubmit = async (e) => {
|
|
2364
|
+
e.preventDefault();
|
|
2365
|
+
const result = await useRecoveryCode(code);
|
|
2366
|
+
if (result) {
|
|
2367
|
+
onSuccess?.();
|
|
2368
|
+
}
|
|
2369
|
+
};
|
|
2370
|
+
const handleBackToSignIn = () => {
|
|
2371
|
+
TwoFactorLoopPrevention.reset();
|
|
2372
|
+
window.location.href = signInPath || "/signin";
|
|
2373
|
+
};
|
|
2374
|
+
const isLoopDetected = !TwoFactorLoopPrevention.canRedirect();
|
|
2375
|
+
const renderErrorContent = () => {
|
|
2376
|
+
if (!isError || !errorDetails) return null;
|
|
2377
|
+
if (isLoopDetected) {
|
|
2378
|
+
return /* @__PURE__ */ jsxs("div", { className: "passflow-form-error passflow-form-error--critical", children: [
|
|
2379
|
+
/* @__PURE__ */ jsx(Icon, { size: "medium", id: "warning", type: "general", className: "icon-warning" }),
|
|
2380
|
+
/* @__PURE__ */ jsxs("div", { className: "passflow-form-error-content", children: [
|
|
2381
|
+
/* @__PURE__ */ jsx("span", { className: "passflow-form-error-text", children: TwoFactorLoopPrevention.getLoopDetectedMessage() }),
|
|
2382
|
+
signInPath && /* @__PURE__ */ jsx(
|
|
2383
|
+
Button,
|
|
2384
|
+
{
|
|
2385
|
+
type: "button",
|
|
2386
|
+
size: "medium",
|
|
2387
|
+
variant: "secondary",
|
|
2388
|
+
onClick: handleBackToSignIn,
|
|
2389
|
+
className: "passflow-button-back-to-signin",
|
|
2390
|
+
children: "Back to Sign In"
|
|
2391
|
+
}
|
|
2392
|
+
)
|
|
2393
|
+
] })
|
|
2394
|
+
] });
|
|
2395
|
+
}
|
|
2396
|
+
if (errorDetails.type === "not_enabled") {
|
|
2397
|
+
return /* @__PURE__ */ jsxs("div", { className: "passflow-form-error passflow-form-error--persistent", children: [
|
|
2398
|
+
/* @__PURE__ */ jsx(Icon, { size: "medium", id: "warning", type: "general", className: "icon-warning" }),
|
|
2399
|
+
/* @__PURE__ */ jsxs("div", { className: "passflow-form-error-content", children: [
|
|
2400
|
+
/* @__PURE__ */ jsx("span", { className: "passflow-form-error-text", children: getUserFriendlyErrorMessage(errorDetails) }),
|
|
2401
|
+
signInPath && /* @__PURE__ */ jsx(
|
|
2402
|
+
Button,
|
|
2403
|
+
{
|
|
2404
|
+
type: "button",
|
|
2405
|
+
size: "medium",
|
|
2406
|
+
variant: "secondary",
|
|
2407
|
+
onClick: handleBackToSignIn,
|
|
2408
|
+
className: "passflow-button-back-to-signin",
|
|
2409
|
+
children: "Back to Sign In"
|
|
2410
|
+
}
|
|
2411
|
+
)
|
|
2412
|
+
] })
|
|
2413
|
+
] });
|
|
2414
|
+
}
|
|
2415
|
+
if (errorDetails.type === "expired") {
|
|
2416
|
+
return /* @__PURE__ */ jsxs("div", { className: "passflow-form-info", children: [
|
|
2417
|
+
/* @__PURE__ */ jsx(Icon, { size: "small", id: "info", type: "general", className: "icon-info" }),
|
|
2418
|
+
/* @__PURE__ */ jsx("span", { className: "passflow-form-info-text", children: getUserFriendlyErrorMessage(errorDetails) })
|
|
2419
|
+
] });
|
|
2420
|
+
}
|
|
2421
|
+
return /* @__PURE__ */ jsxs("div", { className: "passflow-form-error", children: [
|
|
2422
|
+
/* @__PURE__ */ jsx(Icon, { size: "small", id: "warning", type: "general", className: "icon-warning" }),
|
|
2423
|
+
/* @__PURE__ */ jsx("span", { className: "passflow-form-error-text", children: getUserFriendlyErrorMessage(errorDetails) })
|
|
2424
|
+
] });
|
|
2425
|
+
};
|
|
2426
|
+
const isInputDisabled = isLoading || errorDetails?.type === "not_enabled" || errorDetails?.type === "expired" || isLoopDetected;
|
|
2427
|
+
return /* @__PURE__ */ jsx(Wrapper, { title, subtitle, className: "passflow-2fa-recovery", children: /* @__PURE__ */ jsxs("form", { onSubmit: handleSubmit, className: "passflow-form", children: [
|
|
2428
|
+
/* @__PURE__ */ jsx("div", { className: "passflow-form-container", children: /* @__PURE__ */ jsxs("div", { className: "passflow-form-field", children: [
|
|
2429
|
+
/* @__PURE__ */ jsx("label", { htmlFor: "recovery-code", className: cn("passflow-field-label", isError && "passflow-field-label--error"), children: "Recovery Code" }),
|
|
2430
|
+
/* @__PURE__ */ jsx(
|
|
2431
|
+
"input",
|
|
2432
|
+
{
|
|
2433
|
+
id: "recovery-code",
|
|
2434
|
+
type: "text",
|
|
2435
|
+
value: code,
|
|
2436
|
+
onChange: handleChange,
|
|
2437
|
+
placeholder: "XXXX-XXXX",
|
|
2438
|
+
className: cn(
|
|
2439
|
+
"passflow-field passflow-field--text passflow-field--focused",
|
|
2440
|
+
isError && "passflow-field--error",
|
|
2441
|
+
isInputDisabled && "passflow-field--disabled"
|
|
2442
|
+
),
|
|
2443
|
+
autoFocus: !isInputDisabled,
|
|
2444
|
+
autoComplete: "off",
|
|
2445
|
+
disabled: isInputDisabled
|
|
2446
|
+
}
|
|
2447
|
+
),
|
|
2448
|
+
renderErrorContent()
|
|
2449
|
+
] }) }),
|
|
2450
|
+
!isInputDisabled && /* @__PURE__ */ jsx(
|
|
2451
|
+
Button,
|
|
2452
|
+
{
|
|
2453
|
+
size: "big",
|
|
2454
|
+
type: "submit",
|
|
2455
|
+
variant: "primary",
|
|
2456
|
+
disabled: code.length < 8 || isLoading,
|
|
2457
|
+
className: "passflow-button-signin",
|
|
2458
|
+
children: isLoading ? "Verifying..." : "Verify"
|
|
2459
|
+
}
|
|
2460
|
+
),
|
|
2461
|
+
onBack && !isInputDisabled && /* @__PURE__ */ jsx("div", { className: "passflow-form-actions", children: /* @__PURE__ */ jsxs("p", { className: "passflow-dont-have-account", children: [
|
|
2462
|
+
"Have your device?",
|
|
2463
|
+
" ",
|
|
2464
|
+
/* @__PURE__ */ jsx(
|
|
2465
|
+
"button",
|
|
2466
|
+
{
|
|
2467
|
+
type: "button",
|
|
2468
|
+
onClick: onBack,
|
|
2469
|
+
className: "passflow-link",
|
|
2470
|
+
style: { background: "none", border: "none", cursor: "pointer" },
|
|
2471
|
+
children: "Use authenticator code"
|
|
2472
|
+
}
|
|
2473
|
+
)
|
|
2474
|
+
] }) })
|
|
2475
|
+
] }) });
|
|
2476
|
+
};
|
|
2477
|
+
|
|
2478
|
+
const TwoFactorVerifyFlow = ({
|
|
2479
|
+
successAuthRedirect,
|
|
2480
|
+
signInPath = routes.signin.path,
|
|
2481
|
+
twoFactorSetupPath = routes.two_factor_setup?.path
|
|
2482
|
+
}) => {
|
|
2483
|
+
const [mode, setMode] = useState("verify");
|
|
2484
|
+
const [shouldBlockRender, setShouldBlockRender] = useState(false);
|
|
2485
|
+
const { navigate } = useNavigation();
|
|
2486
|
+
const passflow = usePassflow();
|
|
2487
|
+
const { errorType } = useTwoFactorVerify();
|
|
2488
|
+
useEffect(() => {
|
|
2489
|
+
if (!passflow.isTwoFactorVerificationRequired()) {
|
|
2490
|
+
if (TwoFactorLoopPrevention.canRedirect()) {
|
|
2491
|
+
TwoFactorLoopPrevention.incrementRedirect();
|
|
2492
|
+
navigate({ to: signInPath });
|
|
2493
|
+
setShouldBlockRender(true);
|
|
2494
|
+
} else {
|
|
2495
|
+
setShouldBlockRender(false);
|
|
2496
|
+
}
|
|
2497
|
+
}
|
|
2498
|
+
}, [passflow, navigate, signInPath]);
|
|
2499
|
+
useEffect(() => {
|
|
2500
|
+
if (errorType === "expired") {
|
|
2501
|
+
if (TwoFactorLoopPrevention.canRedirect()) {
|
|
2502
|
+
TwoFactorLoopPrevention.incrementRedirect();
|
|
2503
|
+
TwoFactorLoopPrevention.setLastErrorType("expired");
|
|
2504
|
+
setTimeout(() => {
|
|
2505
|
+
navigate({ to: signInPath });
|
|
2506
|
+
}, 1500);
|
|
2507
|
+
}
|
|
2508
|
+
} else if (errorType === "not_enabled") {
|
|
2509
|
+
TwoFactorLoopPrevention.setLastErrorType("not_enabled");
|
|
2510
|
+
}
|
|
2511
|
+
}, [errorType, navigate, signInPath]);
|
|
2512
|
+
const handleSuccess = async () => {
|
|
2513
|
+
TwoFactorLoopPrevention.reset();
|
|
2514
|
+
if (successAuthRedirect) {
|
|
2515
|
+
if (!isValidUrl(successAuthRedirect)) {
|
|
2516
|
+
navigate({ to: successAuthRedirect });
|
|
2517
|
+
} else {
|
|
2518
|
+
window.location.href = await getUrlWithTokens(passflow, successAuthRedirect);
|
|
2519
|
+
}
|
|
2520
|
+
}
|
|
2521
|
+
};
|
|
2522
|
+
const handleUseRecovery = () => {
|
|
2523
|
+
setMode("recovery");
|
|
2524
|
+
};
|
|
2525
|
+
const handleBackToVerify = () => {
|
|
2526
|
+
setMode("verify");
|
|
2527
|
+
};
|
|
2528
|
+
if (shouldBlockRender) {
|
|
2529
|
+
return null;
|
|
2530
|
+
}
|
|
2531
|
+
if (!passflow.isTwoFactorVerificationRequired() && TwoFactorLoopPrevention.canRedirect()) {
|
|
2532
|
+
return null;
|
|
2533
|
+
}
|
|
2534
|
+
return /* @__PURE__ */ jsx(Fragment, { children: mode === "verify" ? /* @__PURE__ */ jsx(
|
|
2535
|
+
TwoFactorVerifyForm,
|
|
2536
|
+
{
|
|
2537
|
+
onSuccess: handleSuccess,
|
|
2538
|
+
onUseRecovery: handleUseRecovery,
|
|
2539
|
+
signInPath,
|
|
2540
|
+
twoFactorSetupPath
|
|
2541
|
+
}
|
|
2542
|
+
) : /* @__PURE__ */ jsx(TwoFactorRecoveryForm, { onSuccess: handleSuccess, onBack: handleBackToVerify, signInPath }) });
|
|
1453
2543
|
};
|
|
1454
2544
|
|
|
1455
2545
|
const defaultErrorMessage = "Something went wrong";
|
|
@@ -1479,13 +2569,25 @@ const ErrorComponent = ({ error = defaultErrorMessage, goBackRedirectTo }) => {
|
|
|
1479
2569
|
);
|
|
1480
2570
|
};
|
|
1481
2571
|
|
|
1482
|
-
const excludeRoutes = ["verify-challenge-otp", "password/reset"];
|
|
2572
|
+
const excludeRoutes = ["verify-challenge-otp", "password/reset", "two-factor-verify", "two-factor-recovery"];
|
|
1483
2573
|
const withError = (Component, ErrorComponent) => (props) => {
|
|
1484
2574
|
const context = useContext(PassflowContext);
|
|
1485
2575
|
const { successAuthRedirect } = props;
|
|
1486
2576
|
const { pathname } = window.location;
|
|
1487
|
-
if (
|
|
1488
|
-
|
|
2577
|
+
if (excludeRoutes.some((route) => pathname.includes(route))) {
|
|
2578
|
+
return /* @__PURE__ */ jsx(
|
|
2579
|
+
ErrorBoundary,
|
|
2580
|
+
{
|
|
2581
|
+
FallbackComponent: ({ error }) => /* @__PURE__ */ jsx(ErrorComponent, { goBackRedirectTo: successAuthRedirect ?? "/", error: error.message }),
|
|
2582
|
+
children: /* @__PURE__ */ jsx(Component, { ...props })
|
|
2583
|
+
}
|
|
2584
|
+
);
|
|
2585
|
+
}
|
|
2586
|
+
if (context?.state.isDiscoveringAppId) {
|
|
2587
|
+
return null;
|
|
2588
|
+
}
|
|
2589
|
+
if (!context?.state.appId) {
|
|
2590
|
+
const errorMessage = "Missing appId";
|
|
1489
2591
|
return /* @__PURE__ */ jsx(ErrorComponent, { goBackRedirectTo: successAuthRedirect ?? "/", error: errorMessage });
|
|
1490
2592
|
}
|
|
1491
2593
|
return /* @__PURE__ */ jsx(
|
|
@@ -1508,7 +2610,8 @@ const SignInForm = ({
|
|
|
1508
2610
|
signUpPath = routes.signup.path,
|
|
1509
2611
|
verifyOTPPath = routes.verify_otp.path,
|
|
1510
2612
|
verifyMagicLinkPath = routes.verify_magic_link.path,
|
|
1511
|
-
forgotPasswordPath = routes.forgot_password.path
|
|
2613
|
+
forgotPasswordPath = routes.forgot_password.path,
|
|
2614
|
+
twoFactorVerifyPath = routes.two_factor_verify.path
|
|
1512
2615
|
}) => {
|
|
1513
2616
|
const {
|
|
1514
2617
|
getValues,
|
|
@@ -1523,7 +2626,16 @@ const SignInForm = ({
|
|
|
1523
2626
|
});
|
|
1524
2627
|
const passflow = usePassflow();
|
|
1525
2628
|
const { navigate } = useNavigation();
|
|
1526
|
-
const {
|
|
2629
|
+
const {
|
|
2630
|
+
appSettings,
|
|
2631
|
+
scopes,
|
|
2632
|
+
createTenantForNewUser,
|
|
2633
|
+
passwordPolicy,
|
|
2634
|
+
currentStyles,
|
|
2635
|
+
isError: isErrorApp,
|
|
2636
|
+
error: errorApp,
|
|
2637
|
+
loginAppTheme
|
|
2638
|
+
} = useAppSettings();
|
|
1527
2639
|
const { federatedWithRedirect } = useProvider(successAuthRedirect, createTenantForNewUser);
|
|
1528
2640
|
if (isErrorApp) throw new Error(errorApp);
|
|
1529
2641
|
const authMethods = useMemo(() => getAuthMethods(appSettings?.auth_strategies), [appSettings]);
|
|
@@ -1576,7 +2688,12 @@ const SignInForm = ({
|
|
|
1576
2688
|
};
|
|
1577
2689
|
const status = await fetch(payload, "password");
|
|
1578
2690
|
if (status) {
|
|
1579
|
-
if (
|
|
2691
|
+
if (passflow.isTwoFactorVerificationRequired()) {
|
|
2692
|
+
navigate({ to: twoFactorVerifyPath ?? routes.two_factor_verify.path });
|
|
2693
|
+
return;
|
|
2694
|
+
}
|
|
2695
|
+
if (!isValidUrl(successAuthRedirect ?? appSettings.defaults.redirect))
|
|
2696
|
+
navigate({ to: successAuthRedirect ?? appSettings.defaults.redirect });
|
|
1580
2697
|
else window.location.href = await getUrlWithTokens(passflow, successAuthRedirect ?? appSettings.defaults.redirect);
|
|
1581
2698
|
}
|
|
1582
2699
|
};
|
|
@@ -1587,7 +2704,12 @@ const SignInForm = ({
|
|
|
1587
2704
|
};
|
|
1588
2705
|
const response = await fetch(payload, "passkey");
|
|
1589
2706
|
if (response) {
|
|
1590
|
-
if (
|
|
2707
|
+
if (passflow.isTwoFactorVerificationRequired()) {
|
|
2708
|
+
navigate({ to: twoFactorVerifyPath ?? routes.two_factor_verify.path });
|
|
2709
|
+
return;
|
|
2710
|
+
}
|
|
2711
|
+
if (!isValidUrl(successAuthRedirect ?? appSettings.defaults.redirect))
|
|
2712
|
+
navigate({ to: successAuthRedirect ?? appSettings.defaults.redirect });
|
|
1591
2713
|
else window.location.href = await getUrlWithTokens(passflow, successAuthRedirect ?? appSettings.defaults.redirect);
|
|
1592
2714
|
}
|
|
1593
2715
|
};
|
|
@@ -1966,7 +3088,16 @@ const SignUpForm = ({
|
|
|
1966
3088
|
});
|
|
1967
3089
|
const passflow = usePassflow();
|
|
1968
3090
|
const { navigate } = useNavigation();
|
|
1969
|
-
const {
|
|
3091
|
+
const {
|
|
3092
|
+
appSettings,
|
|
3093
|
+
scopes,
|
|
3094
|
+
createTenantForNewUser,
|
|
3095
|
+
passwordPolicy,
|
|
3096
|
+
currentStyles,
|
|
3097
|
+
isError: isErrorApp,
|
|
3098
|
+
error: errorApp,
|
|
3099
|
+
loginAppTheme
|
|
3100
|
+
} = useAppSettings();
|
|
1970
3101
|
if (isErrorApp) throw new Error(errorApp);
|
|
1971
3102
|
const { federatedWithRedirect } = useProvider(successAuthRedirect, createTenantForNewUser);
|
|
1972
3103
|
const authMethods = useMemo(() => getAuthMethods(appSettings?.auth_strategies), [appSettings]);
|
|
@@ -2020,7 +3151,8 @@ const SignUpForm = ({
|
|
|
2020
3151
|
};
|
|
2021
3152
|
const status = await fetch(payload, "password");
|
|
2022
3153
|
if (status) {
|
|
2023
|
-
if (!isValidUrl(successAuthRedirect ?? appSettings.defaults.redirect))
|
|
3154
|
+
if (!isValidUrl(successAuthRedirect ?? appSettings.defaults.redirect))
|
|
3155
|
+
navigate({ to: successAuthRedirect ?? appSettings.defaults.redirect });
|
|
2024
3156
|
else window.location.href = await getUrlWithTokens(passflow, successAuthRedirect ?? appSettings.defaults.redirect);
|
|
2025
3157
|
}
|
|
2026
3158
|
};
|
|
@@ -2035,7 +3167,8 @@ const SignUpForm = ({
|
|
|
2035
3167
|
};
|
|
2036
3168
|
const response = await fetch(payload, "passkey");
|
|
2037
3169
|
if (response) {
|
|
2038
|
-
if (!isValidUrl(successAuthRedirect ?? appSettings.defaults.redirect))
|
|
3170
|
+
if (!isValidUrl(successAuthRedirect ?? appSettings.defaults.redirect))
|
|
3171
|
+
navigate({ to: successAuthRedirect ?? appSettings.defaults.redirect });
|
|
2039
3172
|
else window.location.href = await getUrlWithTokens(passflow, successAuthRedirect ?? appSettings.defaults.redirect);
|
|
2040
3173
|
}
|
|
2041
3174
|
};
|
|
@@ -2443,66 +3576,6 @@ const VerifyChallengeMagicLink = () => {
|
|
|
2443
3576
|
);
|
|
2444
3577
|
};
|
|
2445
3578
|
|
|
2446
|
-
const VerifyChallengeSuccess = () => {
|
|
2447
|
-
const { currentStyles, loginAppTheme } = useAppSettings();
|
|
2448
|
-
return /* @__PURE__ */ jsx(
|
|
2449
|
-
Wrapper,
|
|
2450
|
-
{
|
|
2451
|
-
iconId: "logo",
|
|
2452
|
-
className: "passflow-verify-challenge-success-wrapper",
|
|
2453
|
-
customCss: currentStyles?.custom_css,
|
|
2454
|
-
customLogo: currentStyles?.logo_url,
|
|
2455
|
-
removeBranding: loginAppTheme?.remove_passflow_logo,
|
|
2456
|
-
children: /* @__PURE__ */ jsx("div", { className: "passflow-verify-challenge-success-wrapper", children: /* @__PURE__ */ jsxs("div", { className: "passflow-verify-challenge-success-container", children: [
|
|
2457
|
-
/* @__PURE__ */ jsx("p", { className: "passflow-verify-challenge-success-text", children: "Successful verification!" }),
|
|
2458
|
-
/* @__PURE__ */ jsx("p", { className: "passflow-verify-challenge-success-text-secondary", children: "But there is no redirect URL for further redirect." })
|
|
2459
|
-
] }) })
|
|
2460
|
-
}
|
|
2461
|
-
);
|
|
2462
|
-
};
|
|
2463
|
-
|
|
2464
|
-
const VerifyChallengeOTPRedirect = ({ otp, challengeId, appId }) => {
|
|
2465
|
-
const passflow = usePassflow();
|
|
2466
|
-
const { navigate } = useNavigation();
|
|
2467
|
-
const [paramsError, setParamsError] = useState(null);
|
|
2468
|
-
const [showSuccessMessage, setShowSuccessMessage] = useState(false);
|
|
2469
|
-
const { fetch, isError, error, isLoading } = usePasswordlessComplete();
|
|
2470
|
-
useEffect(() => {
|
|
2471
|
-
const fetchData = async () => {
|
|
2472
|
-
if (!appId) {
|
|
2473
|
-
setParamsError("Missing required param: app_id");
|
|
2474
|
-
return;
|
|
2475
|
-
}
|
|
2476
|
-
if (!otp) {
|
|
2477
|
-
setParamsError("Missing required param: otp");
|
|
2478
|
-
return;
|
|
2479
|
-
}
|
|
2480
|
-
if (!challengeId) {
|
|
2481
|
-
setParamsError("Missing required param: challenge_id");
|
|
2482
|
-
return;
|
|
2483
|
-
}
|
|
2484
|
-
if (!isLoading) {
|
|
2485
|
-
const response = await fetch({ otp, challenge_id: challengeId });
|
|
2486
|
-
if (response) {
|
|
2487
|
-
if (response.redirect_url) {
|
|
2488
|
-
if (!isValidUrl(response.redirect_url)) navigate({ to: response.redirect_url });
|
|
2489
|
-
else window.location.href = await getUrlWithTokens(passflow, response.redirect_url);
|
|
2490
|
-
} else {
|
|
2491
|
-
setShowSuccessMessage(true);
|
|
2492
|
-
}
|
|
2493
|
-
} else {
|
|
2494
|
-
setParamsError("Something went wrong. Please try again later.");
|
|
2495
|
-
}
|
|
2496
|
-
}
|
|
2497
|
-
};
|
|
2498
|
-
void fetchData();
|
|
2499
|
-
}, []);
|
|
2500
|
-
if (isError && error) throw new Error(error);
|
|
2501
|
-
if (paramsError) throw new Error(paramsError);
|
|
2502
|
-
if (showSuccessMessage) return /* @__PURE__ */ jsx(VerifyChallengeSuccess, {});
|
|
2503
|
-
return null;
|
|
2504
|
-
};
|
|
2505
|
-
|
|
2506
3579
|
const TimerButton = ({ totalSecond, onClick, className = "", ...props }) => {
|
|
2507
3580
|
const [seconds, setSeconds] = useState(totalSecond);
|
|
2508
3581
|
useEffect(() => {
|
|
@@ -2544,6 +3617,24 @@ const TimerButton = ({ totalSecond, onClick, className = "", ...props }) => {
|
|
|
2544
3617
|
);
|
|
2545
3618
|
};
|
|
2546
3619
|
|
|
3620
|
+
const VerifyChallengeSuccess = () => {
|
|
3621
|
+
const { currentStyles, loginAppTheme } = useAppSettings();
|
|
3622
|
+
return /* @__PURE__ */ jsx(
|
|
3623
|
+
Wrapper,
|
|
3624
|
+
{
|
|
3625
|
+
iconId: "logo",
|
|
3626
|
+
className: "passflow-verify-challenge-success-wrapper",
|
|
3627
|
+
customCss: currentStyles?.custom_css,
|
|
3628
|
+
customLogo: currentStyles?.logo_url,
|
|
3629
|
+
removeBranding: loginAppTheme?.remove_passflow_logo,
|
|
3630
|
+
children: /* @__PURE__ */ jsx("div", { className: "passflow-verify-challenge-success-wrapper", children: /* @__PURE__ */ jsxs("div", { className: "passflow-verify-challenge-success-container", children: [
|
|
3631
|
+
/* @__PURE__ */ jsx("p", { className: "passflow-verify-challenge-success-text", children: "Successful verification!" }),
|
|
3632
|
+
/* @__PURE__ */ jsx("p", { className: "passflow-verify-challenge-success-text-secondary", children: "But there is no redirect URL for further redirect." })
|
|
3633
|
+
] }) })
|
|
3634
|
+
}
|
|
3635
|
+
);
|
|
3636
|
+
};
|
|
3637
|
+
|
|
2547
3638
|
const challengeTypeFullString = {
|
|
2548
3639
|
email: "email address",
|
|
2549
3640
|
phone: "phone number"
|
|
@@ -2667,6 +3758,48 @@ const VerifyChallengeOTPManual = ({
|
|
|
2667
3758
|
);
|
|
2668
3759
|
};
|
|
2669
3760
|
|
|
3761
|
+
const VerifyChallengeOTPRedirect = ({ otp, challengeId, appId }) => {
|
|
3762
|
+
const passflow = usePassflow();
|
|
3763
|
+
const { navigate } = useNavigation();
|
|
3764
|
+
const [paramsError, setParamsError] = useState(null);
|
|
3765
|
+
const [showSuccessMessage, setShowSuccessMessage] = useState(false);
|
|
3766
|
+
const { fetch, isError, error, isLoading } = usePasswordlessComplete();
|
|
3767
|
+
useEffect(() => {
|
|
3768
|
+
const fetchData = async () => {
|
|
3769
|
+
if (!appId) {
|
|
3770
|
+
setParamsError("Missing required param: app_id");
|
|
3771
|
+
return;
|
|
3772
|
+
}
|
|
3773
|
+
if (!otp) {
|
|
3774
|
+
setParamsError("Missing required param: otp");
|
|
3775
|
+
return;
|
|
3776
|
+
}
|
|
3777
|
+
if (!challengeId) {
|
|
3778
|
+
setParamsError("Missing required param: challenge_id");
|
|
3779
|
+
return;
|
|
3780
|
+
}
|
|
3781
|
+
if (!isLoading) {
|
|
3782
|
+
const response = await fetch({ otp, challenge_id: challengeId });
|
|
3783
|
+
if (response) {
|
|
3784
|
+
if (response.redirect_url) {
|
|
3785
|
+
if (!isValidUrl(response.redirect_url)) navigate({ to: response.redirect_url });
|
|
3786
|
+
else window.location.href = await getUrlWithTokens(passflow, response.redirect_url);
|
|
3787
|
+
} else {
|
|
3788
|
+
setShowSuccessMessage(true);
|
|
3789
|
+
}
|
|
3790
|
+
} else {
|
|
3791
|
+
setParamsError("Something went wrong. Please try again later.");
|
|
3792
|
+
}
|
|
3793
|
+
}
|
|
3794
|
+
};
|
|
3795
|
+
void fetchData();
|
|
3796
|
+
}, []);
|
|
3797
|
+
if (isError && error) throw new Error(error);
|
|
3798
|
+
if (paramsError) throw new Error(paramsError);
|
|
3799
|
+
if (showSuccessMessage) return /* @__PURE__ */ jsx(VerifyChallengeSuccess, {});
|
|
3800
|
+
return null;
|
|
3801
|
+
};
|
|
3802
|
+
|
|
2670
3803
|
const redirectSearchParamsVerifyChallengeOtpSchema = Yup.object().shape({
|
|
2671
3804
|
appId: Yup.string().required(),
|
|
2672
3805
|
challengeId: Yup.string().required(),
|
|
@@ -3007,7 +4140,11 @@ const ResetPassword = ({ successAuthRedirect }) => {
|
|
|
3007
4140
|
if (status) {
|
|
3008
4141
|
if (!isValidUrl(resetTokenType?.redirect_url ?? successAuthRedirect ?? appSettings.defaults.redirect))
|
|
3009
4142
|
navigate({ to: resetTokenType?.redirect_url ?? successAuthRedirect ?? appSettings.defaults.redirect });
|
|
3010
|
-
else
|
|
4143
|
+
else
|
|
4144
|
+
window.location.href = await getUrlWithTokens(
|
|
4145
|
+
passflow,
|
|
4146
|
+
resetTokenType?.redirect_url ?? successAuthRedirect ?? appSettings.defaults.redirect
|
|
4147
|
+
);
|
|
3011
4148
|
}
|
|
3012
4149
|
};
|
|
3013
4150
|
const handlePasswordChange = async () => {
|
|
@@ -3140,7 +4277,7 @@ const InvitationJoinFlow = ({
|
|
|
3140
4277
|
tenant_name: tenantName,
|
|
3141
4278
|
redirect_url: redirectUrl
|
|
3142
4279
|
} = invitationTokenData;
|
|
3143
|
-
const parsedTokenCache = passflow.
|
|
4280
|
+
const parsedTokenCache = passflow.getParsedTokens();
|
|
3144
4281
|
const onClickNavigateToSignInHandler = () => navigate({ to: signInPath, search: window.location.search });
|
|
3145
4282
|
const onClickNavigateToSignUpHandler = () => navigate({ to: signUpPath, search: window.location.search });
|
|
3146
4283
|
if (!parsedTokenCache?.access_token) onClickNavigateToSignInHandler();
|
|
@@ -4245,6 +5382,13 @@ function useNavigateUnstable() {
|
|
|
4245
5382
|
}, [basename, navigator, routePathnamesJson, locationPathname, dataRouterContext]);
|
|
4246
5383
|
return navigate;
|
|
4247
5384
|
}
|
|
5385
|
+
function useParams() {
|
|
5386
|
+
let {
|
|
5387
|
+
matches
|
|
5388
|
+
} = React.useContext(RouteContext);
|
|
5389
|
+
let routeMatch = matches[matches.length - 1];
|
|
5390
|
+
return routeMatch ? routeMatch.params : {};
|
|
5391
|
+
}
|
|
4248
5392
|
function useRoutes(routes, locationArg) {
|
|
4249
5393
|
return useRoutesImpl(routes, locationArg);
|
|
4250
5394
|
}
|
|
@@ -4498,13 +5642,13 @@ function _renderMatches(matches, parentMatches, dataRouterState, future) {
|
|
|
4498
5642
|
}) : getChildren();
|
|
4499
5643
|
}, null);
|
|
4500
5644
|
}
|
|
4501
|
-
var DataRouterHook$1 = /* @__PURE__ */ function(DataRouterHook2) {
|
|
5645
|
+
var DataRouterHook$1 = /* @__PURE__ */ (function(DataRouterHook2) {
|
|
4502
5646
|
DataRouterHook2["UseBlocker"] = "useBlocker";
|
|
4503
5647
|
DataRouterHook2["UseRevalidator"] = "useRevalidator";
|
|
4504
5648
|
DataRouterHook2["UseNavigateStable"] = "useNavigate";
|
|
4505
5649
|
return DataRouterHook2;
|
|
4506
|
-
}(DataRouterHook$1 || {});
|
|
4507
|
-
var DataRouterStateHook$1 = /* @__PURE__ */ function(DataRouterStateHook2) {
|
|
5650
|
+
})(DataRouterHook$1 || {});
|
|
5651
|
+
var DataRouterStateHook$1 = /* @__PURE__ */ (function(DataRouterStateHook2) {
|
|
4508
5652
|
DataRouterStateHook2["UseBlocker"] = "useBlocker";
|
|
4509
5653
|
DataRouterStateHook2["UseLoaderData"] = "useLoaderData";
|
|
4510
5654
|
DataRouterStateHook2["UseActionData"] = "useActionData";
|
|
@@ -4516,7 +5660,7 @@ var DataRouterStateHook$1 = /* @__PURE__ */ function(DataRouterStateHook2) {
|
|
|
4516
5660
|
DataRouterStateHook2["UseNavigateStable"] = "useNavigate";
|
|
4517
5661
|
DataRouterStateHook2["UseRouteId"] = "useRouteId";
|
|
4518
5662
|
return DataRouterStateHook2;
|
|
4519
|
-
}(DataRouterStateHook$1 || {});
|
|
5663
|
+
})(DataRouterStateHook$1 || {});
|
|
4520
5664
|
function useDataRouterContext(hookName) {
|
|
4521
5665
|
let ctx = React.useContext(DataRouterContext);
|
|
4522
5666
|
!ctx ? invariant(false) : void 0;
|
|
@@ -4541,8 +5685,8 @@ function useCurrentRouteId(hookName) {
|
|
|
4541
5685
|
function useRouteError() {
|
|
4542
5686
|
var _state$errors;
|
|
4543
5687
|
let error = React.useContext(RouteErrorContext);
|
|
4544
|
-
let state = useDataRouterState();
|
|
4545
|
-
let routeId = useCurrentRouteId();
|
|
5688
|
+
let state = useDataRouterState(DataRouterStateHook$1.UseRouteError);
|
|
5689
|
+
let routeId = useCurrentRouteId(DataRouterStateHook$1.UseRouteError);
|
|
4546
5690
|
if (error !== void 0) {
|
|
4547
5691
|
return error;
|
|
4548
5692
|
}
|
|
@@ -4888,6 +6032,7 @@ const index = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({
|
|
|
4888
6032
|
useInRouterContext,
|
|
4889
6033
|
useLocation,
|
|
4890
6034
|
useNavigate,
|
|
6035
|
+
useParams,
|
|
4891
6036
|
useRouteError,
|
|
4892
6037
|
useRoutes
|
|
4893
6038
|
}, Symbol.toStringTag, { value: 'Module' }));
|
|
@@ -4975,7 +6120,8 @@ const PassflowWrapper = ({
|
|
|
4975
6120
|
signUpPath: routesWithPrefix.signup,
|
|
4976
6121
|
forgotPasswordPath: routesWithPrefix.forgot_password,
|
|
4977
6122
|
verifyMagicLinkPath: routesWithPrefix.verify_magic_link,
|
|
4978
|
-
verifyOTPPath: routesWithPrefix.verify_otp
|
|
6123
|
+
verifyOTPPath: routesWithPrefix.verify_otp,
|
|
6124
|
+
twoFactorVerifyPath: routesWithPrefix.two_factor_verify
|
|
4979
6125
|
}
|
|
4980
6126
|
)
|
|
4981
6127
|
}
|
|
@@ -5043,6 +6189,34 @@ const PassflowWrapper = ({
|
|
|
5043
6189
|
)
|
|
5044
6190
|
}
|
|
5045
6191
|
),
|
|
6192
|
+
/* @__PURE__ */ jsx(
|
|
6193
|
+
Route,
|
|
6194
|
+
{
|
|
6195
|
+
path: routesWithPrefix.two_factor_verify,
|
|
6196
|
+
element: /* @__PURE__ */ jsx(
|
|
6197
|
+
TwoFactorVerifyFlow,
|
|
6198
|
+
{
|
|
6199
|
+
successAuthRedirect,
|
|
6200
|
+
signInPath: routesWithPrefix.signin,
|
|
6201
|
+
twoFactorSetupPath: routesWithPrefix.two_factor_setup
|
|
6202
|
+
}
|
|
6203
|
+
)
|
|
6204
|
+
}
|
|
6205
|
+
),
|
|
6206
|
+
/* @__PURE__ */ jsx(
|
|
6207
|
+
Route,
|
|
6208
|
+
{
|
|
6209
|
+
path: routesWithPrefix.two_factor_setup,
|
|
6210
|
+
element: /* @__PURE__ */ jsx(
|
|
6211
|
+
TwoFactorSetupFlow,
|
|
6212
|
+
{
|
|
6213
|
+
successAuthRedirect,
|
|
6214
|
+
twoFactorVerifyPath: routesWithPrefix.two_factor_verify,
|
|
6215
|
+
signInPath: routesWithPrefix.signin
|
|
6216
|
+
}
|
|
6217
|
+
)
|
|
6218
|
+
}
|
|
6219
|
+
),
|
|
5046
6220
|
/* @__PURE__ */ jsx(
|
|
5047
6221
|
Route,
|
|
5048
6222
|
{
|
|
@@ -5062,15 +6236,116 @@ const PassflowWrapper = ({
|
|
|
5062
6236
|
};
|
|
5063
6237
|
const PassflowFlow = withError(PassflowWrapper, ErrorComponent);
|
|
5064
6238
|
|
|
6239
|
+
const TwoFactorSetupMagicLinkFlow = ({
|
|
6240
|
+
onSuccess,
|
|
6241
|
+
onError,
|
|
6242
|
+
redirectOnSuccess = true,
|
|
6243
|
+
signInPath = routes.signin.path
|
|
6244
|
+
}) => {
|
|
6245
|
+
const { token } = useParams();
|
|
6246
|
+
const { navigate } = useNavigation();
|
|
6247
|
+
const { isLoading, isRetrying, isValidated, error, retry, retryCountdown } = useTwoFactorSetupMagicLink(token || "");
|
|
6248
|
+
useEffect(() => {
|
|
6249
|
+
if (error) {
|
|
6250
|
+
onError?.(error);
|
|
6251
|
+
}
|
|
6252
|
+
}, [error, onError]);
|
|
6253
|
+
const handleSetupComplete = () => {
|
|
6254
|
+
onSuccess?.();
|
|
6255
|
+
if (redirectOnSuccess) {
|
|
6256
|
+
navigate({
|
|
6257
|
+
to: signInPath
|
|
6258
|
+
});
|
|
6259
|
+
}
|
|
6260
|
+
};
|
|
6261
|
+
const handleCancel = () => {
|
|
6262
|
+
navigate({ to: signInPath });
|
|
6263
|
+
};
|
|
6264
|
+
if (isLoading) {
|
|
6265
|
+
return /* @__PURE__ */ jsx(
|
|
6266
|
+
Wrapper,
|
|
6267
|
+
{
|
|
6268
|
+
title: "Validating Magic Link",
|
|
6269
|
+
subtitle: "Please wait while we verify your link...",
|
|
6270
|
+
className: "passflow-2fa-magic-link-loading",
|
|
6271
|
+
children: /* @__PURE__ */ jsxs("div", { className: "passflow-loading-container", role: "status", "aria-live": "polite", "aria-busy": "true", children: [
|
|
6272
|
+
/* @__PURE__ */ jsx(Icon, { id: "logo", type: "general", size: "large", className: "passflow-loading-spinner" }),
|
|
6273
|
+
/* @__PURE__ */ jsx("p", { className: "passflow-loading-text", children: "Validating your magic link..." })
|
|
6274
|
+
] })
|
|
6275
|
+
}
|
|
6276
|
+
);
|
|
6277
|
+
}
|
|
6278
|
+
if (error) {
|
|
6279
|
+
const isRetryable = error.code === "SERVER_ERROR" || error.code === "RATE_LIMITED";
|
|
6280
|
+
const isRateLimited = error.code === "RATE_LIMITED" && retryCountdown !== null && retryCountdown > 0;
|
|
6281
|
+
return /* @__PURE__ */ jsx(
|
|
6282
|
+
Wrapper,
|
|
6283
|
+
{
|
|
6284
|
+
title: "Magic Link Validation Failed",
|
|
6285
|
+
subtitle: getErrorSubtitle(error.code),
|
|
6286
|
+
className: "passflow-2fa-magic-link-error",
|
|
6287
|
+
children: /* @__PURE__ */ jsxs("div", { className: "passflow-error-container", role: "alert", "aria-live": "assertive", children: [
|
|
6288
|
+
/* @__PURE__ */ jsx("div", { className: "passflow-error-icon", children: /* @__PURE__ */ jsx(Icon, { id: "warning", type: "general", size: "large" }) }),
|
|
6289
|
+
/* @__PURE__ */ jsx("p", { className: "passflow-error-message", id: "error-message", children: error.message }),
|
|
6290
|
+
/* @__PURE__ */ jsxs("div", { className: "passflow-error-actions", children: [
|
|
6291
|
+
isRetryable && /* @__PURE__ */ jsx(
|
|
6292
|
+
Button,
|
|
6293
|
+
{
|
|
6294
|
+
size: "big",
|
|
6295
|
+
type: "button",
|
|
6296
|
+
variant: "primary",
|
|
6297
|
+
onClick: retry,
|
|
6298
|
+
disabled: isRateLimited || isRetrying,
|
|
6299
|
+
"aria-describedby": "error-message",
|
|
6300
|
+
children: isRetrying ? "Retrying..." : isRateLimited ? `Try again in ${formatCountdown(retryCountdown)}` : "Try Again"
|
|
6301
|
+
}
|
|
6302
|
+
),
|
|
6303
|
+
/* @__PURE__ */ jsx(Button, { size: "big", type: "button", variant: "secondary", onClick: () => navigate({ to: signInPath }), children: "Back to Sign In" })
|
|
6304
|
+
] }),
|
|
6305
|
+
!isRetryable && /* @__PURE__ */ jsx("p", { className: "passflow-error-help-text", children: "Please contact your administrator to request a new magic link." })
|
|
6306
|
+
] })
|
|
6307
|
+
}
|
|
6308
|
+
);
|
|
6309
|
+
}
|
|
6310
|
+
if (isValidated) {
|
|
6311
|
+
return /* @__PURE__ */ jsx(TwoFactorSetupForm, { onComplete: handleSetupComplete, onCancel: handleCancel });
|
|
6312
|
+
}
|
|
6313
|
+
return null;
|
|
6314
|
+
};
|
|
6315
|
+
function getErrorSubtitle(code) {
|
|
6316
|
+
switch (code) {
|
|
6317
|
+
case "EXPIRED_TOKEN":
|
|
6318
|
+
return "This magic link has expired";
|
|
6319
|
+
case "INVALID_TOKEN":
|
|
6320
|
+
return "This magic link is invalid";
|
|
6321
|
+
case "REVOKED_TOKEN":
|
|
6322
|
+
return "This magic link has been revoked";
|
|
6323
|
+
case "RATE_LIMITED":
|
|
6324
|
+
return "Too many attempts";
|
|
6325
|
+
case "SERVER_ERROR":
|
|
6326
|
+
return "Something went wrong";
|
|
6327
|
+
default:
|
|
6328
|
+
return "An error occurred";
|
|
6329
|
+
}
|
|
6330
|
+
}
|
|
6331
|
+
function formatCountdown(seconds) {
|
|
6332
|
+
if (seconds === null) return "";
|
|
6333
|
+
const mins = Math.floor(seconds / 60);
|
|
6334
|
+
const secs = seconds % 60;
|
|
6335
|
+
return `${mins}:${secs.toString().padStart(2, "0")}`;
|
|
6336
|
+
}
|
|
6337
|
+
|
|
5065
6338
|
const PassflowProvider = ({
|
|
5066
6339
|
children,
|
|
5067
6340
|
navigate: initialNavigate,
|
|
5068
6341
|
router = "default",
|
|
5069
6342
|
...config
|
|
5070
6343
|
}) => {
|
|
6344
|
+
const needsDiscovery = !config.appId;
|
|
5071
6345
|
const [state, dispatch] = useReducer(passflowReducer, {
|
|
5072
6346
|
...initialState,
|
|
5073
|
-
...config
|
|
6347
|
+
...config,
|
|
6348
|
+
isDiscoveringAppId: needsDiscovery
|
|
5074
6349
|
});
|
|
5075
6350
|
const [navigate, setNavigate] = useState(() => {
|
|
5076
6351
|
if (initialNavigate) {
|
|
@@ -5079,6 +6354,52 @@ const PassflowProvider = ({
|
|
|
5079
6354
|
return defaultNavigate;
|
|
5080
6355
|
});
|
|
5081
6356
|
const passflow = useMemo(() => new Passflow(state), [state]);
|
|
6357
|
+
const discoveryAttemptedRef = useRef(false);
|
|
6358
|
+
useEffect(() => {
|
|
6359
|
+
if (needsDiscovery && !discoveryAttemptedRef.current && state.isDiscoveringAppId) {
|
|
6360
|
+
discoveryAttemptedRef.current = true;
|
|
6361
|
+
const discoverAppId = async () => {
|
|
6362
|
+
const urlsToTry = ["/settings"];
|
|
6363
|
+
if (config.url) {
|
|
6364
|
+
urlsToTry.push(`${config.url}/settings`);
|
|
6365
|
+
}
|
|
6366
|
+
for (const url of urlsToTry) {
|
|
6367
|
+
try {
|
|
6368
|
+
const response = await fetch(url);
|
|
6369
|
+
if (response.ok) {
|
|
6370
|
+
const settings = await response.json();
|
|
6371
|
+
const discoveredAppId = settings.login_app?.app_id || settings.appId;
|
|
6372
|
+
if (discoveredAppId) {
|
|
6373
|
+
passflow.setAppId(discoveredAppId);
|
|
6374
|
+
dispatch({
|
|
6375
|
+
type: "SET_PASSFLOW_STATE",
|
|
6376
|
+
payload: {
|
|
6377
|
+
...state,
|
|
6378
|
+
appId: discoveredAppId,
|
|
6379
|
+
scopes: settings.login_app?.scopes || settings.scopes || state.scopes,
|
|
6380
|
+
createTenantForNewUser: settings.login_app?.create_tenant_for_new_user ?? settings.createTenantForNewUser ?? state.createTenantForNewUser,
|
|
6381
|
+
isDiscoveringAppId: false
|
|
6382
|
+
}
|
|
6383
|
+
});
|
|
6384
|
+
return;
|
|
6385
|
+
}
|
|
6386
|
+
}
|
|
6387
|
+
} catch (error) {
|
|
6388
|
+
continue;
|
|
6389
|
+
}
|
|
6390
|
+
}
|
|
6391
|
+
console.warn("Failed to discover appId from /settings");
|
|
6392
|
+
dispatch({
|
|
6393
|
+
type: "SET_PASSFLOW_STATE",
|
|
6394
|
+
payload: {
|
|
6395
|
+
...state,
|
|
6396
|
+
isDiscoveringAppId: false
|
|
6397
|
+
}
|
|
6398
|
+
});
|
|
6399
|
+
};
|
|
6400
|
+
void discoverAppId();
|
|
6401
|
+
}
|
|
6402
|
+
}, [needsDiscovery, state.isDiscoveringAppId, config.url, passflow, state]);
|
|
5082
6403
|
const passflowValue = useMemo(() => ({ state, dispatch, passflow }), [state, passflow]);
|
|
5083
6404
|
const handleSetNavigate = useCallback((newNavigate) => {
|
|
5084
6405
|
setNavigate(() => newNavigate || defaultNavigate);
|
|
@@ -5094,5 +6415,5 @@ const PassflowProvider = ({
|
|
|
5094
6415
|
return /* @__PURE__ */ jsx(PassflowContext.Provider, { value: passflowValue, children: /* @__PURE__ */ jsx(NavigationContext$1.Provider, { value: navigationValue, children: /* @__PURE__ */ jsx(AuthProvider, { children }) }) });
|
|
5095
6416
|
};
|
|
5096
6417
|
|
|
5097
|
-
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, VerifyChallengeMagicLink, VerifyChallengeOTP, Wrapper, useAppSettings, useAuth, useAuthCloudRedirect, useForgotPassword, useJoinInvite, useLogout, useNavigation, useOutsideClick, usePassflow, usePasswordlessComplete, useProvider, useResetPassword, useSignIn, useSignUp, useUserPasskeys };
|
|
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 };
|
|
5098
6419
|
//# sourceMappingURL=index.es.js.map
|