Skip to content

Commit 9a8242a

Browse files
Add support for sign in functionality
1 parent 212bb2a commit 9a8242a

File tree

3 files changed

+151
-17
lines changed

3 files changed

+151
-17
lines changed

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

Lines changed: 139 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -18,43 +18,165 @@
1818
import * as React from "react";
1919

2020
import AuthComponentWrapper from "../../../../../components/authCompWrapper";
21-
import FeatureWrapper from "../../../../../components/featureWrapper";
22-
import SuperTokens from "../../../../../superTokens";
21+
import { useUserContext } from "../../../../../usercontext";
22+
import { useRethrowInRender } from "../../../../../utils";
23+
import { handleFormSubmit } from "../../../../emailpassword/components/library/functions/form";
24+
import { useSessionContext } from "../../../../session";
25+
import Session from "../../../../session/recipe";
2326
import { ContinueWithPasskeyTheme } from "../../themes/continueWithPasskey";
24-
import { defaultTranslationsWebauthn } from "../../themes/translations";
2527

26-
import type { UserContext, PartialAuthComponentProps } from "../../../../../types";
28+
import type { UserContext, PartialAuthComponentProps, APIFormField } from "../../../../../types";
29+
import type { AuthSuccessContext } from "../../../../authRecipe/types";
2730
import type Recipe from "../../../recipe";
28-
import type { ComponentOverrideMap } from "../../../types";
31+
import type { ComponentOverrideMap, SignInThemeProps } from "../../../types";
32+
import type { User } from "supertokens-web-js/types";
2933

