From d79d6cbfc10c0150e900208857facafad7b6df6e Mon Sep 17 00:00:00 2001 From: Victor Bojica Date: Fri, 14 Feb 2025 16:01:03 +0200 Subject: [PATCH] test fixes, mising tests and build code --- lib/build/recipe/webauthn/api/signup.js | 13 - lib/build/recipe/webauthn/index.d.ts | 6 +- .../recipe/webauthn/recipeImplementation.js | 4 +- lib/build/recipe/webauthn/types.d.ts | 6 +- lib/ts/recipe/webauthn/api/signup.ts | 1 - .../recipe/webauthn/recipeImplementation.ts | 6 +- lib/ts/recipe/webauthn/types.ts | 26 +- test/webauthn/apis.test.js | 2 +- test/webauthn/recipeImplementation.test.js | 678 ++++++++++-------- 9 files changed, 411 insertions(+), 331 deletions(-) diff --git a/lib/build/recipe/webauthn/api/signup.js b/lib/build/recipe/webauthn/api/signup.js index c196907c7..cdff2a598 100644 --- a/lib/build/recipe/webauthn/api/signup.js +++ b/lib/build/recipe/webauthn/api/signup.js @@ -13,15 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; Object.defineProperty(exports, "__esModule", { value: true }); const utils_1 = require("../../../utils"); const utils_2 = require("./utils"); -const error_1 = __importDefault(require("../error")); const authUtils_1 = require("../../../authUtils"); async function signUpAPI(apiImplementation, tenantId, options, userContext) { if (apiImplementation.signUpPOST === undefined) { @@ -59,13 +53,6 @@ async function signUpAPI(apiImplementation, tenantId, options, userContext) { options.res, Object.assign({ status: "OK" }, utils_1.getBackwardsCompatibleUserInfo(options.req, result, userContext)) ); - } else if (result.status === "GENERAL_ERROR") { - utils_1.send200Response(options.res, result); - } else if (result.status === "EMAIL_ALREADY_EXISTS_ERROR") { - throw new error_1.default({ - type: error_1.default.BAD_INPUT_ERROR, - message: "This email already exists. Please sign in instead.", - }); } else { utils_1.send200Response(options.res, result); } diff --git a/lib/build/recipe/webauthn/index.d.ts b/lib/build/recipe/webauthn/index.d.ts index e4bece567..bb6e0de0c 100644 --- a/lib/build/recipe/webauthn/index.d.ts +++ b/lib/build/recipe/webauthn/index.d.ts @@ -185,15 +185,15 @@ export default class Wrapper { | { status: "EMAIL_ALREADY_EXISTS_ERROR"; } - | { - status: "INVALID_CREDENTIALS_ERROR"; - } | { status: "OPTIONS_NOT_FOUND_ERROR"; } | { status: "INVALID_OPTIONS_ERROR"; } + | { + status: "INVALID_CREDENTIALS_ERROR"; + } | { status: "INVALID_AUTHENTICATOR_ERROR"; reason: string; diff --git a/lib/build/recipe/webauthn/recipeImplementation.js b/lib/build/recipe/webauthn/recipeImplementation.js index 96c6f295a..91117d902 100644 --- a/lib/build/recipe/webauthn/recipeImplementation.js +++ b/lib/build/recipe/webauthn/recipeImplementation.js @@ -363,10 +363,10 @@ function getRecipeInterface(querier, getWebauthnConfig) { new normalisedURLPath_1.default( `/${ tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : tenantId - }/recipe/webauthn/options/${webauthnGeneratedOptionsId}` + }/recipe/webauthn/options/remove` ), {}, - {}, + { webauthnGeneratedOptionsId }, userContext ); }, diff --git a/lib/build/recipe/webauthn/types.d.ts b/lib/build/recipe/webauthn/types.d.ts index 96472bbb2..bfe7dcfa9 100644 --- a/lib/build/recipe/webauthn/types.d.ts +++ b/lib/build/recipe/webauthn/types.d.ts @@ -90,15 +90,15 @@ declare type CreateNewRecipeUserErrorResponse = | { status: "EMAIL_ALREADY_EXISTS_ERROR"; } - | { - status: "INVALID_CREDENTIALS_ERROR"; - } | { status: "OPTIONS_NOT_FOUND_ERROR"; } | { status: "INVALID_OPTIONS_ERROR"; } + | { + status: "INVALID_CREDENTIALS_ERROR"; + } | { status: "INVALID_AUTHENTICATOR_ERROR"; reason: string; diff --git a/lib/ts/recipe/webauthn/api/signup.ts b/lib/ts/recipe/webauthn/api/signup.ts index f78f30e8d..aa3a0a6be 100644 --- a/lib/ts/recipe/webauthn/api/signup.ts +++ b/lib/ts/recipe/webauthn/api/signup.ts @@ -20,7 +20,6 @@ import { } from "../../../utils"; import { validateWebauthnGeneratedOptionsIdOrThrowError, validateCredentialOrThrowError } from "./utils"; import { APIInterface, APIOptions } from ".."; -import STError from "../error"; import { UserContext } from "../../../types"; import { AuthUtils } from "../../../authUtils"; diff --git a/lib/ts/recipe/webauthn/recipeImplementation.ts b/lib/ts/recipe/webauthn/recipeImplementation.ts index 9e8eaba38..62b7174c0 100644 --- a/lib/ts/recipe/webauthn/recipeImplementation.ts +++ b/lib/ts/recipe/webauthn/recipeImplementation.ts @@ -345,12 +345,10 @@ export default function getRecipeInterface( removeGeneratedOptions: async function ({ webauthnGeneratedOptionsId, tenantId, userContext }) { return await querier.sendDeleteRequest( new NormalisedURLPath( - `/${ - tenantId === undefined ? DEFAULT_TENANT_ID : tenantId - }/recipe/webauthn/options/${webauthnGeneratedOptionsId}` + `/${tenantId === undefined ? DEFAULT_TENANT_ID : tenantId}/recipe/webauthn/options/remove` ), {}, - {}, + { webauthnGeneratedOptionsId }, userContext ); }, diff --git a/lib/ts/recipe/webauthn/types.ts b/lib/ts/recipe/webauthn/types.ts index ee301bbec..00383b8df 100644 --- a/lib/ts/recipe/webauthn/types.ts +++ b/lib/ts/recipe/webauthn/types.ts @@ -134,7 +134,7 @@ type VerifyCredentialsErrorResponse = type SignInErrorResponse = | VerifyCredentialsErrorResponse | { - status: "LINKING_TO_SESSION_USER_FAILED"; + status: "LINKING_TO_SESSION_USER_FAILED"; // test no reason: | "EMAIL_VERIFICATION_REQUIRED" | "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" @@ -142,27 +142,27 @@ type SignInErrorResponse = | "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; }; -type GenerateRecoverAccountTokenErrorResponse = { status: "UNKNOWN_USER_ID_ERROR" }; +type GenerateRecoverAccountTokenErrorResponse = { status: "UNKNOWN_USER_ID_ERROR" }; // test yes -type ConsumeRecoverAccountTokenErrorResponse = { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR" }; +type ConsumeRecoverAccountTokenErrorResponse = { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR" }; // test yes type RegisterCredentialErrorResponse = - | { status: "INVALID_CREDENTIALS_ERROR" } - | { status: "OPTIONS_NOT_FOUND_ERROR" } - | { status: "INVALID_OPTIONS_ERROR" } - | { status: "INVALID_AUTHENTICATOR_ERROR"; reason: string }; + | { status: "INVALID_CREDENTIALS_ERROR" } // test yes + | { status: "OPTIONS_NOT_FOUND_ERROR" } // test yes + | { status: "INVALID_OPTIONS_ERROR" } // test yes + | { status: "INVALID_AUTHENTICATOR_ERROR"; reason: string }; // test yes - only if cred is for other rpId -type GetUserFromRecoverAccountTokenErrorResponse = { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR" }; +type GetUserFromRecoverAccountTokenErrorResponse = { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR" }; // test yes -type RemoveCredentialErrorResponse = { status: "CREDENTIAL_NOT_FOUND_ERROR" }; +type RemoveCredentialErrorResponse = { status: "CREDENTIAL_NOT_FOUND_ERROR" }; // test yes -type GetCredentialErrorResponse = { status: "CREDENTIAL_NOT_FOUND_ERROR" }; +type GetCredentialErrorResponse = { status: "CREDENTIAL_NOT_FOUND_ERROR" }; // test yes -type RemoveGeneratedOptionsErrorResponse = { status: "OPTIONS_NOT_FOUND_ERROR" }; +type RemoveGeneratedOptionsErrorResponse = { status: "OPTIONS_NOT_FOUND_ERROR" }; // test yes -type GetGeneratedOptionsErrorResponse = { status: "OPTIONS_NOT_FOUND_ERROR" }; +type GetGeneratedOptionsErrorResponse = { status: "OPTIONS_NOT_FOUND_ERROR" }; // test yes -type UpdateUserEmailErrorResponse = { status: "EMAIL_ALREADY_EXISTS_ERROR" } | { status: "UNKNOWN_USER_ID_ERROR" }; +type UpdateUserEmailErrorResponse = { status: "EMAIL_ALREADY_EXISTS_ERROR" } | { status: "UNKNOWN_USER_ID_ERROR" }; // test yes type Base64URLString = string; diff --git a/test/webauthn/apis.test.js b/test/webauthn/apis.test.js index f2de2809c..08fbd3214 100644 --- a/test/webauthn/apis.test.js +++ b/test/webauthn/apis.test.js @@ -946,7 +946,7 @@ describe(`apisFunctions: ${printPath("[test/webauthn/apis.test.js]")}`, function } }) ); - assert.equal(recoverAccountResponse.status, "INVALID_OPTIONS_ERROR"); + assert.equal(recoverAccountResponse.status, "INVALID_AUTHENTICATOR_ERROR"); }); }); }); diff --git a/test/webauthn/recipeImplementation.test.js b/test/webauthn/recipeImplementation.test.js index aecc995d8..139bfa293 100644 --- a/test/webauthn/recipeImplementation.test.js +++ b/test/webauthn/recipeImplementation.test.js @@ -372,7 +372,7 @@ describe(`recipeImplementationFunctions: ${printPath("[test/webauthn/recipeImple const email = "test@example.com"; const registerOptionsResponse = await createRegisterOptions(email); - assert(registerOptionsResponse.status === "OK"); + assert.equal(registerOptionsResponse.status, "OK"); const { createCredential } = await getWebauthnLib(); const credential = createCredential(registerOptionsResponse, { @@ -404,7 +404,7 @@ describe(`recipeImplementationFunctions: ${printPath("[test/webauthn/recipeImple const { email, signUpResponse: existingUser } = await createUser(rpId, rpName, origin); const registerOptionsResponse = await createRegisterOptions(email); - assert(registerOptionsResponse.status === "OK"); + assert.equal(registerOptionsResponse.status, "OK"); const { createCredential } = await getWebauthnLib(); const credential = createCredential(registerOptionsResponse, { @@ -430,7 +430,7 @@ describe(`recipeImplementationFunctions: ${printPath("[test/webauthn/recipeImple const email = "test@example.com"; const registerOptionsResponse = await createRegisterOptions(email); - assert(registerOptionsResponse.status === "OK"); + assert.equal(registerOptionsResponse.status, "OK"); const { createCredential } = await getWebauthnLib(); const credential = createCredential(registerOptionsResponse, { @@ -458,7 +458,7 @@ describe(`recipeImplementationFunctions: ${printPath("[test/webauthn/recipeImple const email = "test@example.com"; const registerOptionsResponse = await createRegisterOptions(email); - assert(registerOptionsResponse.status === "OK"); + assert.equal(registerOptionsResponse.status, "OK"); const { createCredential } = await getWebauthnLib(); const credential = createCredential(registerOptionsResponse, { @@ -487,7 +487,7 @@ describe(`recipeImplementationFunctions: ${printPath("[test/webauthn/recipeImple const email = "test@example.com"; const registerOptionsResponse = await createRegisterOptions(email); - assert(registerOptionsResponse.status === "OK"); + assert.equal(registerOptionsResponse.status, "OK"); const { createCredential } = await getWebauthnLib(); const credential = createCredential(registerOptionsResponse, { @@ -516,7 +516,7 @@ describe(`recipeImplementationFunctions: ${printPath("[test/webauthn/recipeImple const email = "test@example.com"; const registerOptionsResponse = await createRegisterOptions(email); - assert(registerOptionsResponse.status === "OK"); + assert.equal(registerOptionsResponse.status, "OK"); const { createCredential } = await getWebauthnLib(); const credential = createCredential(registerOptionsResponse, { @@ -542,7 +542,7 @@ describe(`recipeImplementationFunctions: ${printPath("[test/webauthn/recipeImple const email = "test@example.com"; const registerOptionsResponse = await createRegisterOptions(email); - assert(registerOptionsResponse.status === "OK"); + assert.equal(registerOptionsResponse.status, "OK"); const { createCredential } = await getWebauthnLib(); const credential = createCredential(registerOptionsResponse, { @@ -568,7 +568,7 @@ describe(`recipeImplementationFunctions: ${printPath("[test/webauthn/recipeImple const email = "test@example.com"; const registerOptionsResponse = await createRegisterOptions(email); - assert(registerOptionsResponse.status === "OK"); + assert.equal(registerOptionsResponse.status, "OK"); const { createCredential } = await getWebauthnLib(); const credential = createCredential(registerOptionsResponse, { @@ -630,8 +630,6 @@ describe(`recipeImplementationFunctions: ${printPath("[test/webauthn/recipeImple userContext, }); - console.log(signInResponse); - assert.equal(signInResponse.status, "CREDENTIAL_NOT_FOUND_ERROR"); }); @@ -719,107 +717,43 @@ describe(`recipeImplementationFunctions: ${printPath("[test/webauthn/recipeImple }); }); - describe.skip("[getGeneratedOptions]", function () { - it("returns all the required fields", async function () { - await initST(); - - // passing valid field - let registerOptionsResponse = await createRegisterOptions("test@example.com"); - - assert(registerOptionsResponse.status === "OK"); - - const generatedOptions = await getWebAuthnRecipe().recipeInterfaceImpl.getGeneratedOptions({ - webauthnGeneratedOptionsId: registerOptionsResponse.webauthnGeneratedOptionsId, - userContext, - tenantId: "public", - }); - - assert(generatedOptions.status === "OK"); - - assert(generatedOptions.origin === origin); - assert(generatedOptions.email === "test@example.com"); - assert(generatedOptions.relyingPartyId === rpId); - assert(generatedOptions.relyingPartyName === rpName); - assert(generatedOptions.userVerification === "preferred"); - assert(generatedOptions.userPresence === true); - assert(typeof generatedOptions.webauthnGeneratedOptionsId === "string"); - assert(typeof generatedOptions.challenge === "string"); - assert(typeof generatedOptions.createdAt === "number"); - assert(typeof generatedOptions.expiresAt === "number"); - assert(typeof generatedOptions.timeout === "number"); - }); - - it("returns the correct error if the options id is invalid", async function () { + describe("[generateRecoverAccountToken]", function () { + it("should generate a recover account token", async function () { await initST(); - // passing valid field - let registerOptionsResponse = await createRegisterOptions("test@example.com"); - - assert(registerOptionsResponse.status === "OK"); - - const generatedOptions = await getWebAuthnRecipe().recipeInterfaceImpl.getGeneratedOptions({ - webauthnGeneratedOptionsId: "invalid", - userContext, - tenantId: "public", - }); - - assert(generatedOptions.status === "OPTIONS_NOT_FOUND_ERROR"); - }); - }); - - describe.skip("[generateRecoverAccountToken]", function () { - it("should return an error if the user doesn't exist", async function () { - await initST(); + const { email, signUpResponse } = await createUser(rpId, rpName, origin); const generateRecoverAccountTokenResponse = await getWebAuthnRecipe().recipeInterfaceImpl.generateRecoverAccountToken( { - userId: "test", - email: "test@supertokens.com", + userId: signUpResponse.user.id, + email, tenantId: "public", userContext, } ); - assert(generateRecoverAccountTokenResponse.status === "UNKNOWN_USER_ID_ERROR"); + assert.equal(generateRecoverAccountTokenResponse.status, "OK"); + assert.equal(typeof generateRecoverAccountTokenResponse.token, "string"); }); - it("should generate a recover account token", async function () { + it("should return an error if the user doesn't exist", async function () { await initST(); - const { email, signUpResponse } = await createUser(rpId, rpName, origin); - const generateRecoverAccountTokenResponse = await getWebAuthnRecipe().recipeInterfaceImpl.generateRecoverAccountToken( { - userId: signUpResponse.user.id, - email, + userId: "test", + email: "test@supertokens.com", tenantId: "public", userContext, } ); - assert(generateRecoverAccountTokenResponse.status === "OK"); - assert(typeof generateRecoverAccountTokenResponse.token === "string"); + assert.equal(generateRecoverAccountTokenResponse.status, "UNKNOWN_USER_ID_ERROR"); }); }); - describe.skip("[getUserFromRecoverAccountToken]", function () { - it("throws an error if the token is invalid", async function () { - await initST(); - - const app = express(); - app.use(middleware()); - app.use(errorHandler()); - - const user = await getWebAuthnRecipe().recipeInterfaceImpl.getUserFromRecoverAccountToken({ - token: "test", - tenantId: "public", - userContext, - }); - - assert(user.status === "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR"); - }); - - it("return the correct user", async function () { + describe("[consumeRecoverAccountToken]", function () { + it("should consume the token", async function () { await initST(); const { email, signUpResponse } = await createUser(rpId, rpName, origin); @@ -832,9 +766,9 @@ describe(`recipeImplementationFunctions: ${printPath("[test/webauthn/recipeImple userContext, } ); - assert(generateRecoverAccountTokenResponse.status === "OK"); + assert.equal(generateRecoverAccountTokenResponse.status, "OK"); - const getUserFromRecoverAccountTokenResponse = await getWebAuthnRecipe().recipeInterfaceImpl.getUserFromRecoverAccountToken( + const consumeRecoverAccountTokenResponse = await getWebAuthnRecipe().recipeInterfaceImpl.consumeRecoverAccountToken( { token: generateRecoverAccountTokenResponse.token, tenantId: "public", @@ -842,13 +776,11 @@ describe(`recipeImplementationFunctions: ${printPath("[test/webauthn/recipeImple } ); - assert(getUserFromRecoverAccountTokenResponse.status === "OK"); - assert(!!getUserFromRecoverAccountTokenResponse.user); - assert(!!getUserFromRecoverAccountTokenResponse.user.emails.find((e) => e === email)); + assert.equal(consumeRecoverAccountTokenResponse.status, "OK"); + assert.equal(consumeRecoverAccountTokenResponse.userId, signUpResponse.user.id); + assert.equal(consumeRecoverAccountTokenResponse.email, email); }); - }); - describe.skip("[consumeRecoverAccountToken]", function () { it("should return an error if the token is invalid", async function () { await initST(); @@ -860,66 +792,17 @@ describe(`recipeImplementationFunctions: ${printPath("[test/webauthn/recipeImple } ); - assert(consumeRecoverAccountTokenResponse.status === "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR"); - }); - - it("should consume the token", async function () { - await initST(); - - const { email, signUpResponse } = await createUser(rpId, rpName, origin); - - const generateRecoverAccountTokenResponse = await getWebAuthnRecipe().recipeInterfaceImpl.generateRecoverAccountToken( - { - userId: signUpResponse.user.id, - email, - tenantId: "public", - userContext, - } - ); - assert(generateRecoverAccountTokenResponse.status === "OK"); - - const consumeRecoverAccountTokenResponse = await getWebAuthnRecipe().recipeInterfaceImpl.consumeRecoverAccountToken( - { - token: generateRecoverAccountTokenResponse.token, - tenantId: "public", - userContext, - } - ); - - assert(consumeRecoverAccountTokenResponse.status === "OK"); - assert(consumeRecoverAccountTokenResponse.userId === signUpResponse.user.id); - assert(consumeRecoverAccountTokenResponse.email === email); + assert.equal(consumeRecoverAccountTokenResponse.status, "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR"); }); }); - describe.skip("[registerCredential]", function () { + describe("[registerCredential]", function () { it("should create a new credential for an existing user", async function () { await initST(); const { email, signUpResponse } = await createUser(rpId, rpName, origin); - const app = express(); - app.use(middleware()); - app.use(errorHandler()); - - let registerOptionsResponse = await new Promise((resolve, reject) => - request(app) - // @ts-ignore - .post("/auth/webauthn/options/register") - .send({ - email, - }) - .expect(200) - .end((err, res) => { - if (err) { - reject(err); - } else { - resolve(JSON.parse(res.text)); - } - }) - ); - - assert(registerOptionsResponse.status === "OK"); + const registerOptionsResponse = await createRegisterOptions(email); const { createCredential } = await getWebauthnLib(); const credential = createCredential(registerOptionsResponse, { @@ -937,7 +820,7 @@ describe(`recipeImplementationFunctions: ${printPath("[test/webauthn/recipeImple userContext, }); - assert(registerCredentialResponse.status === "OK"); + assert.equal(registerCredentialResponse.status, "OK"); }); it("should create multiple new credentials for an existing user", async function () { @@ -945,28 +828,9 @@ describe(`recipeImplementationFunctions: ${printPath("[test/webauthn/recipeImple const { email, signUpResponse } = await createUser(rpId, rpName, origin); - const app = express(); - app.use(middleware()); - app.use(errorHandler()); - - let registerOptionsResponse1 = await new Promise((resolve, reject) => - request(app) - // @ts-ignore - .post("/auth/webauthn/options/register") - .send({ - email, - }) - .expect(200) - .end((err, res) => { - if (err) { - reject(err); - } else { - resolve(JSON.parse(res.text)); - } - }) - ); + const registerOptionsResponse1 = await createRegisterOptions(email); - assert(registerOptionsResponse1.status === "OK"); + assert.equal(registerOptionsResponse1.status, "OK"); const { createCredential } = await getWebauthnLib(); const credential1 = createCredential(registerOptionsResponse1, { @@ -984,26 +848,9 @@ describe(`recipeImplementationFunctions: ${printPath("[test/webauthn/recipeImple userContext, }); - assert(registerCredentialResponse1.status === "OK"); - - let registerOptionsResponse2 = await new Promise((resolve, reject) => - request(app) - // @ts-ignore - .post("/auth/webauthn/options/register") - .send({ - email, - }) - .expect(200) - .end((err, res) => { - if (err) { - reject(err); - } else { - resolve(JSON.parse(res.text)); - } - }) - ); + assert.equal(registerCredentialResponse1.status, "OK"); - assert(registerOptionsResponse2.status === "OK"); + const registerOptionsResponse2 = await createRegisterOptions(email); const credential2 = createCredential(registerOptionsResponse2, { rpId, @@ -1020,36 +867,15 @@ describe(`recipeImplementationFunctions: ${printPath("[test/webauthn/recipeImple userContext, }); - assert(registerCredentialResponse2.status === "OK"); + assert.equal(registerCredentialResponse2.status, "OK"); }); - it("should return a parsable error if the options id is invalid", async function () { + it("should return the correct error if the options id is wrong", async function () { await initST(); const { email, signUpResponse } = await createUser(rpId, rpName, origin); - const app = express(); - app.use(middleware()); - app.use(errorHandler()); - - let registerOptionsResponse = await new Promise((resolve, reject) => - request(app) - // @ts-ignore - .post("/auth/webauthn/options/register") - .send({ - email, - }) - .expect(200) - .end((err, res) => { - if (err) { - reject(err); - } else { - resolve(JSON.parse(res.text)); - } - }) - ); - - assert(registerOptionsResponse.status === "OK"); + const registerOptionsResponse = await createRegisterOptions(email); const { createCredential } = await getWebauthnLib(); const credential = createCredential(registerOptionsResponse, { @@ -1067,36 +893,15 @@ describe(`recipeImplementationFunctions: ${printPath("[test/webauthn/recipeImple userContext, }); - assert(registerCredentialResponse.status !== "OK"); + assert.equal(registerCredentialResponse.status, "OPTIONS_NOT_FOUND_ERROR"); }); - it("should return the correct error if the credential is invalid", async function () { + it("when credential clientDataJSON is null, should return the correct error if the credential is invalid", async function () { await initST(); const { email, signUpResponse } = await createUser(rpId, rpName, origin); - const app = express(); - app.use(middleware()); - app.use(errorHandler()); - - let registerOptionsResponse = await new Promise((resolve, reject) => - request(app) - // @ts-ignore - .post("/auth/webauthn/options/register") - .send({ - email, - }) - .expect(200) - .end((err, res) => { - if (err) { - reject(err); - } else { - resolve(JSON.parse(res.text)); - } - }) - ); - - assert(registerOptionsResponse.status === "OK"); + const registerOptionsResponse = await createRegisterOptions(email); const { createCredential } = await getWebauthnLib(); const credential = createCredential(registerOptionsResponse, { @@ -1112,7 +917,6 @@ describe(`recipeImplementationFunctions: ${printPath("[test/webauthn/recipeImple webauthnGeneratedOptionsId: registerOptionsResponse.webauthnGeneratedOptionsId, credential: { ...credential, - id: "invalid", response: { ...credential.response, clientDataJSON: "invalid", @@ -1121,34 +925,15 @@ describe(`recipeImplementationFunctions: ${printPath("[test/webauthn/recipeImple userContext, }); - assert(registerCredentialResponse.status === "INVALID_CREDENTIALS_ERROR"); + assert.equal(registerCredentialResponse.status, "INVALID_CREDENTIALS_ERROR"); }); - it("should return the correct error if the register options id is wrong", async function () { + it("when credential type is integer, should return the correct error if the credential is invalid", async function () { await initST(); const { email, signUpResponse } = await createUser(rpId, rpName, origin); - const app = express(); - app.use(middleware()); - app.use(errorHandler()); - - let registerOptionsResponse = await new Promise((resolve, reject) => - request(app) - // @ts-ignore - .post("/auth/webauthn/options/register") - .send({ - email, - }) - .expect(200) - .end((err, res) => { - if (err) { - reject(err); - } else { - resolve(JSON.parse(res.text)); - } - }) - ); + const registerOptionsResponse = await createRegisterOptions(email); const { createCredential } = await getWebauthnLib(); const credential = createCredential(registerOptionsResponse, { @@ -1161,41 +946,52 @@ describe(`recipeImplementationFunctions: ${printPath("[test/webauthn/recipeImple const registerCredentialResponse = await getWebAuthnRecipe().recipeInterfaceImpl.registerCredential({ recipeUserId: signUpResponse.user.id, - webauthnGeneratedOptionsId: "invalid", - credential: credential, + webauthnGeneratedOptionsId: registerOptionsResponse.webauthnGeneratedOptionsId, + credential: { + ...credential, + type: 1, + }, userContext, }); - assert(registerCredentialResponse.status === "OPTIONS_NOT_FOUND_ERROR"); + assert.equal(registerCredentialResponse.status, "INVALID_CREDENTIALS_ERROR"); }); - it("should return the correct error if the register options are wrong", 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, signUpResponse } = await createUser(rpId, rpName, origin); - const app = express(); - app.use(middleware()); - app.use(errorHandler()); - - let registerOptionsResponse = await new Promise((resolve, reject) => - request(app) - // @ts-ignore - .post("/auth/webauthn/options/register") - .send({ - email, - }) - .expect(200) - .end((err, res) => { - if (err) { - reject(err); - } else { - resolve(JSON.parse(res.text)); - } - }) - ); + const registerOptionsResponse = await createRegisterOptions(email); + + const { createCredential } = await getWebauthnLib(); + const credential = createCredential(registerOptionsResponse, { + rpId, + rpName, + origin, + userNotPresent: false, + userNotVerified: false, + }); + + const registerCredentialResponse = await getWebAuthnRecipe().recipeInterfaceImpl.registerCredential({ + recipeUserId: signUpResponse.user.id, + webauthnGeneratedOptionsId: registerOptionsResponse.webauthnGeneratedOptionsId, + credential: { + ...credential, + id: null, + }, + userContext, + }); + + assert.equal(registerCredentialResponse.status, "INVALID_CREDENTIALS_ERROR"); + }); + + it("should return the correct error if the credential is wrong", async function () { + await initST(); + + const { email, signUpResponse } = await createUser(rpId, rpName, origin); - assert(registerOptionsResponse.status === "OK"); + const registerOptionsResponse = await createRegisterOptions(email); const { createCredential } = await getWebauthnLib(); const credential = createCredential(registerOptionsResponse, { @@ -1213,27 +1009,327 @@ describe(`recipeImplementationFunctions: ${printPath("[test/webauthn/recipeImple userContext, }); - assert(registerCredentialResponse.status === "INVALID_OPTIONS_ERROR"); + assert.equal(registerCredentialResponse.status, "INVALID_AUTHENTICATOR_ERROR"); + }); + + it("should return the correct error if the options timeout is exceeded", async function () { + await initST({ registerTimeout: 500 }); + const { email, signUpResponse } = await createUser(rpId, rpName, origin); + + const registerOptionsResponse = await createRegisterOptions(email); + + const { createCredential } = await getWebauthnLib(); + const credential = createCredential(registerOptionsResponse, { + rpId, + rpName, + origin, + userNotPresent: false, + userNotVerified: false, + }); + + await new Promise((resolve) => setTimeout(resolve, 1000)); + + const registerCredentialResponse = await getWebAuthnRecipe().recipeInterfaceImpl.registerCredential({ + recipeUserId: signUpResponse.user.id, + webauthnGeneratedOptionsId: registerOptionsResponse.webauthnGeneratedOptionsId, + credential, + userContext, + }); + + assert.equal(registerCredentialResponse.status, "INVALID_OPTIONS_ERROR"); + }); + }); + + describe("[getUserFromRecoverAccountToken]", function () { + it("should return the correct user", async function () { + await initST(); + + const { email, signUpResponse } = await createUser(rpId, rpName, origin); + + const generateRecoverAccountTokenResponse = await getWebAuthnRecipe().recipeInterfaceImpl.generateRecoverAccountToken( + { + userId: signUpResponse.user.id, + email, + tenantId: "public", + userContext, + } + ); + assert.equal(generateRecoverAccountTokenResponse.status, "OK"); + + const getUserFromRecoverAccountTokenResponse = await getWebAuthnRecipe().recipeInterfaceImpl.getUserFromRecoverAccountToken( + { + token: generateRecoverAccountTokenResponse.token, + tenantId: "public", + userContext, + } + ); + + assert.equal(getUserFromRecoverAccountTokenResponse.status, "OK"); + assert(!!getUserFromRecoverAccountTokenResponse.user); + assert(!!getUserFromRecoverAccountTokenResponse.user.emails.find((e) => e === email)); + }); + + it("should return the correct error if the token is invalid", async function () { + await initST(); + + const user = await getWebAuthnRecipe().recipeInterfaceImpl.getUserFromRecoverAccountToken({ + token: "test", + tenantId: "public", + userContext, + }); + + assert.equal(user.status, "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR"); + }); + }); + + describe("[removeCredential]", function () { + it("should remove the credential if it exists", async function () { + await initST(); + + const { email, signUpResponse, credential } = await createUser(rpId, rpName, origin); + + const removeCredentialResponse = await getWebAuthnRecipe().recipeInterfaceImpl.removeCredential({ + recipeUserId: signUpResponse.user.id, + webauthnCredentialId: credential.attestation.id, + userContext, + }); + + assert.equal(removeCredentialResponse.status, "OK"); + }); + + it("should return the correct error if the credential is not found", async function () { + await initST(); + + const { email, signUpResponse, credential } = await createUser(rpId, rpName, origin); + + const removeCredentialResponse = await getWebAuthnRecipe().recipeInterfaceImpl.removeCredential({ + recipeUserId: signUpResponse.user.id, + webauthnCredentialId: "invalid", + userContext, + }); + + assert.equal(removeCredentialResponse.status, "CREDENTIAL_NOT_FOUND_ERROR"); + }); + + it("should return the correct error if the user is not found", async function () { + await initST(); + + const { email, signUpResponse, credential } = await createUser(rpId, rpName, origin); + + const removeCredentialResponse = await getWebAuthnRecipe().recipeInterfaceImpl.removeCredential({ + recipeUserId: "invalid", + webauthnCredentialId: credential.attestation.id, + userContext, + }); + + assert.equal(removeCredentialResponse.status, "CREDENTIAL_NOT_FOUND_ERROR"); }); }); - describe.skip("[listCredentials]", function () { + describe("[getCredential]", function () { + it("should return the correct credential if it exists", async function () { + await initST(); + + const { email, signUpResponse, credential } = await createUser(rpId, rpName, origin); + + const getCredentialResponse = await getWebAuthnRecipe().recipeInterfaceImpl.getCredential({ + recipeUserId: signUpResponse.user.id, + webauthnCredentialId: credential.attestation.id, + userContext, + }); + + assert.equal(getCredentialResponse.status, "OK"); + }); + + it("should return the correct error if the credential is not found", async function () { + await initST(); + + const { email, signUpResponse, credential } = await createUser(rpId, rpName, origin); + + const getCredentialResponse = await getWebAuthnRecipe().recipeInterfaceImpl.getCredential({ + recipeUserId: signUpResponse.user.id, + webauthnCredentialId: "invalid", + userContext, + }); + + assert.equal(getCredentialResponse.status, "CREDENTIAL_NOT_FOUND_ERROR"); + }); + + it("should return the correct error if the user is not found", async function () { + await initST(); + + const { email, signUpResponse, credential } = await createUser(rpId, rpName, origin); + + const getCredentialResponse = await getWebAuthnRecipe().recipeInterfaceImpl.getCredential({ + recipeUserId: "invalid", + webauthnCredentialId: credential.attestation.id, + userContext, + }); + + assert.equal(getCredentialResponse.status, "CREDENTIAL_NOT_FOUND_ERROR"); + }); + }); + + describe("[listCredentials]", function () { it("should return only one credential if only one is registered for a user", async function () { await initST(); - const { signUpResponse } = await createUser(rpId, rpName, origin); + const { signUpResponse, credential } = await createUser(rpId, rpName, origin); + + const listCredentialsResponse = await getWebAuthnRecipe().recipeInterfaceImpl.listCredentials({ + recipeUserId: signUpResponse.user.id, + userContext, + }); + + assert.equal(listCredentialsResponse.status, "OK"); + assert.equal(listCredentialsResponse.credentials.length, 1); + assert.equal(listCredentialsResponse.credentials[0].webauthnCredentialId, credential.attestation.id); + }); + + it("should return multiple credentials if multiple are registered for a user", async function () { + await initST(); + + const { signUpResponse, credential: credential1, email } = await createUser(rpId, rpName, origin); + + const registerOptionsResponse = await createRegisterOptions(email); + const { createCredential } = await getWebauthnLib(); + const credential2 = createCredential(registerOptionsResponse, { + rpId, + rpName, + origin, + userNotPresent: false, + userNotVerified: false, + }); + + await getWebAuthnRecipe().recipeInterfaceImpl.registerCredential({ + recipeUserId: signUpResponse.user.id, + webauthnGeneratedOptionsId: registerOptionsResponse.webauthnGeneratedOptionsId, + credential: credential2, + userContext, + }); + + const listCredentialsResponse = await getWebAuthnRecipe().recipeInterfaceImpl.listCredentials({ + recipeUserId: signUpResponse.user.id, + userContext, + }); + + assert.equal(listCredentialsResponse.status, "OK"); + assert.equal(listCredentialsResponse.credentials.length, 2); + assert( + listCredentialsResponse.credentials.find((c) => c.webauthnCredentialId === credential1.attestation.id) + ); + assert(listCredentialsResponse.credentials.find((c) => c.webauthnCredentialId === credential2.id)); + }); + + it("should return no credentials if no credentials are registered for a user", async function () { + await initST(); + + const { signUpResponse, credential, email } = await createUser(rpId, rpName, origin); - const app = express(); - app.use(middleware()); - app.use(errorHandler()); + await getWebAuthnRecipe().recipeInterfaceImpl.removeCredential({ + recipeUserId: signUpResponse.user.id, + webauthnCredentialId: credential.attestation.id, + userContext, + }); + + const listCredentialsResponse = await getWebAuthnRecipe().recipeInterfaceImpl.listCredentials({ + recipeUserId: signUpResponse.user.id, + userContext, + }); + + assert.equal(listCredentialsResponse.status, "OK"); + assert.equal(listCredentialsResponse.credentials.length, 0); + }); + + it("should the correct credentials for a user when there are multiple users", async function () { + await initST(); + + await createUser(rpId, rpName, origin); + const { signUpResponse, credential } = await createUser(rpId, rpName, origin); const listCredentialsResponse = await getWebAuthnRecipe().recipeInterfaceImpl.listCredentials({ recipeUserId: signUpResponse.user.id, userContext, }); - console.log(listCredentialsResponse); - assert(listCredentialsResponse.status === "OK"); + assert.equal(listCredentialsResponse.status, "OK"); + assert.equal(listCredentialsResponse.credentials.length, 1); + assert.equal(listCredentialsResponse.credentials[0].webauthnCredentialId, credential.attestation.id); + }); + }); + + describe("[removeGeneratedOptions]", function () { + it("should remove the generated options if they exist", async function () { + await initST(); + + let registerOptionsResponse = await createRegisterOptions("test@example.com"); + + const removeGeneratedOptionsResponse = await getWebAuthnRecipe().recipeInterfaceImpl.removeGeneratedOptions( + { + webauthnGeneratedOptionsId: registerOptionsResponse.webauthnGeneratedOptionsId, + userContext, + tenantId: "public", + } + ); + + assert.equal(removeGeneratedOptionsResponse.status, "OK"); + }); + + it("should return the correct error if the options id is invalid", async function () { + await initST(); + + const removeGeneratedOptionsResponse = await getWebAuthnRecipe().recipeInterfaceImpl.removeGeneratedOptions( + { + webauthnGeneratedOptionsId: "invalid", + userContext, + tenantId: "public", + } + ); + + assert.equal(removeGeneratedOptionsResponse.status, "OPTIONS_NOT_FOUND_ERROR"); + }); + }); + + describe("[getGeneratedOptions]", function () { + it("should return all the required fields", async function () { + await initST(); + + // passing valid field + let registerOptionsResponse = await createRegisterOptions("test@example.com"); + + assert.equal(registerOptionsResponse.status, "OK"); + + const generatedOptions = await getWebAuthnRecipe().recipeInterfaceImpl.getGeneratedOptions({ + webauthnGeneratedOptionsId: registerOptionsResponse.webauthnGeneratedOptionsId, + userContext, + tenantId: "public", + }); + + assert.equal(generatedOptions.status, "OK"); + + assert.equal(generatedOptions.origin, origin); + assert.equal(generatedOptions.email, "test@example.com"); + assert.equal(generatedOptions.relyingPartyId, rpId); + assert.equal(generatedOptions.relyingPartyName, rpName); + assert.equal(generatedOptions.userVerification, "preferred"); + assert.equal(generatedOptions.userPresence, true); + assert.equal(typeof generatedOptions.webauthnGeneratedOptionsId, "string"); + assert.equal(typeof generatedOptions.challenge, "string"); + assert.equal(typeof generatedOptions.createdAt, "number"); + assert.equal(typeof generatedOptions.expiresAt, "number"); + assert.equal(typeof generatedOptions.timeout, "number"); + }); + + it("should return the correct error if the options id is invalid", async function () { + await initST(); + + const generatedOptions = await getWebAuthnRecipe().recipeInterfaceImpl.getGeneratedOptions({ + webauthnGeneratedOptionsId: "invalid", + userContext, + tenantId: "public", + }); + + assert.equal(generatedOptions.status, "OPTIONS_NOT_FOUND_ERROR"); }); }); });