From 928d2cb59a3246f23f16cc6bbc78762173e264be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mih=C3=A1ly=20Lengyel?= Date: Tue, 26 Nov 2024 16:07:22 +0100 Subject: [PATCH] fix: validate MFA claim before allowing TOTP device removal (#963) * fix: validate MFA claim before allowing TOTP device removal * fix: fix how we check the MFA claim in removeDevice --- .gitignore | 7 ++- CHANGELOG.md | 4 ++ lib/build/recipe/totp/api/implementation.js | 47 ++++++++++++++++++++- lib/build/recipe/totp/api/removeDevice.js | 5 ++- lib/build/version.d.ts | 2 +- lib/build/version.js | 2 +- lib/ts/recipe/totp/api/implementation.ts | 11 ++++- lib/ts/recipe/totp/api/removeDevice.ts | 5 ++- lib/ts/version.ts | 2 +- package-lock.json | 4 +- package.json | 2 +- 11 files changed, 80 insertions(+), 11 deletions(-) diff --git a/.gitignore b/.gitignore index d13b6071e..859fcb6d3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ /node_modules /examples/**/node_modules +/examples/**/dist .DS_Store /.history *.js.map @@ -10,4 +11,8 @@ releasePassword .tmp .idea /test_report -/.nyc_output \ No newline at end of file +/test/test-server/node_modules +/.nyc_output +/temp_* +.circleci/.pat +examples/express/with-m2m/clients.json diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a50aaa3c..01163a1a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [unreleased] +## [18.0.3] - 2024-11-28 + +- Fixes an issue where `removeDevice` API allowed removing verifiedTOTP devices without the user completing MFA. + ## [18.0.2] - 2024-07-09 - `refreshPOST` and `refreshSession` now clears all user tokens upon CSRF failures and if no tokens are found. See the latest comment on https://github.com/supertokens/supertokens-node/issues/141 for more details. diff --git a/lib/build/recipe/totp/api/implementation.js b/lib/build/recipe/totp/api/implementation.js index 909546b34..d282a5731 100644 --- a/lib/build/recipe/totp/api/implementation.js +++ b/lib/build/recipe/totp/api/implementation.js @@ -13,13 +13,49 @@ * License for the specific language governing permissions and limitations * under the License. */ +var __createBinding = + (this && this.__createBinding) || + (Object.create + ? function (o, m, k, k2) { + if (k2 === undefined) k2 = k; + Object.defineProperty(o, k2, { + enumerable: true, + get: function () { + return m[k]; + }, + }); + } + : function (o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; + }); +var __setModuleDefault = + (this && this.__setModuleDefault) || + (Object.create + ? function (o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); + } + : function (o, v) { + o["default"] = v; + }); +var __importStar = + (this && this.__importStar) || + function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) + for (var k in mod) + if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; + }; var __importDefault = (this && this.__importDefault) || function (mod) { return mod && mod.__esModule ? mod : { default: mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); -const multifactorauth_1 = __importDefault(require("../../multifactorauth")); +const multifactorauth_1 = __importStar(require("../../multifactorauth")); const recipe_1 = __importDefault(require("../../multifactorauth/recipe")); const error_1 = __importDefault(require("../../session/error")); function getAPIInterface() { @@ -58,6 +94,15 @@ function getAPIInterface() { }, removeDevicePOST: async function ({ deviceName, options, session, userContext }) { const userId = session.getUserId(); + const deviceList = await options.recipeImplementation.listDevices({ + userId, + userContext, + }); + if (deviceList.devices.some((device) => device.name === deviceName && device.verified)) { + await session.assertClaims([ + multifactorauth_1.MultiFactorAuthClaim.validators.hasCompletedMFARequirementsForAuth(), + ]); + } return await options.recipeImplementation.removeDevice({ userId, deviceName, diff --git a/lib/build/recipe/totp/api/removeDevice.js b/lib/build/recipe/totp/api/removeDevice.js index 3893442a1..ef3e0335c 100644 --- a/lib/build/recipe/totp/api/removeDevice.js +++ b/lib/build/recipe/totp/api/removeDevice.js @@ -28,7 +28,10 @@ async function removeDeviceAPI(apiImplementation, options, userContext) { const session = await session_1.default.getSession( options.req, options.res, - { overrideGlobalClaimValidators: () => [], sessionRequired: true }, + { + overrideGlobalClaimValidators: () => [], + sessionRequired: true, + }, userContext ); const bodyParams = await options.req.getJSONBody(); diff --git a/lib/build/version.d.ts b/lib/build/version.d.ts index f474cb1c6..ea48af171 100644 --- a/lib/build/version.d.ts +++ b/lib/build/version.d.ts @@ -1,4 +1,4 @@ // @ts-nocheck -export declare const version = "18.0.2"; +export declare const version = "18.0.3"; export declare const cdiSupported: string[]; export declare const dashboardVersion = "0.11"; diff --git a/lib/build/version.js b/lib/build/version.js index 6a18a166d..c60fcb5cd 100644 --- a/lib/build/version.js +++ b/lib/build/version.js @@ -15,7 +15,7 @@ exports.dashboardVersion = exports.cdiSupported = exports.version = void 0; * License for the specific language governing permissions and limitations * under the License. */ -exports.version = "18.0.2"; +exports.version = "18.0.3"; exports.cdiSupported = ["5.0"]; // Note: The actual script import for dashboard uses v{DASHBOARD_VERSION} exports.dashboardVersion = "0.11"; diff --git a/lib/ts/recipe/totp/api/implementation.ts b/lib/ts/recipe/totp/api/implementation.ts index 444860542..3e1359a3a 100644 --- a/lib/ts/recipe/totp/api/implementation.ts +++ b/lib/ts/recipe/totp/api/implementation.ts @@ -14,7 +14,7 @@ */ import { APIInterface } from "../"; -import MultiFactorAuth from "../../multifactorauth"; +import MultiFactorAuth, { MultiFactorAuthClaim } from "../../multifactorauth"; import MultiFactorAuthRecipe from "../../multifactorauth/recipe"; import SessionError from "../../session/error"; @@ -59,6 +59,15 @@ export default function getAPIInterface(): APIInterface { removeDevicePOST: async function ({ deviceName, options, session, userContext }) { const userId = session.getUserId(); + const deviceList = await options.recipeImplementation.listDevices({ + userId, + userContext, + }); + + if (deviceList.devices.some((device) => device.name === deviceName && device.verified)) { + await session.assertClaims([MultiFactorAuthClaim.validators.hasCompletedMFARequirementsForAuth()]); + } + return await options.recipeImplementation.removeDevice({ userId, deviceName, diff --git a/lib/ts/recipe/totp/api/removeDevice.ts b/lib/ts/recipe/totp/api/removeDevice.ts index 65c57fec1..9e87abe96 100644 --- a/lib/ts/recipe/totp/api/removeDevice.ts +++ b/lib/ts/recipe/totp/api/removeDevice.ts @@ -30,7 +30,10 @@ export default async function removeDeviceAPI( const session = await Session.getSession( options.req, options.res, - { overrideGlobalClaimValidators: () => [], sessionRequired: true }, + { + overrideGlobalClaimValidators: () => [], + sessionRequired: true, + }, userContext ); diff --git a/lib/ts/version.ts b/lib/ts/version.ts index 700fb6fdd..17643ad3a 100644 --- a/lib/ts/version.ts +++ b/lib/ts/version.ts @@ -12,7 +12,7 @@ * License for the specific language governing permissions and limitations * under the License. */ -export const version = "18.0.2"; +export const version = "18.0.3"; export const cdiSupported = ["5.0"]; diff --git a/package-lock.json b/package-lock.json index a09dbd095..46573a1da 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "supertokens-node", - "version": "18.0.2", + "version": "18.0.3", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "supertokens-node", - "version": "18.0.2", + "version": "18.0.3", "license": "Apache-2.0", "dependencies": { "content-type": "^1.0.5", diff --git a/package.json b/package.json index 70a8e3d69..f412b6c1b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "supertokens-node", - "version": "18.0.2", + "version": "18.0.3", "description": "NodeJS driver for SuperTokens core", "main": "index.js", "scripts": {