@passflow/react 0.0.1 → 0.2.8
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 +1450 -136
- 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/forgot-password/forgot-password.d.ts.map +1 -1
- 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/verify-challenge-otp-manual.d.ts.map +1 -1
- 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.2.8";
|
|
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,139 @@ 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
|
+
default:
|
|
296
|
+
return error.message;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
const STORAGE_KEY_PREFIX = "passflow_2fa";
|
|
301
|
+
const REDIRECT_COUNT_KEY = `${STORAGE_KEY_PREFIX}_redirect_count`;
|
|
302
|
+
const LAST_ERROR_KEY = `${STORAGE_KEY_PREFIX}_last_error_type`;
|
|
303
|
+
const MAX_REDIRECTS = 3;
|
|
304
|
+
const TwoFactorLoopPrevention = {
|
|
305
|
+
/**
|
|
306
|
+
* Check if redirect should be allowed
|
|
307
|
+
* @returns true if redirect is safe, false if loop detected
|
|
308
|
+
*/
|
|
309
|
+
canRedirect() {
|
|
310
|
+
try {
|
|
311
|
+
const count = this.getRedirectCount();
|
|
312
|
+
return count < MAX_REDIRECTS;
|
|
313
|
+
} catch {
|
|
314
|
+
return true;
|
|
315
|
+
}
|
|
316
|
+
},
|
|
317
|
+
/**
|
|
318
|
+
* Increment redirect counter
|
|
319
|
+
*/
|
|
320
|
+
incrementRedirect() {
|
|
321
|
+
try {
|
|
322
|
+
const count = this.getRedirectCount();
|
|
323
|
+
sessionStorage.setItem(REDIRECT_COUNT_KEY, String(count + 1));
|
|
324
|
+
} catch {
|
|
325
|
+
}
|
|
326
|
+
},
|
|
327
|
+
/**
|
|
328
|
+
* Get current redirect count
|
|
329
|
+
*/
|
|
330
|
+
getRedirectCount() {
|
|
331
|
+
try {
|
|
332
|
+
const value = sessionStorage.getItem(REDIRECT_COUNT_KEY);
|
|
333
|
+
return value ? Number.parseInt(value, 10) : 0;
|
|
334
|
+
} catch {
|
|
335
|
+
return 0;
|
|
336
|
+
}
|
|
337
|
+
},
|
|
338
|
+
/**
|
|
339
|
+
* Reset redirect counter (call on successful auth)
|
|
340
|
+
*/
|
|
341
|
+
reset() {
|
|
342
|
+
try {
|
|
343
|
+
sessionStorage.removeItem(REDIRECT_COUNT_KEY);
|
|
344
|
+
sessionStorage.removeItem(LAST_ERROR_KEY);
|
|
345
|
+
} catch {
|
|
346
|
+
}
|
|
347
|
+
},
|
|
348
|
+
/**
|
|
349
|
+
* Track last error type to detect repeated errors
|
|
350
|
+
*/
|
|
351
|
+
setLastErrorType(errorType) {
|
|
352
|
+
try {
|
|
353
|
+
sessionStorage.setItem(LAST_ERROR_KEY, errorType);
|
|
354
|
+
} catch {
|
|
355
|
+
}
|
|
356
|
+
},
|
|
357
|
+
/**
|
|
358
|
+
* Get last error type
|
|
359
|
+
*/
|
|
360
|
+
getLastErrorType() {
|
|
361
|
+
try {
|
|
362
|
+
return sessionStorage.getItem(LAST_ERROR_KEY);
|
|
363
|
+
} catch {
|
|
364
|
+
return null;
|
|
365
|
+
}
|
|
366
|
+
},
|
|
367
|
+
/**
|
|
368
|
+
* Check if same error is repeating
|
|
369
|
+
*/
|
|
370
|
+
isRepeatingError(errorType) {
|
|
371
|
+
const lastError = this.getLastErrorType();
|
|
372
|
+
return lastError === errorType && this.getRedirectCount() > 0;
|
|
373
|
+
},
|
|
374
|
+
/**
|
|
375
|
+
* Get user-friendly message when loop is detected
|
|
376
|
+
*/
|
|
377
|
+
getLoopDetectedMessage() {
|
|
378
|
+
return "Unable to complete authentication. This may be a configuration issue. Please contact support.";
|
|
379
|
+
}
|
|
380
|
+
};
|
|
381
|
+
|
|
246
382
|
const flags = "data:image/png;base64,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";
|
|
247
383
|
|
|
248
384
|
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 +716,9 @@ const initialState = {
|
|
|
580
716
|
appId: void 0,
|
|
581
717
|
scopes: void 0,
|
|
582
718
|
createTenantForNewUser: void 0,
|
|
583
|
-
parseQueryParams: true
|
|
719
|
+
parseQueryParams: true,
|
|
720
|
+
isDiscoveringAppId: false,
|
|
721
|
+
hasSettingsError: false
|
|
584
722
|
};
|
|
585
723
|
const PassflowContext = createContext(void 0);
|
|
586
724
|
const passflowReducer = (state, action) => {
|
|
@@ -619,6 +757,18 @@ const routes = {
|
|
|
619
757
|
},
|
|
620
758
|
passkey: {
|
|
621
759
|
path: "/passkeysss"
|
|
760
|
+
},
|
|
761
|
+
two_factor_verify: {
|
|
762
|
+
path: "/two-factor-verify"
|
|
763
|
+
},
|
|
764
|
+
two_factor_recovery: {
|
|
765
|
+
path: "/two-factor-recovery"
|
|
766
|
+
},
|
|
767
|
+
two_factor_setup: {
|
|
768
|
+
path: "/two-factor-setup"
|
|
769
|
+
},
|
|
770
|
+
two_factor_setup_magic_link: {
|
|
771
|
+
path: "/two-factor-setup-magic-link/:token"
|
|
622
772
|
}
|
|
623
773
|
};
|
|
624
774
|
|
|
@@ -646,7 +796,7 @@ const AuthProvider = ({ children }) => {
|
|
|
646
796
|
setIsLoading(true);
|
|
647
797
|
try {
|
|
648
798
|
const tokens = await passflow.getTokens(doRefresh);
|
|
649
|
-
const parsedTokens = tokens ? passflow.
|
|
799
|
+
const parsedTokens = tokens ? passflow.getParsedTokens() : void 0;
|
|
650
800
|
return {
|
|
651
801
|
tokens,
|
|
652
802
|
parsedTokens
|
|
@@ -697,17 +847,13 @@ const useSignIn = () => {
|
|
|
697
847
|
else if (type === "passkey") {
|
|
698
848
|
await passflow.passkeyAuthenticate(payload);
|
|
699
849
|
} else {
|
|
700
|
-
|
|
701
|
-
cleanup();
|
|
702
|
-
return passwordlessResponse;
|
|
850
|
+
return await passflow.passwordlessSignIn(payload);
|
|
703
851
|
}
|
|
704
|
-
cleanup();
|
|
705
852
|
return true;
|
|
706
853
|
} catch (e) {
|
|
707
854
|
setIsError(true);
|
|
708
855
|
const error = e;
|
|
709
856
|
setErrorMessage(error.message);
|
|
710
|
-
cleanup();
|
|
711
857
|
return false;
|
|
712
858
|
} finally {
|
|
713
859
|
cleanup();
|
|
@@ -852,48 +998,142 @@ const useAppSettings = () => {
|
|
|
852
998
|
const [isError, setIsError] = useState(false);
|
|
853
999
|
const [errorMessage, setErrorMessage] = useState("");
|
|
854
1000
|
const [isLoading, setIsLoading] = useState(false);
|
|
1001
|
+
const isFetchingRef = useRef(false);
|
|
855
1002
|
if (!context) {
|
|
856
1003
|
throw new Error("useAppSetting must be used within an PassflowProvider");
|
|
857
1004
|
}
|
|
858
1005
|
const { state, dispatch } = context;
|
|
859
1006
|
useLayoutEffect(() => {
|
|
860
|
-
if (!state.appSettings) {
|
|
1007
|
+
if (!state.appSettings && !state.hasSettingsError && !isFetchingRef.current) {
|
|
861
1008
|
const fetchAllSettings = async () => {
|
|
1009
|
+
isFetchingRef.current = true;
|
|
862
1010
|
setIsLoading(true);
|
|
863
1011
|
try {
|
|
1012
|
+
if (!passflow.appId && !state.isDiscoveringAppId) {
|
|
1013
|
+
dispatch({
|
|
1014
|
+
type: "SET_PASSFLOW_STATE",
|
|
1015
|
+
payload: {
|
|
1016
|
+
...state,
|
|
1017
|
+
isDiscoveringAppId: true
|
|
1018
|
+
}
|
|
1019
|
+
});
|
|
1020
|
+
try {
|
|
1021
|
+
const urlsToTry = ["/settings"];
|
|
1022
|
+
if (passflow.url) {
|
|
1023
|
+
urlsToTry.push(`${passflow.url}/settings`);
|
|
1024
|
+
}
|
|
1025
|
+
let response = null;
|
|
1026
|
+
for (const url of urlsToTry) {
|
|
1027
|
+
try {
|
|
1028
|
+
const r = await fetch(url);
|
|
1029
|
+
if (r.ok) {
|
|
1030
|
+
response = r;
|
|
1031
|
+
break;
|
|
1032
|
+
}
|
|
1033
|
+
} catch {
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
if (response?.ok) {
|
|
1037
|
+
const settings = await response.json();
|
|
1038
|
+
const discoveredAppId = settings.login_app?.app_id || settings.appId;
|
|
1039
|
+
if (discoveredAppId) {
|
|
1040
|
+
passflow.setAppId(discoveredAppId);
|
|
1041
|
+
const stateUpdates = {
|
|
1042
|
+
appId: discoveredAppId
|
|
1043
|
+
};
|
|
1044
|
+
if (!state.scopes) {
|
|
1045
|
+
const discoveredScopes = settings.login_app?.scopes || settings.scopes;
|
|
1046
|
+
if (discoveredScopes) {
|
|
1047
|
+
stateUpdates.scopes = discoveredScopes;
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
if (isUndefined(state.createTenantForNewUser)) {
|
|
1051
|
+
const createTenant = settings.login_app?.create_tenant_for_new_user ?? settings.createTenantForNewUser;
|
|
1052
|
+
if (createTenant !== void 0) {
|
|
1053
|
+
stateUpdates.createTenantForNewUser = createTenant;
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
dispatch({
|
|
1057
|
+
type: "SET_PASSFLOW_STATE",
|
|
1058
|
+
payload: {
|
|
1059
|
+
...state,
|
|
1060
|
+
...stateUpdates,
|
|
1061
|
+
isDiscoveringAppId: false
|
|
1062
|
+
}
|
|
1063
|
+
});
|
|
1064
|
+
} else {
|
|
1065
|
+
dispatch({
|
|
1066
|
+
type: "SET_PASSFLOW_STATE",
|
|
1067
|
+
payload: {
|
|
1068
|
+
...state,
|
|
1069
|
+
isDiscoveringAppId: false
|
|
1070
|
+
}
|
|
1071
|
+
});
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
} catch (discoveryError) {
|
|
1075
|
+
console.warn("Failed to discover appId from /settings:", discoveryError);
|
|
1076
|
+
dispatch({
|
|
1077
|
+
type: "SET_PASSFLOW_STATE",
|
|
1078
|
+
payload: {
|
|
1079
|
+
...state,
|
|
1080
|
+
isDiscoveringAppId: false
|
|
1081
|
+
}
|
|
1082
|
+
});
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
864
1085
|
let appSettings = {};
|
|
865
1086
|
if (passflow.appId) {
|
|
866
1087
|
appSettings = await passflow.getAppSettings();
|
|
867
1088
|
}
|
|
868
|
-
|
|
869
|
-
|
|
1089
|
+
const finalStateUpdates = {
|
|
1090
|
+
appSettings
|
|
1091
|
+
};
|
|
1092
|
+
if (!state.scopes) finalStateUpdates.scopes = appSettings.defaults?.scopes;
|
|
1093
|
+
if (isUndefined(state.createTenantForNewUser))
|
|
1094
|
+
finalStateUpdates.createTenantForNewUser = appSettings.defaults?.create_tenant_for_new_user;
|
|
870
1095
|
let passwordPolicy = null;
|
|
871
1096
|
if (appSettings.auth_strategies && hasPasswordStrategy(appSettings.auth_strategies)) {
|
|
872
1097
|
passwordPolicy = await passflow.getPasswordPolicySettings();
|
|
873
1098
|
}
|
|
1099
|
+
finalStateUpdates.passwordPolicy = passwordPolicy;
|
|
874
1100
|
dispatch({
|
|
875
1101
|
type: "SET_PASSFLOW_STATE",
|
|
876
1102
|
payload: {
|
|
877
1103
|
...state,
|
|
878
|
-
|
|
879
|
-
passwordPolicy
|
|
1104
|
+
...finalStateUpdates
|
|
880
1105
|
}
|
|
881
1106
|
});
|
|
882
1107
|
} catch (e) {
|
|
883
1108
|
setIsError(true);
|
|
884
1109
|
const error = e;
|
|
885
1110
|
setErrorMessage(error.message);
|
|
1111
|
+
dispatch({
|
|
1112
|
+
type: "SET_PASSFLOW_STATE",
|
|
1113
|
+
payload: {
|
|
1114
|
+
...state,
|
|
1115
|
+
hasSettingsError: true
|
|
1116
|
+
}
|
|
1117
|
+
});
|
|
886
1118
|
} finally {
|
|
887
1119
|
setIsLoading(false);
|
|
1120
|
+
isFetchingRef.current = false;
|
|
888
1121
|
}
|
|
889
1122
|
};
|
|
890
1123
|
void fetchAllSettings();
|
|
891
1124
|
}
|
|
892
|
-
}, [dispatch, state.appSettings,
|
|
1125
|
+
}, [dispatch, state.appSettings, state.hasSettingsError, state.isDiscoveringAppId, passflow]);
|
|
893
1126
|
const reset = () => {
|
|
894
1127
|
setIsError(false);
|
|
895
1128
|
setErrorMessage("");
|
|
896
1129
|
setIsLoading(false);
|
|
1130
|
+
dispatch({
|
|
1131
|
+
type: "SET_PASSFLOW_STATE",
|
|
1132
|
+
payload: {
|
|
1133
|
+
...state,
|
|
1134
|
+
hasSettingsError: false
|
|
1135
|
+
}
|
|
1136
|
+
});
|
|
897
1137
|
};
|
|
898
1138
|
const applyThemeStyles = (style) => {
|
|
899
1139
|
const root = document.documentElement;
|
|
@@ -1146,7 +1386,7 @@ const useNavigation = () => {
|
|
|
1146
1386
|
setNavigate(null);
|
|
1147
1387
|
return;
|
|
1148
1388
|
}
|
|
1149
|
-
const wrappedNavigate = (options) => {
|
|
1389
|
+
const wrappedNavigate = ((options) => {
|
|
1150
1390
|
const { to, search = "", replace = false } = options;
|
|
1151
1391
|
switch (router) {
|
|
1152
1392
|
case "react-router":
|
|
@@ -1177,7 +1417,7 @@ const useNavigation = () => {
|
|
|
1177
1417
|
}
|
|
1178
1418
|
}
|
|
1179
1419
|
}
|
|
1180
|
-
};
|
|
1420
|
+
});
|
|
1181
1421
|
setNavigate(wrappedNavigate);
|
|
1182
1422
|
},
|
|
1183
1423
|
[setNavigate, router]
|
|
@@ -1189,6 +1429,366 @@ const useNavigation = () => {
|
|
|
1189
1429
|
};
|
|
1190
1430
|
};
|
|
1191
1431
|
|
|
1432
|
+
const useTwoFactorStatus = () => {
|
|
1433
|
+
const passflow = usePassflow();
|
|
1434
|
+
const [data, setData] = useState(null);
|
|
1435
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
1436
|
+
const [isError, setIsError] = useState(false);
|
|
1437
|
+
const [errorMessage, setErrorMessage] = useState("");
|
|
1438
|
+
const fetch = useCallback(async () => {
|
|
1439
|
+
setIsLoading(true);
|
|
1440
|
+
setIsError(false);
|
|
1441
|
+
setErrorMessage("");
|
|
1442
|
+
try {
|
|
1443
|
+
const response = await passflow.getTwoFactorStatus();
|
|
1444
|
+
setData(response);
|
|
1445
|
+
return response;
|
|
1446
|
+
} catch (e) {
|
|
1447
|
+
setIsError(true);
|
|
1448
|
+
const error = e;
|
|
1449
|
+
setErrorMessage(error.message);
|
|
1450
|
+
return null;
|
|
1451
|
+
} finally {
|
|
1452
|
+
setIsLoading(false);
|
|
1453
|
+
}
|
|
1454
|
+
}, [passflow]);
|
|
1455
|
+
useLayoutEffect(() => {
|
|
1456
|
+
void fetch();
|
|
1457
|
+
}, [fetch]);
|
|
1458
|
+
return {
|
|
1459
|
+
data,
|
|
1460
|
+
refetch: fetch,
|
|
1461
|
+
isLoading,
|
|
1462
|
+
isError,
|
|
1463
|
+
errorMessage
|
|
1464
|
+
};
|
|
1465
|
+
};
|
|
1466
|
+
|
|
1467
|
+
const useTwoFactorSetup = () => {
|
|
1468
|
+
const passflow = usePassflow();
|
|
1469
|
+
const [setupData, setSetupData] = useState(null);
|
|
1470
|
+
const [recoveryCodes, setRecoveryCodes] = useState([]);
|
|
1471
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
1472
|
+
const [isError, setIsError] = useState(false);
|
|
1473
|
+
const [error, setError] = useState("");
|
|
1474
|
+
const [step, setStep] = useState("idle");
|
|
1475
|
+
const beginSetup = useCallback(async () => {
|
|
1476
|
+
setIsLoading(true);
|
|
1477
|
+
setIsError(false);
|
|
1478
|
+
setError("");
|
|
1479
|
+
try {
|
|
1480
|
+
const response = await passflow.beginTwoFactorSetup();
|
|
1481
|
+
setSetupData(response);
|
|
1482
|
+
setStep("setup");
|
|
1483
|
+
return response;
|
|
1484
|
+
} catch (e) {
|
|
1485
|
+
setIsError(true);
|
|
1486
|
+
const err = e;
|
|
1487
|
+
setError(err.message);
|
|
1488
|
+
return null;
|
|
1489
|
+
} finally {
|
|
1490
|
+
setIsLoading(false);
|
|
1491
|
+
}
|
|
1492
|
+
}, [passflow]);
|
|
1493
|
+
const confirmSetup = useCallback(
|
|
1494
|
+
async (code) => {
|
|
1495
|
+
setIsLoading(true);
|
|
1496
|
+
setIsError(false);
|
|
1497
|
+
setError("");
|
|
1498
|
+
try {
|
|
1499
|
+
const response = await passflow.confirmTwoFactorSetup(code);
|
|
1500
|
+
setRecoveryCodes(response.recovery_codes);
|
|
1501
|
+
setStep("complete");
|
|
1502
|
+
return response;
|
|
1503
|
+
} catch (e) {
|
|
1504
|
+
setIsError(true);
|
|
1505
|
+
const err = e;
|
|
1506
|
+
setError(err.message);
|
|
1507
|
+
return null;
|
|
1508
|
+
} finally {
|
|
1509
|
+
setIsLoading(false);
|
|
1510
|
+
}
|
|
1511
|
+
},
|
|
1512
|
+
[passflow]
|
|
1513
|
+
);
|
|
1514
|
+
const reset = useCallback(() => {
|
|
1515
|
+
setSetupData(null);
|
|
1516
|
+
setRecoveryCodes([]);
|
|
1517
|
+
setIsError(false);
|
|
1518
|
+
setError("");
|
|
1519
|
+
setStep("idle");
|
|
1520
|
+
}, []);
|
|
1521
|
+
return {
|
|
1522
|
+
setupData,
|
|
1523
|
+
recoveryCodes,
|
|
1524
|
+
step,
|
|
1525
|
+
beginSetup,
|
|
1526
|
+
confirmSetup,
|
|
1527
|
+
reset,
|
|
1528
|
+
isLoading,
|
|
1529
|
+
isError,
|
|
1530
|
+
error
|
|
1531
|
+
};
|
|
1532
|
+
};
|
|
1533
|
+
|
|
1534
|
+
const useTwoFactorVerify = () => {
|
|
1535
|
+
const passflow = usePassflow();
|
|
1536
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
1537
|
+
const [isError, setIsError] = useState(false);
|
|
1538
|
+
const [error, setError] = useState("");
|
|
1539
|
+
const [errorType, setErrorType] = useState(null);
|
|
1540
|
+
const [errorDetails, setErrorDetails] = useState(null);
|
|
1541
|
+
const isVerificationRequired = useCallback(() => {
|
|
1542
|
+
return passflow.isTwoFactorVerificationRequired();
|
|
1543
|
+
}, [passflow]);
|
|
1544
|
+
const handleError = useCallback((e) => {
|
|
1545
|
+
const err = e;
|
|
1546
|
+
const classified = classifyTwoFactorError(err.message);
|
|
1547
|
+
setIsError(true);
|
|
1548
|
+
setError(err.message);
|
|
1549
|
+
setErrorType(classified.type);
|
|
1550
|
+
setErrorDetails(classified);
|
|
1551
|
+
}, []);
|
|
1552
|
+
const verify = useCallback(
|
|
1553
|
+
async (code) => {
|
|
1554
|
+
setIsLoading(true);
|
|
1555
|
+
setIsError(false);
|
|
1556
|
+
setError("");
|
|
1557
|
+
setErrorType(null);
|
|
1558
|
+
setErrorDetails(null);
|
|
1559
|
+
try {
|
|
1560
|
+
const response = await passflow.verifyTwoFactor(code);
|
|
1561
|
+
return response;
|
|
1562
|
+
} catch (e) {
|
|
1563
|
+
handleError(e);
|
|
1564
|
+
return null;
|
|
1565
|
+
} finally {
|
|
1566
|
+
setIsLoading(false);
|
|
1567
|
+
}
|
|
1568
|
+
},
|
|
1569
|
+
[passflow, handleError]
|
|
1570
|
+
);
|
|
1571
|
+
const useRecoveryCode = useCallback(
|
|
1572
|
+
async (code) => {
|
|
1573
|
+
setIsLoading(true);
|
|
1574
|
+
setIsError(false);
|
|
1575
|
+
setError("");
|
|
1576
|
+
setErrorType(null);
|
|
1577
|
+
setErrorDetails(null);
|
|
1578
|
+
try {
|
|
1579
|
+
const response = await passflow.useTwoFactorRecoveryCode(code);
|
|
1580
|
+
return response;
|
|
1581
|
+
} catch (e) {
|
|
1582
|
+
handleError(e);
|
|
1583
|
+
return null;
|
|
1584
|
+
} finally {
|
|
1585
|
+
setIsLoading(false);
|
|
1586
|
+
}
|
|
1587
|
+
},
|
|
1588
|
+
[passflow, handleError]
|
|
1589
|
+
);
|
|
1590
|
+
const reset = useCallback(() => {
|
|
1591
|
+
setIsError(false);
|
|
1592
|
+
setError("");
|
|
1593
|
+
setErrorType(null);
|
|
1594
|
+
setErrorDetails(null);
|
|
1595
|
+
}, []);
|
|
1596
|
+
return {
|
|
1597
|
+
isVerificationRequired,
|
|
1598
|
+
verify,
|
|
1599
|
+
useRecoveryCode,
|
|
1600
|
+
reset,
|
|
1601
|
+
isLoading,
|
|
1602
|
+
isError,
|
|
1603
|
+
error,
|
|
1604
|
+
errorType,
|
|
1605
|
+
errorDetails
|
|
1606
|
+
};
|
|
1607
|
+
};
|
|
1608
|
+
|
|
1609
|
+
const useTwoFactorManage = () => {
|
|
1610
|
+
const passflow = usePassflow();
|
|
1611
|
+
const [recoveryCodes, setRecoveryCodes] = useState([]);
|
|
1612
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
1613
|
+
const [isError, setIsError] = useState(false);
|
|
1614
|
+
const [error, setError] = useState("");
|
|
1615
|
+
const disable = useCallback(
|
|
1616
|
+
async (code) => {
|
|
1617
|
+
setIsLoading(true);
|
|
1618
|
+
setIsError(false);
|
|
1619
|
+
setError("");
|
|
1620
|
+
try {
|
|
1621
|
+
await passflow.disableTwoFactor(code);
|
|
1622
|
+
return true;
|
|
1623
|
+
} catch (e) {
|
|
1624
|
+
setIsError(true);
|
|
1625
|
+
const err = e;
|
|
1626
|
+
setError(err.message);
|
|
1627
|
+
return false;
|
|
1628
|
+
} finally {
|
|
1629
|
+
setIsLoading(false);
|
|
1630
|
+
}
|
|
1631
|
+
},
|
|
1632
|
+
[passflow]
|
|
1633
|
+
);
|
|
1634
|
+
const regenerateCodes = useCallback(
|
|
1635
|
+
async (code) => {
|
|
1636
|
+
setIsLoading(true);
|
|
1637
|
+
setIsError(false);
|
|
1638
|
+
setError("");
|
|
1639
|
+
try {
|
|
1640
|
+
const response = await passflow.regenerateTwoFactorRecoveryCodes(code);
|
|
1641
|
+
setRecoveryCodes(response.recovery_codes);
|
|
1642
|
+
return response;
|
|
1643
|
+
} catch (e) {
|
|
1644
|
+
setIsError(true);
|
|
1645
|
+
const err = e;
|
|
1646
|
+
setError(err.message);
|
|
1647
|
+
return null;
|
|
1648
|
+
} finally {
|
|
1649
|
+
setIsLoading(false);
|
|
1650
|
+
}
|
|
1651
|
+
},
|
|
1652
|
+
[passflow]
|
|
1653
|
+
);
|
|
1654
|
+
const reset = useCallback(() => {
|
|
1655
|
+
setRecoveryCodes([]);
|
|
1656
|
+
setIsError(false);
|
|
1657
|
+
setError("");
|
|
1658
|
+
}, []);
|
|
1659
|
+
return {
|
|
1660
|
+
recoveryCodes,
|
|
1661
|
+
disable,
|
|
1662
|
+
regenerateCodes,
|
|
1663
|
+
reset,
|
|
1664
|
+
isLoading,
|
|
1665
|
+
isError,
|
|
1666
|
+
error
|
|
1667
|
+
};
|
|
1668
|
+
};
|
|
1669
|
+
|
|
1670
|
+
const useTwoFactorSetupMagicLink = (token) => {
|
|
1671
|
+
const passflow = usePassflow();
|
|
1672
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
1673
|
+
const [isRetrying, setIsRetrying] = useState(false);
|
|
1674
|
+
const [isValidated, setIsValidated] = useState(false);
|
|
1675
|
+
const [error, setError] = useState(null);
|
|
1676
|
+
const [sessionToken, setSessionToken] = useState(null);
|
|
1677
|
+
const [userId, setUserId] = useState(null);
|
|
1678
|
+
const [appId, setAppId] = useState(null);
|
|
1679
|
+
const [expiresIn, setExpiresIn] = useState(null);
|
|
1680
|
+
const [retryCountdown, setRetryCountdown] = useState(null);
|
|
1681
|
+
const [validatedToken, setValidatedToken] = useState(null);
|
|
1682
|
+
const shouldClearOnUnmount = useRef(true);
|
|
1683
|
+
const countdownTimerRef = useRef(null);
|
|
1684
|
+
const startCountdown = useCallback((seconds) => {
|
|
1685
|
+
if (countdownTimerRef.current) {
|
|
1686
|
+
clearInterval(countdownTimerRef.current);
|
|
1687
|
+
}
|
|
1688
|
+
setRetryCountdown(seconds);
|
|
1689
|
+
countdownTimerRef.current = setInterval(() => {
|
|
1690
|
+
setRetryCountdown((prev) => {
|
|
1691
|
+
if (prev === null || prev <= 1) {
|
|
1692
|
+
if (countdownTimerRef.current) {
|
|
1693
|
+
clearInterval(countdownTimerRef.current);
|
|
1694
|
+
countdownTimerRef.current = null;
|
|
1695
|
+
}
|
|
1696
|
+
return null;
|
|
1697
|
+
}
|
|
1698
|
+
return prev - 1;
|
|
1699
|
+
});
|
|
1700
|
+
}, 1e3);
|
|
1701
|
+
}, []);
|
|
1702
|
+
const validateToken = useCallback(
|
|
1703
|
+
async (isRetry = false) => {
|
|
1704
|
+
if (!isRetry && validatedToken === token && isValidated) {
|
|
1705
|
+
return;
|
|
1706
|
+
}
|
|
1707
|
+
if (isRetry) {
|
|
1708
|
+
setIsRetrying(true);
|
|
1709
|
+
} else {
|
|
1710
|
+
setIsLoading(true);
|
|
1711
|
+
}
|
|
1712
|
+
setError(null);
|
|
1713
|
+
try {
|
|
1714
|
+
const passflowWithMagicLink = passflow;
|
|
1715
|
+
const response = await passflowWithMagicLink.validateTwoFactorSetupMagicLink(token);
|
|
1716
|
+
if (response.success && response.sessionToken && response.userId) {
|
|
1717
|
+
setSessionToken(response.sessionToken);
|
|
1718
|
+
setUserId(response.userId);
|
|
1719
|
+
setAppId(response.appId ?? null);
|
|
1720
|
+
setExpiresIn(response.expiresIn ?? null);
|
|
1721
|
+
setIsValidated(true);
|
|
1722
|
+
setValidatedToken(token);
|
|
1723
|
+
setRetryCountdown(null);
|
|
1724
|
+
shouldClearOnUnmount.current = false;
|
|
1725
|
+
} else if (response.error) {
|
|
1726
|
+
setError(response.error);
|
|
1727
|
+
setIsValidated(false);
|
|
1728
|
+
shouldClearOnUnmount.current = true;
|
|
1729
|
+
if (response.error.code === "RATE_LIMITED" && response.error.retryAfter) {
|
|
1730
|
+
startCountdown(response.error.retryAfter);
|
|
1731
|
+
}
|
|
1732
|
+
}
|
|
1733
|
+
} catch (err) {
|
|
1734
|
+
setError({
|
|
1735
|
+
code: "SERVER_ERROR",
|
|
1736
|
+
message: err instanceof Error ? err.message : "An unexpected error occurred"
|
|
1737
|
+
});
|
|
1738
|
+
setIsValidated(false);
|
|
1739
|
+
shouldClearOnUnmount.current = true;
|
|
1740
|
+
} finally {
|
|
1741
|
+
setIsLoading(false);
|
|
1742
|
+
setIsRetrying(false);
|
|
1743
|
+
}
|
|
1744
|
+
},
|
|
1745
|
+
[token, validatedToken, isValidated, passflow, startCountdown]
|
|
1746
|
+
);
|
|
1747
|
+
const retry = useCallback(async () => {
|
|
1748
|
+
if (retryCountdown !== null && retryCountdown > 0) {
|
|
1749
|
+
return;
|
|
1750
|
+
}
|
|
1751
|
+
setValidatedToken(null);
|
|
1752
|
+
await validateToken(true);
|
|
1753
|
+
}, [validateToken, retryCountdown]);
|
|
1754
|
+
useEffect(() => {
|
|
1755
|
+
if (!token) {
|
|
1756
|
+
setError({
|
|
1757
|
+
code: "INVALID_TOKEN",
|
|
1758
|
+
message: "No token provided"
|
|
1759
|
+
});
|
|
1760
|
+
setIsLoading(false);
|
|
1761
|
+
return;
|
|
1762
|
+
}
|
|
1763
|
+
if (token !== validatedToken) {
|
|
1764
|
+
validateToken();
|
|
1765
|
+
}
|
|
1766
|
+
}, [token, validatedToken, validateToken]);
|
|
1767
|
+
useEffect(() => {
|
|
1768
|
+
return () => {
|
|
1769
|
+
if (countdownTimerRef.current) {
|
|
1770
|
+
clearInterval(countdownTimerRef.current);
|
|
1771
|
+
}
|
|
1772
|
+
if (shouldClearOnUnmount.current) {
|
|
1773
|
+
const passflowWithSession = passflow;
|
|
1774
|
+
passflowWithSession.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,514 @@ 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) => /* @__PURE__ */ jsx("code", { className: "passflow-2fa-recovery-code", children: recoveryCode }, recoveryCode)) }),
|
|
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
|
+
autoComplete: "off",
|
|
2444
|
+
disabled: isInputDisabled
|
|
2445
|
+
}
|
|
2446
|
+
),
|
|
2447
|
+
renderErrorContent()
|
|
2448
|
+
] }) }),
|
|
2449
|
+
!isInputDisabled && /* @__PURE__ */ jsx(
|
|
2450
|
+
Button,
|
|
2451
|
+
{
|
|
2452
|
+
size: "big",
|
|
2453
|
+
type: "submit",
|
|
2454
|
+
variant: "primary",
|
|
2455
|
+
disabled: code.length < 8 || isLoading,
|
|
2456
|
+
className: "passflow-button-signin",
|
|
2457
|
+
children: isLoading ? "Verifying..." : "Verify"
|
|
2458
|
+
}
|
|
2459
|
+
),
|
|
2460
|
+
onBack && !isInputDisabled && /* @__PURE__ */ jsx("div", { className: "passflow-form-actions", children: /* @__PURE__ */ jsxs("p", { className: "passflow-dont-have-account", children: [
|
|
2461
|
+
"Have your device?",
|
|
2462
|
+
" ",
|
|
2463
|
+
/* @__PURE__ */ jsx(
|
|
2464
|
+
"button",
|
|
2465
|
+
{
|
|
2466
|
+
type: "button",
|
|
2467
|
+
onClick: onBack,
|
|
2468
|
+
className: "passflow-link",
|
|
2469
|
+
style: { background: "none", border: "none", cursor: "pointer" },
|
|
2470
|
+
children: "Use authenticator code"
|
|
2471
|
+
}
|
|
2472
|
+
)
|
|
2473
|
+
] }) })
|
|
2474
|
+
] }) });
|
|
2475
|
+
};
|
|
2476
|
+
|
|
2477
|
+
const TwoFactorVerifyFlow = ({
|
|
2478
|
+
successAuthRedirect,
|
|
2479
|
+
signInPath = routes.signin.path,
|
|
2480
|
+
twoFactorSetupPath = routes.two_factor_setup?.path
|
|
2481
|
+
}) => {
|
|
2482
|
+
const [mode, setMode] = useState("verify");
|
|
2483
|
+
const [shouldBlockRender, setShouldBlockRender] = useState(false);
|
|
2484
|
+
const { navigate } = useNavigation();
|
|
2485
|
+
const passflow = usePassflow();
|
|
2486
|
+
const { errorType } = useTwoFactorVerify();
|
|
2487
|
+
useEffect(() => {
|
|
2488
|
+
if (!passflow.isTwoFactorVerificationRequired()) {
|
|
2489
|
+
if (TwoFactorLoopPrevention.canRedirect()) {
|
|
2490
|
+
TwoFactorLoopPrevention.incrementRedirect();
|
|
2491
|
+
navigate({ to: signInPath });
|
|
2492
|
+
setShouldBlockRender(true);
|
|
2493
|
+
} else {
|
|
2494
|
+
setShouldBlockRender(false);
|
|
2495
|
+
}
|
|
2496
|
+
}
|
|
2497
|
+
}, [passflow, navigate, signInPath]);
|
|
2498
|
+
useEffect(() => {
|
|
2499
|
+
if (errorType === "expired") {
|
|
2500
|
+
if (TwoFactorLoopPrevention.canRedirect()) {
|
|
2501
|
+
TwoFactorLoopPrevention.incrementRedirect();
|
|
2502
|
+
TwoFactorLoopPrevention.setLastErrorType("expired");
|
|
2503
|
+
setTimeout(() => {
|
|
2504
|
+
navigate({ to: signInPath });
|
|
2505
|
+
}, 1500);
|
|
2506
|
+
}
|
|
2507
|
+
} else if (errorType === "not_enabled") {
|
|
2508
|
+
TwoFactorLoopPrevention.setLastErrorType("not_enabled");
|
|
2509
|
+
}
|
|
2510
|
+
}, [errorType, navigate, signInPath]);
|
|
2511
|
+
const handleSuccess = async () => {
|
|
2512
|
+
TwoFactorLoopPrevention.reset();
|
|
2513
|
+
if (successAuthRedirect) {
|
|
2514
|
+
if (!isValidUrl(successAuthRedirect)) {
|
|
2515
|
+
navigate({ to: successAuthRedirect });
|
|
2516
|
+
} else {
|
|
2517
|
+
window.location.href = await getUrlWithTokens(passflow, successAuthRedirect);
|
|
2518
|
+
}
|
|
2519
|
+
}
|
|
2520
|
+
};
|
|
2521
|
+
const handleUseRecovery = () => {
|
|
2522
|
+
setMode("recovery");
|
|
2523
|
+
};
|
|
2524
|
+
const handleBackToVerify = () => {
|
|
2525
|
+
setMode("verify");
|
|
2526
|
+
};
|
|
2527
|
+
if (shouldBlockRender) {
|
|
2528
|
+
return null;
|
|
2529
|
+
}
|
|
2530
|
+
if (!passflow.isTwoFactorVerificationRequired() && TwoFactorLoopPrevention.canRedirect()) {
|
|
2531
|
+
return null;
|
|
2532
|
+
}
|
|
2533
|
+
return /* @__PURE__ */ jsx(Fragment, { children: mode === "verify" ? /* @__PURE__ */ jsx(
|
|
2534
|
+
TwoFactorVerifyForm,
|
|
2535
|
+
{
|
|
2536
|
+
onSuccess: handleSuccess,
|
|
2537
|
+
onUseRecovery: handleUseRecovery,
|
|
2538
|
+
signInPath,
|
|
2539
|
+
twoFactorSetupPath
|
|
2540
|
+
}
|
|
2541
|
+
) : /* @__PURE__ */ jsx(TwoFactorRecoveryForm, { onSuccess: handleSuccess, onBack: handleBackToVerify, signInPath }) });
|
|
1453
2542
|
};
|
|
1454
2543
|
|
|
1455
2544
|
const defaultErrorMessage = "Something went wrong";
|
|
@@ -1479,13 +2568,25 @@ const ErrorComponent = ({ error = defaultErrorMessage, goBackRedirectTo }) => {
|
|
|
1479
2568
|
);
|
|
1480
2569
|
};
|
|
1481
2570
|
|
|
1482
|
-
const excludeRoutes = ["verify-challenge-otp", "password/reset"];
|
|
2571
|
+
const excludeRoutes = ["verify-challenge-otp", "password/reset", "two-factor-verify", "two-factor-recovery"];
|
|
1483
2572
|
const withError = (Component, ErrorComponent) => (props) => {
|
|
1484
2573
|
const context = useContext(PassflowContext);
|
|
1485
2574
|
const { successAuthRedirect } = props;
|
|
1486
2575
|
const { pathname } = window.location;
|
|
1487
|
-
if (
|
|
1488
|
-
|
|
2576
|
+
if (excludeRoutes.some((route) => pathname.includes(route))) {
|
|
2577
|
+
return /* @__PURE__ */ jsx(
|
|
2578
|
+
ErrorBoundary,
|
|
2579
|
+
{
|
|
2580
|
+
FallbackComponent: ({ error }) => /* @__PURE__ */ jsx(ErrorComponent, { goBackRedirectTo: successAuthRedirect ?? "/", error: error.message }),
|
|
2581
|
+
children: /* @__PURE__ */ jsx(Component, { ...props })
|
|
2582
|
+
}
|
|
2583
|
+
);
|
|
2584
|
+
}
|
|
2585
|
+
if (context?.state.isDiscoveringAppId) {
|
|
2586
|
+
return null;
|
|
2587
|
+
}
|
|
2588
|
+
if (!context?.state.appId) {
|
|
2589
|
+
const errorMessage = "Missing appId";
|
|
1489
2590
|
return /* @__PURE__ */ jsx(ErrorComponent, { goBackRedirectTo: successAuthRedirect ?? "/", error: errorMessage });
|
|
1490
2591
|
}
|
|
1491
2592
|
return /* @__PURE__ */ jsx(
|
|
@@ -1508,7 +2609,8 @@ const SignInForm = ({
|
|
|
1508
2609
|
signUpPath = routes.signup.path,
|
|
1509
2610
|
verifyOTPPath = routes.verify_otp.path,
|
|
1510
2611
|
verifyMagicLinkPath = routes.verify_magic_link.path,
|
|
1511
|
-
forgotPasswordPath = routes.forgot_password.path
|
|
2612
|
+
forgotPasswordPath = routes.forgot_password.path,
|
|
2613
|
+
twoFactorVerifyPath = routes.two_factor_verify.path
|
|
1512
2614
|
}) => {
|
|
1513
2615
|
const {
|
|
1514
2616
|
getValues,
|
|
@@ -1523,7 +2625,16 @@ const SignInForm = ({
|
|
|
1523
2625
|
});
|
|
1524
2626
|
const passflow = usePassflow();
|
|
1525
2627
|
const { navigate } = useNavigation();
|
|
1526
|
-
const {
|
|
2628
|
+
const {
|
|
2629
|
+
appSettings,
|
|
2630
|
+
scopes,
|
|
2631
|
+
createTenantForNewUser,
|
|
2632
|
+
passwordPolicy,
|
|
2633
|
+
currentStyles,
|
|
2634
|
+
isError: isErrorApp,
|
|
2635
|
+
error: errorApp,
|
|
2636
|
+
loginAppTheme
|
|
2637
|
+
} = useAppSettings();
|
|
1527
2638
|
const { federatedWithRedirect } = useProvider(successAuthRedirect, createTenantForNewUser);
|
|
1528
2639
|
if (isErrorApp) throw new Error(errorApp);
|
|
1529
2640
|
const authMethods = useMemo(() => getAuthMethods(appSettings?.auth_strategies), [appSettings]);
|
|
@@ -1576,8 +2687,13 @@ const SignInForm = ({
|
|
|
1576
2687
|
};
|
|
1577
2688
|
const status = await fetch(payload, "password");
|
|
1578
2689
|
if (status) {
|
|
1579
|
-
if (
|
|
1580
|
-
|
|
2690
|
+
if (passflow.isTwoFactorVerificationRequired()) {
|
|
2691
|
+
navigate({ to: twoFactorVerifyPath ?? routes.two_factor_verify.path });
|
|
2692
|
+
return;
|
|
2693
|
+
}
|
|
2694
|
+
const redirectUrl = successAuthRedirect ?? appSettings?.defaults.redirect ?? "";
|
|
2695
|
+
if (!isValidUrl(redirectUrl)) navigate({ to: redirectUrl });
|
|
2696
|
+
else window.location.href = await getUrlWithTokens(passflow, redirectUrl);
|
|
1581
2697
|
}
|
|
1582
2698
|
};
|
|
1583
2699
|
const onSubmitPasskeyHandler = async (passkeyPayload) => {
|
|
@@ -1587,8 +2703,13 @@ const SignInForm = ({
|
|
|
1587
2703
|
};
|
|
1588
2704
|
const response = await fetch(payload, "passkey");
|
|
1589
2705
|
if (response) {
|
|
1590
|
-
if (
|
|
1591
|
-
|
|
2706
|
+
if (passflow.isTwoFactorVerificationRequired()) {
|
|
2707
|
+
navigate({ to: twoFactorVerifyPath ?? routes.two_factor_verify.path });
|
|
2708
|
+
return;
|
|
2709
|
+
}
|
|
2710
|
+
const redirectUrl = successAuthRedirect ?? appSettings?.defaults.redirect ?? "";
|
|
2711
|
+
if (!isValidUrl(redirectUrl)) navigate({ to: redirectUrl });
|
|
2712
|
+
else window.location.href = await getUrlWithTokens(passflow, redirectUrl);
|
|
1592
2713
|
}
|
|
1593
2714
|
};
|
|
1594
2715
|
const onSubmitPasswordlessHandler = async (userPayload) => {
|
|
@@ -1597,8 +2718,7 @@ const SignInForm = ({
|
|
|
1597
2718
|
...userPayload,
|
|
1598
2719
|
challenge_type: getPasswordlessData(authMethods, defaultMethod)?.challengeType,
|
|
1599
2720
|
create_tenant: createTenantForNewUser,
|
|
1600
|
-
|
|
1601
|
-
redirect_url: successAuthRedirect ?? appSettings.defaults.redirect,
|
|
2721
|
+
redirect_url: successAuthRedirect ?? appSettings?.defaults.redirect,
|
|
1602
2722
|
...!isEmpty(inviteToken) && { invite_token: inviteToken }
|
|
1603
2723
|
};
|
|
1604
2724
|
const response = await fetch(payload, "passwordless");
|
|
@@ -1966,7 +3086,16 @@ const SignUpForm = ({
|
|
|
1966
3086
|
});
|
|
1967
3087
|
const passflow = usePassflow();
|
|
1968
3088
|
const { navigate } = useNavigation();
|
|
1969
|
-
const {
|
|
3089
|
+
const {
|
|
3090
|
+
appSettings,
|
|
3091
|
+
scopes,
|
|
3092
|
+
createTenantForNewUser,
|
|
3093
|
+
passwordPolicy,
|
|
3094
|
+
currentStyles,
|
|
3095
|
+
isError: isErrorApp,
|
|
3096
|
+
error: errorApp,
|
|
3097
|
+
loginAppTheme
|
|
3098
|
+
} = useAppSettings();
|
|
1970
3099
|
if (isErrorApp) throw new Error(errorApp);
|
|
1971
3100
|
const { federatedWithRedirect } = useProvider(successAuthRedirect, createTenantForNewUser);
|
|
1972
3101
|
const authMethods = useMemo(() => getAuthMethods(appSettings?.auth_strategies), [appSettings]);
|
|
@@ -2020,23 +3149,24 @@ const SignUpForm = ({
|
|
|
2020
3149
|
};
|
|
2021
3150
|
const status = await fetch(payload, "password");
|
|
2022
3151
|
if (status) {
|
|
2023
|
-
|
|
2024
|
-
|
|
3152
|
+
const redirectUrl = successAuthRedirect ?? appSettings?.defaults.redirect ?? "";
|
|
3153
|
+
if (!isValidUrl(redirectUrl)) navigate({ to: redirectUrl });
|
|
3154
|
+
else window.location.href = await getUrlWithTokens(passflow, redirectUrl);
|
|
2025
3155
|
}
|
|
2026
3156
|
};
|
|
2027
3157
|
const onSubmitPasskeyHandler = async () => {
|
|
3158
|
+
const redirectUrl = successAuthRedirect ?? appSettings?.defaults.redirect ?? "";
|
|
2028
3159
|
const payload = {
|
|
2029
3160
|
relying_party_id: relyingPartyId,
|
|
2030
3161
|
create_tenant: createTenantForNewUser,
|
|
2031
|
-
|
|
2032
|
-
redirect_url: successAuthRedirect ?? appSettings.defaults.redirect,
|
|
3162
|
+
redirect_url: redirectUrl,
|
|
2033
3163
|
...!isEmpty(inviteToken) && { invite_token: inviteToken },
|
|
2034
3164
|
scopes
|
|
2035
3165
|
};
|
|
2036
3166
|
const response = await fetch(payload, "passkey");
|
|
2037
3167
|
if (response) {
|
|
2038
|
-
if (!isValidUrl(
|
|
2039
|
-
else window.location.href = await getUrlWithTokens(passflow,
|
|
3168
|
+
if (!isValidUrl(redirectUrl)) navigate({ to: redirectUrl });
|
|
3169
|
+
else window.location.href = await getUrlWithTokens(passflow, redirectUrl);
|
|
2040
3170
|
}
|
|
2041
3171
|
};
|
|
2042
3172
|
const onSubmitPasswordlessHandler = async (userPayload) => {
|
|
@@ -2045,8 +3175,7 @@ const SignUpForm = ({
|
|
|
2045
3175
|
...userPayload,
|
|
2046
3176
|
challenge_type: currentChallegeType,
|
|
2047
3177
|
create_tenant: createTenantForNewUser,
|
|
2048
|
-
|
|
2049
|
-
redirect_url: successAuthRedirect ?? appSettings.defaults.redirect,
|
|
3178
|
+
redirect_url: successAuthRedirect ?? appSettings?.defaults.redirect,
|
|
2050
3179
|
...!isEmpty(inviteToken) && { invite_token: inviteToken }
|
|
2051
3180
|
};
|
|
2052
3181
|
const response = await fetch(payload, "passwordless");
|
|
@@ -2443,66 +3572,6 @@ const VerifyChallengeMagicLink = () => {
|
|
|
2443
3572
|
);
|
|
2444
3573
|
};
|
|
2445
3574
|
|
|
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
3575
|
const TimerButton = ({ totalSecond, onClick, className = "", ...props }) => {
|
|
2507
3576
|
const [seconds, setSeconds] = useState(totalSecond);
|
|
2508
3577
|
useEffect(() => {
|
|
@@ -2544,6 +3613,24 @@ const TimerButton = ({ totalSecond, onClick, className = "", ...props }) => {
|
|
|
2544
3613
|
);
|
|
2545
3614
|
};
|
|
2546
3615
|
|
|
3616
|
+
const VerifyChallengeSuccess = () => {
|
|
3617
|
+
const { currentStyles, loginAppTheme } = useAppSettings();
|
|
3618
|
+
return /* @__PURE__ */ jsx(
|
|
3619
|
+
Wrapper,
|
|
3620
|
+
{
|
|
3621
|
+
iconId: "logo",
|
|
3622
|
+
className: "passflow-verify-challenge-success-wrapper",
|
|
3623
|
+
customCss: currentStyles?.custom_css,
|
|
3624
|
+
customLogo: currentStyles?.logo_url,
|
|
3625
|
+
removeBranding: loginAppTheme?.remove_passflow_logo,
|
|
3626
|
+
children: /* @__PURE__ */ jsx("div", { className: "passflow-verify-challenge-success-wrapper", children: /* @__PURE__ */ jsxs("div", { className: "passflow-verify-challenge-success-container", children: [
|
|
3627
|
+
/* @__PURE__ */ jsx("p", { className: "passflow-verify-challenge-success-text", children: "Successful verification!" }),
|
|
3628
|
+
/* @__PURE__ */ jsx("p", { className: "passflow-verify-challenge-success-text-secondary", children: "But there is no redirect URL for further redirect." })
|
|
3629
|
+
] }) })
|
|
3630
|
+
}
|
|
3631
|
+
);
|
|
3632
|
+
};
|
|
3633
|
+
|
|
2547
3634
|
const challengeTypeFullString = {
|
|
2548
3635
|
email: "email address",
|
|
2549
3636
|
phone: "phone number"
|
|
@@ -2574,8 +3661,7 @@ const VerifyChallengeOTPManual = ({
|
|
|
2574
3661
|
const payload = {
|
|
2575
3662
|
create_tenant: createTenantForNewUser,
|
|
2576
3663
|
challenge_type: challengeType,
|
|
2577
|
-
|
|
2578
|
-
redirect_url: successAuthRedirect ?? appSettings.defaults.redirect,
|
|
3664
|
+
redirect_url: successAuthRedirect ?? appSettings?.defaults.redirect ?? "",
|
|
2579
3665
|
...identity === "email" ? { email: identityValue } : { phone: identityValue }
|
|
2580
3666
|
};
|
|
2581
3667
|
const refetchResponse = await refetch(payload, type);
|
|
@@ -2667,6 +3753,48 @@ const VerifyChallengeOTPManual = ({
|
|
|
2667
3753
|
);
|
|
2668
3754
|
};
|
|
2669
3755
|
|
|
3756
|
+
const VerifyChallengeOTPRedirect = ({ otp, challengeId, appId }) => {
|
|
3757
|
+
const passflow = usePassflow();
|
|
3758
|
+
const { navigate } = useNavigation();
|
|
3759
|
+
const [paramsError, setParamsError] = useState(null);
|
|
3760
|
+
const [showSuccessMessage, setShowSuccessMessage] = useState(false);
|
|
3761
|
+
const { fetch, isError, error, isLoading } = usePasswordlessComplete();
|
|
3762
|
+
useEffect(() => {
|
|
3763
|
+
const fetchData = async () => {
|
|
3764
|
+
if (!appId) {
|
|
3765
|
+
setParamsError("Missing required param: app_id");
|
|
3766
|
+
return;
|
|
3767
|
+
}
|
|
3768
|
+
if (!otp) {
|
|
3769
|
+
setParamsError("Missing required param: otp");
|
|
3770
|
+
return;
|
|
3771
|
+
}
|
|
3772
|
+
if (!challengeId) {
|
|
3773
|
+
setParamsError("Missing required param: challenge_id");
|
|
3774
|
+
return;
|
|
3775
|
+
}
|
|
3776
|
+
if (!isLoading) {
|
|
3777
|
+
const response = await fetch({ otp, challenge_id: challengeId });
|
|
3778
|
+
if (response) {
|
|
3779
|
+
if (response.redirect_url) {
|
|
3780
|
+
if (!isValidUrl(response.redirect_url)) navigate({ to: response.redirect_url });
|
|
3781
|
+
else window.location.href = await getUrlWithTokens(passflow, response.redirect_url);
|
|
3782
|
+
} else {
|
|
3783
|
+
setShowSuccessMessage(true);
|
|
3784
|
+
}
|
|
3785
|
+
} else {
|
|
3786
|
+
setParamsError("Something went wrong. Please try again later.");
|
|
3787
|
+
}
|
|
3788
|
+
}
|
|
3789
|
+
};
|
|
3790
|
+
void fetchData();
|
|
3791
|
+
}, []);
|
|
3792
|
+
if (isError && error) throw new Error(error);
|
|
3793
|
+
if (paramsError) throw new Error(paramsError);
|
|
3794
|
+
if (showSuccessMessage) return /* @__PURE__ */ jsx(VerifyChallengeSuccess, {});
|
|
3795
|
+
return null;
|
|
3796
|
+
};
|
|
3797
|
+
|
|
2670
3798
|
const redirectSearchParamsVerifyChallengeOtpSchema = Yup.object().shape({
|
|
2671
3799
|
appId: Yup.string().required(),
|
|
2672
3800
|
challengeId: Yup.string().required(),
|
|
@@ -2786,8 +3914,7 @@ const ForgotPassword = ({
|
|
|
2786
3914
|
...isEmail && { email: values.email_or_username },
|
|
2787
3915
|
...isUsername && { username: values.email_or_username },
|
|
2788
3916
|
...isPhone && { phone: validatedPhone.phoneNumber },
|
|
2789
|
-
|
|
2790
|
-
redirect_url: successResetRedirect ?? appSettings.defaults.redirect
|
|
3917
|
+
redirect_url: successResetRedirect ?? appSettings?.defaults.redirect
|
|
2791
3918
|
};
|
|
2792
3919
|
const status = await fetch(payload);
|
|
2793
3920
|
if (status) {
|
|
@@ -3005,9 +4132,13 @@ const ResetPassword = ({ successAuthRedirect }) => {
|
|
|
3005
4132
|
const resetTokenType = resetTokenData;
|
|
3006
4133
|
const status = await fetch(values.password);
|
|
3007
4134
|
if (status) {
|
|
3008
|
-
if (!isValidUrl(resetTokenType?.redirect_url ?? successAuthRedirect ?? appSettings
|
|
3009
|
-
navigate({ to: resetTokenType?.redirect_url ?? successAuthRedirect ?? appSettings
|
|
3010
|
-
else
|
|
4135
|
+
if (!isValidUrl(resetTokenType?.redirect_url ?? successAuthRedirect ?? appSettings?.defaults.redirect))
|
|
4136
|
+
navigate({ to: resetTokenType?.redirect_url ?? successAuthRedirect ?? appSettings?.defaults.redirect });
|
|
4137
|
+
else
|
|
4138
|
+
window.location.href = await getUrlWithTokens(
|
|
4139
|
+
passflow,
|
|
4140
|
+
resetTokenType?.redirect_url ?? successAuthRedirect ?? appSettings?.defaults.redirect
|
|
4141
|
+
);
|
|
3011
4142
|
}
|
|
3012
4143
|
};
|
|
3013
4144
|
const handlePasswordChange = async () => {
|
|
@@ -3140,7 +4271,7 @@ const InvitationJoinFlow = ({
|
|
|
3140
4271
|
tenant_name: tenantName,
|
|
3141
4272
|
redirect_url: redirectUrl
|
|
3142
4273
|
} = invitationTokenData;
|
|
3143
|
-
const parsedTokenCache = passflow.
|
|
4274
|
+
const parsedTokenCache = passflow.getParsedTokens();
|
|
3144
4275
|
const onClickNavigateToSignInHandler = () => navigate({ to: signInPath, search: window.location.search });
|
|
3145
4276
|
const onClickNavigateToSignUpHandler = () => navigate({ to: signUpPath, search: window.location.search });
|
|
3146
4277
|
if (!parsedTokenCache?.access_token) onClickNavigateToSignInHandler();
|
|
@@ -3169,7 +4300,7 @@ const InvitationJoinFlow = ({
|
|
|
3169
4300
|
type: "button",
|
|
3170
4301
|
variant: "primary",
|
|
3171
4302
|
className: "passflow-button-invitation-join",
|
|
3172
|
-
onClick: () => void onClickAcceptInvitationHandler(redirectUrl ?? successAuthRedirect ?? appSettings
|
|
4303
|
+
onClick: () => void onClickAcceptInvitationHandler(redirectUrl ?? successAuthRedirect ?? appSettings?.defaults.redirect),
|
|
3173
4304
|
disabled: isInvitationJoinLoading,
|
|
3174
4305
|
children: "Accept invitation"
|
|
3175
4306
|
}
|
|
@@ -4245,6 +5376,13 @@ function useNavigateUnstable() {
|
|
|
4245
5376
|
}, [basename, navigator, routePathnamesJson, locationPathname, dataRouterContext]);
|
|
4246
5377
|
return navigate;
|
|
4247
5378
|
}
|
|
5379
|
+
function useParams() {
|
|
5380
|
+
let {
|
|
5381
|
+
matches
|
|
5382
|
+
} = React.useContext(RouteContext);
|
|
5383
|
+
let routeMatch = matches[matches.length - 1];
|
|
5384
|
+
return routeMatch ? routeMatch.params : {};
|
|
5385
|
+
}
|
|
4248
5386
|
function useRoutes(routes, locationArg) {
|
|
4249
5387
|
return useRoutesImpl(routes, locationArg);
|
|
4250
5388
|
}
|
|
@@ -4498,13 +5636,13 @@ function _renderMatches(matches, parentMatches, dataRouterState, future) {
|
|
|
4498
5636
|
}) : getChildren();
|
|
4499
5637
|
}, null);
|
|
4500
5638
|
}
|
|
4501
|
-
var DataRouterHook$1 = /* @__PURE__ */ function(DataRouterHook2) {
|
|
5639
|
+
var DataRouterHook$1 = /* @__PURE__ */ (function(DataRouterHook2) {
|
|
4502
5640
|
DataRouterHook2["UseBlocker"] = "useBlocker";
|
|
4503
5641
|
DataRouterHook2["UseRevalidator"] = "useRevalidator";
|
|
4504
5642
|
DataRouterHook2["UseNavigateStable"] = "useNavigate";
|
|
4505
5643
|
return DataRouterHook2;
|
|
4506
|
-
}(DataRouterHook$1 || {});
|
|
4507
|
-
var DataRouterStateHook$1 = /* @__PURE__ */ function(DataRouterStateHook2) {
|
|
5644
|
+
})(DataRouterHook$1 || {});
|
|
5645
|
+
var DataRouterStateHook$1 = /* @__PURE__ */ (function(DataRouterStateHook2) {
|
|
4508
5646
|
DataRouterStateHook2["UseBlocker"] = "useBlocker";
|
|
4509
5647
|
DataRouterStateHook2["UseLoaderData"] = "useLoaderData";
|
|
4510
5648
|
DataRouterStateHook2["UseActionData"] = "useActionData";
|
|
@@ -4516,7 +5654,7 @@ var DataRouterStateHook$1 = /* @__PURE__ */ function(DataRouterStateHook2) {
|
|
|
4516
5654
|
DataRouterStateHook2["UseNavigateStable"] = "useNavigate";
|
|
4517
5655
|
DataRouterStateHook2["UseRouteId"] = "useRouteId";
|
|
4518
5656
|
return DataRouterStateHook2;
|
|
4519
|
-
}(DataRouterStateHook$1 || {});
|
|
5657
|
+
})(DataRouterStateHook$1 || {});
|
|
4520
5658
|
function useDataRouterContext(hookName) {
|
|
4521
5659
|
let ctx = React.useContext(DataRouterContext);
|
|
4522
5660
|
!ctx ? invariant(false) : void 0;
|
|
@@ -4541,8 +5679,8 @@ function useCurrentRouteId(hookName) {
|
|
|
4541
5679
|
function useRouteError() {
|
|
4542
5680
|
var _state$errors;
|
|
4543
5681
|
let error = React.useContext(RouteErrorContext);
|
|
4544
|
-
let state = useDataRouterState();
|
|
4545
|
-
let routeId = useCurrentRouteId();
|
|
5682
|
+
let state = useDataRouterState(DataRouterStateHook$1.UseRouteError);
|
|
5683
|
+
let routeId = useCurrentRouteId(DataRouterStateHook$1.UseRouteError);
|
|
4546
5684
|
if (error !== void 0) {
|
|
4547
5685
|
return error;
|
|
4548
5686
|
}
|
|
@@ -4888,6 +6026,7 @@ const index = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({
|
|
|
4888
6026
|
useInRouterContext,
|
|
4889
6027
|
useLocation,
|
|
4890
6028
|
useNavigate,
|
|
6029
|
+
useParams,
|
|
4891
6030
|
useRouteError,
|
|
4892
6031
|
useRoutes
|
|
4893
6032
|
}, Symbol.toStringTag, { value: 'Module' }));
|
|
@@ -4975,7 +6114,8 @@ const PassflowWrapper = ({
|
|
|
4975
6114
|
signUpPath: routesWithPrefix.signup,
|
|
4976
6115
|
forgotPasswordPath: routesWithPrefix.forgot_password,
|
|
4977
6116
|
verifyMagicLinkPath: routesWithPrefix.verify_magic_link,
|
|
4978
|
-
verifyOTPPath: routesWithPrefix.verify_otp
|
|
6117
|
+
verifyOTPPath: routesWithPrefix.verify_otp,
|
|
6118
|
+
twoFactorVerifyPath: routesWithPrefix.two_factor_verify
|
|
4979
6119
|
}
|
|
4980
6120
|
)
|
|
4981
6121
|
}
|
|
@@ -5043,6 +6183,34 @@ const PassflowWrapper = ({
|
|
|
5043
6183
|
)
|
|
5044
6184
|
}
|
|
5045
6185
|
),
|
|
6186
|
+
/* @__PURE__ */ jsx(
|
|
6187
|
+
Route,
|
|
6188
|
+
{
|
|
6189
|
+
path: routesWithPrefix.two_factor_verify,
|
|
6190
|
+
element: /* @__PURE__ */ jsx(
|
|
6191
|
+
TwoFactorVerifyFlow,
|
|
6192
|
+
{
|
|
6193
|
+
successAuthRedirect,
|
|
6194
|
+
signInPath: routesWithPrefix.signin,
|
|
6195
|
+
twoFactorSetupPath: routesWithPrefix.two_factor_setup
|
|
6196
|
+
}
|
|
6197
|
+
)
|
|
6198
|
+
}
|
|
6199
|
+
),
|
|
6200
|
+
/* @__PURE__ */ jsx(
|
|
6201
|
+
Route,
|
|
6202
|
+
{
|
|
6203
|
+
path: routesWithPrefix.two_factor_setup,
|
|
6204
|
+
element: /* @__PURE__ */ jsx(
|
|
6205
|
+
TwoFactorSetupFlow,
|
|
6206
|
+
{
|
|
6207
|
+
successAuthRedirect,
|
|
6208
|
+
twoFactorVerifyPath: routesWithPrefix.two_factor_verify,
|
|
6209
|
+
signInPath: routesWithPrefix.signin
|
|
6210
|
+
}
|
|
6211
|
+
)
|
|
6212
|
+
}
|
|
6213
|
+
),
|
|
5046
6214
|
/* @__PURE__ */ jsx(
|
|
5047
6215
|
Route,
|
|
5048
6216
|
{
|
|
@@ -5062,15 +6230,116 @@ const PassflowWrapper = ({
|
|
|
5062
6230
|
};
|
|
5063
6231
|
const PassflowFlow = withError(PassflowWrapper, ErrorComponent);
|
|
5064
6232
|
|
|
6233
|
+
const TwoFactorSetupMagicLinkFlow = ({
|
|
6234
|
+
onSuccess,
|
|
6235
|
+
onError,
|
|
6236
|
+
redirectOnSuccess = true,
|
|
6237
|
+
signInPath = routes.signin.path
|
|
6238
|
+
}) => {
|
|
6239
|
+
const { token } = useParams();
|
|
6240
|
+
const { navigate } = useNavigation();
|
|
6241
|
+
const { isLoading, isRetrying, isValidated, error, retry, retryCountdown } = useTwoFactorSetupMagicLink(token || "");
|
|
6242
|
+
useEffect(() => {
|
|
6243
|
+
if (error) {
|
|
6244
|
+
onError?.(error);
|
|
6245
|
+
}
|
|
6246
|
+
}, [error, onError]);
|
|
6247
|
+
const handleSetupComplete = () => {
|
|
6248
|
+
onSuccess?.();
|
|
6249
|
+
if (redirectOnSuccess) {
|
|
6250
|
+
navigate({
|
|
6251
|
+
to: signInPath
|
|
6252
|
+
});
|
|
6253
|
+
}
|
|
6254
|
+
};
|
|
6255
|
+
const handleCancel = () => {
|
|
6256
|
+
navigate({ to: signInPath });
|
|
6257
|
+
};
|
|
6258
|
+
if (isLoading) {
|
|
6259
|
+
return /* @__PURE__ */ jsx(
|
|
6260
|
+
Wrapper,
|
|
6261
|
+
{
|
|
6262
|
+
title: "Validating Magic Link",
|
|
6263
|
+
subtitle: "Please wait while we verify your link...",
|
|
6264
|
+
className: "passflow-2fa-magic-link-loading",
|
|
6265
|
+
children: /* @__PURE__ */ jsxs("div", { className: "passflow-loading-container", role: "status", "aria-live": "polite", "aria-busy": "true", children: [
|
|
6266
|
+
/* @__PURE__ */ jsx(Icon, { id: "logo", type: "general", size: "large", className: "passflow-loading-spinner" }),
|
|
6267
|
+
/* @__PURE__ */ jsx("p", { className: "passflow-loading-text", children: "Validating your magic link..." })
|
|
6268
|
+
] })
|
|
6269
|
+
}
|
|
6270
|
+
);
|
|
6271
|
+
}
|
|
6272
|
+
if (error) {
|
|
6273
|
+
const isRetryable = error.code === "SERVER_ERROR" || error.code === "RATE_LIMITED";
|
|
6274
|
+
const isRateLimited = error.code === "RATE_LIMITED" && retryCountdown !== null && retryCountdown > 0;
|
|
6275
|
+
return /* @__PURE__ */ jsx(
|
|
6276
|
+
Wrapper,
|
|
6277
|
+
{
|
|
6278
|
+
title: "Magic Link Validation Failed",
|
|
6279
|
+
subtitle: getErrorSubtitle(error.code),
|
|
6280
|
+
className: "passflow-2fa-magic-link-error",
|
|
6281
|
+
children: /* @__PURE__ */ jsxs("div", { className: "passflow-error-container", role: "alert", "aria-live": "assertive", children: [
|
|
6282
|
+
/* @__PURE__ */ jsx("div", { className: "passflow-error-icon", children: /* @__PURE__ */ jsx(Icon, { id: "warning", type: "general", size: "large" }) }),
|
|
6283
|
+
/* @__PURE__ */ jsx("p", { className: "passflow-error-message", id: "error-message", children: error.message }),
|
|
6284
|
+
/* @__PURE__ */ jsxs("div", { className: "passflow-error-actions", children: [
|
|
6285
|
+
isRetryable && /* @__PURE__ */ jsx(
|
|
6286
|
+
Button,
|
|
6287
|
+
{
|
|
6288
|
+
size: "big",
|
|
6289
|
+
type: "button",
|
|
6290
|
+
variant: "primary",
|
|
6291
|
+
onClick: retry,
|
|
6292
|
+
disabled: isRateLimited || isRetrying,
|
|
6293
|
+
"aria-describedby": "error-message",
|
|
6294
|
+
children: isRetrying ? "Retrying..." : isRateLimited ? `Try again in ${formatCountdown(retryCountdown)}` : "Try Again"
|
|
6295
|
+
}
|
|
6296
|
+
),
|
|
6297
|
+
/* @__PURE__ */ jsx(Button, { size: "big", type: "button", variant: "secondary", onClick: () => navigate({ to: signInPath }), children: "Back to Sign In" })
|
|
6298
|
+
] }),
|
|
6299
|
+
!isRetryable && /* @__PURE__ */ jsx("p", { className: "passflow-error-help-text", children: "Please contact your administrator to request a new magic link." })
|
|
6300
|
+
] })
|
|
6301
|
+
}
|
|
6302
|
+
);
|
|
6303
|
+
}
|
|
6304
|
+
if (isValidated) {
|
|
6305
|
+
return /* @__PURE__ */ jsx(TwoFactorSetupForm, { onComplete: handleSetupComplete, onCancel: handleCancel });
|
|
6306
|
+
}
|
|
6307
|
+
return null;
|
|
6308
|
+
};
|
|
6309
|
+
function getErrorSubtitle(code) {
|
|
6310
|
+
switch (code) {
|
|
6311
|
+
case "EXPIRED_TOKEN":
|
|
6312
|
+
return "This magic link has expired";
|
|
6313
|
+
case "INVALID_TOKEN":
|
|
6314
|
+
return "This magic link is invalid";
|
|
6315
|
+
case "REVOKED_TOKEN":
|
|
6316
|
+
return "This magic link has been revoked";
|
|
6317
|
+
case "RATE_LIMITED":
|
|
6318
|
+
return "Too many attempts";
|
|
6319
|
+
case "SERVER_ERROR":
|
|
6320
|
+
return "Something went wrong";
|
|
6321
|
+
default:
|
|
6322
|
+
return "An error occurred";
|
|
6323
|
+
}
|
|
6324
|
+
}
|
|
6325
|
+
function formatCountdown(seconds) {
|
|
6326
|
+
if (seconds === null) return "";
|
|
6327
|
+
const mins = Math.floor(seconds / 60);
|
|
6328
|
+
const secs = seconds % 60;
|
|
6329
|
+
return `${mins}:${secs.toString().padStart(2, "0")}`;
|
|
6330
|
+
}
|
|
6331
|
+
|
|
5065
6332
|
const PassflowProvider = ({
|
|
5066
6333
|
children,
|
|
5067
6334
|
navigate: initialNavigate,
|
|
5068
6335
|
router = "default",
|
|
5069
6336
|
...config
|
|
5070
6337
|
}) => {
|
|
6338
|
+
const needsDiscovery = !config.appId;
|
|
5071
6339
|
const [state, dispatch] = useReducer(passflowReducer, {
|
|
5072
6340
|
...initialState,
|
|
5073
|
-
...config
|
|
6341
|
+
...config,
|
|
6342
|
+
isDiscoveringAppId: needsDiscovery
|
|
5074
6343
|
});
|
|
5075
6344
|
const [navigate, setNavigate] = useState(() => {
|
|
5076
6345
|
if (initialNavigate) {
|
|
@@ -5079,6 +6348,51 @@ const PassflowProvider = ({
|
|
|
5079
6348
|
return defaultNavigate;
|
|
5080
6349
|
});
|
|
5081
6350
|
const passflow = useMemo(() => new Passflow(state), [state]);
|
|
6351
|
+
const discoveryAttemptedRef = useRef(false);
|
|
6352
|
+
useEffect(() => {
|
|
6353
|
+
if (needsDiscovery && !discoveryAttemptedRef.current && state.isDiscoveringAppId) {
|
|
6354
|
+
discoveryAttemptedRef.current = true;
|
|
6355
|
+
const discoverAppId = async () => {
|
|
6356
|
+
const urlsToTry = ["/settings"];
|
|
6357
|
+
if (config.url) {
|
|
6358
|
+
urlsToTry.push(`${config.url}/settings`);
|
|
6359
|
+
}
|
|
6360
|
+
for (const url of urlsToTry) {
|
|
6361
|
+
try {
|
|
6362
|
+
const response = await fetch(url);
|
|
6363
|
+
if (response.ok) {
|
|
6364
|
+
const settings = await response.json();
|
|
6365
|
+
const discoveredAppId = settings.login_app?.app_id || settings.appId;
|
|
6366
|
+
if (discoveredAppId) {
|
|
6367
|
+
passflow.setAppId(discoveredAppId);
|
|
6368
|
+
dispatch({
|
|
6369
|
+
type: "SET_PASSFLOW_STATE",
|
|
6370
|
+
payload: {
|
|
6371
|
+
...state,
|
|
6372
|
+
appId: discoveredAppId,
|
|
6373
|
+
scopes: settings.login_app?.scopes || settings.scopes || state.scopes,
|
|
6374
|
+
createTenantForNewUser: settings.login_app?.create_tenant_for_new_user ?? settings.createTenantForNewUser ?? state.createTenantForNewUser,
|
|
6375
|
+
isDiscoveringAppId: false
|
|
6376
|
+
}
|
|
6377
|
+
});
|
|
6378
|
+
return;
|
|
6379
|
+
}
|
|
6380
|
+
}
|
|
6381
|
+
} catch (error) {
|
|
6382
|
+
}
|
|
6383
|
+
}
|
|
6384
|
+
console.warn("Failed to discover appId from /settings");
|
|
6385
|
+
dispatch({
|
|
6386
|
+
type: "SET_PASSFLOW_STATE",
|
|
6387
|
+
payload: {
|
|
6388
|
+
...state,
|
|
6389
|
+
isDiscoveringAppId: false
|
|
6390
|
+
}
|
|
6391
|
+
});
|
|
6392
|
+
};
|
|
6393
|
+
void discoverAppId();
|
|
6394
|
+
}
|
|
6395
|
+
}, [needsDiscovery, state.isDiscoveringAppId, config.url, passflow, state]);
|
|
5082
6396
|
const passflowValue = useMemo(() => ({ state, dispatch, passflow }), [state, passflow]);
|
|
5083
6397
|
const handleSetNavigate = useCallback((newNavigate) => {
|
|
5084
6398
|
setNavigate(() => newNavigate || defaultNavigate);
|
|
@@ -5094,5 +6408,5 @@ const PassflowProvider = ({
|
|
|
5094
6408
|
return /* @__PURE__ */ jsx(PassflowContext.Provider, { value: passflowValue, children: /* @__PURE__ */ jsx(NavigationContext$1.Provider, { value: navigationValue, children: /* @__PURE__ */ jsx(AuthProvider, { children }) }) });
|
|
5095
6409
|
};
|
|
5096
6410
|
|
|
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 };
|
|
6411
|
+
export { Button, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, FieldPassword, FieldPhone, FieldText, ForgotPassword, ForgotPasswordSuccess, Icon, InvitationJoin, Link, PassflowFlow, PassflowProvider, Popover, PopoverAnchor, PopoverContent, PopoverTrigger, ProvidersBox, ResetPassword, SignIn, SignInForm, SignUp, SignUpForm, Switch, TwoFactorRecoveryForm, TwoFactorSetupForm, TwoFactorSetupMagicLinkFlow, TwoFactorVerifyForm, VerifyChallengeMagicLink, VerifyChallengeOTP, Wrapper, useAppSettings, useAuth, useAuthCloudRedirect, useForgotPassword, useJoinInvite, useLogout, useNavigation, useOutsideClick, usePassflow, usePasswordlessComplete, useProvider, useResetPassword, useSignIn, useSignUp, useTwoFactorManage, useTwoFactorSetup, useTwoFactorSetupMagicLink, useTwoFactorStatus, useTwoFactorVerify, useUserPasskeys };
|
|
5098
6412
|
//# sourceMappingURL=index.es.js.map
|