Laravel Throttle Middleware: is there a callback / handler for it? - php

I want to use Laravel's Throttle Middleware, but I want to also log any cases where the user made too many attempts.
Is there any Callback / Handler where I can detect this and do something with it?
Let's say I have a route like this:
Route::get('foo', array('before' => 'throttle:5,10', function () {
return 'You shall pass!';
}, ));

If it reaches the max count, it will fire this event:
Illuminate\Auth\Events\Lockout
So you can probably listen to it
protected $listen = [
...
'Illuminate\Auth\Events\Lockout' => [
'App\Listeners\LogLockout',
],
];

Illuminate\Auth\Events\Lockout event is only fired in Illuminate\Foundation\Auth\ThrottlesLogins but you can still fire it by hand.
This is how I've done that:
In app/Exceptions/Handler.php add these use statements:
use Illuminate\Auth\Events\Lockout;
use Symfony\Component\HttpKernel\Exception\HttpException;
Add this to report method of the same file:
if ($exception instanceof HttpException) {
if ('Too Many Attempts.' == $exception->getMessage()) {
event(new Lockout(request()));
}
}
Execute php artisan make:listener LogThrottleLimitReached and add this code inside handle method of app/Listeners/LogThrottleLimitReached.php file:
$request = $event->request;
\Log::error(sprintf(
'Throttling rate limit reached. URL: %s, Body: %s, IP: %s',
$request->url(),
json_encode($request->all()),
$request->ip()
));
Register that listener in app/Providers/EventServiceProvider.php:
use App\Listeners\LogThrottleLimitReached;
use Illuminate\Auth\Events\Lockout;
and
protected $listen = [
Lockout::class => [
LogThrottleLimitReached::class,
],
];

You can just extend the ThrottleRequests class and redeclare its buildException, or try handling its exception (\Symfony\Component\HttpKernel\Exception\HttpException with 429 'Too Many Attempts.') inside app/Exceptions/Handler.php.

Related

Laravel API rate limiter for specific method

Is there any way to apply rate limit (throttle) for specific method like POST, PUT to prevent multiple api hitting within seconds
I tried to apply limit for whole api in /app/Http/Kernel.php
protected $middlewareGroups = [
'web' => [
...
],
'api' => [
'throttle:1,0.008', <<--- its prevent all api within 0.48 sec
],
];
Problem :
we need to prevent only selected methods.
There are number of ways to do it, You can create another middleware which you can use and group the routes you want to apply custom throttle.
Additionally, you can straightly apply the throttle when defining a route
Route::post('/wiggle', function () {
//
})->middleware(['auth:api','throttle:1,0.008']);
or
Route::middleware(['auth:api','throttle:1,0.008'])->group(function () {
Route::post('wiggle', [YourClass::class, 'wiggle'])->name('wiggle');
});
You can use multiple ways to make rate limit in Laravel.
One of ways is Middleware. silver already describe the way.
Second way is using Illuminate\Support\Facades\RateLimiter (Laravel 8 or higher)
For example, if you want to send email verification messages with rate limit 1 message per 60 seconds.
namespace App\Http\Controllers;
use Illuminate\Support\Facades\RateLimiter;
class EmailVerificationController extends Controller
{
public function send(Request $request)
{
$user = Auth::user();
$email = $request->input('email');
$resendSmsTimeoutSecs = 60;
$rateLimiterKey = 'email-verification:' . $email;
RateLimiter::attempt($rateLimiterKey, 1,
function () use ($user) {
$user->sendEmailVerification();
},
$resendSmsTimeoutSecs
);
return response()->json([
'resend_timeout' => RateLimiter::availableIn($rateLimiterKey)
]);
}
}
About RateLimiter

It is posible to stop event listener propagation when first listener fails? Laravel Lumen

I have one event with two listeners. I need to stop listener propagation when the first listener fails. For example:
Event: RegisterUserEvent
Listeners: StoreUserListener and SendVerificationEmailListener
If, for any reason, the user can't be stored in database, I want that SendVerificationEmailListener doesn't execute.
I'm I using Lumen 8.x and the events are processed with Redis.
Part of my code is (Registering Events and Listeners):
`protected $listen = [
RegisterReservationEvent::class => [
RegisterReservationInDatabase::class,
SendReservationEmail::class
],
];`
First listener executed:
`public function handle(RegisterReservationEvent $event): bool
{
try {
if (formReservationExists($event->formId, $event->guestReservationId)) {
throw new FormException("Reservation {$event->guestReservationId} already registered in form {$event->formId}");
}
$data = [
'form_id' => $event->formId,
'guest_reservation_id' => $event->guestReservationId,
'token' => Str::uuid()->toString(),
'status' => 'ready_to_send',
];
$reservation = FormReservation::create($data);
if ($reservation === null) {
throw new FormException("Error saving reservation {$event->guestReservationId} in form {$event->formId}");
}
} catch (Exception $e) {
dump($e->getMessage());
return false;
}
}`
And the listener that I don't want to execute is:
`public function handle(RegisterReservationEvent $event)
{
dump('Executed for ' . $event->guestReservationId);
}`
But it was executed anyway..
I am using Redis to process the listeners.
And this is the result when queue runs
Thanks.
I don't know about lumen however in laravel according to their documentation.
Sometimes, you may wish to stop the propagation of an event to other listeners. You may do so by returning false from your listener's handle method.
Maybe it is the same for lumen.
source
Edit: just found the same in lumen's documentation.
Stopping The Propagation Of An Event
Sometimes, you may wish to stop the propagation of an event to other listeners. You may do so using by returning false from your listener's handle method.

