Limit login attempts by IP address

This commit is contained in:
Pig Fang 2018-08-16 17:57:24 +08:00
parent bf8ec12645
commit 40deffb3b9
4 changed files with 64 additions and 11 deletions

View File

@ -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
]);
}
}

View File

@ -26,8 +26,9 @@
<script>
Object.defineProperty(window, '__bs_data__', {
configurable: false,
get: function () {
return Object.freeze({ tooManyFails: {{ session('login_fails') > 3 ? 'true' : 'false' }} })
return Object.freeze({ tooManyFails: {{ cache(sha1('login_fails_'.Utils::getClientIp())) > 3 ? 'true' : 'false' }} })
}
})
</script>

View File

@ -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

View File

@ -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 () {