Skip to content

Commit 43433e2

Browse files
committed
fix: Fix passkey mfa sign in flow
1 parent a0c706b commit 43433e2

File tree

8 files changed

+192
-82
lines changed

8 files changed

+192
-82
lines changed

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

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ export const useFeatureReducer = (): [WebAuthnMFAState, React.Dispatch<WebAuthnM
7373
error: undefined,
7474
deviceSupported: false,
7575
canRegisterPasskey: false,
76+
hasRegisteredPassKey: false,
7677
loaded: false,
7778
showBackButton: true,
7879
email: undefined,
@@ -242,7 +243,8 @@ export const MFAFeature: React.FC<
242243
<ComponentOverrideContext.Provider value={recipeComponentOverrides}>
243244
<FeatureWrapper
244245
useShadowDom={SuperTokens.getInstanceOrThrow().useShadowDom}
245-
defaultStore={defaultTranslationsWebauthn}>
246+
defaultStore={defaultTranslationsWebauthn}
247+
>
246248
<MFAFeatureInner {...props} />
247249
</FeatureWrapper>
248250
</ComponentOverrideContext.Provider>
@@ -348,12 +350,6 @@ function useOnLoad(
348350
}
349351
}
350352

351-
const alreadySetup = mfaInfo.factors.alreadySetup.includes(FactorIds.WEBAUTHN);
352-
if (alreadySetup) {
353-
dispatch({ type: "setError", accessDenied: true, error: "SOMETHING_WENT_WRONG_ERROR_RELOAD" });
354-
return;
355-
}
356-
357353
// If the next array only has a single option, it means the we were redirected here
358354
// automatically during the sign in process. In that case, anywhere the back button
359355
// could go would redirect back here, making it useless.
@@ -365,21 +361,23 @@ function useOnLoad(
365361
const mfaInfoEmails = mfaInfo.emails[FactorIds.WEBAUTHN];
366362
const email = mfaInfoEmails ? mfaInfoEmails[0] : undefined;
367363

368-
const canRegisterPasskey = !mfaInfo.factors.alreadySetup.includes(FactorIds.WEBAUTHN);
364+
const canRegisterPasskey = mfaInfo.factors.allowedToSetup.includes(FactorIds.WEBAUTHN);
365+
const hasRegisteredPassKey = mfaInfo.factors.alreadySetup.includes(FactorIds.WEBAUTHN);
369366
const browserSupportsWebauthnResponse = await props.recipe.webJSRecipe.doesBrowserSupportWebAuthn({
370367
userContext: userContext,
371368
});
372-
const browserSupportsWebauthn =
369+
const deviceSupported =
373370
browserSupportsWebauthnResponse.status === "OK" &&
374371
browserSupportsWebauthnResponse?.browserSupportsWebauthn;
375372

376373
dispatch({
377374
type: "load",
378375
canRegisterPasskey,
376+
hasRegisteredPassKey,
379377
error,
380378
showBackButton,
381379
email,
382-
deviceSupported: browserSupportsWebauthn,
380+
deviceSupported,
383381
});
384382
},
385383
[dispatch, recipeImplementation, props.recipe, userContext]

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

