Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Webauthn support #870

Draft
wants to merge 71 commits into
base: 0.48
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
71 commits
Select commit Hold shift + click to select a range
698eb5f
Add init setup for webauthn recipe
deepjyoti30-st Dec 19, 2024
c4397f2
Define recipe methods to make them override-able
deepjyoti30-st Dec 19, 2024
04a6d99
Add component override definition
deepjyoti30-st Dec 19, 2024
b7baba2
Add definition of prebuiltui
deepjyoti30-st Dec 19, 2024
ebcbc17
Add more changes for supporting feature components
deepjyoti30-st Dec 23, 2024
13e68ad
Add some fixes for webauthn
deepjyoti30-st Dec 23, 2024
3826230
Undo a change for made for testing
deepjyoti30-st Dec 23, 2024
3eb3f40
Add fix for making continue with passkey component show up
deepjyoti30-st Dec 23, 2024
1155318
Update the continue with passwordless button text
deepjyoti30-st Dec 24, 2024
f9331e0
Update the play details for signup with passkey
deepjyoti30-st Dec 24, 2024
ff08dd7
Add some fixes for the webauthn sign up /in functionality
deepjyoti30-st Dec 24, 2024
93023f8
Add support for passing isSignUp as a property in preload checks func…
deepjyoti30-st Dec 24, 2024
0d3b409
Add support for a proper sign up theme component
deepjyoti30-st Dec 24, 2024
b6b7ce3
Add init support for using FormBase for rendering the sign up form
deepjyoti30-st Dec 26, 2024
5065b04
Add more fixes for properly rendering the full page passkey page
deepjyoti30-st Dec 26, 2024
d9f77f7
Add support for reusing auth style components in webauthn full pages
deepjyoti30-st Dec 26, 2024
d51fd35
Add support for recover account button in sign up for webauthn
deepjyoti30-st Dec 26, 2024
2a9f4c3
Add support for continue without passkey link below continue button
deepjyoti30-st Dec 26, 2024
17b43ef
Make continue button uppercase for webauthn signup
deepjyoti30-st Dec 26, 2024
bc37a5b
Make resetFactorList a supported prop for component props
deepjyoti30-st Dec 26, 2024
64da711
Add fixes for making the sign up component buttons work properly
deepjyoti30-st Dec 26, 2024
7f35b15
Make continue without passkey a separate component
deepjyoti30-st Dec 26, 2024
ea1051d
Make the form on submit function re-usable
deepjyoti30-st Dec 27, 2024
436a99c
Add support for rendering the confirmation view in sign up for passkey
deepjyoti30-st Dec 27, 2024
cd4e11d
Add support for showing the continue with part in the intermediate step
deepjyoti30-st Dec 27, 2024
5707a18
Add support for passkey feature blocks
deepjyoti30-st Dec 27, 2024
41a83fe
Finish the UI implementation of the passkey confirmation component
deepjyoti30-st Dec 27, 2024
db6ca4b
Add updated callAPI functions for signup
deepjyoti30-st Dec 27, 2024
a60197d
Add support for handling recoverable errors in the UI
deepjyoti30-st Dec 30, 2024
212bb2a
Add support for handling all scenarios of signup flow
deepjyoti30-st Dec 30, 2024
9a8242a
Add support for sign in functionality
deepjyoti30-st Jan 2, 2025
70ab209
Refactor the code to create SignInTheme
deepjyoti30-st Jan 2, 2025
de98ff4
Add fixes for making sign in work properly for passkey
deepjyoti30-st Jan 2, 2025
ffec16d
Add support for Recover Account form component in webauthn
deepjyoti30-st Jan 2, 2025
52e6685
Fix the width of sub header for account recovery form
deepjyoti30-st Jan 2, 2025
fd8a5d9
Add support for sending recovery email for webauthn
deepjyoti30-st Jan 3, 2025
700809f
Add init support for a recovery using token feature
deepjyoti30-st Jan 7, 2025
e3a6d63
Add support for rendering passkey link form
deepjyoti30-st Jan 7, 2025
0404d22
Add fixes for properly showing the create a passkey form in token rec…
deepjyoti30-st Jan 13, 2025
e591acf
Add support for parsing email to be read from query in recovery flow
deepjyoti30-st Jan 13, 2025
6d2700d
Add support for account recovery success screen
deepjyoti30-st Jan 13, 2025
f61ec69
Add support for redirecting to sign in page on recovery success
deepjyoti30-st Jan 13, 2025
efff4bc
Add support for getting register options on load
deepjyoti30-st Jan 13, 2025
2e35f22
Add support for handling fetch errors while recovery registration opt…
deepjyoti30-st Jan 14, 2025
bb17066
Add support for showing caution on signup page for email
deepjyoti30-st Jan 14, 2025
e6f63ad
Update stories/allrecipes.stories.tsx
deepjyoti30-st Jan 15, 2025
5c317b6
Update lib/ts/recipe/webauthn/components/themes/translations.ts
deepjyoti30-st Jan 15, 2025
53dc48a
Update lib/ts/recipe/webauthn/components/themes/signUp/recoverAccount…
deepjyoti30-st Jan 15, 2025
afc087b
Update lib/ts/recipe/webauthn/components/themes/signUp/recoverAccount…
deepjyoti30-st Jan 15, 2025
c304741
Merge branch 'feat/webauthn/base' of github.com-supertokens:supertoke…
deepjyoti30-st Jan 15, 2025
b983a3e
Add some fixes for requested changes
deepjyoti30-st Jan 15, 2025
01324d4
Add a merge fix
deepjyoti30-st Jan 15, 2025
b418d2c
Refactor formBase functions to use handleCallAPI
deepjyoti30-st Jan 15, 2025
fb7ecc2
Move svg components to assets directory
deepjyoti30-st Jan 15, 2025
ae49de0
Move more svg components to assets
deepjyoti30-st Jan 15, 2025
8c47428
Add more fixes based on requested changes
deepjyoti30-st Jan 15, 2025
4138be6
Use CSS variables for re-usable colors
deepjyoti30-st Jan 15, 2025
e9378b2
Add fixes for passkey feature blocks not rendering
deepjyoti30-st Jan 16, 2025
ce414be
Fix build issues
deepjyoti30-st Jan 17, 2025
5476719
Add init changes to refactor recover account email to be a separate f…
deepjyoti30-st Jan 20, 2025
7ca8eac
Add support for recovery email being a separate route on its own
deepjyoti30-st Jan 20, 2025
0d69bad
Add support for properly redirecting on recover account click
deepjyoti30-st Jan 20, 2025
caddefe
Add support for considering the expiresAt field while recovering account
deepjyoti30-st Jan 21, 2025
e1eab89
Add basic tests, mocks and helpers for webauthn signup
deepjyoti30-st Jan 22, 2025
e9694cc
Add webauthn mock with testContext and more tests
deepjyoti30-st Jan 23, 2025
e83124b
Add more signup tests for webauthn
deepjyoti30-st Jan 24, 2025
099ae3b
Add support for not requiring email for sign in
deepjyoti30-st Jan 27, 2025
f81fa58
Add mocks for webauthn sign in and in react 16 examples
deepjyoti30-st Jan 27, 2025
4fb94f1
Add init tests for sign in for webauthn
deepjyoti30-st Jan 28, 2025
8a149f9
Add test for checking general error on sign in
deepjyoti30-st Jan 28, 2025
3b100bc
Add support for proper e2e testing for the sign in functionality of p…
deepjyoti30-st Jan 30, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
145 changes: 145 additions & 0 deletions examples/for-tests-react-16/src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import UserRoles from "supertokens-auth-react/recipe/userroles";
import Multitenancy from "supertokens-auth-react/recipe/multitenancy";
import MultiFactorAuth from "supertokens-auth-react/recipe/multifactorauth";
import TOTP from "supertokens-auth-react/recipe/totp";
import STGeneralError from "supertokens-web-js/lib/build/error";
import Webauthn from "supertokens-auth-react/recipe/webauthn";