Laravel 6 - Pass a parameter to $app->when()->needs()->give()

I am trying to use this package to push notifications to users via OneSignal. However I needed to make a little change. My API serves two (related) apps and I have two OneSignal configs. I am trying to override its ServiceProvider (using this technique).
The ServiceProvider presents itself as follows
<?php
namespace NotificationChannels\OneSignal;
use Berkayk\OneSignal\OneSignalClient;
use Illuminate\Support\ServiceProvider;
use NotificationChannels\OneSignal\Exceptions\InvalidConfiguration;
class OneSignalServiceProvider extends ServiceProvider
{
/**
* Bootstrap the application services.
*/
public function boot()
{
$this->app->when(OneSignalChannel::class)
->needs(OneSignalClient::class)
->give(function () {
$oneSignalConfig = config('services.onesignal');
if (is_null($oneSignalConfig)) {
throw InvalidConfiguration::configurationNotSet();
}
return new OneSignalClient(
$oneSignalConfig['app_id'],
$oneSignalConfig['rest_api_key'],
''
);
});
}
}
The behavior that I want to change is located in the line
$oneSignalConfig = config('services.onesignal');
As it assumes that my config/services.php has the following entry (stated in the doc) :
// config/services.php
...
'onesignal' => [
'app_id' => env('ONESIGNAL_APP_ID'),
'rest_api_key' => env('ONESIGNAL_REST_API_KEY')
],
...
Whereas I want to set my config/services.php as follows
// config/services.php
...
'onesignal' => [
'app1' => [
'app_id' => env('ONESIGNAL_1_APP_ID'),
'rest_api_key' => env('ONESIGNAL_1_REST_API_KEY')
],
'app2' => [
'app_id' => env('ONESIGNAL_2_APP_ID'),
'rest_api_key' => env('ONESIGNAL_2_REST_API_KEY')
],
],
...
And I want somehow to tell my ServiceProvider (through some kind of parameter) to either do
$oneSignalConfig = config('services.onesignal.app1');
OR
$oneSignalConfig = config('services.onesignal.app2');
But I didn't find any way to pass a parameter to the class, the boot function or the give method (and if I understood well I shouldn't even be doing that).
The only way I could think of is to create two classes that extend the OneSignalChannel::class
and duplicate code in the boot function so it becomes as follows :
public function boot()
{
$this->app->when(FirstOneSignalChannel::class)
->needs(OneSignalClient::class)
->give(function () {
$oneSignalConfig = config('services.onesignal.app1');
if (is_null($oneSignalConfig)) {
throw InvalidConfiguration::configurationNotSet();
}
return new OneSignalClient(
$oneSignalConfig['app_id'],
$oneSignalConfig['rest_api_key'],
''
);
});
$this->app->when(SecondOneSignalChannel::class)
->needs(OneSignalClient::class)
->give(function () {
$oneSignalConfig = config('services.onesignal.app2');
if (is_null($oneSignalConfig)) {
throw InvalidConfiguration::configurationNotSet();
}
return new OneSignalClient(
$oneSignalConfig['app_id'],
$oneSignalConfig['rest_api_key'],
''
);
});
}
The difference in the when provoking a difference in the config but it seems a lot of duplication and not extensible (what if I had three apps).
Should I use this method, or is there a way to pass a parameter to this ServiceProvider or is there another solution ?
https://stackoverflow.com/a/34224082/10371024
I could see what you need, but to pass parameter to boot method is not a good idea according to Laravel architecture. You may try to get what you want with using events as Vladislav suggested.

Error with an api route - Laravel 5.3

I'm trying to access to this route: http://anaketesting.tk/product-service/payment-notification Really is a route for api, but consuming the route, have same error that browser.
My route try 1:
Route::get('/product-service/payment-notification', "ProductServiceController#notification")->name('productService.notification');
My route try 2:
Route::get('/product-service/payment-notification', function(){
return \Response::json([
'CREATED' => true
], 201); #also i tryed return 201 directly...
});
My route try 3:
Route::get('product-service/payment-notification', [
'as' => 'productService.notification',
'uses' => 'ProductServiceController#notification'
]);
My notification méthod
public function notification(Request $request){
$date = Carbon::now();
$date = $date->format('Ymdhis');
file_put_contents(storage_path().'/notification_'.$date.'.json', \Response::json($request));
return \Response::json([
'CREATED' => true
], 201);
}
I have not an storage/logs error with this method, is as was ignored. Please help ;)
see the RouteServiceProvider of Laravel 5.3, it shows that api routes being grouped and prefixed with api by default.
/app/Providers/RouteServiceProvider.php
/**
* Define the "api" routes for the application.
*
* These routes are typically stateless.
*
* #return void
*/
protected function mapApiRoutes()
{
Route::group([
'middleware' => 'api',
'namespace' => $this->namespace,
'prefix' => 'api',
], function ($router) {
require base_path('routes/api.php');
});
}
So you need to prefix api in your url. for example
to call this route
Route::get('/product-service/payment-notification', "ProductServiceController#notification")->name('productService.notification');
you need to call
http://anaketesting.tk/api/product-service/payment-notification
not
http://anaketesting.tk/product-service/payment-notification
in TRY3, as your system is skipping some logs, there might be two different issues
Your system doesn't have sufficient permission to write data into log files. in order to use this, you have to give permission to your log files.
You can handle these types of exceptions by using CustomException,
go to App/Exceptions/Handler.php
public function render($request, Exception $exception)
{
if($exception instanceof RouteNotFoundException){
abort(404,'Invalid Route Requested');
}else{
return parent::render($request, $exception);
}
}

