Skip to content

Commit

Permalink
pr fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
niftyvictor committed Nov 8, 2024
1 parent b44752f commit dba5cda
Show file tree
Hide file tree
Showing 9 changed files with 157 additions and 111 deletions.
51 changes: 23 additions & 28 deletions lib/ts/recipe/webauthn/api/implementation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import { getRecoverAccountLink } from "../utils";
import { logDebugMessage } from "../../../logger";
import { RecipeLevelUser } from "../../accountlinking/types";
import { getUser } from "../../..";
import { CredentialPayload } from "../types";
import { CredentialPayload, ResidentKey, UserVerification } from "../types";

export default function getAPIImplementation(): APIInterface {
return {
Expand Down Expand Up @@ -60,19 +60,19 @@ export default function getAPIImplementation(): APIInterface {
}[];
authenticatorSelection: {
requireResidentKey: boolean;
residentKey: "required" | "preferred" | "discouraged";
userVerification: "required" | "preferred" | "discouraged";
residentKey: ResidentKey;
userVerification: UserVerification;
};
}
| { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR" }
| { status: "INVALID_EMAIL_ERROR"; err: string }
> {
const relyingPartyId = await options.config.relyingPartyId({
const relyingPartyId = await options.config.getRelyingPartyId({
tenantId,
request: options.req,
userContext,
});
const relyingPartyName = await options.config.relyingPartyName({
const relyingPartyName = await options.config.getRelyingPartyName({
tenantId,
userContext,
});
Expand Down Expand Up @@ -139,12 +139,12 @@ export default function getAPIImplementation(): APIInterface {
webauthnGeneratedOptionsId: string;
challenge: string;
timeout: number;
userVerification: "required" | "preferred" | "discouraged";
userVerification: UserVerification;
}
| GeneralErrorResponse
| { status: "WRONG_CREDENTIALS_ERROR" }
> {
const relyingPartyId = await options.config.relyingPartyId({
const relyingPartyId = await options.config.getRelyingPartyId({
tenantId,
request: options.req,
userContext,
Expand Down Expand Up @@ -391,35 +391,30 @@ export default function getAPIImplementation(): APIInterface {

const recipeId = "webauthn";

// do the verification before in order to retrieve the user email
const verifyCredentialsResponse = await options.recipeImplementation.verifyCredentials({
credential,
const generatedOptions = await options.recipeImplementation.getGeneratedOptions({
webauthnGeneratedOptionsId,
tenantId,
userContext,
});
const checkCredentialsOnTenant = async () => {
return verifyCredentialsResponse.status === "OK";
};

// doing it like this because the email is only available after verifyCredentials is called
let email: string;
if (verifyCredentialsResponse.status == "OK") {
const loginMethod = verifyCredentialsResponse.user.loginMethods.find((lm) => lm.recipeId === recipeId);
// there should be a webauthn login method and an email when trying to sign in using webauthn
if (!loginMethod || !loginMethod.email) {
return AuthUtils.getErrorStatusResponseWithReason(
verifyCredentialsResponse,
errorCodeMap,
"SIGN_IN_NOT_ALLOWED"
);
}
email = loginMethod?.email;
} else {
if (generatedOptions.status !== "OK") {
return {
status: "WRONG_CREDENTIALS_ERROR",
};
}
let email = generatedOptions.email;

const checkCredentialsOnTenant = async () => {
return (
(
await options.recipeImplementation.verifyCredentials({
credential,
webauthnGeneratedOptionsId,
tenantId,
userContext,
})
).status === "OK"
);
};

const authenticatingUser = await AuthUtils.getAuthenticatingUserAndAddToCurrentTenantIfRequired({
accountInfo: { email },
Expand Down
6 changes: 2 additions & 4 deletions lib/ts/recipe/webauthn/api/recoverAccount.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
*/

import { send200Response } from "../../../utils";
import { validateCredentialOrThrowError, validatewebauthnGeneratedOptionsIdOrThrowError } from "./utils";
import { validateCredentialOrThrowError, validateWebauthnGeneratedOptionsIdOrThrowError } from "./utils";
import STError from "../error";
import { APIInterface, APIOptions } from "../";
import { UserContext } from "../../../types";
Expand All @@ -25,14 +25,12 @@ export default async function recoverAccount(
options: APIOptions,
userContext: UserContext
): Promise<boolean> {
// Logic as per https://github.com/supertokens/supertokens-node/issues/22#issuecomment-710512442

if (apiImplementation.recoverAccountPOST === undefined) {
return false;
}

const requestBody = await options.req.getJSONBody();
let webauthnGeneratedOptionsId = await validatewebauthnGeneratedOptionsIdOrThrowError(
let webauthnGeneratedOptionsId = await validateWebauthnGeneratedOptionsIdOrThrowError(
requestBody.webauthnGeneratedOptionsId
);
let credential = await validateCredentialOrThrowError(requestBody.credential);
Expand Down
4 changes: 2 additions & 2 deletions lib/ts/recipe/webauthn/api/signin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {
getNormalisedShouldTryLinkingWithSessionUserFlag,
send200Response,
} from "../../../utils";
import { validatewebauthnGeneratedOptionsIdOrThrowError, validateCredentialOrThrowError } from "./utils";
import { validateWebauthnGeneratedOptionsIdOrThrowError, validateCredentialOrThrowError } from "./utils";
import { APIInterface, APIOptions } from "..";
import { UserContext } from "../../../types";
import { AuthUtils } from "../../../authUtils";
Expand All @@ -34,7 +34,7 @@ export default async function signInAPI(
}

const requestBody = await options.req.getJSONBody();
const webauthnGeneratedOptionsId = await validatewebauthnGeneratedOptionsIdOrThrowError(
const webauthnGeneratedOptionsId = await validateWebauthnGeneratedOptionsIdOrThrowError(
requestBody.webauthnGeneratedOptionsId
);
const credential = await validateCredentialOrThrowError(requestBody.credential);
Expand Down
14 changes: 4 additions & 10 deletions lib/ts/recipe/webauthn/api/signup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {
getNormalisedShouldTryLinkingWithSessionUserFlag,
send200Response,
} from "../../../utils";
import { validatewebauthnGeneratedOptionsIdOrThrowError, validateCredentialOrThrowError } from "./utils";
import { validateWebauthnGeneratedOptionsIdOrThrowError, validateCredentialOrThrowError } from "./utils";
import { APIInterface, APIOptions } from "..";
import STError from "../error";
import { UserContext } from "../../../types";
Expand All @@ -35,7 +35,7 @@ export default async function signUpAPI(
}

const requestBody = await options.req.getJSONBody();
const webauthnGeneratedOptionsId = await validatewebauthnGeneratedOptionsIdOrThrowError(
const webauthnGeneratedOptionsId = await validateWebauthnGeneratedOptionsIdOrThrowError(
requestBody.webauthnGeneratedOptionsId
);
const credential = await validateCredentialOrThrowError(requestBody.credential);
Expand Down Expand Up @@ -70,14 +70,8 @@ export default async function signUpAPI(
send200Response(options.res, result);
} else if (result.status === "EMAIL_ALREADY_EXISTS_ERROR") {
throw new STError({
type: STError.FIELD_ERROR,
payload: [
{
id: "email",
error: "This email already exists. Please sign in instead.",
},
],
message: "Error in input formFields",
type: STError.BAD_INPUT_ERROR,
message: "This email already exists. Please sign in instead.",
});
} else {
send200Response(options.res, result);
Expand Down
2 changes: 1 addition & 1 deletion lib/ts/recipe/webauthn/api/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
*/
import STError from "../error";

export async function validatewebauthnGeneratedOptionsIdOrThrowError(
export async function validateWebauthnGeneratedOptionsIdOrThrowError(
webauthnGeneratedOptionsId: string
): Promise<string> {
if (webauthnGeneratedOptionsId === undefined) {
Expand Down
19 changes: 2 additions & 17 deletions lib/ts/recipe/webauthn/error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,26 +16,11 @@
import STError from "../../error";

export default class SessionError extends STError {
static FIELD_ERROR: "FIELD_ERROR" = "FIELD_ERROR";

constructor(
options:
| {
type: "FIELD_ERROR";
payload: {
id: string;
error: string;
}[];
message: string;
}
| {
type: "BAD_INPUT_ERROR";
message: string;
}
) {
constructor(options: { type: "BAD_INPUT_ERROR"; message: string }) {
super({
...options,
});

this.fromRecipe = "webauthn";
}
}
90 changes: 86 additions & 4 deletions lib/ts/recipe/webauthn/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,15 @@

import Recipe from "./recipe";
import SuperTokensError from "./error";
import { RecipeInterface, APIOptions, APIInterface, TypeWebauthnEmailDeliveryInput, CredentialPayload } from "./types";
import {
RecipeInterface,
APIOptions,
APIInterface,
TypeWebauthnEmailDeliveryInput,
CredentialPayload,
UserVerification,
ResidentKey,
} from "./types";
import RecipeUserId from "../../recipeUserId";
import { DEFAULT_TENANT_ID } from "../multitenancy/constants";
import { getRecoverAccountLink } from "./utils";
Expand Down Expand Up @@ -73,8 +81,8 @@ export default class Wrapper {
}[];
authenticatorSelection: {
requireResidentKey: boolean;
residentKey: "required" | "preferred" | "discouraged";
userVerification: "required" | "preferred" | "discouraged";
residentKey: ResidentKey;
userVerification: UserVerification;
};
}
| { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR" }
Expand Down Expand Up @@ -118,7 +126,7 @@ export default class Wrapper {
webauthnGeneratedOptionsId: string;
challenge: string;
timeout: number;
userVerification: "required" | "preferred" | "discouraged";
userVerification: UserVerification;
}
| { status: "WRONG_CREDENTIALS_ERROR" }
> {
Expand All @@ -132,6 +140,80 @@ export default class Wrapper {
});
}

static signUp(
tenantId: string,
webauthnGeneratedOptionsId: string,
credential: CredentialPayload,
session?: undefined,
userContext?: Record<string, any>
): Promise<
| {
status: "OK";
user: User;
recipeUserId: RecipeUserId;
}
| { status: "EMAIL_ALREADY_EXISTS_ERROR" }
| { status: "WRONG_CREDENTIALS_ERROR" }
| { status: "INVALID_AUTHENTICATOR_ERROR"; reason: string }
>;
static signUp(
tenantId: string,
webauthnGeneratedOptionsId: string,
credential: CredentialPayload,
session: SessionContainerInterface,
userContext?: Record<string, any>
): Promise<
| {
status: "OK";
user: User;
recipeUserId: RecipeUserId;
}
| { status: "EMAIL_ALREADY_EXISTS_ERROR" }
| { status: "WRONG_CREDENTIALS_ERROR" }
| { status: "INVALID_AUTHENTICATOR_ERROR"; reason: string }
| {
status: "LINKING_TO_SESSION_USER_FAILED";
reason:
| "EMAIL_VERIFICATION_REQUIRED"
| "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"
| "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"
| "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR";
}
>;
static signUp(
tenantId: string,
webauthnGeneratedOptionsId: string,
credential: CredentialPayload,
session?: SessionContainerInterface,
userContext?: Record<string, any>
): Promise<
| {
status: "OK";
user: User;
recipeUserId: RecipeUserId;
}
| { status: "EMAIL_ALREADY_EXISTS_ERROR" }
| { status: "WRONG_CREDENTIALS_ERROR" }
| { status: "INVALID_AUTHENTICATOR_ERROR"; reason: string }
| {
status: "LINKING_TO_SESSION_USER_FAILED";
reason:
| "EMAIL_VERIFICATION_REQUIRED"
| "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"
| "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"
| "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR";
}
> {
return Recipe.getInstanceOrThrowError().recipeInterfaceImpl.signUp({
webauthnGeneratedOptionsId,
credential,
session,
shouldTryLinkingWithSessionUser: !!session,
tenantId: tenantId === undefined ? DEFAULT_TENANT_ID : tenantId,
userContext: getUserContext(userContext),
});
}

static signIn(
tenantId: string,
webauthnGeneratedOptionsId: string,
Expand Down
Loading

0 comments on commit dba5cda

Please sign in to comment.