30-
export const SignInWithPasskeyFeature: React.FC<
34+
export function useChildProps(
35+
recipe: Recipe,
36+
factorIds: string[],
37+
onAuthSuccess: (successContext: AuthSuccessContext) => Promise<void>,
38+
error: string | undefined,
39+
onError: (err: string) => void,
40+
userContext: UserContext,
41+
clearError: () => void,
42+
resetFactorList: () => void,
43+
onSignInUpSwitcherClick: () => void
44+
): SignInThemeProps {
45+
const session = useSessionContext();
46+
const recipeImplementation = recipe.webJSRecipe;
47+
const rethrowInRender = useRethrowInRender();
48+
49+
return React.useMemo(() => {
50+
return {
51+
userContext,
52+
onSuccess: async (result: { createdNewRecipeUser: boolean; user: User }) => {
53+
let payloadAfterCall;
54+
try {
55+
payloadAfterCall = await Session.getInstanceOrThrow().getAccessTokenPayloadSecurely({
56+
userContext,
57+
});
58+
} catch {
59+
payloadAfterCall = undefined;
60+
}
61+
return onAuthSuccess({
62+
createdNewUser: result.createdNewRecipeUser && result.user.loginMethods.length === 1,
63+
isNewRecipeUser: result.createdNewRecipeUser,
64+
newSessionCreated:
65+
session.loading ||
66+
!session.doesSessionExist ||
67+
(payloadAfterCall !== undefined &&
68+
session.accessTokenPayload.sessionHandle !== payloadAfterCall.sessionHandle),
69+
recipeId: "webauthn",
70+
}).catch(rethrowInRender);
71+
},
72+
error,
73+
onError,
74+
clearError,
75+
onFetchError: async (/* err: Response*/) => {
76+
// TODO: Do we need to do something else?
77+
onError("SOMETHING_WENT_WRONG_ERROR");
78+
},
79+
factorIds,
80+
recipeImplementation: recipeImplementation,
81+
config: recipe.config,
82+
resetFactorList: resetFactorList,
83+
onSignInUpSwitcherClick,
84+
};
85+
}, [error, factorIds, userContext, recipeImplementation]);
86+
}
87+
88+
const SignInFeatureInner: React.FC<
3189
PartialAuthComponentProps & {
3290
recipe: Recipe;
3391
factorIds: string[];
34-
userContext?: UserContext;
3592
useComponentOverrides: () => ComponentOverrideMap;
93+
userContext?: UserContext;
3694
}
3795
> = (props) => {
38-
const recipeComponentOverrides = props.useComponentOverrides();
96+
let userContext = useUserContext();
97+
if (props.userContext !== undefined) {
98+
userContext = props.userContext;
99+
}
100+
const childProps = useChildProps(
101+
props.recipe,
102+
props.factorIds,
103+
props.onAuthSuccess,
104+
props.error,
105+
props.onError,
106+
userContext,
107+
props.clearError,
108+
props.resetFactorList,
109+
props.onSignInUpSwitcherClick
110+
)!;
39111

40-
// TODO: Define the code to handle sign in properly through this component.
41-
const handleWebauthnSignInClick = () => {
42-
alert("This is yet to be defined!");
43-
return;
112+
const callAPI = React.useCallback(
113+
async (_: APIFormField[], __: (id: string, value: string) => any) => {
114+
const email = prompt("Enter email ID");
115+
if (email === null) {
116+
alert("Please enter an email");
117+
return;
118+
}
119+
120+
const response = await childProps.recipeImplementation.authenticateCredentialWithSignIn({
121+
email: email,
122+
userContext,
123+
});
124+
125+
return response;
126+
},
127+
[childProps, userContext]
128+
);
129+
130+
// Define the code to handle sign in properly through this component.
131+
const handleWebauthnSignInClick = async () => {
132+
await handleFormSubmit({
133+
callAPI: callAPI,
134+
clearError: () => alert("Clear error"),
135+
onError: () => alert("Error"),
136+
onFetchError: () => alert("Fetch error"),
137+
onSuccess: (payload) => console.warn("payload: ", payload),
138+
});
44139
};
45140

46141
return (
47-
<AuthComponentWrapper recipeComponentOverrides={recipeComponentOverrides}>
48-
<FeatureWrapper
49-
useShadowDom={SuperTokens.getInstanceOrThrow().useShadowDom}
50-
defaultStore={defaultTranslationsWebauthn}>
142+
<React.Fragment>
143+
{/* No custom theme, use default. */}
144+
{props.children === undefined && (
51145
<ContinueWithPasskeyTheme
52146
{...props}
53147
continueWithPasskeyClicked={handleWebauthnSignInClick}
54148
config={props.recipe.config}
55149
continueFor="SIGN_IN"
56150
/>
57-
</FeatureWrapper>
151+
)}
152+
153+
{/* Otherwise, custom theme is provided, propagate props. */}
154+
{props.children &&
155+
React.Children.map(props.children, (child) => {
156+
if (React.isValidElement(child)) {
157+
return React.cloneElement(child, {
158+
...childProps,
159+
});
160+
}
161+
return child;
162+
})}
163+
</React.Fragment>
164+
);
165+
};
166+
167+
export const SignInWithPasskeyFeature: React.FC<
168+
PartialAuthComponentProps & {
169+
recipe: Recipe;
170+
factorIds: string[];
171+
userContext?: UserContext;
172+
useComponentOverrides: () => ComponentOverrideMap;
173+
}
174+
> = (props) => {
175+
const recipeComponentOverrides = props.useComponentOverrides();
176+
177+
return (
178+
<AuthComponentWrapper recipeComponentOverrides={recipeComponentOverrides}>
179+
<SignInFeatureInner {...props} />
58180
</AuthComponentWrapper>
59181
);
60182
};

lib/ts/recipe/webauthn/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,8 @@ export type SignUpThemeProps = {
152152
onSignInUpSwitcherClick: () => void;
153153
};
154154

155+
export type SignInThemeProps = SignUpThemeProps;
156+
155157
export type SignUpFormProps = {
156158
clearError: () => void;
157159
onError: (error: string) => void;

stories/allrecipes.stories.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,16 @@ export const SignUpPasskey: Story = {
123123
},
124124
};
125125

126+
export const SignInPasskey: Story = {
127+
args: {
128+
path: "/auth",
129+
"multifactorauth.initialized": false,
130+
"passwordless.initialized": false,
131+
"webauthn.initialized": true,
132+
defaultToSignUp: false,
133+
},
134+
};
135+
126136
export const SignUpFieldErrors: Story = {
127137
args: {
128138
path: "/auth",

0 commit comments

Comments
 (0)