From 12bdea1f06b8d6fe68586502a07a12cb85a8dead Mon Sep 17 00:00:00 2001 From: Coskun Aydinoglu Date: Mon, 22 Jun 2026 09:59:54 +0000 Subject: [PATCH] fix(two-factor-auth): propagate global 2FA toggle to all users (SER-2911) Toggling the global 2FA setting now updates every member's per-user 2FA switch instead of only affecting newly created users: - Enabling 2FA globally turns the switch on for all existing users. - Disabling 2FA globally turns the switch off for all users and clears their stored secret, so users created while it was enabled are no longer prompted after it is turned off. While 2FA is globally disabled, users can still enable/disable it for their own account; while globally enabled they cannot disable it. The change reacts only to an actual admin toggle (an initialization guard avoids touching members on the routine config load at process startup). Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01V4HJ5qrPiDme1u8XqcjxqF --- CHANGELOG.md | 3 +++ plugins/two-factor-auth/api/api.js | 40 ++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 04d6fed07b4..203674cfa98 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ ## Version 25.03.xx +Fixes: +- [two-factor-auth] Toggling global 2FA now propagates to all users: disabling it turns the 2FA switch off for every user (so users created while it was enabled are no longer prompted), and enabling it turns the switch on for all users + Enterprise Fixes: - [journey_engine] Avoid throwing on duplicate events and prevent overwriting existing event map entries during event creation diff --git a/plugins/two-factor-auth/api/api.js b/plugins/two-factor-auth/api/api.js index 0f15b74fc3a..d421216dc8a 100644 --- a/plugins/two-factor-auth/api/api.js +++ b/plugins/two-factor-auth/api/api.js @@ -9,8 +9,48 @@ var plugin = {}, const { generateQRCode } = require('../lib.js'); const FEATURE_NAME = 'two_factor_auth'; +// Tracks the global 2FA state this process has already reconciled to the +// members collection, so the onchange handler only reacts to an actual admin +// toggle and not to the initial config load that happens on every startup. +var lastGlobalState; + +/** + * Propagate a change of the global 2FA setting to every member's per-user + * switch (SER-2911). Enabling it globally turns the 2FA switch on for all + * users; disabling it globally turns the switch off for all users and clears + * any stored secret. While 2FA is globally disabled users may still enable or + * disable it individually for their own account. + * @param {boolean} enabled - new value of two-factor-auth.globally_enabled + */ +function propagateGlobalToMembers(enabled) { + if (typeof lastGlobalState === "undefined") { + // first call is the initial config load - record it, do not touch members + lastGlobalState = enabled; + return; + } + if (lastGlobalState === enabled || !common.db) { + return; + } + lastGlobalState = enabled; + + var update = enabled + ? {$set: {"two_factor_auth.enabled": true}} + : {$set: {"two_factor_auth.enabled": false}, $unset: {"two_factor_auth.secret_token": ""}}; + + common.db.collection("members").updateMany({}, update, function(err) { + if (err) { + log.e(`Failed to propagate global 2FA (${enabled}) to members: ${err.message}`); + } + else { + log.i(`Global 2FA ${enabled ? "enabled" : "disabled"} - updated 2FA switch for all members`); + } + }); +} + plugins.setConfigs("two-factor-auth", { globally_enabled: false +}, false, function(config) { + propagateGlobalToMembers(!!config.globally_enabled); }); plugins.register("/permissions/features", function(ob) {