Skip to content

Commit 526962a

Browse files
committed
fix: Prevent passkey registration if already setup
1 parent 5b6fe29 commit 526962a

File tree

5 files changed

+38
-18
lines changed

5 files changed

+38
-18
lines changed

lib/ts/recipe/webauthn/components/features/mfa/index.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ export const useFeatureReducer = (): [WebAuthnMFAState, React.Dispatch<WebAuthnM
6363
deviceSupported: action.deviceSupported,
6464
email: action.email,
6565
showBackButton: action.showBackButton,
66+
canRegisterPasskey: action.canRegisterPasskey,
6667
};
6768
default:
6869
return oldState;
@@ -71,6 +72,7 @@ export const useFeatureReducer = (): [WebAuthnMFAState, React.Dispatch<WebAuthnM
7172
{
7273
error: undefined,
7374
deviceSupported: false,
75+
canRegisterPasskey: false,
7476
loaded: false,
7577
showBackButton: true,
7678
email: undefined,
@@ -364,6 +366,7 @@ function useOnLoad(
364366
const mfaInfoEmails = mfaInfo.emails[FactorIds.WEBAUTHN];
365367
const email = mfaInfoEmails ? mfaInfoEmails[0] : undefined;
366368

369+
const canRegisterPasskey = !mfaInfo.factors.alreadySetup.includes(FactorIds.WEBAUTHN);
367370
const browserSupportsWebauthnResponse = await props.recipe.webJSRecipe.doesBrowserSupportWebAuthn({
368371
userContext: userContext,
369372
});
@@ -373,6 +376,7 @@ function useOnLoad(
373376

374377
dispatch({
375378
type: "load",
379+
canRegisterPasskey,
376380
error,
377381
showBackButton,
378382
email,

lib/ts/recipe/webauthn/components/themes/mfa/index.tsx

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,17 +43,22 @@ export function MFATheme(props: WebAuthnMFAProps): JSX.Element {
4343
const t = useTranslation();
4444

4545
const onRegisterPasskeyClick = React.useCallback(() => {
46+
if (!props.featureState.canRegisterPasskey) return;
4647
if (props.featureState.email) {
4748
setActiveScreen(MFAScreens.SignUpConfirmation);
4849
} else {
4950
setActiveScreen(MFAScreens.SignUp);
5051
}
51-
}, [props.featureState.email]);
52+
}, [props.featureState.email, props.featureState.canRegisterPasskey]);
5253

53-
const onSignUpContinue = React.useCallback((email: string) => {
54-
setActiveScreen(MFAScreens.SignUpConfirmation);
55-
setSignUpEmail(email);
56-
}, []);
54+
const onSignUpContinue = React.useCallback(
55+
(email: string) => {
56+
if (!props.featureState.canRegisterPasskey) return;
57+
setActiveScreen(MFAScreens.SignUpConfirmation);
58+
setSignUpEmail(email);
59+
},
60+
[props.featureState.canRegisterPasskey]
61+
);
5762

5863
const clearError = React.useCallback(() => {
5964
props.dispatch({ type: "setError", error: undefined });
@@ -67,8 +72,9 @@ export function MFATheme(props: WebAuthnMFAProps): JSX.Element {
6772
);
6873

6974
const onClickSignUpBackButton = React.useCallback(() => {
75+
if (!props.featureState.canRegisterPasskey) return;
7076
setActiveScreen(MFAScreens.SignIn);
71-
}, []);
77+
}, [props.featureState.canRegisterPasskey]);
7278

7379
const onClickSignUpConfirmationBackButton = React.useCallback(() => {
7480
if (props.featureState.email) {
@@ -101,6 +107,7 @@ export function MFATheme(props: WebAuthnMFAProps): JSX.Element {
101107
{activeScreen === MFAScreens.SignIn ? (
102108
<WebauthnMFASignIn
103109
onBackButtonClicked={props.featureState.showBackButton ? onBackButtonClicked : undefined}
110+
canRegisterPasskey={props.featureState.canRegisterPasskey}
104111
onSignIn={onSignIn}
105112
error={props.featureState.error}
106113
onRegisterPasskeyClick={onRegisterPasskeyClick}

lib/ts/recipe/webauthn/components/themes/mfa/signIn.tsx

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export type MFASignInProps = {
1212
onBackButtonClicked?: () => void;
1313
onSignIn: () => Promise<void>;
1414
onRegisterPasskeyClick: () => void;
15+
canRegisterPasskey: boolean;
1516
error: string | undefined;
1617
deviceSupported: boolean;
1718
};
@@ -41,6 +42,7 @@ export const WebauthnMFASignIn = withOverride(
4142
) : (
4243
<div data-supertokens="headerTitle webauthn-mfa">{t("WEBAUTHN_MFA_SIGN_IN_HEADER_TITLE")}</div>
4344
)}
45+
4446
<div data-supertokens="headerSubtitle secondaryText">{t("WEBAUTHN_MFA_SIGN_IN_HEADER_SUBTITLE")}</div>
4547
<Button
4648
disabled={!props.deviceSupported || isLoading}
@@ -52,17 +54,21 @@ export const WebauthnMFASignIn = withOverride(
5254
icon={PasskeyIcon}
5355
/>
5456
{props.error !== undefined && <GeneralError error={props.error} />}
55-
<div data-supertokens="passkeyMfaSignInDivider">
56-
<div data-supertokens="divider" />
57-
<span>or</span>
58-
<div data-supertokens="divider" />
59-
</div>
60-
<div data-supertokens="headerSubtitle secondaryText">
61-
<span data-supertokens="link" onClick={props.onRegisterPasskeyClick}>
62-
{t("WEBAUTHN_MFA_REGISTER_PASSKEY_LINK")}
63-
</span>
64-
{t("WEBAUTHN_MFA_REGISTER_PASSKEY_SUBTITLE")}
65-
</div>
57+
{props.canRegisterPasskey && (
58+
<>
59+
<div data-supertokens="passkeyMfaSignInDivider">
60+
<div data-supertokens="divider" />
61+
<span>or</span>
62+
<div data-supertokens="divider" />
63+
</div>
64+
<div data-supertokens="headerSubtitle secondaryText">
65+
<span data-supertokens="link" onClick={props.onRegisterPasskeyClick}>
66+
{t("WEBAUTHN_MFA_REGISTER_PASSKEY_LINK")}
67+
</span>
68+
{t("WEBAUTHN_MFA_REGISTER_PASSKEY_SUBTITLE")}
69+
</div>
70+
</>
71+
)}
6672
{!props.deviceSupported && <PasskeyNotSupportedError />}
6773
</Fragment>
6874
);

lib/ts/recipe/webauthn/components/themes/translations.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ export const defaultTranslationsWebauthn = {
5555
// WebAuthn MFA translations
5656
WEBAUTHN_MFA_SIGN_IN_HEADER_TITLE: "Use a Passkey",
5757
WEBAUTHN_MFA_SIGN_IN_HEADER_SUBTITLE:
58-
"To finish signing in, click the button and follow the instructions from your browser.",
58+
"To finish signing in, click the button and follow the browser instructions.",
5959
WEBAUTHN_MFA_DIVIDER: "or",
6060
WEBAUTHN_MFA_REGISTER_PASSKEY_SUBTITLE: "Set up a new authentication method to use for future logins.",
6161
WEBAUTHN_MFA_REGISTER_PASSKEY_LINK: "Register a passkey",

lib/ts/recipe/webauthn/types.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,7 @@ export type WebAuthnMFAAction =
271271
error: string | undefined;
272272
email: string | undefined;
273273
showBackButton: boolean;
274+
canRegisterPasskey: boolean;
274275
};
275276

276277
type WebAuthnMFAInitialState = {
@@ -279,6 +280,7 @@ type WebAuthnMFAInitialState = {
279280
accessDenied: boolean;
280281
deviceSupported: boolean;
281282
showBackButton: boolean;
283+
canRegisterPasskey: false;
282284
email: undefined;
283285
};
284286

@@ -288,6 +290,7 @@ type WebAuthnMFALoadedState = {
288290
accessDenied: boolean;
289291
deviceSupported: boolean;
290292
showBackButton: boolean;
293+
canRegisterPasskey: boolean;
291294
email: string | undefined;
292295
};
293296

0 commit comments

Comments
 (0)