-
Notifications
You must be signed in to change notification settings - Fork 11.6k
[12.x] [POC] Allow rate limit hits to only be recorded after successful responses #56933
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
f322327
to
52c94d7
Compare
CMIIW, but aren't rate limiter supposedly reset after successful response? |
No, they are managed into the cache and reset when their period ends. For example, the If it got reset after a successful response, one could do as many requests as they want in a day, and that limit wouldn't serve any purpose. |
Totally understand this is a proof of concept PR, so not covering every base right now. I like the more generic idea of being able to rate limit based on the response. We do this to protect against enumeration attacks, e.g., RateLimiter::for('resources', fn ($request) => [
Limit::perMinute(60)->by($request->ip()),
Limit::perMinute(5)->by('.404'.$request->ip())->onNotFound(),
]); Good requests get 60 per minute. If the user is continually hitting 404s, we assume enumeration attack and block for the remainder of the minute. So more generally, I like the idea of a low-level hook that all of this is built on top of: RateLimiter::for('resources', fn ($request) => [
Limit::perMinute(60)->by($request->ip()),
Limit::perMinute(5)
->by('.404'.$request->ip())
->when(function (Response $response) {
return $response->status() === 404;
}),
]); |
@timacdonald Funnily enough, that was exactly my first thought as well. However, when I actually started implementing it I realized that |
I see. I don't think it could hurt to add a HTTP layer concern into framework/src/Illuminate/Cache/RateLimiting/Limit.php Lines 132 to 143 in bb00401
The Side note: I wonder if RateLimiter::for('resources', fn ($request) => [
Limit::perMinute(60)->by($request->ip()),
Limit::perMinute(5)
->by('.404'.$request->ip())
->after(function (Response $response) {
return $response->status() === 404;
}),
]); |
@timacdonald That callback can return whatever it wants. Its technical return type is But to implement what we want here, we would have to pass in an instance of Adding our helper methods, such as class Limit
{
// ...
public function onNotFound()
{
$this->onResponse(function (Response $response) {
return $response->status() == 404;
});
return $this;
}
} Now... here too we can cheat, by not type-hinting the parameter, but that's even worse 😑 |
Gotcha. Dunno; I don't really see a problem with it, but that is just me. I'm sure we have other instances of stuff in sub-packages that are really there as holistic framework affordance. Else, we could always add But it is all good, I just wanted to share my thoughts, not specifically for there to be change here. Think it is a good feature. |
@timacdonald in the past, I put code in a macro in I thought it's overkill here, but... 🤷 |
I see this is a POC, but is it planned to also have the behavior of the I have a job that hits an external service. It can fail before making the request for some other reasons. It would be nice to only hit the limiter if the request, and therefore the job, succeeds. |
I personally like @timacdonald's idea of the lower-level hook and we should just make that happen. 👍 |
52c94d7
to
d05bda4
Compare
I don't know how. Gonna close this out, and let someone else pick it up if they want. |
Happy to take a look on Monday |
Turns out I was excited enough to do it today :) |
No tests yet, as this just a proof of concept for now. Waiting to hear from @taylorotwell if this is how we should implement it.
This PR adds the
onSuccess()
method to theLimit
class, which will only record a hit after a successful response:Background
Imagine the following rate limit:
This will only allow a single signup per day from a given IP address, which is great.
However, if the user submits the signup form with some validation errors, they will use up their rate limit, and will no longer be able to submit the form again until the next day. That's not what we want.
Solution
By adding
onSuccess()
to theLimit
:...we can now have a rate limit that only gets a hit after the form is successfully processed. Form submissions with validation errors do not count against the rate limit.
It can also be combined with a general, more lax rate limit:
This will allow up to 10 failed requests per minute, but only a single successful request per day.