how to avoid running burst operations in laravel middleware when refresh - php

im using laravel middleware (visits) to create visits record in database like this:
public function handle(Request $request, Closure $next)
{
Visit::create([
'ip' => 'user ip',
'browser' => 'user browser',
'url' => url()->current(),
'referer' => $request->headers->get('referer'),
]);
return $next($request);
}
the problem is here:
If the user refreshes the page quickly, a large number of records will be saved
what can i do to prevent this?

You could create a unique job with some of the parameters that saves the Visit instead of directly saving it, that should work.

Related

Secure link hash for password reset system

Just want to be sure if that's the "right way" and most importantly if it's "secure way". I'm making simple form for someone who want's to reset password (not logged in). I don't want to use Laravel way (no dedicated database for that). Simply I'm sending crypted link, link is valid for 10 minutes.
public function sendConfirmationLink(Request $request)
{
$validatedData = $request->validate([
'email' => 'required|email|exists:users',
]);
$user = User::where('email', $request->get('email'))->firstOrFail();
$parameters = [
'user_id' => $user->id,
'date' => Carbon::now(),
'type' => 'password_reset',
];
$passwordResetLink = Crypt::encrypt($parameters);
SendPasswordResetLink::dispatch($user, $passwordResetLink)->onQueue('high');
}
Later on, I'm decrypting that hash and I'm making password change.
public function resetPassword(Request $request)
{
$validatedData = $request->validate([
'hash' => 'required',
'new_password' => 'required|string|min:6|confirmed',
]);
try {
$decrypted = Crypt::decrypt($request->get('hash'));
$password = Hash::make($request->get('new_password'));
$user = User::where('id', $decrypted['user_id'])->firstOrFail();
$user->password = $password;
$user->save();
} catch (DecryptException $e) {
abort(404);
}
}
If I'm right... that "link" will be extremely difficult to crack and it's valid for only 10 minutes. Is it in any way less secure then native Laravel version with similar code stored in database?
Yes I know that Laravel Auth can do it for me. I want use my method in more then password reset so I'm looking for answear if it's secure this way.
The Crypt library from Laravel uses a combination of OpenSSL and AES-256-CBC with a MAC signing. I think for a 10 min period, that should be safe enough.
A more serious problem is, that you can't "disable" a link after usage. So theoretically everyone with the link can change the account password infinite times (in a 10 min period).
A solution for that problem could be, that you add the current password hash into your link-hash and then compare that. In that case the password change would only work one-time per link.

Table column filled later on

I'm new on Laravel and as I'm playing around with it I encounter this issue.
I have a registration system which worked fine but now I wanted to add a new field in my table (description field for users).
However, this description field, I don't want to be filled when the user signs up, I want the user to fill this when he gets on his profile and updates a modal window.
The problem is, if I let that filed empty, I get an error when I sign up saying that the description filed can't be empty.
This is what I use in my UserController in order to update the description field but I'm not sure if is correct.
public function postDesc(Request $request){
$this->validate($request, [
'description' => 'required|min:20'
]);
$user = User::all();
$user->description = $request->input('description');
$user->save();
return redirect()->route('user.profile.edit');
}
This is how I opened the form:
{!! Form::open(['method' => 'PUT', 'action' => 'UserController#postDesc', 'class' => 'profile-form']) !!}
You use required validation rule, that's why you get the message. You should use different validation rules for register page and profile update form.
Good practice is to create two Request classes and use them for validation of two forms.
In this scenario, I will prefer to keep your description column nullalbe(). So it won't throw an error that description field is empty at the time of sign up.
And later you can update the description field.
public function postDesc(Request $request)
{
$this->validate($request, [
'description' => 'required|min:20'
]);
// Get the logged in user id using auth and then updating description filed
$user = User::where('user_id', Auth::id())
->update([
'description' => $request->description
]);
return redirect()->route('user.profile.edit');
}

Laravel 5 - MethodNotAllowedHttpException when validation is false

