toBeFalse(); $operator = Operator::factory()->role('Owner')->create(); $this->actingAs($operator, 'operator')->get(route('admin.overview'))->assertOk(); }); it('sends an operator without two-factor to the setup page once the switch is on', function () { AppSettings::set('console.require_2fa', true); $operator = Operator::factory()->role('Owner')->create(); $this->actingAs($operator, 'operator') ->get(route('admin.overview')) ->assertRedirect(route('admin.two-factor-setup')); }); it('no longer exempts admin.settings itself — only enrolment', function () { // Codex R15 P1b: the earlier shape exempted the whole of admin.settings, // on the theory that it was "the page where two-factor is set up". That // component also carries staff management, site visibility, network // restrictions, account changes, and the two-factor policy switch // itself — so an unenrolled operator got unrestricted access to all of // that, not just enrolment. An Owner here on purpose: staff management is // Owner-only, so this is the account that would have the most to reach. AppSettings::set('console.require_2fa', true); $operator = Operator::factory()->role('Owner')->create(); $this->actingAs($operator, 'operator') ->get(route('admin.settings')) ->assertRedirect(route('admin.two-factor-setup')); }); it('lets an operator with confirmed two-factor through', function () { AppSettings::set('console.require_2fa', true); $operator = Operator::factory()->role('Owner')->create([ 'two_factor_secret' => encrypt('JBSWY3DPEHPK3PXP'), 'two_factor_confirmed_at' => now(), ]); $this->actingAs($operator, 'operator')->get(route('admin.overview'))->assertOk(); }); it('refuses to switch it on while your own account has no two-factor', function () { // The lock-out this exists to prevent: the page that would turn it back off // sits behind the switch. Same shape as console.allowed_ips. $operator = Operator::factory()->role('Owner')->create(); Livewire::actingAs($operator, 'operator')->test(Settings::class) ->set('requireTwoFactor', true) ->call('saveTwoFactorPolicy') ->assertHasErrors('requireTwoFactor'); expect(AppSettings::bool('console.require_2fa', false))->toBeFalse(); }); it('lets an operator without two-factor genuinely enrol while the switch is on, then reach the console', function () { // The trap this guards against: the exemption alone is not enough if // there is nowhere to actually enrol behind it. Uses a non-Owner role — // the requirement applies to every operator, not only the one who can // flip the switch. AppSettings::set('console.require_2fa', true); $operator = Operator::factory()->role('Support')->create(['password' => 'password']); // Bounced off an ordinary page… $this->actingAs($operator, 'operator') ->get(route('admin.overview')) ->assertRedirect(route('admin.two-factor-setup')); // …and off admin.settings too — that page is no longer the exemption… $this->actingAs($operator, 'operator') ->get(route('admin.settings')) ->assertRedirect(route('admin.two-factor-setup')); // …but the two-factor setup page itself is genuinely reachable, not // another bounce in disguise. $this->actingAs($operator, 'operator') ->get(route('admin.two-factor-setup')) ->assertOk(); // Enrol for real: password confirmation, then a real TOTP code. $page = Livewire::actingAs($operator, 'operator') ->test(TwoFactorSetup::class) ->set('confirmablePassword', 'password') ->call('confirmPassword') ->assertHasNoErrors() ->call('enableTwoFactor'); $code = app(Google2FA::class)->getCurrentOtp(decrypt($operator->refresh()->two_factor_secret)); $page->set('twoFactorCode', $code)->call('confirmTwoFactor')->assertHasNoErrors(); expect($operator->refresh()->two_factor_confirmed_at)->not->toBeNull(); // And now the rest of the console actually opens — admin.settings // included, the very page this test proved was blocked above. $this->actingAs($operator->fresh(), 'operator') ->get(route('admin.overview')) ->assertOk(); $this->actingAs($operator->fresh(), 'operator') ->get(route('admin.settings')) ->assertOk(); }); it('allows it once your own two-factor is confirmed', function () { $operator = Operator::factory()->role('Owner')->create([ 'two_factor_secret' => encrypt('JBSWY3DPEHPK3PXP'), 'two_factor_confirmed_at' => now(), ]); Livewire::actingAs($operator, 'operator')->test(Settings::class) ->set('requireTwoFactor', true) ->call('saveTwoFactorPolicy') ->assertHasNoErrors(); expect(AppSettings::bool('console.require_2fa', false))->toBeTrue(); });