Skip to content

Commit d2bffb8

Browse files
committed
Add TWO_FACTOR_ENABLED global kill-switch to MFA gate
MFAGateService::requiresChallenge() had no master on/off switch, contradicting the SDS idp-mfa.md §10.1 rollout plan, which requires being able to instantly revert to password-only login without a code rollback if something goes wrong post-deploy. config/two_factor.php gains an 'enabled' key (env TWO_FACTOR_ENABLED, default true) checked first in requiresChallenge(), short-circuiting before any per-user or device-trust evaluation.
1 parent 5fc47a4 commit d2bffb8

4 files changed

Lines changed: 35 additions & 1 deletion

File tree

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,4 +52,6 @@ model.sql
5252
/.phpunit.cache/
5353
docker-compose/mysql/model/*.sql
5454
public/assets/*.map
55-
public/assets/css/*.map
55+
public/assets/css/*.map
56+
.codegraph
57+
docs/plans

app/Services/Auth/MFAGateService.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ public function __construct(
3030

3131
public function requiresChallenge(User $user, ?string $cookieToken): bool
3232
{
33+
if (!config('two_factor.enabled', true)) {
34+
return false;
35+
}
3336
if (!$user->shouldRequire2FA()) {
3437
return false;
3538
}

config/two_factor.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,18 @@
1515
use App\libs\Auth\Models\IGroupSlugs;
1616

1717
return [
18+
/*
19+
|--------------------------------------------------------------------------
20+
| Global Kill-Switch
21+
|--------------------------------------------------------------------------
22+
|
23+
| Master switch for the whole 2FA gate (SDS idp-mfa.md §10.1 rollout plan).
24+
| Defaults on; set TWO_FACTOR_ENABLED=false in a specific environment to
25+
| instantly revert to password-only login with no code rollback needed.
26+
|
27+
*/
28+
'enabled' => env('TWO_FACTOR_ENABLED', true),
29+
1830
/*
1931
|--------------------------------------------------------------------------
2032
| Enforced Groups

tests/Unit/MFAGateServiceTest.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
use App\Services\Auth\IDeviceTrustService;
1919
use App\Services\Auth\MFAGateService;
2020
use Auth\User;
21+
use Illuminate\Support\Facades\Config;
2122
use Mockery;
2223
use Tests\TestCase;
2324
use Mockery\MockInterface;
@@ -110,4 +111,20 @@ public function testRequiresChallengePassesThroughEmptyStringCookieToDeviceTrust
110111

111112
$this->assertTrue($this->service->requiresChallenge($user, ''));
112113
}
114+
115+
// -------------------------------------------------------------------------
116+
// Global kill-switch (SDS idp-mfa.md §10.1): TWO_FACTOR_ENABLED=false must
117+
// short-circuit the gate before any per-user or device-trust evaluation.
118+
// -------------------------------------------------------------------------
119+
120+
public function testRequiresChallengeReturnsFalseWhenTwoFactorGloballyDisabled(): void
121+
{
122+
Config::set('two_factor.enabled', false);
123+
124+
$user = Mockery::mock(User::class);
125+
$user->shouldNotReceive('shouldRequire2FA');
126+
$this->deviceTrustService->shouldNotReceive('isDeviceTrusted');
127+
128+
$this->assertFalse($this->service->requiresChallenge($user, null));
129+
}
113130
}

0 commit comments

Comments
 (0)