Always when the validation fails, I get a MethodNotAllowedHttpException
routes.php
Route::post('download', 'UrlController#download');
Route::post('search', 'UrlController#search');
UrlController.php
public function download(DownloadRequest $request)
{
dd($request->all());
}
DownloadRequest.php
public function authorize()
{
return true;
}
public function rules()
{
return [
'format' => 'required|between:1,13'
];
}
name.blade.php
{!! Form::open(['url' => 'download']) !!}
{!! Form::select('format', [
'Please select format',
'FormatGrp1' => [1 => 'best', 'p1','p2', 'p3', 'p4'],
'FormatGrp2' => [6 => 'p5', 'p6']
]) !!}
When "Please select format" is chosen and the form is submitted, I always get this error because "Please select format" has value 0 and i specified values must be between 1 and 13. (Look at DownloadRequest.php)
Thanks for help!
The error didn't come from the validation.
It was because it called the URL to go back and display the errors. And this is the search method.
So cause of the logic in search method the exception has been thrown.
When you have this error, in your if ($validator->fails()) { } consider that you are going to open the view you are working on for the first time, and add ->withErrors($validator)
For example:
public function edit($id)
{
$exams = Exam::all();
return view('exams.index', compact("exams"));
}
...
public function update(Request $request,$id)
{
$validator = Validator::make($request->all(),[
'start' => 'required',
'end' => 'required|after:start'
]);
if ($validator->fails())
{
$exams = Exam::all();
return view('exams.index', compact("exams"))->withErrors($validator);
}
//Your update code if validator not fails
}
I didn't exactly understand what this user was trying to explain as the actual problem or solution - I came across this question as I had the same issue and thought I would describe what I had done in error and how I solved it...
For me, I was building a site where users would submit photos. When the user clicked on the "add photo" it took them to a page where they had to check a box to accept a legal disclaimer. This form with the checkbox was a POST request. After they accepted it they would get re-directed to the photo submission page... WHICH WAS ALSO A FORM WITH A POST REQUEST. This was my issue: back to back POST request pages. If the user entered invalid data on the submission form, or didn't enter data in a field at all Laravel tries to essentially hit the "back button" in your browser and keep the form filled with the data the user did enter. The problem is that the "back" button (or the way the user came to this page) was from a POST request, so it couldn't do it. It gave me the error described above. Once I switched the legal acceptance page to a GET request form and updated the route to match everything started working fine. It was a foolish error on my part, I just hope to mitigate this frustration for others as they are learning to develop in Laravel. Have a great day!

How to change / Custom password field name for Laravel 4 and Laravel 5 user authentication [duplicate]

