5 use BookStack\Auth\Access\Mfa\MfaValue;
6 use BookStack\Auth\Access\Mfa\TotpService;
7 use BookStack\Auth\User;
8 use Illuminate\Support\Facades\Hash;
9 use PragmaRX\Google2FA\Google2FA;
11 use Tests\TestResponse;
13 class MfaVerificationTest extends TestCase
15 public function test_totp_verification()
17 [$user, $secret, $loginResp] = $this->startTotpLogin();
18 $loginResp->assertRedirect('/mfa/verify');
20 $resp = $this->get('/mfa/verify');
21 $resp->assertSee('Verify Access');
22 $resp->assertSee('Enter the code, generated using your mobile app, below:');
23 $resp->assertElementExists('form[action$="/mfa/verify/totp"] input[name="code"]');
25 $google2fa = new Google2FA();
26 $resp = $this->post('/mfa/verify/totp', [
27 'code' => $google2fa->getCurrentOtp($secret),
29 $resp->assertRedirect('/');
30 $this->assertEquals($user->id, auth()->user()->id);
33 public function test_totp_verification_fails_on_missing_invalid_code()
35 [$user, $secret, $loginResp] = $this->startTotpLogin();
37 $resp = $this->get('/mfa/verify');
38 $resp = $this->post('/mfa/verify/totp', [
41 $resp->assertRedirect('/mfa/verify');
43 $resp = $this->get('/mfa/verify');
44 $resp->assertSeeText('The code field is required.');
45 $this->assertNull(auth()->user());
47 $resp = $this->post('/mfa/verify/totp', [
50 $resp->assertRedirect('/mfa/verify');
51 $resp = $this->get('/mfa/verify');
53 $resp->assertSeeText('The provided code is not valid or has expired.');
54 $this->assertNull(auth()->user());
58 * @return Array<User, string, TestResponse>
60 protected function startTotpLogin(): array
62 $secret = $this->app->make(TotpService::class)->generateSecret();
63 $user = $this->getEditor();
64 $user->password = Hash::make('password');
66 MfaValue::upsertWithValue($user, MfaValue::METHOD_TOTP, $secret);
67 $loginResp = $this->post('/login', [
68 'email' => $user->email,
69 'password' => 'password',
72 return [$user, $secret, $loginResp];