diff --git a/lib/build/recipe/webauthn/recipeImplementation.js b/lib/build/recipe/webauthn/recipeImplementation.js index 0e42c40fc..3d597f5f5 100644 --- a/lib/build/recipe/webauthn/recipeImplementation.js +++ b/lib/build/recipe/webauthn/recipeImplementation.js @@ -308,8 +308,9 @@ function getRecipeInterface(querier, getWebauthnConfig) { }, registerCredential: async function ({ webauthnGeneratedOptionsId, credential, userContext, recipeUserId }) { return await querier.sendPostRequest( - new normalisedURLPath_1.default(`/recipe/webauthn/user/${recipeUserId}/credential/register`), + new normalisedURLPath_1.default(`/recipe/webauthn/user/credential/register`), { + recipeUserId, webauthnGeneratedOptionsId, credential, }, diff --git a/lib/ts/recipe/webauthn/recipeImplementation.ts b/lib/ts/recipe/webauthn/recipeImplementation.ts index 952c11a02..1aebd319a 100644 --- a/lib/ts/recipe/webauthn/recipeImplementation.ts +++ b/lib/ts/recipe/webauthn/recipeImplementation.ts @@ -288,8 +288,9 @@ export default function getRecipeInterface( registerCredential: async function ({ webauthnGeneratedOptionsId, credential, userContext, recipeUserId }) { return await querier.sendPostRequest( - new NormalisedURLPath(`/recipe/webauthn/user/${recipeUserId}/credential/register`), + new NormalisedURLPath(`/recipe/webauthn/user/credential/register`), { + recipeUserId, webauthnGeneratedOptionsId, credential, }, diff --git a/test/webauthn/apis.test.js b/test/webauthn/apis.test.js index 9baf078bb..c282c6545 100644 --- a/test/webauthn/apis.test.js +++ b/test/webauthn/apis.test.js @@ -27,8 +27,7 @@ let { middleware, errorHandler } = require("../../framework/express"); let { isCDIVersionCompatible } = require("../utils"); const { readFile } = require("fs/promises"); const nock = require("nock"); - -require("./wasm_exec"); +const getWebauthnLib = require("./lib/getWebAuthnLib"); describe(`apisFunctions: ${printPath("[test/webauthn/apis.test.js]")}`, function () { beforeEach(async function () { @@ -84,8 +83,6 @@ describe(`apisFunctions: ${printPath("[test/webauthn/apis.test.js]")}`, function }) ); - console.log("test registerOptions with default values", registerOptionsResponse); - assert(registerOptionsResponse.status === "OK"); assert(typeof registerOptionsResponse.webauthnGeneratedOptionsId === "string"); @@ -180,7 +177,6 @@ describe(`apisFunctions: ${printPath("[test/webauthn/apis.test.js]")}`, function } }) ); - console.log("test registerOptions with custom values", registerOptionsResponse); assert(registerOptionsResponse.status === "OK"); @@ -202,7 +198,6 @@ describe(`apisFunctions: ${printPath("[test/webauthn/apis.test.js]")}`, function userContext: {}, } ); - console.log("generatedOptions", generatedOptions); assert(generatedOptions.origin === "testOrigin.com"); }); }); @@ -246,7 +241,6 @@ describe(`apisFunctions: ${printPath("[test/webauthn/apis.test.js]")}`, function } }) ); - console.log("test signInOptions with default values", signInOptionsResponse); assert(signInOptionsResponse.status === "OK"); @@ -262,7 +256,6 @@ describe(`apisFunctions: ${printPath("[test/webauthn/apis.test.js]")}`, function userContext: {}, } ); - console.log("generatedOptions", generatedOptions); assert(generatedOptions.relyingPartyId === "api.supertokens.io"); assert(generatedOptions.origin === "https://supertokens.io"); @@ -318,7 +311,6 @@ describe(`apisFunctions: ${printPath("[test/webauthn/apis.test.js]")}`, function } }) ); - console.log("test signInOptions with custom values", signInOptionsResponse); assert(signInOptionsResponse.status === "OK"); @@ -400,7 +392,6 @@ describe(`apisFunctions: ${printPath("[test/webauthn/apis.test.js]")}`, function } }) ); - console.log("registerOptionsResponse", registerOptionsResponse); assert(registerOptionsResponse.status === "OK"); const { createCredential } = await getWebauthnLib(); @@ -411,7 +402,6 @@ describe(`apisFunctions: ${printPath("[test/webauthn/apis.test.js]")}`, function userNotPresent: false, userNotVerified: false, }); - console.log("credential", credential); let signUpResponse = await new Promise((resolve, reject) => request(app) @@ -503,7 +493,6 @@ describe(`apisFunctions: ${printPath("[test/webauthn/apis.test.js]")}`, function } }) ); - console.log("registerOptionsResponse", registerOptionsResponse); assert(registerOptionsResponse.status === "OK"); let signInOptionsResponse = await new Promise((resolve, reject) => @@ -724,83 +713,3 @@ describe(`apisFunctions: ${printPath("[test/webauthn/apis.test.js]")}`, function }); }); }); - -const getWebauthnLib = async () => { - const wasmBuffer = await readFile(__dirname + "/webauthn.wasm"); - - // Set up the WebAssembly module instance - const go = new Go(); - const { instance } = await WebAssembly.instantiate(wasmBuffer, go.importObject); - go.run(instance); - - // Export extractURL from the global object - const createCredential = ( - registerOptions, - { userNotPresent = true, userNotVerified = true, rpId, rpName, origin } - ) => { - const registerOptionsString = JSON.stringify(registerOptions); - const result = global.createCredential( - registerOptionsString, - rpId, - rpName, - origin, - userNotPresent, - userNotVerified - ); - - if (!result) { - throw new Error("Failed to create credential"); - } - - try { - const credential = JSON.parse(result); - return credential; - } catch (e) { - throw new Error("Failed to parse credential"); - } - }; - - const createAndAssertCredential = ( - registerOptions, - signInOptions, - { userNotPresent = false, userNotVerified = false, rpId, rpName, origin } - ) => { - const registerOptionsString = JSON.stringify(registerOptions); - const signInOptionsString = JSON.stringify(signInOptions); - - const result = global.createAndAssertCredential( - registerOptionsString, - signInOptionsString, - rpId, - rpName, - origin, - userNotPresent, - userNotVerified - ); - - if (!result) { - throw new Error("Failed to create/assert credential"); - } - - try { - const parsedResult = JSON.parse(result); - return { attestation: parsedResult.attestation, assertion: parsedResult.assertion }; - } catch (e) { - throw new Error("Failed to parse result"); - } - }; - - return { createCredential, createAndAssertCredential }; -}; - -const log = ({ ...args }) => { - Object.keys(args).forEach((key) => { - console.log(); - console.log("------------------------------------------------"); - console.log(`${key}`); - console.log("------------------------------------------------"); - console.log(JSON.stringify(args[key], null, 2)); - console.log("================================================"); - console.log(); - }); -}; diff --git a/test/webauthn/lib/createUser.js b/test/webauthn/lib/createUser.js new file mode 100644 index 000000000..f18d8b3b0 --- /dev/null +++ b/test/webauthn/lib/createUser.js @@ -0,0 +1,62 @@ +const request = require("supertest"); +const express = require("express"); + +let { middleware, errorHandler } = require("../../../framework/express"); + +const getWebauthnLib = require("./getWebAuthnLib"); + +const createUser = async (rpId, rpName, origin) => { + const app = express(); + app.use(middleware()); + app.use(errorHandler()); + + const email = `${Math.random().toString().slice(2)}@supertokens.com`; + let registerOptionsResponse = await new Promise((resolve, reject) => + request(app) + .post("/auth/webauthn/options/register") + .send({ + email, + }) + .expect(200) + .end((err, res) => { + if (err) { + console.log(err); + reject(err); + } else { + resolve(JSON.parse(res.text)); + } + }) + ); + + const { createCredential } = await getWebauthnLib(); + const credential = createCredential(registerOptionsResponse, { + rpId, + rpName, + origin, + userNotPresent: false, + userNotVerified: false, + }); + + let signUpResponse = await new Promise((resolve, reject) => + request(app) + .post("/auth/webauthn/signup") + .send({ + credential, + webauthnGeneratedOptionsId: registerOptionsResponse.webauthnGeneratedOptionsId, + shouldTryLinkingWithSessionUser: false, + }) + .expect(200) + .end((err, res) => { + if (err) { + console.log(err); + reject(err); + } else { + resolve(JSON.parse(res.text)); + } + }) + ); + + return { email, signUpResponse, registerOptionsResponse, credential }; +}; + +module.exports = createUser; diff --git a/test/webauthn/lib/getWebAuthnLib.js b/test/webauthn/lib/getWebAuthnLib.js new file mode 100644 index 000000000..3f41931df --- /dev/null +++ b/test/webauthn/lib/getWebAuthnLib.js @@ -0,0 +1,73 @@ +const { readFile } = require("fs/promises"); + +require("./wasm_exec"); + +const getWebauthnLib = async () => { + const wasmBuffer = await readFile(__dirname + "/webauthn.wasm"); + + // Set up the WebAssembly module instance + const go = new Go(); + const { instance } = await WebAssembly.instantiate(wasmBuffer, go.importObject); + go.run(instance); + + // Export extractURL from the global object + const createCredential = ( + registerOptions, + { userNotPresent = true, userNotVerified = true, rpId, rpName, origin } + ) => { + const registerOptionsString = JSON.stringify(registerOptions); + const result = global.createCredential( + registerOptionsString, + rpId, + rpName, + origin, + userNotPresent, + userNotVerified + ); + + if (!result) { + throw new Error("Failed to create credential"); + } + + try { + const credential = JSON.parse(result); + return credential; + } catch (e) { + throw new Error("Failed to parse credential"); + } + }; + + const createAndAssertCredential = ( + registerOptions, + signInOptions, + { userNotPresent = false, userNotVerified = false, rpId, rpName, origin } + ) => { + const registerOptionsString = JSON.stringify(registerOptions); + const signInOptionsString = JSON.stringify(signInOptions); + + const result = global.createAndAssertCredential( + registerOptionsString, + signInOptionsString, + rpId, + rpName, + origin, + userNotPresent, + userNotVerified + ); + + if (!result) { + throw new Error("Failed to create/assert credential"); + } + + try { + const parsedResult = JSON.parse(result); + return { attestation: parsedResult.attestation, assertion: parsedResult.assertion }; + } catch (e) { + throw new Error("Failed to parse result"); + } + }; + + return { createCredential, createAndAssertCredential }; +}; + +module.exports = getWebauthnLib; diff --git a/test/webauthn/lib/getWebAuthnRecipe.js b/test/webauthn/lib/getWebAuthnRecipe.js new file mode 100644 index 000000000..2cfb4bb6c --- /dev/null +++ b/test/webauthn/lib/getWebAuthnRecipe.js @@ -0,0 +1,7 @@ +let SuperTokens = require("../../../lib/build/supertokens").default; + +const getWebAuthnRecipe = () => { + return SuperTokens.getInstanceOrThrowError().recipeModules.find((rm) => rm.getRecipeId() === "webauthn"); +}; + +module.exports = getWebAuthnRecipe; diff --git a/test/webauthn/wasm_exec.js b/test/webauthn/lib/wasm_exec.js similarity index 100% rename from test/webauthn/wasm_exec.js rename to test/webauthn/lib/wasm_exec.js diff --git a/test/webauthn/webauthn.wasm b/test/webauthn/lib/webauthn.wasm similarity index 100% rename from test/webauthn/webauthn.wasm rename to test/webauthn/lib/webauthn.wasm diff --git a/test/webauthn/recipeImplementation.test.js b/test/webauthn/recipeImplementation.test.js index 9965f96a3..d95920a73 100644 --- a/test/webauthn/recipeImplementation.test.js +++ b/test/webauthn/recipeImplementation.test.js @@ -25,10 +25,11 @@ let { ProcessState } = require("../../lib/build/processState"); let SuperTokens = require("../../lib/build/supertokens").default; let { middleware, errorHandler } = require("../../framework/express"); let { isCDIVersionCompatible } = require("../utils"); -const { readFile } = require("fs/promises"); -const nock = require("nock"); -require("./wasm_exec"); +const getWebauthnLib = require("./lib/getWebAuthnLib"); +const getWebAuthnRecipe = require("./lib/getWebAuthnRecipe"); + +const createUser = require("./lib/createUser"); describe(`recipeImplementationFunctions: ${printPath("[test/webauthn/recipeImplementation.test.js]")}`, function () { beforeEach(async function () { @@ -43,7 +44,7 @@ describe(`recipeImplementationFunctions: ${printPath("[test/webauthn/recipeImple }); describe("[getGeneratedOptions]", function () { - it("test it returns all the required fields", async function () { + it("returns an error if the email is invalid", async function () { const connectionURI = await startST(); STExpress.init({ @@ -71,7 +72,7 @@ describe(`recipeImplementationFunctions: ${printPath("[test/webauthn/recipeImple request(app) .post("/auth/webauthn/options/register") .send({ - email: "test@example.com", + email: "", }) .expect(200) .end((err, res) => { @@ -84,17 +85,57 @@ describe(`recipeImplementationFunctions: ${printPath("[test/webauthn/recipeImple }) ); - console.log("test registerOptions with default values", registerOptionsResponse); + assert(registerOptionsResponse.status === "INVALID_EMAIL_ERROR"); + assert(typeof registerOptionsResponse.err === "string"); + }); - assert(registerOptionsResponse.status === "OK"); + it("returns all the required fields", async function () { + const connectionURI = await startST(); - const generatedOptions = await SuperTokens.getInstanceOrThrowError().recipeModules[0].recipeInterfaceImpl.getGeneratedOptions( - { - webauthnGeneratedOptionsId: registerOptionsResponse.webauthnGeneratedOptionsId, - userContext: {}, - } + STExpress.init({ + supertokens: { + connectionURI, + }, + appInfo: { + apiDomain: "api.supertokens.io", + appName: "SuperTokens", + websiteDomain: "supertokens.io", + }, + recipeList: [WebAuthn.init()], + }); + + // run test if current CDI version >= 2.11 + // todo update this to crrect version + if (!(await isCDIVersionCompatible("2.11"))) return; + + const app = express(); + app.use(middleware()); + app.use(errorHandler()); + + // passing valid field + let registerOptionsResponse = await new Promise((resolve, reject) => + request(app) + .post("/auth/webauthn/options/register") + .send({ + email: "test@example.com", + }) + .expect(200) + .end((err, res) => { + if (err) { + console.log(err); + reject(err); + } else { + resolve(JSON.parse(res.text)); + } + }) ); - console.log("generatedOptions", generatedOptions); + + assert(registerOptionsResponse.status === "OK"); + + const generatedOptions = await getWebAuthnRecipe().recipeInterfaceImpl.getGeneratedOptions({ + webauthnGeneratedOptionsId: registerOptionsResponse.webauthnGeneratedOptionsId, + userContext: {}, + }); assert(generatedOptions.status === "OK"); @@ -109,16 +150,705 @@ describe(`recipeImplementationFunctions: ${printPath("[test/webauthn/recipeImple assert(typeof generatedOptions.timeout === "number"); }); }); -}); -const log = ({ ...args }) => { - Object.keys(args).forEach((key) => { - console.log(); - console.log("------------------------------------------------"); - console.log(`${key}`); - console.log("------------------------------------------------"); - console.log(JSON.stringify(args[key], null, 2)); - console.log("================================================"); - console.log(); + describe("[generateRecoverAccountToken]", function () { + it("should return an error if the user doesn't exist", async function () { + const connectionURI = await startST(); + + const origin = "https://supertokens.io"; + const rpId = "supertokens.io"; + const rpName = "SuperTokens"; + + STExpress.init({ + supertokens: { + connectionURI, + }, + appInfo: { + apiDomain: "api.supertokens.io", + appName: "SuperTokens", + websiteDomain: "supertokens.io", + }, + recipeList: [ + Session.init(), + WebAuthn.init({ + getOrigin: async () => { + return origin; + }, + getRelyingPartyId: async () => { + return rpId; + }, + getRelyingPartyName: async () => { + return rpName; + }, + }), + ], + }); + + // run test if current CDI version >= 2.11 + // todo update this to crrect version + if (!(await isCDIVersionCompatible("2.11"))) return; + + const generateRecoverAccountTokenResponse = await getWebAuthnRecipe().recipeInterfaceImpl.generateRecoverAccountToken( + { + userId: "test", + email: "test@supertokens.com", + tenantId: "public", + userContext: {}, + } + ); + + assert(generateRecoverAccountTokenResponse.status === "UNKNOWN_USER_ID_ERROR"); + }); + + it("should generate a recover account token", async function () { + const connectionURI = await startST(); + + const origin = "https://supertokens.io"; + const rpId = "supertokens.io"; + const rpName = "SuperTokens"; + + STExpress.init({ + supertokens: { + connectionURI, + }, + appInfo: { + apiDomain: "api.supertokens.io", + appName: "SuperTokens", + websiteDomain: "supertokens.io", + }, + recipeList: [ + Session.init(), + WebAuthn.init({ + getOrigin: async () => { + return origin; + }, + getRelyingPartyId: async () => { + return rpId; + }, + getRelyingPartyName: async () => { + return rpName; + }, + }), + ], + }); + + // run test if current CDI version >= 2.11 + // todo update this to crrect version + if (!(await isCDIVersionCompatible("2.11"))) return; + + 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"); + assert(typeof generateRecoverAccountTokenResponse.token === "string"); + }); + }); + + describe("[getUserFromRecoverAccountToken]", function () { + it("throws an error if the token is invalid", async function () { + const connectionURI = await startST(); + + STExpress.init({ + supertokens: { + connectionURI, + }, + appInfo: { + apiDomain: "api.supertokens.io", + appName: "SuperTokens", + websiteDomain: "supertokens.io", + }, + recipeList: [WebAuthn.init()], + }); + + // run test if current CDI version >= 2.11 + // todo update this to crrect version + if (!(await isCDIVersionCompatible("2.11"))) return; + + 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 () { + const connectionURI = await startST(); + + const origin = "https://supertokens.io"; + const rpId = "supertokens.io"; + const rpName = "SuperTokens"; + + STExpress.init({ + supertokens: { + connectionURI, + }, + appInfo: { + apiDomain: "api.supertokens.io", + appName: "SuperTokens", + websiteDomain: "supertokens.io", + }, + recipeList: [ + Session.init(), + WebAuthn.init({ + getOrigin: async () => { + return origin; + }, + getRelyingPartyId: async () => { + return rpId; + }, + getRelyingPartyName: async () => { + return rpName; + }, + }), + ], + }); + + // run test if current CDI version >= 2.11 + // todo update this to crrect version + if (!(await isCDIVersionCompatible("2.11"))) return; + + const { email, signUpResponse } = await createUser(rpId, rpName, origin); + + const generateRecoverAccountTokenResponse = await getWebAuthnRecipe().recipeInterfaceImpl.generateRecoverAccountToken( + { + userId: signUpResponse.user.id, + email, + tenantId: "public", + userContext: {}, + } + ); + + const getUserFromRecoverAccountTokenResponse = await getWebAuthnRecipe().recipeInterfaceImpl.getUserFromRecoverAccountToken( + { + token: generateRecoverAccountTokenResponse.token, + tenantId: "public", + userContext: {}, + } + ); + + assert(getUserFromRecoverAccountTokenResponse.status === "OK"); + assert(!!getUserFromRecoverAccountTokenResponse.user); + assert(!!getUserFromRecoverAccountTokenResponse.user.emails.find((e) => e === email)); + }); + }); + + describe("[consumeRecoverAccountToken]", function () { + it("should return an error if the token is invalid", async function () { + const connectionURI = await startST(); + + const origin = "https://supertokens.io"; + const rpId = "supertokens.io"; + const rpName = "SuperTokens"; + + STExpress.init({ + supertokens: { + connectionURI, + }, + appInfo: { + apiDomain: "api.supertokens.io", + appName: "SuperTokens", + websiteDomain: "supertokens.io", + }, + recipeList: [ + Session.init(), + WebAuthn.init({ + getOrigin: async () => { + return origin; + }, + getRelyingPartyId: async () => { + return rpId; + }, + getRelyingPartyName: async () => { + return rpName; + }, + }), + ], + }); + + // run test if current CDI version >= 2.11 + // todo update this to crrect version + if (!(await isCDIVersionCompatible("2.11"))) return; + + const consumeRecoverAccountTokenResponse = await getWebAuthnRecipe().recipeInterfaceImpl.consumeRecoverAccountToken( + { + token: "test", + tenantId: "public", + userContext: {}, + } + ); + + assert(consumeRecoverAccountTokenResponse.status === "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR"); + }); + + it("should consume the token", async function () { + const connectionURI = await startST(); + + const origin = "https://supertokens.io"; + const rpId = "supertokens.io"; + const rpName = "SuperTokens"; + + STExpress.init({ + supertokens: { + connectionURI, + }, + appInfo: { + apiDomain: "api.supertokens.io", + appName: "SuperTokens", + websiteDomain: "supertokens.io", + }, + recipeList: [ + Session.init(), + WebAuthn.init({ + getOrigin: async () => { + return origin; + }, + getRelyingPartyId: async () => { + return rpId; + }, + getRelyingPartyName: async () => { + return rpName; + }, + }), + ], + }); + + // run test if current CDI version >= 2.11 + // todo update this to crrect version + if (!(await isCDIVersionCompatible("2.11"))) return; + + const { email, signUpResponse } = await createUser(rpId, rpName, origin); + + const generateRecoverAccountTokenResponse = await getWebAuthnRecipe().recipeInterfaceImpl.generateRecoverAccountToken( + { + userId: signUpResponse.user.id, + email, + tenantId: "public", + userContext: {}, + } + ); + + const consumeRecoverAccountTokenResponse = await getWebAuthnRecipe().recipeInterfaceImpl.consumeRecoverAccountToken( + { + token: generateRecoverAccountTokenResponse.token, + tenantId: "public", + userContext: {}, + } + ); + + assert(consumeRecoverAccountTokenResponse.status === "OK"); + }); + }); + + describe("[registerCredential]", function () { + it("should create a new credential for an existing user", async function () { + const connectionURI = await startST(); + + const origin = "https://supertokens.io"; + const rpId = "supertokens.io"; + const rpName = "SuperTokens"; + + STExpress.init({ + supertokens: { + connectionURI, + }, + appInfo: { + apiDomain: "api.supertokens.io", + appName: "SuperTokens", + websiteDomain: "supertokens.io", + }, + recipeList: [ + Session.init(), + WebAuthn.init({ + getOrigin: async () => { + return origin; + }, + getRelyingPartyId: async () => { + return rpId; + }, + getRelyingPartyName: async () => { + return rpName; + }, + }), + ], + }); + + // run test if current CDI version >= 2.11 + // todo update this to crrect version + if (!(await isCDIVersionCompatible("2.11"))) return; + + 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) + .post("/auth/webauthn/options/register") + .send({ + email, + }) + .expect(200) + .end((err, res) => { + if (err) { + console.log(err); + reject(err); + } else { + resolve(JSON.parse(res.text)); + } + }) + ); + + 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, + userContext: {}, + }); + + assert(registerCredentialResponse.status === "OK"); + }); + + it("should return a parsable error if the options id is invalid", async function () { + const connectionURI = await startST(); + + const origin = "https://supertokens.io"; + const rpId = "supertokens.io"; + const rpName = "SuperTokens"; + + STExpress.init({ + supertokens: { + connectionURI, + }, + appInfo: { + apiDomain: "api.supertokens.io", + appName: "SuperTokens", + websiteDomain: "supertokens.io", + }, + recipeList: [ + Session.init(), + WebAuthn.init({ + getOrigin: async () => { + return origin; + }, + getRelyingPartyId: async () => { + return rpId; + }, + getRelyingPartyName: async () => { + return rpName; + }, + }), + ], + }); + + // run test if current CDI version >= 2.11 + // todo update this to crrect version + if (!(await isCDIVersionCompatible("2.11"))) return; + + 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) + .post("/auth/webauthn/options/register") + .send({ + email, + }) + .expect(200) + .end((err, res) => { + if (err) { + console.log(err); + reject(err); + } else { + resolve(JSON.parse(res.text)); + } + }) + ); + + 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: "invalid", + credential, + userContext: {}, + }); + + assert(registerCredentialResponse.status !== "OK"); + }); + + it("should return the correct error if the credential is invalid", async function () { + const connectionURI = await startST(); + + const origin = "https://supertokens.io"; + const rpId = "supertokens.io"; + const rpName = "SuperTokens"; + + STExpress.init({ + supertokens: { + connectionURI, + }, + appInfo: { + apiDomain: "api.supertokens.io", + appName: "SuperTokens", + websiteDomain: "supertokens.io", + }, + recipeList: [ + Session.init(), + WebAuthn.init({ + getOrigin: async () => { + return origin; + }, + getRelyingPartyId: async () => { + return rpId; + }, + getRelyingPartyName: async () => { + return rpName; + }, + }), + ], + }); + + // run test if current CDI version >= 2.11 + // todo update this to crrect version + if (!(await isCDIVersionCompatible("2.11"))) return; + + 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) + .post("/auth/webauthn/options/register") + .send({ + email, + }) + .expect(200) + .end((err, res) => { + if (err) { + console.log(err); + reject(err); + } else { + resolve(JSON.parse(res.text)); + } + }) + ); + + 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: "invalid", + response: { + ...credential.response, + clientDataJSON: "invalid", + }, + }, + userContext: {}, + }); + + assert(registerCredentialResponse.status === "INVALID_CREDENTIALS_ERROR"); + }); + + it("should return the correct error if the register options id is wrong", async function () { + const connectionURI = await startST(); + + const origin = "https://supertokens.io"; + const rpId = "supertokens.io"; + const rpName = "SuperTokens"; + + STExpress.init({ + supertokens: { + connectionURI, + }, + appInfo: { + apiDomain: "api.supertokens.io", + appName: "SuperTokens", + websiteDomain: "supertokens.io", + }, + recipeList: [ + Session.init(), + WebAuthn.init({ + getOrigin: async () => { + return origin; + }, + getRelyingPartyId: async () => { + return rpId; + }, + getRelyingPartyName: async () => { + return rpName; + }, + }), + ], + }); + + // run test if current CDI version >= 2.11 + // todo update this to crrect version + if (!(await isCDIVersionCompatible("2.11"))) return; + + 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) + .post("/auth/webauthn/options/register") + .send({ + email, + }) + .expect(200) + .end((err, res) => { + if (err) { + console.log(err); + reject(err); + } else { + resolve(JSON.parse(res.text)); + } + }) + ); + + 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: "invalid", + credential: credential, + userContext: {}, + }); + + assert(registerCredentialResponse.status === "GENERATED_OPTIONS_NOT_FOUND_ERROR"); + }); + + it("should return the correct error if the register options are wrong", async function () { + const connectionURI = await startST(); + + const origin = "https://supertokens.io"; + const rpId = "supertokens.io"; + const rpName = "SuperTokens"; + + STExpress.init({ + supertokens: { + connectionURI, + }, + appInfo: { + apiDomain: "api.supertokens.io", + appName: "SuperTokens", + websiteDomain: "supertokens.io", + }, + recipeList: [ + Session.init(), + WebAuthn.init({ + getOrigin: async () => { + return origin; + }, + getRelyingPartyId: async () => { + return rpId; + }, + getRelyingPartyName: async () => { + return rpName; + }, + }), + ], + }); + + // run test if current CDI version >= 2.11 + // todo update this to crrect version + if (!(await isCDIVersionCompatible("2.11"))) return; + + 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) + .post("/auth/webauthn/options/register") + .send({ + email, + }) + .expect(200) + .end((err, res) => { + if (err) { + console.log(err); + reject(err); + } else { + resolve(JSON.parse(res.text)); + } + }) + ); + + const { createCredential } = await getWebauthnLib(); + const credential = createCredential(registerOptionsResponse, { + rpId: rpId + ".co", + rpName, + origin: origin + ".co", + userNotPresent: false, + userNotVerified: false, + }); + + const registerCredentialResponse = await getWebAuthnRecipe().recipeInterfaceImpl.registerCredential({ + recipeUserId: signUpResponse.user.id, + webauthnGeneratedOptionsId: registerOptionsResponse.webauthnGeneratedOptionsId, + credential: credential, + userContext: {}, + }); + + assert(registerCredentialResponse.status === "INVALID_GENERATED_OPTIONS_ERROR"); + }); }); -}; +});