Skip to content

Commit

Permalink
Merge pull request #126 from supertokens/feat/rename-functions-to-be-…
Browse files Browse the repository at this point in the history
…more-descriptive

feat: rename functions and refactor native library calls into separate functions
  • Loading branch information
deepjyoti30-st authored Dec 19, 2024
2 parents a9de691 + dbe0aa2 commit 97aa10f
Show file tree
Hide file tree
Showing 3 changed files with 220 additions and 106 deletions.
111 changes: 86 additions & 25 deletions lib/ts/recipe/webauthn/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,14 @@ import { GeneralErrorResponse, User } from "../../types";
import { getNormalisedUserContext } from "../../utils";
import { RecipeFunctionOptions } from "../recipeModule/types";
import Recipe from "./recipe";
import { CredentialPayload, ResidentKey, UserInput, UserVerification } from "./types";
import {
CredentialPayload,
ResidentKey,
UserInput,
UserVerification,
RegistrationOptions,
AuthenticationOptions,
} from "./types";

export default class RecipeWrapper {
static init(config?: UserInput) {
Expand All @@ -39,7 +46,7 @@ export default class RecipeWrapper {
*
* @returns `{ status: "OK", ...}` if successful along a description of the created webauthn details (challenge, etc.)
*/
static registerOptions(
static getRegisterOptions(
input: { options?: RecipeFunctionOptions; userContext: any } & (
| { email: string }
| { recoverAccountToken: string }
Expand Down Expand Up @@ -90,7 +97,7 @@ export default class RecipeWrapper {
fetchResponse: Response;
}
> {
return Recipe.getInstanceOrThrow().recipeImplementation.registerOptions({
return Recipe.getInstanceOrThrow().recipeImplementation.getRegisterOptions({
...input,
userContext: getNormalisedUserContext(input?.userContext),
});
Expand All @@ -108,7 +115,7 @@ export default class RecipeWrapper {
*
* @returns `{ status: "OK", ...}` if successful along a description of the webauthn options (challenge, etc.)
*/
static signInOptions(input: { email: string; options?: RecipeFunctionOptions; userContext: any }): Promise<
static getSignInOptions(input: { email: string; options?: RecipeFunctionOptions; userContext: any }): Promise<
| {
status: "OK";
webauthnGeneratedOptionsId: string;
Expand All @@ -123,7 +130,7 @@ export default class RecipeWrapper {
}
| GeneralErrorResponse
> {
return Recipe.getInstanceOrThrow().recipeImplementation.signInOptions({
return Recipe.getInstanceOrThrow().recipeImplementation.getSignInOptions({
...input,
userContext: getNormalisedUserContext(input?.userContext),
});
Expand Down Expand Up @@ -217,14 +224,14 @@ export default class RecipeWrapper {
*
* @returns `{ status: "OK", ...}` if successful along with a boolean indicating existence
*/
static emailExists(input: { email: string; options?: RecipeFunctionOptions; userContext: any }): Promise<
static getEmailExists(input: { email: string; options?: RecipeFunctionOptions; userContext: any }): Promise<
| {
status: "OK";
exists: boolean;
}
| GeneralErrorResponse
> {
return Recipe.getInstanceOrThrow().recipeImplementation.emailExists({
return Recipe.getInstanceOrThrow().recipeImplementation.getEmailExists({
...input,
userContext: input?.userContext,
});
Expand Down Expand Up @@ -298,6 +305,45 @@ export default class RecipeWrapper {
});
}

/**
* Register credential with the passed options by using native webauthn functions.
*
* It uses `@simplewebauthn/browser` to make the webauthn calls.
*
* @param registrationOptions Options to pass for the registration.
*
* @returns `{ status: "OK", ...}` if successful along with registration response received
*/
static registerCredential(input: { registrationOptions: RegistrationOptions }): Promise<
| {
status: "OK";
registrationResponse: RegistrationResponseJSON;
}
| { status: "AUTHENTICATOR_ALREADY_REGISTERED" }
| { status: "FAILED_TO_REGISTER_USER"; error: any }
> {
return Recipe.getInstanceOrThrow().recipeImplementation.registerCredential(input);
}

/**
* Authenticate the credential with the passed options by using native webauthn functions.
*
* It uses `@simplewebauthn/browser` to make the webauthn calls.
*
* @param authenticationOptions Options to pass for the authentication.
*
* @returns `{ status: "OK", ...}` if successful along with authentication response received
*/
static authenticateCredential(input: { authenticationOptions: AuthenticationOptions }): Promise<
| {
status: "OK";
authenticationResponse: AuthenticationResponseJSON;
}
| { status: "FAILED_TO_AUTHENTICATE_USER"; error: any }
> {
return Recipe.getInstanceOrThrow().recipeImplementation.authenticateCredential(input);
}

/**
* Register the new device and signup the user with the passed email ID.
*
Expand All @@ -311,7 +357,11 @@ export default class RecipeWrapper {
*
* @returns `{ status: "OK", ...}` if successful along a description of the user details (id, etc.) and email
*/
static registerAndSignUp(input: { email: string; options?: RecipeFunctionOptions; userContext: any }): Promise<
static registerCredentialWithSignUp(input: {
email: string;
options?: RecipeFunctionOptions;
userContext: any;
}): Promise<
| {
status: "OK";
user: User;
Expand All @@ -338,8 +388,9 @@ export default class RecipeWrapper {
| { status: "INVALID_AUTHENTICATOR_ERROR"; reason: string; fetchResponse: Response }
| { status: "EMAIL_ALREADY_EXISTS_ERROR"; fetchResponse: Response }
| { status: "AUTHENTICATOR_ALREADY_REGISTERED" }
| { status: "FAILED_TO_REGISTER_USER"; error: any }
> {
return Recipe.getInstanceOrThrow().recipeImplementation.registerAndSignUp({
return Recipe.getInstanceOrThrow().recipeImplementation.registerCredentialWithSignUp({
...input,
userContext: input?.userContext,
});
Expand All @@ -358,7 +409,11 @@ export default class RecipeWrapper {
*
* @returns `{ status: "OK", ...}` if successful along a description of the user details (id, etc.) and email
*/
static authenticateAndSignIn(input: { email: string; options?: RecipeFunctionOptions; userContext: any }): Promise<
static authenticateCredentialWithSignIn(input: {
email: string;
options?: RecipeFunctionOptions;
userContext: any;
}): Promise<
| {
status: "OK";
user: User;
Expand All @@ -374,9 +429,10 @@ export default class RecipeWrapper {
reason: string;
fetchResponse: Response;
}
| { status: "FAILED_TO_AUTHENTICATE_USER"; error: any }
| GeneralErrorResponse
> {
return Recipe.getInstanceOrThrow().recipeImplementation.authenticateAndSignIn({
return Recipe.getInstanceOrThrow().recipeImplementation.authenticateCredentialWithSignIn({
...input,
userContext: input?.userContext,
});
Expand All @@ -395,7 +451,7 @@ export default class RecipeWrapper {
*
* @returns `{ status: "OK", ...}` if successful along a description of the user details (id, etc.) and email
*/
static registerAndRecoverAccount(input: {
static registerCredentialWithRecoverAccount(input: {
recoverAccountToken: string;
options?: RecipeFunctionOptions;
userContext: any;
Expand All @@ -421,36 +477,41 @@ export default class RecipeWrapper {
| { status: "INVALID_GENERATED_OPTIONS_ERROR"; fetchResponse: Response }
| { status: "INVALID_AUTHENTICATOR_ERROR"; reason: string; fetchResponse: Response }
| { status: "AUTHENTICATOR_ALREADY_REGISTERED" }
| { status: "FAILED_TO_REGISTER_USER"; error: any }
> {
return Recipe.getInstanceOrThrow().recipeImplementation.registerAndRecoverAccount({
return Recipe.getInstanceOrThrow().recipeImplementation.registerCredentialWithRecoverAccount({
...input,
userContext: input?.userContext,
});
}
}

const init = RecipeWrapper.init;
const registerOptions = RecipeWrapper.registerOptions;
const signInOptions = RecipeWrapper.signInOptions;
const getRegisterOptions = RecipeWrapper.getRegisterOptions;
const getSignInOptions = RecipeWrapper.getSignInOptions;
const signUp = RecipeWrapper.signUp;
const signIn = RecipeWrapper.signIn;
const emailExists = RecipeWrapper.emailExists;
const getEmailExists = RecipeWrapper.getEmailExists;
const generateRecoverAccountToken = RecipeWrapper.generateRecoverAccountToken;
const recoverAccount = RecipeWrapper.recoverAccount;
const registerAndSignup = RecipeWrapper.registerAndSignUp;
const authenticateAndSignIn = RecipeWrapper.authenticateAndSignIn;
const registerAndRecoverAccount = RecipeWrapper.registerAndRecoverAccount;
const registerCredentialWithSignUp = RecipeWrapper.registerCredentialWithSignUp;
const authenticateCredentialWithSignIn = RecipeWrapper.authenticateCredentialWithSignIn;
const registerCredentialWithRecoverAccount = RecipeWrapper.registerCredentialWithRecoverAccount;
const registerCredential = RecipeWrapper.registerCredential;
const authenticateCredential = RecipeWrapper.authenticateCredential;

export {
init,
registerOptions,
signInOptions,
getRegisterOptions,
getSignInOptions,
signUp,
signIn,
emailExists,
getEmailExists,
generateRecoverAccountToken,
recoverAccount,
registerAndSignup,
authenticateAndSignIn,
registerAndRecoverAccount,
registerCredentialWithSignUp,
authenticateCredentialWithSignIn,
registerCredentialWithRecoverAccount,
registerCredential,
authenticateCredential,
};
95 changes: 59 additions & 36 deletions lib/ts/recipe/webauthn/recipeImplementation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export default function getRecipeImplementation(
const querier = new Querier(recipeImplInput.recipeId, recipeImplInput.appInfo);

return {
registerOptions: async function ({
getRegisterOptions: async function ({
options,
userContext,
email,
Expand Down Expand Up @@ -115,7 +115,7 @@ export default function getRecipeImplementation(
fetchResponse,
};
},
signInOptions: async function ({ email, options, userContext }) {
getSignInOptions: async function ({ email, options, userContext }) {
const { jsonBody, fetchResponse } = await querier.post<
| {
status: "OK";
Expand Down Expand Up @@ -244,7 +244,7 @@ export default function getRecipeImplementation(
fetchResponse,
};
},
emailExists: async function ({ email, options, userContext }) {
getEmailExists: async function ({ email, options, userContext }) {
const { jsonBody, fetchResponse } = await querier.get<
| {
status: "OK";
Expand Down Expand Up @@ -354,9 +354,29 @@ export default function getRecipeImplementation(
fetchResponse,
};
},
registerAndSignUp: async function ({ email, options, userContext }) {
registerCredential: async function ({ registrationOptions }) {
let registrationResponse: RegistrationResponseJSON;
try {
registrationResponse = await startRegistration({ optionsJSON: registrationOptions });
} catch (error: any) {
if (error.name === "InvalidStateError") {
return { status: "AUTHENTICATOR_ALREADY_REGISTERED" };
}

return {
status: "FAILED_TO_REGISTER_USER",
error: error,
};
}

return {
status: "OK",
registrationResponse,
};
},
registerCredentialWithSignUp: async function ({ email, options, userContext }) {
// Get the registration options by using the passed email ID.
const registrationOptions = await this.registerOptions({ options, userContext, email });
const registrationOptions = await this.getRegisterOptions({ options, userContext, email });
if (registrationOptions?.status !== "OK") {
// If we did not get an OK status, we need to return the error as is.

Expand All @@ -371,56 +391,65 @@ export default function getRecipeImplementation(
}

// We should have received a valid registration options response.
let registrationResponse: RegistrationResponseJSON;
try {
registrationResponse = await startRegistration({ optionsJSON: registrationOptions });
} catch (error: any) {
if (error.name === "InvalidStateError") {
return { status: "AUTHENTICATOR_ALREADY_REGISTERED" };
}

throw error;
const registerCredentialResponse = await this.registerCredential({ registrationOptions });
if (registerCredentialResponse.status !== "OK") {
return registerCredentialResponse;
}

// We should have a valid registration response for the passed credentials
// and we are good to go ahead and verify them.
return await this.signUp({
webauthnGeneratedOptionsId: registrationOptions.webauthnGeneratedOptionsId,
credential: registrationResponse,
credential: registerCredentialResponse.registrationResponse,
options,
userContext,
});
},
authenticateAndSignIn: async function ({ email, options, userContext }) {
authenticateCredential: async function ({ authenticationOptions }) {
let authenticationResponse: AuthenticationResponseJSON;
try {
authenticationResponse = await startAuthentication({ optionsJSON: authenticationOptions });
} catch (error: any) {
return {
status: "FAILED_TO_AUTHENTICATE_USER",
error: error,
};
}

return {
status: "OK",
authenticationResponse: authenticationResponse,
};
},
authenticateCredentialWithSignIn: async function ({ email, options, userContext }) {
// Make a call to get the sign in options using the entered email ID.
const signInOptions = await this.signInOptions({ email, options, userContext });
const signInOptions = await this.getSignInOptions({ email, options, userContext });
if (signInOptions?.status !== "OK") {
// We want to return the error as is if status was not "OK"
return signInOptions;
}

// We should have the options ready and are good to start the authentication
let authenticationResponse: AuthenticationResponseJSON;
try {
authenticationResponse = await startAuthentication({ optionsJSON: signInOptions });
} catch (error: any) {
// TODO: Do we need to do something with the error besides throwing it?
throw error;
const authenticateCredentialResponse = await this.authenticateCredential({
authenticationOptions: signInOptions,
});
if (authenticateCredentialResponse.status !== "OK") {
return authenticateCredentialResponse;
}

// We should have a valid authentication response at this point so we can
// go ahead and sign in the user.
return await this.signIn({
webauthnGeneratedOptionsId: signInOptions.webauthnGeneratedOptionsId,
credential: authenticationResponse,
credential: authenticateCredentialResponse.authenticationResponse,
options: options,
userContext: userContext,
});
},
registerAndRecoverAccount: async function ({ recoverAccountToken, options, userContext }) {
registerCredentialWithRecoverAccount: async function ({ recoverAccountToken, options, userContext }) {
// Get the registration options based on the recoverAccountToken and
// register the device against the user.
const registrationOptions = await this.registerOptions({ options, userContext, recoverAccountToken });
const registrationOptions = await this.getRegisterOptions({ options, userContext, recoverAccountToken });
if (registrationOptions?.status !== "OK") {
// If we did not get an OK status, we need to return the error as is.

Expand All @@ -435,21 +464,15 @@ export default function getRecipeImplementation(
}

// We should have received a valid registration options response.
let registrationResponse: RegistrationResponseJSON;
try {
registrationResponse = await startRegistration({ optionsJSON: registrationOptions });
} catch (error: any) {
if (error.name === "InvalidStateError") {
return { status: "AUTHENTICATOR_ALREADY_REGISTERED" };
}

throw error;
const registerCredentialResponse = await this.registerCredential({ registrationOptions });
if (registerCredentialResponse.status !== "OK") {
return registerCredentialResponse;
}

return await this.recoverAccount({
token: recoverAccountToken,
webauthnGeneratedOptionsId: registrationOptions.webauthnGeneratedOptionsId,
credential: registrationResponse,
credential: registerCredentialResponse.registrationResponse,
options,
userContext,
});
Expand Down
Loading

0 comments on commit 97aa10f

Please sign in to comment.