Skip to content

Commit

Permalink
added more error tests
Browse files Browse the repository at this point in the history
  • Loading branch information
niftyvictor committed Feb 14, 2025
1 parent ade3611 commit 3276174
Show file tree
Hide file tree
Showing 2 changed files with 200 additions and 37 deletions.
22 changes: 11 additions & 11 deletions lib/ts/recipe/webauthn/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,16 +101,16 @@ export type TypeInputValidateEmailAddress = (
type RegisterOptionsErrorResponse =
| { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR" } // test yes
| { status: "INVALID_EMAIL_ERROR"; err: string } // test yes
| { status: "INVALID_OPTIONS_ERROR" }; // test no - no validation on the core yet
| { status: "INVALID_OPTIONS_ERROR" }; // test yes

type SignInOptionsErrorResponse = { status: "INVALID_OPTIONS_ERROR" }; // test no - no validation on the core yet
type SignInOptionsErrorResponse = { status: "INVALID_OPTIONS_ERROR" }; // test yes

type CreateNewRecipeUserErrorResponse =
| { status: "EMAIL_ALREADY_EXISTS_ERROR" } // test yes
| { status: "INVALID_CREDENTIALS_ERROR" } // test yes - possibly wrong implementation
| { status: "OPTIONS_NOT_FOUND_ERROR" } // test yes
| { status: "INVALID_OPTIONS_ERROR" } // test no - no validation on the core yet
| { status: "INVALID_AUTHENTICATOR_ERROR"; reason: string }; // test no - might drop this in favor of invalid credentials if not possible to distinguish
| { status: "INVALID_OPTIONS_ERROR" } // test yes
| { status: "INVALID_CREDENTIALS_ERROR" } // test yes
| { status: "INVALID_AUTHENTICATOR_ERROR"; reason: string }; // test no

type SignUpErrorResponse =
| CreateNewRecipeUserErrorResponse
Expand All @@ -124,12 +124,12 @@ type SignUpErrorResponse =
};

type VerifyCredentialsErrorResponse =
| { status: "INVALID_CREDENTIALS_ERROR" }
| { status: "INVALID_OPTIONS_ERROR" }
| { status: "INVALID_AUTHENTICATOR_ERROR" }
| { status: "CREDENTIAL_NOT_FOUND_ERROR" }
| { status: "UNKNOWN_USER_ID_ERROR" }
| { status: "OPTIONS_NOT_FOUND_ERROR" };
| { status: "INVALID_CREDENTIALS_ERROR" } // test yes
| { status: "INVALID_OPTIONS_ERROR" } // test yes
| { status: "INVALID_AUTHENTICATOR_ERROR" } // test no
| { status: "CREDENTIAL_NOT_FOUND_ERROR" } // test yes
| { status: "UNKNOWN_USER_ID_ERROR" } // this shouldn't happen - there should be a fk between the user and the credential
| { status: "OPTIONS_NOT_FOUND_ERROR" }; // test yes

type SignInErrorResponse =
| VerifyCredentialsErrorResponse
Expand Down
215 changes: 189 additions & 26 deletions test/webauthn/recipeImplementation.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ describe(`recipeImplementationFunctions: ${printPath("[test/webauthn/recipeImple
assert.equal(generatedOptions.user.displayName, email);
});

it("should throw an error if the recover account token is invalid", async function () {
it("should return the correct error if the recover account token is invalid", async function () {
await initST();

const generatedOptions = await getWebAuthnRecipe().recipeInterfaceImpl.registerOptions({
Expand All @@ -118,7 +118,7 @@ describe(`recipeImplementationFunctions: ${printPath("[test/webauthn/recipeImple
assert.equal(generatedOptions.status, "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR");
});

it("should throw an error if the email is invalid", async function () {
it("should return the correct error if the email is invalid", async function () {
await initST();

const generatedOptions = await getWebAuthnRecipe().recipeInterfaceImpl.registerOptions({
Expand All @@ -133,7 +133,7 @@ describe(`recipeImplementationFunctions: ${printPath("[test/webauthn/recipeImple
assert.equal(generatedOptions.status, "INVALID_EMAIL_ERROR");
});

it("should return the correct error if the passed options are invalid", async function () {
it("when attestation is invalid, should return the correct error", async function () {
await initST();

const email = "[email protected]";
Expand All @@ -150,6 +150,108 @@ describe(`recipeImplementationFunctions: ${printPath("[test/webauthn/recipeImple
assert.equal(generatedOptions.status, "INVALID_OPTIONS_ERROR");
});

it("when residentKey is invalid, should return the correct error", async function () {
await initST();

const email = "[email protected]";
const generatedOptions = await getWebAuthnRecipe().recipeInterfaceImpl.registerOptions({
relyingPartyId: rpId,
relyingPartyName: rpName,
residentKey: "invalid",
origin,
email,
tenantId: "public",
userContext,
});

assert.equal(generatedOptions.status, "INVALID_OPTIONS_ERROR");
});

it("when supportedAlgorithmIds is invalid, should return the correct error", async function () {
await initST();

const email = "[email protected]";
const generatedOptions = await getWebAuthnRecipe().recipeInterfaceImpl.registerOptions({
relyingPartyId: rpId,
relyingPartyName: rpName,
supportedAlgorithmIds: "invalid",
origin,
email,
tenantId: "public",
userContext,
});

assert.equal(generatedOptions.status, "INVALID_OPTIONS_ERROR");
});

it("when timeout is negative, should return the correct error", async function () {
await initST();

const email = "[email protected]";
const generatedOptions = await getWebAuthnRecipe().recipeInterfaceImpl.registerOptions({
relyingPartyId: rpId,
relyingPartyName: rpName,
timeout: -1000,
origin,
email,
tenantId: "public",
userContext,
});

assert.equal(generatedOptions.status, "INVALID_OPTIONS_ERROR");
});

it("when timeout is invalid, should return the correct error", async function () {
await initST();

const email = "[email protected]";
const generatedOptions = await getWebAuthnRecipe().recipeInterfaceImpl.registerOptions({
relyingPartyId: rpId,
relyingPartyName: rpName,
timeout: "invalid",
origin,
email,
tenantId: "public",
userContext,
});

assert.equal(generatedOptions.status, "INVALID_OPTIONS_ERROR");
});

it("when userPresence is invalid, should return the correct error", async function () {
await initST();

const email = "[email protected]";
const generatedOptions = await getWebAuthnRecipe().recipeInterfaceImpl.registerOptions({
relyingPartyId: rpId,
relyingPartyName: rpName,
userPresence: "invalid",
origin,
email,
tenantId: "public",
userContext,
});

assert.equal(generatedOptions.status, "INVALID_OPTIONS_ERROR");
});

it("when userVerification is invalid, should return the correct error", async function () {
await initST();

const email = "[email protected]";
const generatedOptions = await getWebAuthnRecipe().recipeInterfaceImpl.registerOptions({
relyingPartyId: rpId,
relyingPartyName: rpName,
userVerification: "invalid",
origin,
email,
tenantId: "public",
userContext,
});

assert.equal(generatedOptions.status, "INVALID_OPTIONS_ERROR");
});

it("should return the correct error if the options origin does not match the relying party id", async function () {
await initST();

Expand Down Expand Up @@ -189,7 +291,7 @@ describe(`recipeImplementationFunctions: ${printPath("[test/webauthn/recipeImple
assert.equal(generatedOptions.createdAt + generatedOptions.timeout, generatedOptions.expiresAt);
});

it("should return the correct error if the passed options are invalid", async function () {
it("when userVerification is invalid, should return the correct error", async function () {
await initST();

const generatedOptions = await getWebAuthnRecipe().recipeInterfaceImpl.signInOptions({
Expand All @@ -204,6 +306,51 @@ describe(`recipeImplementationFunctions: ${printPath("[test/webauthn/recipeImple
assert.equal(generatedOptions.status, "INVALID_OPTIONS_ERROR");
});

it("when userPresence is invalid, should return the correct error", async function () {
await initST();

const generatedOptions = await getWebAuthnRecipe().recipeInterfaceImpl.signInOptions({
relyingPartyId: rpId,
relyingPartyName: rpName,
userPresence: "invalid",
origin,
tenantId: "public",
userContext,
});

assert.equal(generatedOptions.status, "INVALID_OPTIONS_ERROR");
});

it("when timeout is invalid, should return the correct error", async function () {
await initST();

const generatedOptions = await getWebAuthnRecipe().recipeInterfaceImpl.signInOptions({
relyingPartyId: rpId,
relyingPartyName: rpName,
timeout: "invalid",
origin,
tenantId: "public",
userContext,
});

assert.equal(generatedOptions.status, "INVALID_OPTIONS_ERROR");
});

it("when timeout is negative, should return the correct error", async function () {
await initST();

const generatedOptions = await getWebAuthnRecipe().recipeInterfaceImpl.signInOptions({
relyingPartyId: rpId,
relyingPartyName: rpName,
timeout: -1000,
origin,
tenantId: "public",
userContext,
});

assert.equal(generatedOptions.status, "INVALID_OPTIONS_ERROR");
});

it("should return the correct error if the options origin does not match the relying party id", async function () {
await initST();

Expand Down Expand Up @@ -325,8 +472,8 @@ describe(`recipeImplementationFunctions: ${printPath("[test/webauthn/recipeImple
const signUpResponse2 = await getWebAuthnRecipe().recipeInterfaceImpl.signUp({
webauthnGeneratedOptionsId: registerOptionsResponse.webauthnGeneratedOptionsId,
credential: {
type: 1,
...credential,
type: 1,
},
tenantId: "public",
userContext,
Expand All @@ -335,7 +482,7 @@ describe(`recipeImplementationFunctions: ${printPath("[test/webauthn/recipeImple
assert.equal(signUpResponse2.status, "INVALID_CREDENTIALS_ERROR");
});

it("should return the correct error if the options do not exist", async function () {
it("when credential id is null or undefined, should return the correct error if the credential is invalid", async function () {
await initST();

const email = "[email protected]";
Expand All @@ -344,24 +491,27 @@ describe(`recipeImplementationFunctions: ${printPath("[test/webauthn/recipeImple

const { createCredential } = await getWebauthnLib();
const credential = createCredential(registerOptionsResponse, {
rpId,
rpName,
origin,
rpId: rpId,
rpName: rpName,
origin: origin,
userNotPresent: false,
userNotVerified: false,
});

const signUpResponse = await getWebAuthnRecipe().recipeInterfaceImpl.signUp({
webauthnGeneratedOptionsId: "invalid",
credential,
const signUpResponse2 = await getWebAuthnRecipe().recipeInterfaceImpl.signUp({
webauthnGeneratedOptionsId: registerOptionsResponse.webauthnGeneratedOptionsId,
credential: {
...credential,
id: null,
},
tenantId: "public",
userContext,
});

assert.equal(signUpResponse.status, "OPTIONS_NOT_FOUND_ERROR");
assert.equal(signUpResponse2.status, "INVALID_CREDENTIALS_ERROR");
});

it("should return the correct error if the origin of the credential does not match the origin of the options", async function () {
it("should return the correct error if the options do not exist", async function () {
await initST();

const email = "[email protected]";
Expand All @@ -372,33 +522,27 @@ describe(`recipeImplementationFunctions: ${printPath("[test/webauthn/recipeImple
const credential = createCredential(registerOptionsResponse, {
rpId,
rpName,
origin: "https://test.com",
origin,
userNotPresent: false,
userNotVerified: false,
});

const signUpResponse = await getWebAuthnRecipe().recipeInterfaceImpl.signUp({
webauthnGeneratedOptionsId: registerOptionsResponse.webauthnGeneratedOptionsId,
webauthnGeneratedOptionsId: "invalid",
credential,
tenantId: "public",
userContext,
});

assert.equal(signUpResponse.status, "INVALID_OPTIONS_ERROR");
assert.equal(signUpResponse.status, "OPTIONS_NOT_FOUND_ERROR");
});

it("should return the correct error if the origin of the credential is not part of the relying party id", async function () {
it("should return the correct error if the origin of the credential does not match the origin of the options", async function () {
await initST();

const email = "[email protected]";
const registerOptionsResponse = await getWebAuthnRecipe().recipeInterfaceImpl.registerOptions({
relyingPartyId: rpId,
relyingPartyName: rpName,
origin: "https://test.com",
email,
tenantId: "public",
userContext,
});
const registerOptionsResponse = await createRegisterOptions(email);
assert(registerOptionsResponse.status === "OK");

const { createCredential } = await getWebauthnLib();
const credential = createCredential(registerOptionsResponse, {
Expand Down Expand Up @@ -486,6 +630,8 @@ describe(`recipeImplementationFunctions: ${printPath("[test/webauthn/recipeImple
userContext,
});

console.log(signInResponse);

assert.equal(signInResponse.status, "CREDENTIAL_NOT_FOUND_ERROR");
});

Expand Down Expand Up @@ -546,8 +692,25 @@ describe(`recipeImplementationFunctions: ${printPath("[test/webauthn/recipeImple
const signInResponse2 = await getWebAuthnRecipe().recipeInterfaceImpl.signIn({
webauthnGeneratedOptionsId: signInOptionsResponse.webauthnGeneratedOptionsId,
credential: {
...credential.assertion,
type: 1,
},
tenantId: "public",
userContext,
});
assert.equal(signInResponse2.status, "INVALID_CREDENTIALS_ERROR");
});

it("when credential id is null or undefined, should return the correct error if the credential is invalid", async function () {
await initST();

const { signUpResponse, signInOptionsResponse, credential } = await createUser(rpId, rpName, origin);

const signInResponse2 = await getWebAuthnRecipe().recipeInterfaceImpl.signIn({
webauthnGeneratedOptionsId: signInOptionsResponse.webauthnGeneratedOptionsId,
credential: {
...credential.assertion,
id: null,
},
tenantId: "public",
userContext,
Expand Down

0 comments on commit 3276174

Please sign in to comment.