Skip to content

Commit

Permalink
fix: validate MFA claim before allowing TOTP device removal (#963)
Browse files Browse the repository at this point in the history
* fix: validate MFA claim before allowing TOTP device removal

* fix: fix how we check the MFA claim in removeDevice
  • Loading branch information
porcellus authored Nov 26, 2024
1 parent 1896eab commit 69c29e3
Show file tree
Hide file tree
Showing 12 changed files with 88 additions and 11 deletions.
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/node_modules
/examples/**/node_modules
/examples/**/dist
.DS_Store
/.history
*.js.map
Expand All @@ -12,4 +13,6 @@ releasePassword
/test_report
/temp_test_exports
/temp_*
/.nyc_output
/.nyc_output
.circleci/.pat
examples/express/with-m2m/clients.json
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [unreleased]

## [20.1.6] - 2024-11-26

- Fixes an issue where `removeDevice` API allowed removing verifiedTOTP devices without the user completing MFA.

## [20.1.5] - 2024-10-09

- Fixes an issue where users were not able to reset their password if a user with the same email address was created before account linking was enabled.
Expand Down
47 changes: 46 additions & 1 deletion lib/build/recipe/totp/api/implementation.js
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -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,
Expand Down
5 changes: 4 additions & 1 deletion lib/build/recipe/totp/api/removeDevice.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
2 changes: 1 addition & 1 deletion lib/build/version.d.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion lib/build/version.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 10 additions & 1 deletion lib/ts/recipe/totp/api/implementation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down Expand Up @@ -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,
Expand Down
5 changes: 4 additions & 1 deletion lib/ts/recipe/totp/api/removeDevice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
);

Expand Down
2 changes: 1 addition & 1 deletion lib/ts/version.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
* License for the specific language governing permissions and limitations
* under the License.
*/
export const version = "20.1.5";
export const version = "20.1.6";

export const cdiSupported = ["5.1"];

Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "supertokens-node",
"version": "20.1.5",
"version": "20.1.6",
"description": "NodeJS driver for SuperTokens core",
"main": "index.js",
"scripts": {
Expand Down
10 changes: 10 additions & 0 deletions test/test-server/src/testFunctionMapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,16 @@ export function getFunc(evalStr: string): (...args: any[]) => any {
}

if (evalStr.startsWith("multifactorauth.init.override.functions")) {
if (evalStr.includes(`getMFARequirementsForAuth:async()=>["totp"]`)) {
return (e) => {
return {
...e,
getMFARequirementsForAuth: (e) => {
return ["totp"];
},
};
};
}
return (e) => {
return {
...e,
Expand Down

0 comments on commit 69c29e3

Please sign in to comment.