From 40deffb3b9cdee4959904ecc14ebefc8a8f4742d Mon Sep 17 00:00:00 2001 From: Pig Fang Date: Thu, 16 Aug 2018 17:57:24 +0800 Subject: [PATCH] Limit login attempts by IP address --- app/Http/Controllers/AuthController.php | 16 ++++++---- resources/views/auth/login.tpl | 3 +- tests/AuthControllerTest.php | 14 ++++++--- tests/TestCase.php | 42 +++++++++++++++++++++++++ 4 files changed, 64 insertions(+), 11 deletions(-) diff --git a/app/Http/Controllers/AuthController.php b/app/Http/Controllers/AuthController.php index 517c529e..edaa4f47 100644 --- a/app/Http/Controllers/AuthController.php +++ b/app/Http/Controllers/AuthController.php @@ -7,8 +7,7 @@ use Log; use Mail; use View; use Utils; -use Cookie; -use Option; +use Cache; use Session; use App\Events; use App\Models\User; @@ -39,7 +38,11 @@ class AuthController extends Controller // it will return a null value. $user = $users->get($identification, $authType); - if (session('login_fails', 0) > 3) { + // Require CAPTCHA if user fails to login more than 3 times + $loginFailsCacheKey = sha1('login_fails_'.Utils::getClientIp()); + $loginFails = (int) Cache::get($loginFailsCacheKey, 0); + + if ($loginFails > 3) { $this->validate($request, ['captcha' => 'required|captcha']); } @@ -53,14 +56,15 @@ class AuthController extends Controller event(new Events\UserLoggedIn($user)); - session()->forget('last_requested_path'); + Cache::forget($loginFailsCacheKey); return json(trans('auth.login.success'), 0); } else { - Session::put('login_fails', session('login_fails', 0) + 1); + // Increase the counter + Cache::put($loginFailsCacheKey, ++$loginFails); return json(trans('auth.validation.password'), 1, [ - 'login_fails' => session('login_fails') + 'login_fails' => $loginFails ]); } } diff --git a/resources/views/auth/login.tpl b/resources/views/auth/login.tpl index af0542fc..dac349ff 100644 --- a/resources/views/auth/login.tpl +++ b/resources/views/auth/login.tpl @@ -26,8 +26,9 @@ diff --git a/tests/AuthControllerTest.php b/tests/AuthControllerTest.php index 56ce4934..a27ee8a8 100644 --- a/tests/AuthControllerTest.php +++ b/tests/AuthControllerTest.php @@ -77,6 +77,8 @@ class AuthControllerTest extends TestCase $this->flushSession(); + $loginFailsCacheKey = sha1('login_fails_'.Utils::getClientIp()); + // Logging in should be failed if password is wrong $this->postJson( '/auth/login', [ @@ -88,12 +90,12 @@ class AuthControllerTest extends TestCase 'msg' => trans('auth.validation.password'), 'login_fails' => 1 ] - )->assertSessionHas('login_fails', 1); + ); // Unable to assert cache content since array driver has unexpected behaviors $this->flushSession(); // Should check captcha if there are too many fails - $this->withSession(['login_fails' => 4]) + $this->withCache([$loginFailsCacheKey => 4]) ->postJson( '/auth/login', [ 'identification' => $user->email, @@ -103,6 +105,7 @@ class AuthControllerTest extends TestCase 'msg' => trans('validation.required', ['attribute' => 'captcha']) ]); + $this->flushCache(); $this->flushSession(); // Should return a warning if user isn't existed @@ -118,7 +121,8 @@ class AuthControllerTest extends TestCase $this->flushSession(); // Should clean the `login_fails` session if logged in successfully - $this->withSession(['login_fails' => 1])->postJson('/auth/login', [ + $this->withCache([$loginFailsCacheKey => 1]) + ->postJson('/auth/login', [ 'identification' => $user->email, 'password' => '12345678' ])->assertJson( @@ -126,8 +130,10 @@ class AuthControllerTest extends TestCase 'errno' => 0, 'msg' => trans('auth.login.success') ] - )->assertSessionMissing('login_fails'); + ); + $this->assertCacheMissing($loginFailsCacheKey); + $this->flushCache(); $this->flushSession(); // Logged in should be in success if logged in with player name diff --git a/tests/TestCase.php b/tests/TestCase.php index eeeedd90..404e3075 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -43,6 +43,48 @@ class TestCase extends Illuminate\Foundation\Testing\TestCase return $this->actingAs($role); } + /** + * Set the cache to the given array. + * + * @param array $data + * @param int $minutes + * @return $this + */ + public function withCache(array $data, $minutes = 60) + { + foreach ($data as $key => $value) { + $this->app['cache.store']->put($key, $value, $minutes); + } + return $this; + } + + /** + * Assert that the cache does not have a given key. + * + * @param string|array $key + * @return void + */ + public function assertCacheMissing($key) + { + if (is_array($key)) { + foreach ($key as $k) { + $this->assertCacheMissing($k); + } + } else { + $this->assertFalse($this->app['cache.store']->has($key), "Cache has unexpected key: $key"); + } + } + + /** + * Flush all of the current cache data. + * + * @return void + */ + public function flushCache() + { + $this->app['cache.store']->flush(); + } + protected function tearDown() { $this->beforeApplicationDestroyed(function () {