Skip to content

Commit

Permalink
fixed implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
niftyvictor committed Nov 6, 2024
1 parent ea42bd1 commit b8cffc1
Show file tree
Hide file tree
Showing 7 changed files with 71 additions and 61 deletions.
41 changes: 24 additions & 17 deletions lib/ts/recipe/webauthn/api/implementation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,7 @@ export default function getAPIImplementation(): APIInterface {
};
}
| { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR" }
| { status: "EMAIL_MISSING_ERROR" }
| { status: "REGISTER_OPTIONS_NOT_ALLOWED"; reason: string }
| { status: "INVALID_EMAIL_ERROR"; err: string }
> {
const relyingPartyId = await options.config.relyingPartyId({
tenantId,
Expand Down Expand Up @@ -125,10 +124,12 @@ export default function getAPIImplementation(): APIInterface {
},

signInOptionsPOST: async function ({
email,
tenantId,
options,
userContext,
}: {
email?: string;
tenantId: string;
options: APIOptions;
userContext: UserContext;
Expand All @@ -141,6 +142,7 @@ export default function getAPIImplementation(): APIInterface {
userVerification: "required" | "preferred" | "discouraged";
}
| GeneralErrorResponse
| { status: "WRONG_CREDENTIALS_ERROR" }
> {
const relyingPartyId = await options.config.relyingPartyId({
tenantId,
Expand All @@ -159,6 +161,7 @@ export default function getAPIImplementation(): APIInterface {
const userVerification = DEFAULT_SIGNIN_OPTIONS_USER_VERIFICATION;

let response = await options.recipeImplementation.signInOptions({
email,
userVerification,
origin,
relyingPartyId,
Expand All @@ -181,7 +184,6 @@ export default function getAPIImplementation(): APIInterface {
},

signUpPOST: async function ({
email,
webauthnGeneratedOptionsId,
credential,
tenantId,
Expand All @@ -190,28 +192,28 @@ export default function getAPIImplementation(): APIInterface {
options,
userContext,
}: {
email: string;
webauthnGeneratedOptionsId: string;
credential: CredentialPayload;
tenantId: string;
session?: SessionContainerInterface;
session: SessionContainerInterface | undefined;
shouldTryLinkingWithSessionUser: boolean | undefined;
options: APIOptions;
userContext: UserContext;
// should also have the email or recoverAccountToken in order to do the preauth checks
}): Promise<
| {
status: "OK";
session: SessionContainerInterface;
user: User;
}
| GeneralErrorResponse
| {
status: "SIGN_UP_NOT_ALLOWED";
reason: string;
}
| { status: "EMAIL_ALREADY_EXISTS_ERROR" }
| { status: "WRONG_CREDENTIALS_ERROR" }
| { status: "INVALID_AUTHENTICATOR_ERROR"; reason: string }
| { status: "EMAIL_ALREADY_EXISTS_ERROR" }
| GeneralErrorResponse
> {
const errorCodeMap = {
SIGN_UP_NOT_ALLOWED:
Expand All @@ -232,13 +234,25 @@ export default function getAPIImplementation(): APIInterface {
},
};

const generatedOptions = await options.recipeImplementation.getGeneratedOptions({
webauthnGeneratedOptionsId,
tenantId,
userContext,
});
if (generatedOptions.status !== "OK") {
return { status: "WRONG_CREDENTIALS_ERROR" };
}

const email = generatedOptions.email;

// NOTE: Following checks will likely never throw an error as the
// check for type is done in a parent function but they are kept
// here to be on the safe side.
if (typeof email !== "string")
if (!email) {
throw new Error(
"Should never come here since we already check that the email value is a string in validateFormFieldsOrThrowError"
);
}

const preAuthCheckRes = await AuthUtils.preAuthChecks({
authenticatingAccountInfo: {
Expand Down Expand Up @@ -353,9 +367,7 @@ export default function getAPIImplementation(): APIInterface {
session: SessionContainerInterface;
user: User;
}
| {
status: "WRONG_CREDENTIALS_ERROR";
}
| { status: "WRONG_CREDENTIALS_ERROR" }
| {
status: "SIGN_IN_NOT_ALLOWED";
reason: string;
Expand Down Expand Up @@ -826,13 +838,9 @@ export default function getAPIImplementation(): APIInterface {
email: string;
}
| GeneralErrorResponse
| {
status: "CONSUME_RECOVER_ACCOUNT_TOKEN_NOT_ALLOWED";
reason: string;
}
| { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR" }
| { status: "WRONG_CREDENTIALS_ERROR" }
| { status: "INVALID_AUTHENTICATOR_ERROR"; reason: string }
| { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR" }
> {
async function markEmailAsVerified(recipeUserId: RecipeUserId, email: string) {
const emailVerificationInstance = EmailVerification.getInstance();
Expand Down Expand Up @@ -874,7 +882,6 @@ export default function getAPIImplementation(): APIInterface {
let updateResponse = await options.recipeImplementation.registerCredential({
recipeUserId,
webauthnGeneratedOptionsId,
tenantId,
credential,
userContext,
});
Expand Down
8 changes: 1 addition & 7 deletions lib/ts/recipe/webauthn/api/signup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,7 @@ import {
getNormalisedShouldTryLinkingWithSessionUserFlag,
send200Response,
} from "../../../utils";
import {
validateEmailOrThrowError,
validatewebauthnGeneratedOptionsIdOrThrowError,
validateCredentialOrThrowError,
} from "./utils";
import { validatewebauthnGeneratedOptionsIdOrThrowError, validateCredentialOrThrowError } from "./utils";
import { APIInterface, APIOptions } from "..";
import STError from "../error";
import { UserContext } from "../../../types";
Expand All @@ -39,7 +35,6 @@ export default async function signUpAPI(
}

const requestBody = await options.req.getJSONBody();
const email = await validateEmailOrThrowError(requestBody.email);
const webauthnGeneratedOptionsId = await validatewebauthnGeneratedOptionsIdOrThrowError(
requestBody.webauthnGeneratedOptionsId
);
Expand All @@ -58,7 +53,6 @@ export default async function signUpAPI(
}

let result = await apiImplementation.signUpPOST({
email,
credential,
webauthnGeneratedOptionsId,
tenantId,
Expand Down
10 changes: 0 additions & 10 deletions lib/ts/recipe/webauthn/api/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,6 @@
* under the License.
*/
import STError from "../error";
import { defaultEmailValidator } from "../utils";

export async function validateEmailOrThrowError(email: string): Promise<string> {
const error = await defaultEmailValidator(email);
if (error) {
throw newBadRequestError(error);
}

return email;
}

export async function validatewebauthnGeneratedOptionsIdOrThrowError(
webauthnGeneratedOptionsId: string
Expand Down
56 changes: 33 additions & 23 deletions lib/ts/recipe/webauthn/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,11 @@ import { getRecoverAccountLink } from "./utils";
import { getRequestFromUserContext, getUser } from "../..";
import { getUserContext } from "../../utils";
import { SessionContainerInterface } from "../session/types";
import { User, UserContext } from "../../types";
import { User } from "../../types";
import {
DEFAULT_REGISTER_OPTIONS_REQUIRE_RESIDENT_KEY,
DEFAULT_REGISTER_OPTIONS_RESIDENT_KEY,
DEFAULT_REGISTER_OPTIONS_SUPPORTED_ALGORITHM_IDS,
DEFAULT_REGISTER_OPTIONS_USER_VERIFICATION,
DEFAULT_SIGNIN_OPTIONS_USER_VERIFICATION,
} from "./constants";
Expand All @@ -35,8 +36,9 @@ export default class Wrapper {

static Error = SuperTokensError;

static registerOptions(
email: string,
static async registerOptions(
email: string | undefined,
recoverAccountToken: string | undefined,
relyingPartyId: string,
relyingPartyName: string,
origin: string,
Expand Down Expand Up @@ -76,14 +78,24 @@ export default class Wrapper {
};
}
| { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR" }
| { status: "EMAIL_MISSING_ERROR" }
| { status: "INVALID_EMAIL_ERROR" }
| { status: "INVALID_EMAIL_ERROR"; err: string }
> {
let payload: { email: string } | { recoverAccountToken: string } | null = email
? { email }
: recoverAccountToken
? { recoverAccountToken }
: null;

if (!payload) {
return { status: "INVALID_EMAIL_ERROR", err: "Email is missing" };
}

return Recipe.getInstanceOrThrowError().recipeInterfaceImpl.registerOptions({
requireResidentKey: DEFAULT_REGISTER_OPTIONS_REQUIRE_RESIDENT_KEY,
residentKey: DEFAULT_REGISTER_OPTIONS_RESIDENT_KEY,
userVerification: DEFAULT_REGISTER_OPTIONS_USER_VERIFICATION,
email,
supportedAlgorithmIds: DEFAULT_REGISTER_OPTIONS_SUPPORTED_ALGORITHM_IDS,
...payload,
relyingPartyId,
relyingPartyName,
origin,
Expand All @@ -100,13 +112,16 @@ export default class Wrapper {
timeout: number,
tenantId: string,
userContext: Record<string, any>
): Promise<{
status: "OK";
webauthnGeneratedOptionsId: string;
challenge: string;
timeout: number;
userVerification: "required" | "preferred" | "discouraged";
}> {
): Promise<
| {
status: "OK";
webauthnGeneratedOptionsId: string;
challenge: string;
timeout: number;
userVerification: "required" | "preferred" | "discouraged";
}
| { status: "WRONG_CREDENTIALS_ERROR" }
> {
return Recipe.getInstanceOrThrowError().recipeInterfaceImpl.signInOptions({
userVerification: DEFAULT_SIGNIN_OPTIONS_USER_VERIFICATION,
relyingPartyId,
Expand All @@ -123,11 +138,7 @@ export default class Wrapper {
credential: CredentialPayload,
session?: undefined,
userContext?: Record<string, any>
): Promise<
| { status: "OK"; user: User; recipeUserId: RecipeUserId }
| { status: "WRONG_CREDENTIALS_ERROR" }
| { status: "INVALID_AUTHENTICATOR_ERROR" }
>;
): Promise<{ status: "OK"; user: User; recipeUserId: RecipeUserId } | { status: "WRONG_CREDENTIALS_ERROR" }>;
static signIn(
tenantId: string,
webauthnGeneratedOptionsId: string,
Expand All @@ -137,7 +148,6 @@ export default class Wrapper {
): Promise<
| { status: "OK"; user: User; recipeUserId: RecipeUserId }
| { status: "WRONG_CREDENTIALS_ERROR" }
| { status: "INVALID_AUTHENTICATOR_ERROR" }
| {
status: "LINKING_TO_SESSION_USER_FAILED";
reason:
Expand All @@ -156,7 +166,6 @@ export default class Wrapper {
): Promise<
| { status: "OK"; user: User; recipeUserId: RecipeUserId }
| { status: "WRONG_CREDENTIALS_ERROR" }
| { status: "INVALID_AUTHENTICATOR_ERROR" }
| {
status: "LINKING_TO_SESSION_USER_FAILED";
reason:
Expand All @@ -181,7 +190,7 @@ export default class Wrapper {
webauthnGeneratedOptionsId: string,
credential: CredentialPayload,
userContext?: Record<string, any>
): Promise<{ status: "OK" } | { status: "WRONG_CREDENTIALS_ERROR" } | { status: "INVALID_AUTHENTICATOR_ERROR" }> {
): Promise<{ status: "OK" } | { status: "WRONG_CREDENTIALS_ERROR" }> {
const resp = await Recipe.getInstanceOrThrowError().recipeInterfaceImpl.verifyCredentials({
webauthnGeneratedOptionsId,
credential,
Expand Down Expand Up @@ -220,7 +229,7 @@ export default class Wrapper {
});
}

static async recoverAccountUsingToken(
static async recoverAccount(
tenantId: string,
webauthnGeneratedOptionsId: string,
token: string,
Expand Down Expand Up @@ -286,6 +295,7 @@ export default class Wrapper {
| {
status: "OK" | "WRONG_CREDENTIALS_ERROR";
}
| { status: "WRONG_CREDENTIALS_ERROR" }
| { status: "INVALID_AUTHENTICATOR_ERROR"; reason: string }
> {
return Recipe.getInstanceOrThrowError().recipeInterfaceImpl.registerCredential({
Expand Down Expand Up @@ -383,7 +393,7 @@ export let verifyCredentials = Wrapper.verifyCredentials;

export let generateRecoverAccountToken = Wrapper.generateRecoverAccountToken;

export let recoverAccountUsingToken = Wrapper.recoverAccountUsingToken;
export let recoverAccount = Wrapper.recoverAccount;

export let consumeRecoverAccountToken = Wrapper.consumeRecoverAccountToken;

Expand Down
11 changes: 10 additions & 1 deletion lib/ts/recipe/webauthn/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -525,7 +525,12 @@ type SignUpPOSTErrorResponse =
| { status: "WRONG_CREDENTIALS_ERROR" }
| { status: "INVALID_AUTHENTICATOR_ERROR"; reason: string };

type SignInPOSTErrorResponse = { status: "WRONG_CREDENTIALS_ERROR" };
type SignInPOSTErrorResponse =
| { status: "WRONG_CREDENTIALS_ERROR" }
| {
status: "SIGN_IN_NOT_ALLOWED";
reason: string;
};

type GenerateRecoverAccountTokenPOSTErrorResponse = {
status: "RECOVER_ACCOUNT_NOT_ALLOWED";
Expand Down Expand Up @@ -649,6 +654,10 @@ export type APIInterface = {
}
| GeneralErrorResponse
// | SignInPOSTErrorResponse
| {
status: "SIGN_IN_NOT_ALLOWED";
reason: string;
}
| { status: "WRONG_CREDENTIALS_ERROR" }
>);

Expand Down
2 changes: 1 addition & 1 deletion lib/ts/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ export type User = {
}[];
webauthn: {
credentialIds: string[];
}[];
};
loginMethods: (RecipeLevelUser & {
verified: boolean;
hasSameEmailAs: (email: string | undefined) => boolean;
Expand Down
4 changes: 2 additions & 2 deletions lib/ts/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ export class User implements UserType {
}[];
public readonly webauthn: {
credentialIds: string[];
}[];
};
public readonly loginMethods: LoginMethod[];

public readonly timeJoined: number; // minimum timeJoined value from linkedRecipes
Expand Down Expand Up @@ -143,7 +143,7 @@ export type UserWithoutHelperFunctions = {
}[];
webauthn: {
credentialIds: string[];
}[];
};
loginMethods: {
recipeId: "emailpassword" | "thirdparty" | "passwordless" | "webauthn";
recipeUserId: string;
Expand Down

0 comments on commit b8cffc1

Please sign in to comment.