mirror of
https://github.com/coollabsio/coolify.git
synced 2026-08-28 02:24:23 -05:00
fix(auth): use Cloudflare client IP for authentication rate limits
This commit is contained in:
@@ -92,7 +92,7 @@ class CreateNewUser implements CreatesNewUsers
|
||||
{
|
||||
$keys = [
|
||||
[
|
||||
'key' => 'registration:ip:'.sha1((string) request()->ip()),
|
||||
'key' => 'registration:ip:'.sha1(auth_rate_limit_ip(request())),
|
||||
'max' => self::REGISTRATION_IP_MAX_ATTEMPTS,
|
||||
'decay' => self::REGISTRATION_IP_DECAY_SECONDS,
|
||||
],
|
||||
|
||||
@@ -60,7 +60,7 @@ class RouteServiceProvider extends ServiceProvider
|
||||
});
|
||||
|
||||
RateLimiter::for('login', function (Request $request) {
|
||||
return Limit::perMinute(5)->by((string) $request->email.'|'.$request->ip());
|
||||
return Limit::perMinute(5)->by((string) $request->email.'|'.auth_rate_limit_ip($request));
|
||||
});
|
||||
|
||||
RateLimiter::for('two-factor', function (Request $request) {
|
||||
@@ -69,7 +69,7 @@ class RouteServiceProvider extends ServiceProvider
|
||||
|
||||
RateLimiter::for('forgot-password', function (Request $request) {
|
||||
$limits = [
|
||||
Limit::perMinutes(10, 3)->by('forgot-password:ip:'.sha1((string) $request->ip())),
|
||||
Limit::perMinutes(10, 3)->by('forgot-password:ip:'.sha1(auth_rate_limit_ip($request))),
|
||||
];
|
||||
|
||||
$emailIdentity = normalize_email_identity($request->input('email'));
|
||||
@@ -81,7 +81,7 @@ class RouteServiceProvider extends ServiceProvider
|
||||
});
|
||||
|
||||
RateLimiter::for('magic-link', function (Request $request) {
|
||||
return Limit::perMinute(5)->by(hash('sha256', (string) $request->input('token').'|'.$request->ip()));
|
||||
return Limit::perMinute(5)->by(hash('sha256', (string) $request->input('token').'|'.auth_rate_limit_ip($request)));
|
||||
});
|
||||
|
||||
RateLimiter::for('force-password-reset', function (Request $request) {
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
function auth_rate_limit_ip(Request $request): string
|
||||
{
|
||||
$cloudflareIp = $request->header('CF-Connecting-IP');
|
||||
|
||||
if (isCloud() && is_string($cloudflareIp) && filter_var($cloudflareIp, FILTER_VALIDATE_IP) !== false) {
|
||||
return $cloudflareIp;
|
||||
}
|
||||
|
||||
return (string) $request->ip();
|
||||
}
|
||||
@@ -8,6 +8,8 @@ use Illuminate\Support\Facades\RateLimiter;
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
beforeEach(function () {
|
||||
config()->set('app.maintenance.store', 'array');
|
||||
|
||||
InstanceSettings::forceCreate(['id' => 0]);
|
||||
RateLimiter::clear('login');
|
||||
|
||||
@@ -66,3 +68,27 @@ test('successful login is still possible within rate limit', function () {
|
||||
$response->assertRedirect();
|
||||
expect($response->status())->not->toBe(429);
|
||||
});
|
||||
|
||||
test('cloud login rate limits use the Cloudflare client ip', function () {
|
||||
config()->set('constants.coolify.self_hosted', false);
|
||||
|
||||
foreach (range(1, 5) as $attempt) {
|
||||
$this->withHeader('CF-Connecting-IP', '2001:db8::10')
|
||||
->withHeader('X-Forwarded-For', '2001:db8::10, 108.162.221.29')
|
||||
->withServerVariables(['REMOTE_ADDR' => '10.0.0.5'])
|
||||
->post('/login', [
|
||||
'email' => 'test@example.com',
|
||||
'password' => 'wrong-password',
|
||||
])
|
||||
->assertRedirect();
|
||||
}
|
||||
|
||||
$this->withHeader('CF-Connecting-IP', '2001:db8::20')
|
||||
->withHeader('X-Forwarded-For', '2001:db8::20, 108.162.221.29')
|
||||
->withServerVariables(['REMOTE_ADDR' => '10.0.0.5'])
|
||||
->post('/login', [
|
||||
'email' => 'test@example.com',
|
||||
'password' => 'wrong-password',
|
||||
])
|
||||
->assertRedirect();
|
||||
});
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
it('uses the Cloudflare client ip on cloud', function () {
|
||||
config()->set('constants.coolify.self_hosted', false);
|
||||
|
||||
$request = Request::create('/', server: [
|
||||
'REMOTE_ADDR' => '10.0.0.5',
|
||||
'HTTP_CF_CONNECTING_IP' => '2001:db8::10',
|
||||
]);
|
||||
|
||||
expect(auth_rate_limit_ip($request))->toBe('2001:db8::10');
|
||||
});
|
||||
|
||||
it('falls back to the resolved request ip when the Cloudflare header is invalid', function () {
|
||||
config()->set('constants.coolify.self_hosted', false);
|
||||
|
||||
$request = Request::create('/', server: [
|
||||
'REMOTE_ADDR' => '203.0.113.10',
|
||||
'HTTP_CF_CONNECTING_IP' => 'invalid',
|
||||
]);
|
||||
|
||||
expect(auth_rate_limit_ip($request))->toBe('203.0.113.10');
|
||||
});
|
||||
|
||||
it('ignores the Cloudflare header on self-hosted instances', function () {
|
||||
config()->set('constants.coolify.self_hosted', true);
|
||||
|
||||
$request = Request::create('/', server: [
|
||||
'REMOTE_ADDR' => '203.0.113.20',
|
||||
'HTTP_CF_CONNECTING_IP' => '2001:db8::20',
|
||||
]);
|
||||
|
||||
expect(auth_rate_limit_ip($request))->toBe('203.0.113.20');
|
||||
});
|
||||
@@ -6,6 +6,8 @@ use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
beforeEach(function () {
|
||||
config()->set('app.maintenance.store', 'array');
|
||||
|
||||
InstanceSettings::query()->forceCreate(['id' => 0]);
|
||||
});
|
||||
|
||||
|
||||
@@ -8,6 +8,8 @@ use Illuminate\Support\Facades\RateLimiter;
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
beforeEach(function () {
|
||||
config()->set('app.maintenance.store', 'array');
|
||||
|
||||
InstanceSettings::query()->forceCreate([
|
||||
'id' => 0,
|
||||
'is_registration_enabled' => true,
|
||||
|
||||
Reference in New Issue
Block a user