import axios from "axios";
import { useSessionContext } from "supertokens-auth-react/recipe/session";
Expand Down Expand Up @@ -260,6 +262,9 @@ if (enabledRecipes.includes("emailpassword") || enabledRecipes.includes("thirdpa
if (enabledRecipes.includes("passwordless") || enabledRecipes.includes("thirdpartypasswordless")) {
recipeList = [getPasswordlessConfigs(testContext), ...recipeList];
}
if (enabledRecipes.includes("webauthn")) {
recipeList = [getWebauthnConfigs(testContext), ...recipeList];
}
if (emailVerificationMode !== "OFF") {
recipeList.push(getEmailVerificationConfigs(testContext));
}
Expand Down Expand Up @@ -885,6 +890,146 @@ function getThirdPartyConfigs({ staticProviderList, disableDefaultUI, thirdParty
});
}

function getWebauthnConfigs({ throwWebauthnError, webauthnErrorStatus }) {
return Webauthn.init({
style: `
[data-supertokens~=container] {
font-family: cursive;
}
`,
override: {
functions: (implementation) => {
const log = logWithPrefix(`ST_LOGS WEBAUTHN OVERRIDE`);

return {
...implementation,
getRegisterOptions(...args) {
log(`GET REGISTER OPTIONS`);
return implementation.getRegisterOptions(...args);
},
async getSignInOptions(...args) {
log(`GET SIGN IN OPTIONS`);
return implementation.getSignInOptions(...args);
},
async authenticateCredential(...args) {
log("AUTHENTICATE CREDENTIAL");

// We will make a network call to get the registration options
// for sign up.
// Then we will use the registrationOptions and the passed args
// to create and assert a credential.

const email = `${Math.random().toString().slice(2)}@supertokens.com`;
const registrationOptions = await implementation.getRegisterOptions({
userContext: {},
email,
});
const signInOptions = args[0].authenticationOptions;

const response = await fetch(`${getApiDomain()}/test/webauthn/create-and-assert-credential`, {
method: "POST",
body: JSON.stringify({
registerOptionsResponse: registrationOptions,
signInOptionsResponse: signInOptions,
rpId: "localhost",
rpName: "localhost",
origin: "http://localhost:3031",
}),
headers: {
"Content-Type": "application/json",
},
});

if (!response.ok) {
throw new STGeneralError("TEST ERROR: CREATING CREDENTIAL FAILED");
}

const { attestation, assertion } = (await response.json()).credential;

// Sign up the user using the attestation;
await implementation.signUp({
webauthnGeneratedOptionsId: registrationOptions.webauthnGeneratedOptionsId,
credential: attestation,
userContext: {},
});

return { authenticationResponse: assertion, status: "OK" };
},
async signIn(...args) {
log(`SIGN IN`);
return implementation.signIn(...args);
},
registerCredentialWithSignUp(...args) {
log(`GET REGISTER OPTIONS WITH SIGN UP`);

// We will throw an error if it is asked for.
if (throwWebauthnError) {
throw new STGeneralError("TEST ERROR");
}

// Return error status if the user passed that.
if (webauthnErrorStatus) {
return {
status: webauthnErrorStatus,
};
}

// We are mocking the popup since it's not possible to
// test the webauthn popup.
return {
status: "OK",
user: {},
fetchResponse: {},
};
},
authenticateCredentialWithSignIn(...args) {
log(`AUTHENTICATE CREDENTIAL WITH SIGN IN`);

// We will throw an error if it is asked for.
if (throwWebauthnError) {
throw new STGeneralError("TEST ERROR");
}

// Return error status if the user passed that.
if (webauthnErrorStatus) {
return {
status: webauthnErrorStatus,
};
}

return implementation.authenticateCredentialWithSignIn(...args);
},
};
},
},
preAPIHook: async (context) => {
if (localStorage.getItem(`SHOW_GENERAL_ERROR`)?.includes(context.action)) {
let errorFromStorage = localStorage.getItem("TRANSLATED_GENERAL_ERROR");

if (context.action === "EMAIL_EXISTS") {
context.url += "&generalError=true";
} else {
let jsonBody = JSON.parse(context.requestInit.body);
jsonBody = {
...jsonBody,
generalError: true,
generalErrorMessage: errorFromStorage === null ? undefined : errorFromStorage,
};
context.requestInit.body = JSON.stringify(jsonBody);
}
}
console.log(`ST_LOGS WEBAUTHN PRE_API_HOOKS ${context.action}`);
return context;
},
getRedirectionURL: async (context) => {
console.log(`ST_LOGS WEBAUTHN GET_REDIRECTION_URL ${context.action}`);
},
onHandleEvent: async (context) => {
console.log(`ST_LOGS WEBAUTHN ON_HANDLE_EVENT ${context.action}`);
},
});
}

function setIsNewUserToStorage(recipeName, isNewRecipeUser) {
localStorage.setItem("isNewUserCheck", `${recipeName}-${isNewRecipeUser}`);
}
Expand Down
4 changes: 4 additions & 0 deletions examples/for-tests-react-16/src/AppWithReactDomRouter.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { MultiFactorAuthPreBuiltUI } from "supertokens-auth-react/recipe/multifa
import { TOTPPreBuiltUI } from "supertokens-auth-react/recipe/totp/prebuiltui";
import { AccessDeniedScreen } from "supertokens-auth-react/recipe/session/prebuiltui";
import { getEnabledRecipes, getTestContext } from "./testContext";
import { WebauthnPreBuiltUI } from "supertokens-auth-react/recipe/webauthn/prebuiltui";

function AppWithReactDomRouter(props) {
const context = getTestContext();
Expand All @@ -28,6 +29,9 @@ function AppWithReactDomRouter(props) {
if (enabledRecipes.some((r) => r.endsWith("passwordless"))) {
recipePreBuiltUIList.push(PasswordlessPreBuiltUI);
}
if (enabledRecipes.some((r) => r.endsWith("webauthn"))) {
recipePreBuiltUIList.push(WebauthnPreBuiltUI);
}

if (emailVerificationMode !== "OFF") {
recipePreBuiltUIList.push(EmailVerificationPreBuiltUI);
Expand Down
4 changes: 4 additions & 0 deletions examples/for-tests-react-16/src/AppWithReactDomRouterV5.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { AccessDeniedScreen } from "supertokens-auth-react/recipe/session/prebui
import { MultiFactorAuthPreBuiltUI } from "supertokens-auth-react/recipe/multifactorauth/prebuiltui";
import { TOTPPreBuiltUI } from "supertokens-auth-react/recipe/totp/prebuiltui";
import { getEnabledRecipes, getTestContext } from "./testContext";
import { WebauthnPreBuiltUI } from "supertokens-auth-react/recipe/webauthn/prebuiltui";

function AppWithReactDomRouter(props) {
const context = getTestContext();
Expand All @@ -27,6 +28,9 @@ function AppWithReactDomRouter(props) {
if (enabledRecipes.some((r) => r.endsWith("passwordless"))) {
recipePreBuiltUIList.push(PasswordlessPreBuiltUI);
}
if (enabledRecipes.some((r) => r.endsWith("webauthn"))) {
recipePreBuiltUIList.push(WebauthnPreBuiltUI);
}
if (emailVerificationMode !== "OFF") {
recipePreBuiltUIList.push(EmailVerificationPreBuiltUI);
}
Expand Down
4 changes: 4 additions & 0 deletions examples/for-tests-react-16/src/AppWithoutRouter.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { EmailPasswordPreBuiltUI } from "supertokens-auth-react/recipe/emailpass
import { PasswordlessPreBuiltUI } from "supertokens-auth-react/recipe/passwordless/prebuiltui";
import { ThirdPartyPreBuiltUI } from "supertokens-auth-react/recipe/thirdparty/prebuiltui";
import { EmailVerificationPreBuiltUI } from "supertokens-auth-react/recipe/emailverification/prebuiltui";
import { WebauthnPreBuiltUI } from "supertokens-auth-react/recipe/webauthn/prebuiltui";
import { getEnabledRecipes } from "./testContext";

function AppWithoutRouter() {
Expand Down Expand Up @@ -32,6 +33,9 @@ function Routing() {
if (enabledRecipes.some((r) => r.endsWith("passwordless"))) {
recipePreBuiltUIList.push(PasswordlessPreBuiltUI);
}
if (enabledRecipes.some((r) => r.endsWith("webauthn"))) {
recipePreBuiltUIList.push(WebauthnPreBuiltUI);
}

if (emailVerificationMode !== "OFF") {
recipePreBuiltUIList.push(EmailVerificationPreBuiltUI);
Expand Down
2 changes: 2 additions & 0 deletions examples/for-tests-react-16/src/testContext.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ export function getTestContext() {
signoutOnSessionNotExists: localStorage.getItem("signoutOnSessionNotExists") === "true",
disableRedirectionAfterSuccessfulSignInUp:
localStorage.getItem("disableRedirectionAfterSuccessfulSignInUp") === "true",
throwWebauthnError: localStorage.getItem("throwWebauthnError") === "true",
webauthnErrorStatus: localStorage.getItem("webauthnErrorStatus") || undefined,
};
return ret;
}
Expand Down
Loading
Loading