diff --git a/src/TwoFactorAuthenticationProvider.php b/src/TwoFactorAuthenticationProvider.php index 87b9b615..4a93a4d0 100644 --- a/src/TwoFactorAuthenticationProvider.php +++ b/src/TwoFactorAuthenticationProvider.php @@ -67,6 +67,10 @@ public function qrCodeUrl($companyName, $companyEmail, $secret) */ public function verify($secret, $code) { + if (is_int($customWindow = config('fortify-options.two-factor-authentication.window'))) { + $this->engine->setWindow($customWindow); + } + $timestamp = $this->engine->verifyKeyNewer( $secret, $code, optional($this->cache)->get($key = 'fortify.2fa_codes.'.md5($code)) ); diff --git a/stubs/fortify.php b/stubs/fortify.php index 8ef9e95a..510a5391 100644 --- a/stubs/fortify.php +++ b/stubs/fortify.php @@ -140,6 +140,7 @@ Features::twoFactorAuthentication([ 'confirm' => true, 'confirmPassword' => true, + // 'window' => 0, ]), ], diff --git a/tests/AuthenticatedSessionControllerTest.php b/tests/AuthenticatedSessionControllerTest.php index 02d254ac..f2ee9795 100644 --- a/tests/AuthenticatedSessionControllerTest.php +++ b/tests/AuthenticatedSessionControllerTest.php @@ -296,6 +296,41 @@ public function test_two_factor_challenge_can_be_passed_via_code() ->assertSessionMissing('login.id'); } + public function test_two_factor_challenge_fails_for_old_otp_and_zero_window() + { + app('config')->set('auth.providers.users.model', TestTwoFactorAuthenticationSessionUser::class); + + //Setting window to 0 should mean any old OTP is instantly invalid + app('config')->set('fortify.features', [ + Features::twoFactorAuthentication(['window' => 0]), + ]); + + $this->loadLaravelMigrations(['--database' => 'testbench']); + $this->artisan('migrate', ['--database' => 'testbench'])->run(); + + $tfaEngine = app(Google2FA::class); + $userSecret = $tfaEngine->generateSecretKey(); + $currentTs = $tfaEngine->getTimestamp(); + $previousOtp = $tfaEngine->oathTotp($userSecret, $currentTs - 1); + + $user = TestTwoFactorAuthenticationSessionUser::forceCreate([ + 'name' => 'Taylor Otwell', + 'email' => 'taylor@laravel.com', + 'password' => bcrypt('secret'), + 'two_factor_secret' => encrypt($userSecret), + ]); + + $response = $this->withSession([ + 'login.id' => $user->id, + 'login.remember' => false, + ])->withoutExceptionHandling()->post('/two-factor-challenge', [ + 'code' => $previousOtp, + ]); + + $response->assertRedirect('/two-factor-challenge') + ->assertSessionHas('login.id'); + } + public function test_two_factor_challenge_can_be_passed_via_recovery_code() { app('config')->set('auth.providers.users.model', TestTwoFactorAuthenticationSessionUser::class);