mirror of
https://github.com/Bubka/2FAuth.git
synced 2025-04-30 12:24:32 +02:00
Make Login & API throttling editable using the .env file - Close #163
This commit is contained in:
parent
9913560787
commit
140cc70cef
17
.env.example
17
.env.example
@ -103,8 +103,25 @@ MAIL_FROM_NAME=null
|
|||||||
MAIL_FROM_ADDRESS=null
|
MAIL_FROM_ADDRESS=null
|
||||||
|
|
||||||
|
|
||||||
|
#### API settings ####
|
||||||
|
|
||||||
|
# The maximum number of API calls in a minute from the same IP.
|
||||||
|
# Once reached, all requests from this IP will be rejected until the minute has elapsed.
|
||||||
|
#
|
||||||
|
# Set to null to disable the API throttling.
|
||||||
|
|
||||||
|
THROTTLE_API=60
|
||||||
|
|
||||||
|
|
||||||
#### Authentication settings ####
|
#### Authentication settings ####
|
||||||
|
|
||||||
|
# The number of times per minute a user can fail to log in before being locked out.
|
||||||
|
# Once reached, all login attempts will be rejected until the minute has elapsed.
|
||||||
|
#
|
||||||
|
# This setting applies to both email/password and webauthn login attemps.
|
||||||
|
|
||||||
|
LOGIN_THROTTLE=5
|
||||||
|
|
||||||
# The default authentication guard
|
# The default authentication guard
|
||||||
#
|
#
|
||||||
# Supported:
|
# Supported:
|
||||||
|
9
Dockerfile
vendored
9
Dockerfile
vendored
@ -156,7 +156,16 @@ ENV \
|
|||||||
MAIL_ENCRYPTION=null \
|
MAIL_ENCRYPTION=null \
|
||||||
MAIL_FROM_NAME=null \
|
MAIL_FROM_NAME=null \
|
||||||
MAIL_FROM_ADDRESS=null \
|
MAIL_FROM_ADDRESS=null \
|
||||||
|
# API settings
|
||||||
|
# The maximum number of API calls in a minute from the same IP.
|
||||||
|
# Once reached, all requests from this IP will be rejected until the minute has elapsed.
|
||||||
|
# Set to null to disable the API throttling.
|
||||||
|
THROTTLE_API=60 \
|
||||||
# Authentication settings
|
# Authentication settings
|
||||||
|
# The number of times per minute a user can fail to log in before being locked out.
|
||||||
|
# Once reached, all login attempts will be rejected until the minute has elapsed.
|
||||||
|
# This setting applies to both email/password and webauthn login attemps.
|
||||||
|
LOGIN_THROTTLE=5 \
|
||||||
# The default authentication guard
|
# The default authentication guard
|
||||||
# Supported:
|
# Supported:
|
||||||
# 'web-guard' : The Laravel built-in auth system (default if nulled)
|
# 'web-guard' : The Laravel built-in auth system (default if nulled)
|
||||||
|
@ -27,6 +27,13 @@ class LoginController extends Controller
|
|||||||
|
|
||||||
use AuthenticatesUsers;
|
use AuthenticatesUsers;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The login throttle.
|
||||||
|
*
|
||||||
|
* @var integer
|
||||||
|
*/
|
||||||
|
protected $maxAttempts;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle a login request to the application.
|
* Handle a login request to the application.
|
||||||
*
|
*
|
||||||
@ -39,6 +46,8 @@ public function login(LoginRequest $request)
|
|||||||
{
|
{
|
||||||
Log::info(sprintf('User login requested by %s from %s', var_export($request['email'], true), $request->ip()));
|
Log::info(sprintf('User login requested by %s from %s', var_export($request['email'], true), $request->ip()));
|
||||||
|
|
||||||
|
$this->maxAttempts = config('auth.throttle.login');
|
||||||
|
|
||||||
// If the class is using the ThrottlesLogins trait, we can automatically throttle
|
// If the class is using the ThrottlesLogins trait, we can automatically throttle
|
||||||
// the login attempts for this application. We'll key this by the username and
|
// the login attempts for this application. We'll key this by the username and
|
||||||
// the IP address of the client making these requests into this application.
|
// the IP address of the client making these requests into this application.
|
||||||
|
@ -20,6 +20,13 @@ class WebAuthnLoginController extends Controller
|
|||||||
{
|
{
|
||||||
use AuthenticatesUsers;
|
use AuthenticatesUsers;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The login throttle.
|
||||||
|
*
|
||||||
|
* @var integer
|
||||||
|
*/
|
||||||
|
protected $maxAttempts;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
| WebAuthn Login Controller
|
| WebAuthn Login Controller
|
||||||
@ -67,6 +74,8 @@ public function login(WebauthnAssertedRequest $request)
|
|||||||
{
|
{
|
||||||
Log::info(sprintf('User login via webauthn requested by %s from %s', var_export($request['email'], true), $request->ip()));
|
Log::info(sprintf('User login via webauthn requested by %s from %s', var_export($request['email'], true), $request->ip()));
|
||||||
|
|
||||||
|
$this->maxAttempts = config('auth.throttle.login');
|
||||||
|
|
||||||
// If the class is using the ThrottlesLogins trait, we can automatically throttle
|
// If the class is using the ThrottlesLogins trait, we can automatically throttle
|
||||||
// the login attempts for this application. We'll key this by the username and
|
// the login attempts for this application. We'll key this by the username and
|
||||||
// the IP address of the client making these requests into this application.
|
// the IP address of the client making these requests into this application.
|
||||||
|
@ -73,7 +73,8 @@ private function getApiNamespace(string $version)
|
|||||||
protected function configureRateLimiting()
|
protected function configureRateLimiting()
|
||||||
{
|
{
|
||||||
RateLimiter::for('api', function (Request $request) {
|
RateLimiter::for('api', function (Request $request) {
|
||||||
return Limit::perMinute(60)->by($request->ip());
|
$maxAttempts = config('2fauth.api.throttle');
|
||||||
|
return is_null($maxAttempts) ? Limit::none() : Limit::perMinute($maxAttempts)->by($request->ip());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,6 @@
|
|||||||
'repository' => 'https://github.com/Bubka/2FAuth',
|
'repository' => 'https://github.com/Bubka/2FAuth',
|
||||||
'latestReleaseUrl' => 'https://api.github.com/repos/Bubka/2FAuth/releases/latest',
|
'latestReleaseUrl' => 'https://api.github.com/repos/Bubka/2FAuth/releases/latest',
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
| 2FAuth config
|
| 2FAuth config
|
||||||
@ -29,6 +28,17 @@
|
|||||||
'appSubdirectory' => env('APP_SUBDIRECTORY', ''),
|
'appSubdirectory' => env('APP_SUBDIRECTORY', ''),
|
||||||
],
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| 2FAuth API config
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'api' => [
|
||||||
|
'throttle' => env('THROTTLE_API', 60),
|
||||||
|
],
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
| 2FAuth available translations
|
| 2FAuth available translations
|
||||||
|
@ -2,6 +2,10 @@
|
|||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
||||||
|
'throttle' => [
|
||||||
|
'login' => env('LOGIN_THROTTLE', 5),
|
||||||
|
],
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
| Authentication Defaults
|
| Authentication Defaults
|
||||||
|
@ -54,7 +54,16 @@ services:
|
|||||||
- MAIL_ENCRYPTION=null
|
- MAIL_ENCRYPTION=null
|
||||||
- MAIL_FROM_NAME=null
|
- MAIL_FROM_NAME=null
|
||||||
- MAIL_FROM_ADDRESS=null
|
- MAIL_FROM_ADDRESS=null
|
||||||
|
# API settings
|
||||||
|
# The maximum number of API calls in a minute from the same IP.
|
||||||
|
# Once reached, all requests from this IP will be rejected until the minute has elapsed.
|
||||||
|
# Set to null to disable the API throttling.
|
||||||
|
- THROTTLE_API=60
|
||||||
# Authentication settings
|
# Authentication settings
|
||||||
|
# The number of times per minute a user can fail to log in before being locked out.
|
||||||
|
# Once reached, all login attempts will be rejected until the minute has elapsed.
|
||||||
|
# This setting applies to both email/password and webauthn login attemps.
|
||||||
|
- LOGIN_THROTTLE=5
|
||||||
# The default authentication guard
|
# The default authentication guard
|
||||||
# Supported:
|
# Supported:
|
||||||
# 'web-guard' : The Laravel built-in auth system (default if nulled)
|
# 'web-guard' : The Laravel built-in auth system (default if nulled)
|
||||||
|
39
tests/Api/v1/ThrottlingTest.php
Normal file
39
tests/Api/v1/ThrottlingTest.php
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Unit\Api\v1\Controllers;
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Support\Facades\Config;
|
||||||
|
use Tests\FeatureTestCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers \App\Providers\RouteServiceProvider
|
||||||
|
*/
|
||||||
|
class ThrottlingTest extends FeatureTestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
public function test_api_calls_are_throttled_using_config()
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var \App\Models\User|\Illuminate\Contracts\Auth\Authenticatable
|
||||||
|
*/
|
||||||
|
$user = User::factory()->create();
|
||||||
|
$throttle = 5;
|
||||||
|
|
||||||
|
Config::set('2fauth.api.throttle', $throttle);
|
||||||
|
|
||||||
|
$this->actingAs($user, 'api-guard');
|
||||||
|
|
||||||
|
for ($i=0; $i < $throttle - 1; $i++) {
|
||||||
|
$this->json('GET', '/api/v1/twofaccounts/count');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->json('GET', '/api/v1/twofaccounts/count')
|
||||||
|
->assertOk();
|
||||||
|
|
||||||
|
$this->json('GET', '/api/v1/twofaccounts/count')
|
||||||
|
->assertStatus(429);
|
||||||
|
}
|
||||||
|
}
|
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use Illuminate\Support\Carbon;
|
use Illuminate\Support\Carbon;
|
||||||
|
use Illuminate\Support\Facades\Config;
|
||||||
use Tests\FeatureTestCase;
|
use Tests\FeatureTestCase;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -140,19 +141,23 @@ public function test_user_login_with_invalid_credentials_returns_unauthorized()
|
|||||||
*/
|
*/
|
||||||
public function test_too_many_login_attempts_with_invalid_credentials_returns_too_many_request_error()
|
public function test_too_many_login_attempts_with_invalid_credentials_returns_too_many_request_error()
|
||||||
{
|
{
|
||||||
|
$throttle = 8;
|
||||||
|
Config::set('auth.throttle.login', $throttle);
|
||||||
|
|
||||||
$post = [
|
$post = [
|
||||||
'email' => $this->user->email,
|
'email' => $this->user->email,
|
||||||
'password' => self::WRONG_PASSWORD,
|
'password' => self::WRONG_PASSWORD,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
for ($i=0; $i < $throttle - 1; $i++) {
|
||||||
$this->json('POST', '/user/login', $post);
|
$this->json('POST', '/user/login', $post);
|
||||||
$this->json('POST', '/user/login', $post);
|
}
|
||||||
$this->json('POST', '/user/login', $post);
|
|
||||||
$this->json('POST', '/user/login', $post);
|
|
||||||
$this->json('POST', '/user/login', $post);
|
|
||||||
$response = $this->json('POST', '/user/login', $post);
|
|
||||||
|
|
||||||
$response->assertStatus(429);
|
$this->json('POST', '/user/login', $post)
|
||||||
|
->assertUnauthorized();
|
||||||
|
|
||||||
|
$this->json('POST', '/user/login', $post)
|
||||||
|
->assertStatus(429);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -278,6 +278,9 @@ public function test_webauthn_invalid_login_returns_unauthorized()
|
|||||||
*/
|
*/
|
||||||
public function test_too_many_invalid_login_attempts_returns_too_many_request_error()
|
public function test_too_many_invalid_login_attempts_returns_too_many_request_error()
|
||||||
{
|
{
|
||||||
|
$throttle = 8;
|
||||||
|
Config::set('auth.throttle.login', $throttle);
|
||||||
|
|
||||||
$this->user = User::factory()->create(['email' => self::EMAIL]);
|
$this->user = User::factory()->create(['email' => self::EMAIL]);
|
||||||
|
|
||||||
$this->session(['_webauthn' => new \Laragear\WebAuthn\Challenge(
|
$this->session(['_webauthn' => new \Laragear\WebAuthn\Challenge(
|
||||||
@ -286,14 +289,15 @@ public function test_too_many_invalid_login_attempts_returns_too_many_request_er
|
|||||||
false,
|
false,
|
||||||
)]);
|
)]);
|
||||||
|
|
||||||
|
for ($i=0; $i < $throttle - 1; $i++) {
|
||||||
$this->json('POST', '/webauthn/login', self::ASSERTION_RESPONSE_INVALID);
|
$this->json('POST', '/webauthn/login', self::ASSERTION_RESPONSE_INVALID);
|
||||||
$this->json('POST', '/webauthn/login', self::ASSERTION_RESPONSE_INVALID);
|
}
|
||||||
$this->json('POST', '/webauthn/login', self::ASSERTION_RESPONSE_INVALID);
|
|
||||||
$this->json('POST', '/webauthn/login', self::ASSERTION_RESPONSE_INVALID);
|
|
||||||
$this->json('POST', '/webauthn/login', self::ASSERTION_RESPONSE_INVALID);
|
|
||||||
$response = $this->json('POST', '/webauthn/login', self::ASSERTION_RESPONSE_INVALID);
|
|
||||||
|
|
||||||
$response->assertStatus(429);
|
$this->json('POST', '/webauthn/login', self::ASSERTION_RESPONSE_INVALID)
|
||||||
|
->assertUnauthorized();
|
||||||
|
|
||||||
|
$this->json('POST', '/webauthn/login', self::ASSERTION_RESPONSE_INVALID)
|
||||||
|
->assertStatus(429);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Loading…
Reference in New Issue
Block a user