Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 42 additions & 9 deletions Events.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
use humhub\modules\admin\controllers\UserController as AdminUserController;
use humhub\modules\admin\grid\UserActionColumn;
use humhub\modules\admin\permissions\ManageUsers;
use humhub\modules\twofa\controllers\CheckController;
use humhub\modules\twofa\events\BeforeCheck;
use humhub\modules\twofa\helpers\TwofaHelper;
use humhub\modules\twofa\helpers\TwofaUrl;
Expand Down Expand Up @@ -58,7 +59,7 @@ public static function registerAutoloader()
public static function onBeforeAction($event)
{
if (Yii::$app->user->mustChangePassword()) {
return false;
return;
}

/** @var Controller $controller */
Expand All @@ -68,22 +69,54 @@ public static function onBeforeAction($event)
Yii::$app->session->set('twofa.switchedUserId', Yii::$app->user->id);
}

if (
$controller->module->id === 'fcm-push'
&& $controller->id === 'token'
&& $controller->action->id === 'update'
) {
return false;
// Another event handler (e.g. from a module intercepting the same action) has
// already canceled or redirected the current action; overriding its redirect
// could produce a redirect loop between the two modules
if (!$event->isValid || Yii::$app->response->getIsRedirection()) {
return;
}

// Twofa-own allowlist — deliberately NOT the generic $doNotInterceptActionIds
// flag: that flag is set by controllers for unrelated reasons (e.g. the REST
// module, live polling, account deletion) and honoring it would exempt those
// actions from the second factor. Every entry here is a security decision.
if (self::isTwofaExemptRoute($controller, $event)) {
return;
}

$beforeVerifying = new BeforeCheck();
Yii::$app->trigger($beforeVerifying->name, $beforeVerifying);

if (!$beforeVerifying->handled && TwofaHelper::isVerifyingRequired() && !Yii::$app->getModule('twofa')->isTwofaCheckUrl()) {
return Yii::$app->response->redirect(TwofaUrl::toCheck());
if (!$beforeVerifying->handled && TwofaHelper::isVerifyingRequired()) {
$event->isValid = false;
Yii::$app->response->redirect(TwofaUrl::toCheck());
}
}

/**
* Routes that stay reachable while the two-factor verification is pending.
*
* @param $controller Controller
* @return bool
*/
protected static function isTwofaExemptRoute($controller, $event): bool
{
// The 2fa check page itself — redirecting it would loop onto itself
if ($controller instanceof CheckController) {
return true;
}

// Login and logout must stay reachable
if ($controller instanceof AuthController) {
return true;
}

// The mobile app updates its push token in the background
return $controller->module->id === 'fcm-push'
&& $controller->id === 'token'
&& $event->action->id === 'update';
}

/**
* Check if currently action "Impersonate" is called
*
Expand Down
4 changes: 4 additions & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
Changelog
=========

1.2.3 (Unreleased)
------------------
- Fix: Infinite redirect loop to the 2FA check page when another module intercepts the current action — the handler now yields when another interceptor already redirected the request, cancels the action via `$event->isValid` and uses a twofa-own, security-reviewed exemption list (check page, login/logout, push token update) instead of a generic opt-out flag

1.2.2 (June 18, 2026)
---------------------
- Enh: Automated code refactoring for HumHub 1.18.0-beta.6 using Rector
Expand Down
4 changes: 2 additions & 2 deletions module.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@
"resources/screenshot3.png",
"resources/screenshot4.png"
],
"version": "1.2.2",
"version": "1.2.3",
"humhub": {
"minVersion": "1.18.0-beta.6",
"maxVersion": "1.18"
}
}
}
Loading