This question already has answers here:
laravel 4 custom named password column
(4 answers)
Closed 8 years ago.
I would like to change password field in database when using Laravel authentication. I want my column in users table has name passwd and not password. I tried to run something like this:
Auth::attempt(array(
'user_name' => 'admin',
'passwd' => 'hardpass',
));
but it doesn't work.
I also tried to add in User model the following function:
public function getAuthPassword() {
return $this->passwd;
}
but it also changes nothing. User is still not being authenticated. Is it possible in Laravel to change password field name in database ?
Information
You can change easy all other fields in database and use them for authentication. The only problem is with password field.
In fact password field is in some way hard coded in Laravel (but not the way many think) so you cannot just pass array as you passed in your question.
By default if you pass array to attempt (and probably other Auth functions like validate or once) if you do it this way:
Auth::attempt(array(
'user_name' => 'admin',
'password' => 'hardpass',
));
default Eloquent driver will run the following query:
select * from `users` where `user_name` = 'admin' limit 1;
After getting this data from database it will compare password you gave with password property for User object that was created.
But if you simply use:
Auth::attempt(array(
'user_name' => 'admin',
'passwd' => 'hardpass',
));
the following query will be run:
select * from `users` where `user_name` = 'admin' and `passwd` = 'hardpass' limit 1;
and no user will be found in database (in passwd you store hashed password). This is because Eloquent removes from query password but use any other data to run query. Also if you try here to use 'passwd' => Hash:make($data['password']) although user will be found, comparing password won't work.
Solution
Solution is quite easy. You need to run Auth::attempt like this:
Auth::attempt(array(
'user_name' => 'admin',
'password' => 'hardpass',
));
As you see you still pass password as key (although this column doesn't exits in users table) because only this way Eloquent driver won't use it for building query.
Now in User model (app/models/User.php) file you need to add the following function:
public function getAuthPassword() {
return $this->passwd;
}
As you see you use here the column that really exists in database: passwd.
Using it this way you can have column with password named anything you want and you can still use default Eloquent driver for it.
Sample data to test
I've created very simple test for it.
You just need to replace your app/routes.php file with the following:
Route::get('/', function () {
if (Auth::check()) {
echo "I'm logged in as " . Auth::user()->user_name . "<br />";
echo "<a href='/logout'>Log out</a>";
} else {
echo "I'm NOT logged in<br />";
Auth::attempt(array(
'user_name' => 'admin',
'password' => 'hardpass',
));
if (Auth::check()) {
echo "Now I'm logged in as " . Auth::user()->user_name . "<br />";
echo "<a href='/logout'>Log out</a>";
} else {
echo "I'm still NOT logged in<br />";
}
}
});
Route::get('/logout', function () {
Auth::logout();
return "You have been logged out";
});
Route::get('/db', function () {
if (!Schema::hasTable('users')) {
Schema::create('users', function ($table) {
$table->engine = 'InnoDB';
$table->increments('id');
$table->string('user_name', 60)->unique();
$table->string('passwd', 256);
$table->rememberToken();
$table->timestamps();
});
DB::table('users')->insert(
[
[
'user_name' => 'admin',
'passwd' => Hash::make('hardpass'),
]
]
);
}
echo "Table users has been created";
});
Create empty database and set connection data in app/config/database.php
Now you can run /db url for example http://localhost/yourprojectname/db just to create users table.
Now you can run / url for example http://localhost/yourprojectname/ - as you see user is logged in even if in users table in database you don't have any password column (data for authentication has been passed as strings without any forms but of course in real application you will add them) . You can run this url once more time - as you see user is still logged so it is working as expected.
If you click on Log out link, you will be logged out
Laravel 5 changes for above
This solution was tested in Larave 4.2.9 (everything as above) and also in Laravel 5. In Laravel5 everything works the same but you need of course edit files in different paths:
User model is in app/User.php file
routes are in app/Http/routes.php file
Database config file is in config/database.php file

Laravel 4: Multi-Page Form on the same route?

Hello folks I am stuck.
I want to register a User in Laravel 4. Now the thing is, that I want to first grab the email and password and save them in the database. And in step 2 of the registration process, I want to grab all the other details like first and last name and so on.
The difficulty is, that everything should be under one route called signup, for example everything under http://example.org/signup
Another difficulty is, that I have to access the same route with the same methods (GET & POST) twice, because I once get and post the form for Email and Password, and then I get and post the First, Last and Company Name into the Database.
I came up with the following solution, to store everything into the session, because through the session I can access the variables. So whenever I access my UserController I check, if there is data in the session and if yes, redirect to form 2.
Here are all my files:
http://help.laravel.io/d4104cae42f9a2efe1466ce53d086826bc9f6d7f
My Get-Method from the UserController:
public function create()
{
if(Session::has('email')) {
return View::make('frontend.signup.step2');
}
else {
return View::make('frontend.signup.step1');
}
}
My Post-Method from the UserController:
public function store()
{
// If User has a email and password in the session from the first create-View
// his data should be stored and then he gets redirected to a new create-View
Session::flush();
Session::put('email', Input::get('email'));
Session::put('password', Input::get('password'));
if (Session::has('email')) {
try
{
// Let's register a user.
$user = Sentry::register(array(
'email' => Input::get('email'),
'password' => Input::get('password'),
));
// Let's get the activation code
$activationCode = $user->getActivationCode();
// Send activation code to the user so he can activate the account
// Save Email in Emaillist
Email::create(array(
'email' => Session::get('email')
));
// Redirect
return Redirect::action('UserController#create');
}
return Redirect::route('signup');
}
else {
return 'No Session here';
}
}
Here are my routes:
Route::get('signup', array('as' => 'signup', 'uses' => 'UserController#create'));
Route::post('signup', array('as' => 'signup', 'uses' => 'UserController#store'));
For some reason I believe that it gets unneccessary complicated and I believe that there must be another more simple and intuitiv way to solve this, instead with if statements and redirects to the same controller-method.
Nonetheless I came up with some other solutions, for example just using the "signup" as prefix, but I don't like it that way.
Route::group(array('prefix' => 'signup'), function()
{
Route::get('/', function(){
return 'Yeab bababy yea';
});
Route::get('step1', array('as' => 'signup.step1', 'uses' => 'UserController#getStep1'));
Route::post('step1', array('as' => 'signup.step1', 'uses' => 'UserController#postStep1'));
Route::get('step2', array('as' => 'signup.step2', 'uses' => 'UserController#postStep2'));
Route::post('step2', array('as' => 'signup.step2', 'uses' => 'UserController#postStep2'));
});
Is there any way of accomplishing the task while only using one route and without using clientside Javascript to store the variables in the database? (I am a unexperienced with ajax)
The Goal should be to catch the email and still stay on the same route, like those smart guys here for example:
https://www.crazyegg.com/signup
I hope there is a way. Thank you for your help Internet.
Kind regards,
George
P.S.
It's 1 am here in Germany, so don't be mad if I don't respond the next couple of hours to comments, because I am going to sleep now. Thank you very much.

Categories