Laravel validator throws an exception instead of redirecting back

After I upgraded to Laravel 5.2 I encountered a problem with the laravel validator. When I want to validate data in a controller take for example this code.
<?php
namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
class ContactController extends Controller
{
public function storeContactRequest(Request $request)
{
$this->validate($request, [
'_token' => 'required',
'firstname' => 'required|string'
'lastname' => 'required|string'
'age' => 'required|integer',
'message' => 'required|string'
]);
// Here to store the message.
}
}
But somehow when I enter unvalid data it will not redirect me back to the previous page and flash some messages to the session but it will trigger an exception and gives me a 500 error page back.
This is the exception I get.
I have read in the documentation that the ValidationException is new instead of the HttpResponseException but I don't know if it has anything to do with this.
[2016-01-05 11:49:49] production.ERROR: exception 'Illuminate\Foundation\Validation\ValidationException' with message 'The given data failed to pass validation.' in /home/vagrant/Code/twentyre-webshop/vendor/laravel/framework/src/Illuminate/Foundation/Validation/ValidatesRequests.php:70
And when I use a seperate request class it will just redirect back with the error messages. It seems to me only the validate method used in a controller is affected by this behaviour.
Update your App\Exceptions\Handler class
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Illuminate\Foundation\Validation\ValidationException;
/**
* A list of the exception types that should not be reported.
*
* #var array
*/
protected $dontReport = [
AuthorizationException::class,
HttpException::class,
ModelNotFoundException::class,
ValidationException::class,
];
I also recommend you to read the docs how to migrate to laravel 5.2, because there were some breaking changes. For example this, ValidatesRequests trait throws
Illuminate\Foundation\Validation\ValidationException instead of Illuminate\Http\Exception\HttpResponseException
Documentation how to migrate from Laravel 5.1 to 5.2
Example from laravel docs. You can use Validator facade, for custom validation fails behaviour
public function store(Request $request)
{
$validator = Validator::make($request->all(), [
'title' => 'required|unique:posts|max:255',
'body' => 'required',
]);
if ($validator->fails()) {
return redirect('post/create')
->withErrors($validator)
->withInput();
}
// Store the blog post...
}
This is how I handle it in Laravel 5.3 (by modifying Handler.php)
https://stackoverflow.com/a/42852358/3107185
For laravel 5.2 I had to add this line:
if ($e instanceof ValidationException)
{
return redirect()->back()->withInput();
}
In App\Exceptions\Handler.php,and the following headers:
use Illuminate\Session\TokenMismatchException;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Illuminate\Validation\ValidationException;
use Illuminate\Auth\AuthenticationException;
For my purpose, I was bulding a fully API based application in Laravel 5.3 which I had manually upgraded from Laravel 5.1. and I just needed Laravel to respond back with the validation errors that needed fixing on my FormRequest.
Adding this line:
elseif ($e instanceof ValidationException)
{
return $this->convertValidationExceptionToResponse($e, $request);
}
after this one:
if ($e instanceof ModelNotFoundException) {
$e = new NotFoundHttpException($e->getMessage(), $e);
}
In App\Exceptions\Handler.php did the trick for me and returned expected validation errors when using FormRequest validation.
Please see my comments here: #ratatatKE's comments on github
might save someone time, Another issue is that you are calling validator->validate() in the view, not in the controller
i was calling in the view because i have a lazy load component that triggered on the view
I had the same problem when upgrading 4.2 to 5.3.
This answer worked for me.
Override the method in app/Exceptions/Handler.php
protected function convertExceptionToResponse(Exception $e)
{
if (config('app.debug')) {
$whoops = new \Whoops\Run;
$whoops->pushHandler(new \Whoops\Handler\PrettyPageHandler);
return response()->make(
$whoops->handleException($e),
method_exists($e, 'getStatusCode') ? $e->getStatusCode() : 500,
method_exists($e, 'getHeaders') ? $e->getHeaders() : []
);
}
return parent::convertExceptionToResponse($e);
}
Answer found here: https://laracasts.com/discuss/channels/laravel/whoops-20-laravel-52

Categories