From 3276174dc53dc967e47673ce2338a8eef98332da Mon Sep 17 00:00:00 2001 From: Victor Bojica Date: Fri, 14 Feb 2025 13:35:39 +0200 Subject: [PATCH] added more error tests --- lib/ts/recipe/webauthn/types.ts | 22 +-- test/webauthn/recipeImplementation.test.js | 215 ++++++++++++++++++--- 2 files changed, 200 insertions(+), 37 deletions(-) diff --git a/lib/ts/recipe/webauthn/types.ts b/lib/ts/recipe/webauthn/types.ts index 703e0de5f..ee301bbec 100644 --- a/lib/ts/recipe/webauthn/types.ts +++ b/lib/ts/recipe/webauthn/types.ts @@ -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 @@ -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 diff --git a/test/webauthn/recipeImplementation.test.js b/test/webauthn/recipeImplementation.test.js index e62eae8d8..aecc995d8 100644 --- a/test/webauthn/recipeImplementation.test.js +++ b/test/webauthn/recipeImplementation.test.js @@ -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({ @@ -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({ @@ -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 = "test@example.com"; @@ -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 = "test@example.com"; + 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 = "test@example.com"; + 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 = "test@example.com"; + 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 = "test@example.com"; + 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 = "test@example.com"; + 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 = "test@example.com"; + 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(); @@ -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({ @@ -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(); @@ -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, @@ -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 = "test@example.com"; @@ -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 = "test@example.com"; @@ -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 = "test@example.com"; - 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, { @@ -486,6 +630,8 @@ describe(`recipeImplementationFunctions: ${printPath("[test/webauthn/recipeImple userContext, }); + console.log(signInResponse); + assert.equal(signInResponse.status, "CREDENTIAL_NOT_FOUND_ERROR"); }); @@ -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,