Lines changed: 108 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -37,33 +37,67 @@ function MFAThemeWrapper(props: WebAuthnMFAProps): JSX.Element {
3737
export default MFAThemeWrapper;
3838

3939
export function MFATheme(props: WebAuthnMFAProps): JSX.Element {
40-
const { onBackButtonClicked, onSignIn } = props;
41-
const [activeScreen, setActiveScreen] = React.useState<MFAScreens>(MFAScreens.SignIn);
42-
const [signUpEmail, setSignUpEmail] = React.useState<string>("");
4340
const t = useTranslation();
4441

45-
const onRegisterPasskeyClick = React.useCallback(() => {
46-
if (!props.featureState.canRegisterPasskey) {
47-
return;
48-
}
49-
if (props.featureState.email) {
50-
setActiveScreen(MFAScreens.SignUpConfirmation);
51-
} else {
52-
setActiveScreen(MFAScreens.SignUp);
42+
if (!props.featureState.loaded) {
43+
return <WebauthnMFALoadingScreen />;
44+
}
45+
46+
if (props.featureState.accessDenied) {
47+
return (
48+
<AccessDeniedScreen
49+
useShadowDom={false /* We set this to false, because we are already inside a shadowDom (if required) */}
50+
error={t(props.featureState.error!)}
51+
/>
52+
);
53+
}
54+
55+
return (
56+
<div data-supertokens="container webauthn-mfa">
57+
<div data-supertokens="row">
58+
<MFAThemeRouter {...props} />
59+
</div>
60+
<SuperTokensBranding />
61+
</div>
62+
);
63+
}
64+
65+
function MFAThemeRouter(props: WebAuthnMFAProps): JSX.Element {
66+
const { onBackButtonClicked, onSignIn } = props;
67+
const [activeScreen, setActiveScreen] = React.useState<MFAScreens>(() => {
68+
if (!props.featureState.hasRegisteredPassKey) {
69+
return props.featureState.email ? MFAScreens.SignUpConfirmation : MFAScreens.SignUp;
5370
}
54-
}, [props.featureState.email, props.featureState.canRegisterPasskey]);
71+
return MFAScreens.SignIn;
72+
});
73+
const [email, setEmail] = React.useState<string>("");
74+
const signUpEmail = props.featureState.email || email;
75+
console.log("render");
76+
console.log(signUpEmail);
77+
console.log(email);
5578

5679
const onSignUpContinue = React.useCallback(
5780
(email: string) => {
5881
if (!props.featureState.canRegisterPasskey) {
5982
return;
6083
}
6184
setActiveScreen(MFAScreens.SignUpConfirmation);
62-
setSignUpEmail(email);
85+
setEmail(email);
6386
},
6487
[props.featureState.canRegisterPasskey]
6588
);
6689

90+
const onRegisterPasskeyClick = React.useCallback(() => {
91+
if (!props.featureState.canRegisterPasskey) {
92+
return;
93+
}
94+
if (props.featureState.email) {
95+
setActiveScreen(MFAScreens.SignUpConfirmation);
96+
} else {
97+
setActiveScreen(MFAScreens.SignUp);
98+
}
99+
}, [props.featureState.email, props.featureState.canRegisterPasskey]);
100+
67101
const clearError = React.useCallback(() => {
68102
props.dispatch({ type: "setError", error: undefined });
69103
}, [props]);
@@ -75,71 +109,82 @@ export function MFATheme(props: WebAuthnMFAProps): JSX.Element {
75109
[props]
76110
);
77111

78-
const onClickSignUpBackButton = React.useCallback(() => {
79-
if (!props.featureState.canRegisterPasskey) {
80-
return;
81-
}
82-
setActiveScreen(MFAScreens.SignIn);
83-
}, [props.featureState.canRegisterPasskey]);
84-
85112
const onClickSignUpConfirmationBackButton = React.useCallback(() => {
86-
if (props.featureState.email) {
87-
setActiveScreen(MFAScreens.SignIn);
88-
} else {
113+
if (!props.featureState.email) {
89114
setActiveScreen(MFAScreens.SignUp);
115+
return;
116+
}
117+
if (!props.featureState.hasRegisteredPassKey && !props.featureState.showBackButton) {
118+
return;
90119
}
91-
}, [props.featureState.email]);
120+
if (!props.featureState.hasRegisteredPassKey) {
121+
onBackButtonClicked();
122+
return;
123+
}
124+
setActiveScreen(MFAScreens.SignIn);
125+
}, [
126+
props.featureState.email,
127+
props.featureState.hasRegisteredPassKey,
128+
props.featureState.showBackButton,
129+
onBackButtonClicked,
130+
]);
131+
132+
const onSignUp = React.useCallback(async () => {
133+
console.log("onSignUp");
134+
console.log(props.featureState.email);
135+
console.log(email);
136+
await props.onSignUp(signUpEmail);
137+
}, [props.onSignUp, signUpEmail]);
92138

93139
const onFetchError = React.useCallback(() => {
94140
onError("SOMETHING_WENT_WRONG_ERROR");
95141
}, [onError]);
96142

97-
if (!props.featureState.loaded) {
98-
return <WebauthnMFALoadingScreen />;
143+
const onClickSignUpBackButton = React.useCallback(() => {
144+
if (!props.featureState.hasRegisteredPassKey && !props.featureState.showBackButton) {
145+
return;
146+
}
147+
if (!props.featureState.hasRegisteredPassKey) {
148+
onBackButtonClicked();
149+
return;
150+
}
151+
setActiveScreen(MFAScreens.SignIn);
152+
}, [props.featureState.hasRegisteredPassKey, props.featureState.showBackButton, onBackButtonClicked]);
153+
154+
if (activeScreen === MFAScreens.SignUp) {
155+
return (
156+
<WebauthnMFASignUp
157+
clearError={clearError}
158+
onError={onError}
159+
onFetchError={onFetchError}
160+
error={props.featureState.error}
161+
onContinueClick={onSignUpContinue}
162+
email={email}
163+
onRecoverAccountClick={props.onRecoverAccountClick}
164+
onBackButtonClicked={onClickSignUpBackButton}
165+
/>
166+
);
99167
}
100168

101-
if (props.featureState.accessDenied) {
169+
if (activeScreen === MFAScreens.SignUpConfirmation) {
102170
return (
103-
<AccessDeniedScreen
104-
useShadowDom={false /* We set this to false, because we are already inside a shadowDom (if required) */}
105-
error={t(props.featureState.error!)}
171+
<WebauthnMFASignUpConfirmation
172+
onSignUp={onSignUp}
173+
onBackButtonClicked={onClickSignUpConfirmationBackButton}
174+
email={signUpEmail}
175+
error={props.featureState.error}
106176
/>
107177
);
108178
}
109179

110180
return (
111-
<div data-supertokens="container webauthn-mfa">
112-
<div data-supertokens="row">
113-
{activeScreen === MFAScreens.SignIn ? (
114-
<WebauthnMFASignIn
115-
onBackButtonClicked={props.featureState.showBackButton ? onBackButtonClicked : undefined}
116-
canRegisterPasskey={props.featureState.canRegisterPasskey}
117-
onSignIn={onSignIn}
118-
error={props.featureState.error}
119-
onRegisterPasskeyClick={onRegisterPasskeyClick}
120-
deviceSupported={props.featureState.deviceSupported}
121-
/>
122-
) : activeScreen === MFAScreens.SignUp ? (
123-
<WebauthnMFASignUp
124-
clearError={clearError}
125-
onError={onError}
126-
onFetchError={onFetchError}
127-
error={props.featureState.error}
128-
onContinueClick={onSignUpContinue}
129-
email={signUpEmail}
130-
onRecoverAccountClick={props.onRecoverAccountClick}
131-
onBackButtonClicked={onClickSignUpBackButton}
132-
/>
133-
) : (
134-
<WebauthnMFASignUpConfirmation
135-
onSignUp={props.onSignUp}
136-
onBackButtonClicked={onClickSignUpConfirmationBackButton}
137-
email={props.featureState.email || signUpEmail}
138-
error={props.featureState.error}
139-
/>
140-
)}
141-
</div>
142-
<SuperTokensBranding />
143-
</div>
181+
<WebauthnMFASignIn
182+
onBackButtonClicked={props.featureState.showBackButton ? onBackButtonClicked : undefined}
183+
canRegisterPasskey={props.featureState.canRegisterPasskey}
184+
onSignIn={onSignIn}
185+
error={props.featureState.error}
186+
deviceSupported={props.featureState.deviceSupported}
187+
onRegisterPasskeyClick={onRegisterPasskeyClick}
188+
/>
144189
);
145190
}

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@ import { PasskeyNotSupportedError } from "../error/passkeyNotSupportedError";
1212
export type MFASignInProps = {
1313
onBackButtonClicked?: () => void;
1414
onSignIn: () => Promise<void>;
15-
onRegisterPasskeyClick: () => void;
16-
canRegisterPasskey: boolean;
1715
error: string | undefined;
1816
deviceSupported: boolean;
17+
canRegisterPasskey: boolean;
18+
onRegisterPasskeyClick: () => void;
1919
};
2020

2121
export const WebauthnMFASignIn = withOverride(
@@ -64,7 +64,7 @@ export const WebauthnMFASignIn = withOverride(
6464
</div>
6565
<div data-supertokens="headerSubtitle secondaryText">
6666
<span data-supertokens="link" onClick={props.onRegisterPasskeyClick}>
67-
{t("WEBAUTHN_MFA_REGISTER_PASSKEY_LINK")}
67+
{t("WEBAUTHN_MFA_REGISTER_PASSKEY_TITLE")}
6868
</span>
6969
{t("WEBAUTHN_MFA_REGISTER_PASSKEY_SUBTITLE")}
7070
</div>

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ export const WebauthnMFASignUp = withOverride(
3737
<Fragment>
3838
<div data-supertokens="headerTitle withBackButton webauthn-mfa">
3939
<BackButton onClick={props.onBackButtonClicked} />
40-
{t("WEBAUTHN_MFA_REGISTER_PASSKEY_LINK")}
40+
{t("WEBAUTHN_MFA_REGISTER_PASSKEY_TITLE")}
4141
<span data-supertokens="backButtonPlaceholder backButtonCommon">
4242
{/* empty span for spacing the back button */}
4343
</span>

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import GeneralError from "../../../../emailpassword/components/library/generalEr
99
import { PasskeyFeatureBlockList } from "../signUp/featureBlocks";
1010

1111
export type MFASignUpConfirmationProps = {
12-
onSignUp: (email: string) => Promise<void>;
12+
onSignUp: () => Promise<void>;
1313
onBackButtonClicked: () => void;
1414
email: string;
1515
error?: string;
@@ -23,15 +23,15 @@ export const WebauthnMFASignUpConfirmation = withOverride(
2323

2424
const onClick = React.useCallback(async () => {
2525
setIsLoading(true);
26-
await props.onSignUp(props.email);
26+
await props.onSignUp();
2727
setIsLoading(false);
28-
}, [props]);
28+
}, [props.onSignUp]);
2929

3030
return (
3131
<Fragment>
3232
<div data-supertokens="headerTitle withBackButton webauthn-mfa">
3333
<BackButton onClick={props.onBackButtonClicked} />
34-
{t("WEBAUTHN_MFA_REGISTER_PASSKEY_LINK")}
34+
{t("WEBAUTHN_MFA_REGISTER_PASSKEY_TITLE")}
3535
<span data-supertokens="backButtonPlaceholder backButtonCommon">
3636
{/* empty span for spacing the back button */}
3737
</span>

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,6 @@ export const defaultTranslationsWebauthn = {
5858
"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.",
61-
WEBAUTHN_MFA_REGISTER_PASSKEY_LINK: "Register a passkey",
61+
WEBAUTHN_MFA_REGISTER_PASSKEY_TITLE: "Register a passkey",
6262
},
6363
};

lib/ts/recipe/webauthn/types.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,7 @@ export type WebAuthnMFAAction =
275275
email: string | undefined;
276276
showBackButton: boolean;
277277
canRegisterPasskey: boolean;
278+
hasRegisteredPassKey: boolean;
278279
};
279280

280281
type WebAuthnMFAInitialState = {
@@ -284,6 +285,7 @@ type WebAuthnMFAInitialState = {
284285
deviceSupported: boolean;
285286
showBackButton: boolean;
286287
canRegisterPasskey: false;
288+
hasRegisteredPassKey: false;
287289
email: undefined;
288290
};
289291

@@ -294,6 +296,7 @@ type WebAuthnMFALoadedState = {
294296
deviceSupported: boolean;
295297
showBackButton: boolean;
296298
canRegisterPasskey: boolean;
299+
hasRegisteredPassKey: boolean;
297300
email: string | undefined;
298301
};
299302

0 commit comments

Comments
 (0)