I want to make it so a user can only post 1 comment per minute at most.
I've tried simply using the throttle middleware and it is not working. I can still post comments every second.
Route code:
Route::post('comment/{id}', 'HomeController#comment')->name('comment')->middleware('throttle');
Controller code:
public function comment($id)
{
$this->validate(request(), [
"body" => "required",
]);
$jersey = Jersey::findOrFail($id);
$comment = new Comment;
$comment->user_id = auth()->user()->id;
$comment->jersey_id = $jersey->id;
$comment->body = request()->input('body');
$comment->save();
activity()->by(auth()->user())->withProperties($comment)->log('Commented');
request()->session()->flash('status', 'Comment submitted!');
return redirect()->route('concept', $id);
}
How do I make it so that it will flash an error instead of saving if the user is attempting to post more than 1 comment per minute?
Usually I'm using throttle in route group like that:
Route::group(['middleware' => 'throttle:1'], function () {
// Your routes here
Route::get('/', 'HomeController#comment')->name('comment');
// ...
)}
But in your case you can modify your code with specifying throttle parameters like that:
Route::post('comment/{id}', 'HomeController#comment')->name('comment')->middleware('throttle:1');
Don't forget to clear caches to apply changes.
I ended up using the https://github.com/GrahamCampbell/Laravel-Throttle package.
Related
I use Laravel 9, but it is updated version. I mean, the project was written in laravel 5.4 and updated it to 9 later.
Here's my routes
Route::group(['middleware' => 'locale', 'prefix' => '{locale?}'], function () {
Route::get('/signin', 'Front\UsersController#getSignin');
Route::post('/signin', 'Front\UsersController#postSignin');
});
Here's my tests
public function test_get_signin(){
$response = $this->get('/signin');
$response->assertStatus(302);
}
public function test_post_signin(){
$response = $this->post('am/signin');
$response->assertStatus(302);
}
It works well when I do $this->post('am/signin');
But it is not correct. By the idea it must work when I write $this->post('/signin') without manually adding what should be added automatically. But in fact I get status code 405.
UsersController method for post('/signin) route.
public function postSignin($lang, Request $request){
// 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 IP address of the client making these requests into this application.
if ($this->hasTooManyLoginAttempts($request)) {
$this->fireLockoutEvent($request);
return $this->sendLockoutResponse($request);
}
if (Auth::validate(['email' => $request['email'],'password' => $request['password']])
|| Auth::validate(['username' => $request['email'],'password' => $request['password']])) {
$verify = User::where(['email'=> $request['email']])->first();
$verify = $verify?$verify:User::where(['username'=> $request['email']])->first();
if($verify->status =='blocked'){
if ($request->expectsJson()) {
return response()->json(array('verify'=> trans('email.profile-block')), 422);
}
}elseif(!$verify->verified){
return response()->json(array('verify'=> trans('validation.active_account').' '
.trans('car.click').' <a class="resend-link" href="/'.$lang.'/resendtoken/'.$verify->email_token
.'">'.trans('car.here1').'</a> '.trans('validation.resend_link')), 422);
}elseif($verify->status =='pending'){
return response()->json(array('verify'=> trans('car.company_napp')), 422);
}elseif($verify->status =='active'){
if(filter_var($request->email, FILTER_VALIDATE_EMAIL)){
$field = 'email';
}else{
$field = 'username';
}
if (Auth::attempt([$field => $request->email, 'password'=>$request->password],$request->remember)) {
UserLoginInfo::create(['user_id'=>Auth::id(),'ip_address'=> $request->ip(),
'info'=>json_encode(self::get_user_info())]);
if ($request->remember) {
User::where('id', Auth::id())->update(array('signed_in_for_remember_me' => Carbon::now()));
}
return Auth::user()->balance;
}
}
}
// If the login attempt was unsuccessful we will increment the number of attempts
// to login and redirect the user back to the login form. Of course, when this
// user surpasses their maximum number of attempts they will get locked out.
$this->incrementLoginAttempts($request);
return $this->sendFailedLoginResponse($request);
}
more short:
$this->get('/signin'); gives status code 302
$this->post('am/signin'); gives status code 302
$this->post('/signin'); gives status code 405
I tried
to use withoutMiddleware() - no positive result.
public function test_get_signin(){
$response = $this->withoutMiddleware('locale')->get('/signin');
$response->assertStatus(302);
}
public function test_post_signin(){
$response = $this->withoutMiddleware('locale')->post('/signin');
$response->assertStatus(302);
}
removed "?" symbol from 'prefix' => '{locale?}' in route group - no use.
commented the : Route::get('/signin', 'Front\UsersController#getSignin'); - no use again.
instead of withoutMiddleware('locale') I also used just a withoutMiddleware() - no use again.
IMPORTANT!
Both routes work well when testing with browser. I mean. User can enter his signin page and also successfully be signed in.
What's going on? How can I force post to work automatically with "/signin"? It's also weird, if I manually have to write "am/" then why it returns 302 instead of 200?
Update
I created a new project and tried to experiment there with route group and get, post methods.
Here's the routes
Route::group(['middleware' => 'locale', 'prefix' => '{locale?}'], function () {
Route::get('/test', function () {
return view('welcome');
});
Route::post('/test', function () {
return view('welcome');
});
});
here's the tests
public function test_1()
{
$response = $this->get('/test');
$response->assertStatus(200);
}
public function test_2()
{
$response = $this->post('/test');
$response->assertStatus(200);
}
test1 and test2, both of them return status 404
And when I use tests like this, I mean, adding a prefix manually
public function test_1()
{
$response = $this->get('/am/test');
$response->assertStatus(200);
}
public function test_2()
{
$response = $this->post('/am/test');
$response->assertStatus(200);
}
both of them return 200
This is good, very good. But why in my situation of my current project I get for get method 200 instead of 404 and for post 405 instead of 404, I don't have an idea. The interesting fact. I commented the whole exceptions Handler.php's code and no use. The get method everytime returns 200 when it must return 404.
Update 2
I've got some new interesting info from my experiments. If in my current project I want to test get method with uri "/signin" without '/am' part with $this->withoutMiddleware(); then I get 500, but without $this->withoutMiddleware(); I get 200
And to know what 500 want to say I used $response->dd(); and got this result
Spatie\LaravelIgnition\Exceptions\ViewException: Undefined variable
$errors in file
C:\xampp\htdocs\dashboard\test\hayvcar\storage\framework\views\7ced869fcb986989ef0f5838d6567d1b5fa4f895.php
on line 1
And if use $this->withoutExceptionHandling(); instead of $response->dd();
We'll get this result
Illuminate\View\ViewException : Undefined variable $errors (View: C:\xampp\htdocs\dashboard\test\hayvcar\resources\views\front\transport\index.blade.php)
C:\xampp\htdocs\dashboard\test\hayvcar\storage\framework\views\7ced869fcb986989ef0f5838d6567d1b5fa4f895.php:1
C:\xampp\htdocs\dashboard\test\hayvcar\vendor\laravel\framework\src\Illuminate\View\Engines\PhpEngine.php:60
C:\xampp\htdocs\dashboard\test\hayvcar\vendor\laravel\framework\src\Illuminate\View\Engines\CompilerEngine.php:61
C:\xampp\htdocs\dashboard\test\hayvcar\vendor\laravel\framework\src\Illuminate\View\View.php:139
C:\xampp\htdocs\dashboard\test\hayvcar\vendor\laravel\framework\src\Illuminate\View\View.php:122
C:\xampp\htdocs\dashboard\test\hayvcar\vendor\laravel\framework\src\Illuminate\View\View.php:91
C:\xampp\htdocs\dashboard\test\hayvcar\vendor\laravel\framework\src\Illuminate\Http\Response.php:69
C:\xampp\htdocs\dashboard\test\hayvcar\vendor\laravel\framework\src\Illuminate\Http\Response.php:35
C:\xampp\htdocs\dashboard\test\hayvcar\vendor\laravel\framework\src\Illuminate\Routing\Router.php:833
C:\xampp\htdocs\dashboard\test\hayvcar\vendor\laravel\framework\src\Illuminate\Routing\Router.php:802
C:\xampp\htdocs\dashboard\test\hayvcar\vendor\laravel\framework\src\Illuminate\Routing\Router.php:725
C:\xampp\htdocs\dashboard\test\hayvcar\vendor\laravel\framework\src\Illuminate\Pipeline\Pipeline.php:141
C:\xampp\htdocs\dashboard\test\hayvcar\vendor\laravel\framework\src\Illuminate\Pipeline\Pipeline.php:116
C:\xampp\htdocs\dashboard\test\hayvcar\vendor\laravel\framework\src\Illuminate\Routing\Router.php:726
C:\xampp\htdocs\dashboard\test\hayvcar\vendor\laravel\framework\src\Illuminate\Routing\Router.php:703
C:\xampp\htdocs\dashboard\test\hayvcar\vendor\laravel\framework\src\Illuminate\Routing\Router.php:667
C:\xampp\htdocs\dashboard\test\hayvcar\vendor\laravel\framework\src\Illuminate\Routing\Router.php:656
C:\xampp\htdocs\dashboard\test\hayvcar\vendor\laravel\framework\src\Illuminate\Foundation\Http\Kernel.php:167
C:\xampp\htdocs\dashboard\test\hayvcar\vendor\laravel\framework\src\Illuminate\Pipeline\Pipeline.php:141
C:\xampp\htdocs\dashboard\test\hayvcar\vendor\laravel\framework\src\Illuminate\Pipeline\Pipeline.php:116
C:\xampp\htdocs\dashboard\test\hayvcar\vendor\laravel\framework\src\Illuminate\Foundation\Http\Kernel.php:142
C:\xampp\htdocs\dashboard\test\hayvcar\vendor\laravel\framework\src\Illuminate\Foundation\Http\Kernel.php:111
C:\xampp\htdocs\dashboard\test\hayvcar\vendor\laravel\framework\src\Illuminate\Foundation\Testing\Concerns\MakesHttpRequests.php:526
C:\xampp\htdocs\dashboard\test\hayvcar\vendor\laravel\framework\src\Illuminate\Foundation\Testing\Concerns\MakesHttpRequests.php:293
C:\xampp\htdocs\dashboard\test\hayvcar\tests\Feature\RoutesTesting.php:1718
Caused by
ErrorException: Undefined variable $errors
C:\xampp\htdocs\dashboard\test\hayvcar\vendor\laravel\framework\src\Illuminate\Foundation\Bootstrap\HandleExceptions.php:255
C:\xampp\htdocs\dashboard\test\hayvcar\storage\framework\views\7ced869fcb986989ef0f5838d6567d1b5fa4f895.php:1
C:\xampp\htdocs\dashboard\test\hayvcar\vendor\laravel\framework\src\Illuminate\Filesystem\Filesystem.php:107
C:\xampp\htdocs\dashboard\test\hayvcar\vendor\laravel\framework\src\Illuminate\Filesystem\Filesystem.php:108
C:\xampp\htdocs\dashboard\test\hayvcar\vendor\laravel\framework\src\Illuminate\View\Engines\PhpEngine.php:58
C:\xampp\htdocs\dashboard\test\hayvcar\vendor\laravel\framework\src\Illuminate\View\Engines\CompilerEngine.php:61
C:\xampp\htdocs\dashboard\test\hayvcar\vendor\laravel\framework\src\Illuminate\View\View.php:139
C:\xampp\htdocs\dashboard\test\hayvcar\vendor\laravel\framework\src\Illuminate\View\View.php:122
C:\xampp\htdocs\dashboard\test\hayvcar\vendor\laravel\framework\src\Illuminate\View\View.php:91
C:\xampp\htdocs\dashboard\test\hayvcar\vendor\laravel\framework\src\Illuminate\Http\Response.php:69
C:\xampp\htdocs\dashboard\test\hayvcar\vendor\laravel\framework\src\Illuminate\Http\Response.php:35
C:\xampp\htdocs\dashboard\test\hayvcar\vendor\laravel\framework\src\Illuminate\Routing\Router.php:833
C:\xampp\htdocs\dashboard\test\hayvcar\vendor\laravel\framework\src\Illuminate\Routing\Router.php:802
C:\xampp\htdocs\dashboard\test\hayvcar\vendor\laravel\framework\src\Illuminate\Routing\Router.php:725
C:\xampp\htdocs\dashboard\test\hayvcar\vendor\laravel\framework\src\Illuminate\Pipeline\Pipeline.php:141
C:\xampp\htdocs\dashboard\test\hayvcar\vendor\laravel\framework\src\Illuminate\Pipeline\Pipeline.php:116
C:\xampp\htdocs\dashboard\test\hayvcar\vendor\laravel\framework\src\Illuminate\Routing\Router.php:726
C:\xampp\htdocs\dashboard\test\hayvcar\vendor\laravel\framework\src\Illuminate\Routing\Router.php:703
C:\xampp\htdocs\dashboard\test\hayvcar\vendor\laravel\framework\src\Illuminate\Routing\Router.php:667
C:\xampp\htdocs\dashboard\test\hayvcar\vendor\laravel\framework\src\Illuminate\Routing\Router.php:656
C:\xampp\htdocs\dashboard\test\hayvcar\vendor\laravel\framework\src\Illuminate\Foundation\Http\Kernel.php:167
C:\xampp\htdocs\dashboard\test\hayvcar\vendor\laravel\framework\src\Illuminate\Pipeline\Pipeline.php:141
C:\xampp\htdocs\dashboard\test\hayvcar\vendor\laravel\framework\src\Illuminate\Pipeline\Pipeline.php:116
C:\xampp\htdocs\dashboard\test\hayvcar\vendor\laravel\framework\src\Illuminate\Foundation\Http\Kernel.php:142
C:\xampp\htdocs\dashboard\test\hayvcar\vendor\laravel\framework\src\Illuminate\Foundation\Http\Kernel.php:111
C:\xampp\htdocs\dashboard\test\hayvcar\vendor\laravel\framework\src\Illuminate\Foundation\Testing\Concerns\MakesHttpRequests.php:526
C:\xampp\htdocs\dashboard\test\hayvcar\vendor\laravel\framework\src\Illuminate\Foundation\Testing\Concerns\MakesHttpRequests.php:293
C:\xampp\htdocs\dashboard\test\hayvcar\tests\Feature\RoutesTesting.php:1718
Have you tried delete cached files?
Delete everything in bootstrap\cache and try again on host.
I've explained the whole situation here, for another question, which in this case, it doesn't matter if it's testing or using in browser, when you cache your routes, laravel look for the cached file, instead of defined routes in web.php and api.php.
So if in cached file, you've had route with am/signin then, doesn't matter if you change it to signin or not, in web.php/api.php, it always looking for cached file, which in this case is am/signin.
Temporary questions, answer them by question number :
So you've commented everything in postSignin method, and didn't worked or just a part of it?
You said in browser is working, method is getting $lang from route? because in tests, as far as i can see, you're not passing anything.
Have you tried to change post to patch just for test? (that's because of Patch verb)
You might also try withoutExceptionHandling(); to get more details on error and test output. are you posting full test or just a basic test?
You can add this line to top of your test, $this->withoutExceptionHandling(); and get more details.
Remove $lang from method or just try a new method like below:
public function postSignin(Request $request){
return something or return $this->sendFailedLoginResponse($request); or etc.
}
How did you upgraded your project to Laravel 9? Create a new Laravel project and only test these two routes, get and post.
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
UPDATED 2
I made like and unlike method. But when I try to like the article, it returns an error. Route [login] not defined.
I am using passport API login. I am giving token for login etc... I login without a problem. I see the the pages only auth user can see. But it seems when I like the article, Auth:: doesn't understand user logged in or not. Maybe this is the problem. Because I am using Passport? So in the controller instead of Auth::
I used it like $user = $request->user(); (you can see the controller below.) But still same error popping when I like the article. Route [login] not defined.
controller
public function postLikeArticle( Request $request, $articleID )
{
$article = Article::where('id', '=', $articleID)->first();
$user = $request->user();
$article->likes()->attach( $user->id, [
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s')
]);
return response()->json( ['article_liked' => true], 201 );
}
public function deleteLikeArticle( Request $request, $articleID )
{
$article = Article::where('id', '=', $articleID)->first();
$user = $request->user();
$article->likes()->detach( $user->id );
return response(null, 204);
}
Routes
Route::middleware('auth:api')->group(function() {
Route::get('/user', function (Request $request) {
return $request->user();
});
Route::get('/articles/{id}/like', 'Api\ArticlesController#postLikeArticle');
Route::get('/articles/{id}/like', 'Api\ArticlesController#deleteLikeArticle');
});
If you need to see anymore file. please name it in the comment.
If you need the user to be authenticated then you need to be using the middleware that ensures that. In addition you need to also ensure that you are using the correct HTTP verbs depending on what you need to do, if anything your current definition is creating a conflict:
Route::middleware('auth:api')->post('/articles/{id}/like', 'Api\ArticlesController#postLikeArticle');
Route::middleware('auth:api')->delete('/articles/{id}/like', 'Api\ArticlesController#deleteLikeArticle');
You also need to ensure your front-end is using the correct verbs as well.
There are two reasons:
If you don't use the middleware you're basically saying those routes don't require authentication
If you're using the API guard to authenticate users then you need to explicitly use the auth:api middelware otherswise Laravel will attempt to authenticate the user using the default guard which is usually the session. When using an API you should not use the session
Your routes for like/dislike are not auth protected which means that the user might not be logged in, so you can group all routes requiring an authenticated user like this:
// public routes out of the group, for example:
Auth::routes();
Route::middleware('auth:api')->group(function() {
Route::get('/user', function (Request $request) {
return $request->user();
});
Route::get('/articles/{id}/like', 'Api\ArticlesController#postLikeArticle');
// you cannot have same routes for both, so this
// Route::get('/articles/{id}/like', 'Api\ArticlesController#deleteLikeArticle');
// should be this:
Route::get('/articles/{id}/dislike', 'Api\ArticlesController#deleteLikeArticle');
});
Then either: Auth::id() or Auth::user()->id should give you the same. Or even the helper function so you don't need to worry for the imports..
auth()->id();
// or
auth()->user()->id;
instead of
Auth::user()->id
You can try this
auth()->user()->id
you can refer laravel documentation for more information
Few simple points should resolve your confusion:
"Trying to get property of a non object" means, Auth::user() is not an object which means it's null, you are trying to access a route publicly which needs an authenticated user which leads to our next point.
Routes which use functions postLikeArticle() and deleteLikeArticle() should be inside the authorization middleware 'auth:api'
You are using method get for both of your routes which use above functions, one should be using delete method.
You can use Auth::id() as a substitute of Auth::user()->id just to be short hand.
When using PASSPORT for authentication, you do not follow the usual Laravel way of user authorization hence Auth::user() is useless. In order to fetch the authenticated user, you need to use:
$user = $request->user();
Where $request is an instance of Illuminate\Http\Request
Here is an example for you:
Import the Request class in the beginning of the controller file:
use Illuminate\Http\Request;
and your controller logic:
public function postLikeArticle( Request $request, $articleID )
{
$article = Article::where('id', '=', $articleID)->first();
$user = $request->user();
$article->likes()->attach( $user->id, [
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s')
]);
return response()->json( ['article_liked' => true], 201 );
}
public function deleteLikeArticle( Request $request, $articleID )
{
$article = Article::where('id', '=', $articleID)->first();
$user = $request->user();
$article->likes()->detach( $user->id );
return response(null, 204);
}
I hope it helps
When pressing my send button it's giving error like this-
Here is my routes web.php bellow-
Route::group(['prefix'=>'ajax', 'as'=>'ajax::'], function() {
Route::resource('message/send', 'MessageController#ajaxSendMessage')->name('message.new');
Route::delete('message/delete/{id}', 'MessageController#ajaxDeleteMessage')->name('message.delete');
});
Here is my controller MessageController.php bellow:
public function ajaxSendMessage(Request $request)
{
if ($request->ajax()) {
$rules = [
'message-data'=>'required',
'_id'=>'required'
];
$this->validate($request, $rules);
$body = $request->input('message-data');
$userId = $request->input('_id');
if ($message = Talk::sendMessageByUserId($userId, $body)) {
$html = view('ajax.newMessageHtml', compact('message'))->render();
return response()->json(['status'=>'success', 'html'=>$html], 200);
}
}
}
Resource routes should be named differently:
Route::prefix('ajax')->group(function () {
Route::resource('messages', 'MessageController', ['names' => [
'create' => 'message.new',
'destroy' => 'message.destroy',
]]);
});
Resource routes also point to a controller, instead of a specific method. In MessageController, you should add create and destroy methods.
More info at https://laravel.com/docs/5.4/controllers#restful-naming-resource-routes
You can't name a resource. Laravel by default name it, if you want to name all routes you must specify each one explicitly. It should be like this:
Route::group(['prefix'=>'ajax', 'as'=>'ajax::'], function() {
Route::get('message/send', 'MessageController#ajaxSendMessage')->name('message.new');
Route::delete('message/delete/{id}', 'MessageController#ajaxDeleteMessage')->name('message.delete');
});
Update
Another mistake of yours was trying to resource a single method. A Route::resource() is used to map all basic CRUD routes in Laravel by default. Therefore, you have to pass the base route and the class i.e:
<?php
Route::resource('message', 'MessageController');
Look at web.php line 28.
Whatever object you think has a name() method, hasn't been set, therefore you try and call a method on null.
Look before that line and see where it is (supposed to be) defined, and make sure it is set to what it should be!
Aloha, I'm making a workout manager in which you have a dashboard displaying your 5 last workouts. I have set a form for each one workout for allowing the user to delete any of them. Here the form in the dashboard:
{!! Form::open(['route' => ['dashboard.workout.destroy', $workout->id], 'style' =>'display:inline-block;', 'method' => 'DELETE']) !!}
This route will call this method in WorkoutController.php
public function destroy($id, Request $request)
{
$workout = Workout::findOrFail($id);
$workout->delete();
$message = "Workout deleted successfully!";
return redirect()->route('dashboard.index', ['message' => $message]);
}
And this route will call this method in DashboardController.php
public function index($message = null)
{
$user = Auth::user();
// Workouts
...
// Inbodies
...
// Measures
...
return view('dashboard.index', compact('user','workoutsDesc','workouts','lastInbody','inbodies','measures','lastMeasure','message'));
}
The question is that I'm trying to pass the variable $message from WorkoutController to DashboardController for displaying a successfull alert after deleting a workout, but I don't know how to do it. I have tried with:
return redirect()->action('Dashboard\DashboardController#index', [$message]);
return redirect()->action('Dashboard\DashboardController#index')->with('message', $message);
return redirect()->route('dashboard.index', $message);
But I still trying to find the way for doing it.
First of all, from Laravel 5.1 Documentation:
If your route has parameters, you may pass them as the second argument to the route method
As the message is not a parameter to your route, so you can't pass that. A possible solution can be Flashing data. Check the next controller if the session has that key and contain a value, then add it to a variable and pass to the view.
Hope this works.