On the password reset form the user supplies current_password, password and password-confirmation. Is there a way to specify in the validation rules that current_password (it's hash value) must match the database value?
Currently I have this:
$rules = array(
'current_password' => 'required',
'password' => 'required|confirmed|min:22'
);
Thank you.
UPDATE
Thanks to #ChrisForrence and #Ben, I came up with the following which works great! Much appreciated. Hope this will help someone else:
Validator::extend('hashmatch', function($attribute, $value, $parameters)
{
return Hash::check($value, Auth::user()->$parameters[0]);
});
$messages = array(
'hashmatch' => 'Your current password must match your account password.'
);
$rules = array(
'current_password' => 'required|hashmatch:password',
'password' => 'required|confirmed|min:4|different:current_password'
);
$validation = Validator::make( Input::all(), $rules, $messages );
You can't, bcrypt hashes are unique (they have their own random salt incorporated) so even if you knew the user's plain text password you would't be able do a hash-to-hash comparison.
What you can do is actually check the plain text password against a bcrypt hash by doing Hash::check('plain text password', 'bcrypt hash') on your controller.
Related
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.
I am doing validation this way.
$rules = [
'email'=> 'required|regex:/^.+#.+$/i|unique:tab_example,email,'.$this>get('example_id').',example_id'
];
return $rules;
However, I am not getting success.
The error informs that
the email already exists
What I want is that if the email already exists and is from the same user does not need to inform that the email already exists.
I do not know what the problem is in my code.
You can use
'email' => "required|email|unique:users,email,{$id},id",
The id should be replaced with the primary key column name of the table you use for the unique check. The {$id} should be defined before $rules array like:
$id = $request->route('user')
Sometimes, you may wish to ignore a given ID during the unique check.
For example, consider an "update profile" screen that includes the user name, e-mail address, and location. Of course, you will want to verify that the e-mail address is unique.
However, if the user only changes the name field and not the e-mail field, you do not want a validation error to be thrown because the user is already the owner of the e-mail address.
you can use like:
'email' => [
'required',
Rule::unique('users')->ignore($user->id),
],
Try this
'email' => Rule::unique('users')->ignore($user->id, 'user_id');
Read Under the Section Forcing A Unique Rule To ignore A given Field
Try This way
$rules = [
'email'=> ['required', 'email', \Illuminate\Validation\Rule::unique('tab_example', 'email')->whereNot('example_id',$this->get('example_id'))]
];
Just use
$this->route('example_id')
instead of
$this>get('example_id')
And if you use resource route then use $this->route('user').
$rules = [
'email'=> 'required|regex:/^.+#.+$/i|unique:tab_example,email,'.$this->route('example_id').',example_id'
];
return $rules;
To create a user without the end-user having to type in the details.
Something akin to
User::classcreate([
'email' => $email,
'password' => $rand_pass,
.....
]);
Thanks for the ideas and feedback in advance :)
The use case is.
The end-user invites another user to use the service by typing in their email and it creates a user with a random password before sending a email to the new user with their created details.
You're almost there with your code. It should look like this:
User::create([
'email' => $email,
'password' => bcrypt($rand_pass),
]);
But if you want to hash the password, you should also send the password to that user through email (which is not very secure). When the users logs in for the first time, you should at least require him to change the password.
You can simply use create() method of your User model:
$userData = [
'email' => 'some#email.com',
'password' => 'somepassword'
];
$newUser = User::create($userData);
You'll also need your password hashed in order for it to work with Laravel's authorization. Add the following to your user model - it will hash password before it's saved to the database:
public function setPasswordAttribute($password)
{
$this->attributes['password'] = Hash::make($password);
}
I have a user model that needs to have unique email addresses but I also want to allow them to be left blank in case the user has no email...I see in docs there is a way to make a rule for unique and an exception for an id...but I'm not sure how to make this allow null or blank but unique if it is not. Sorry seems like this is simple but I can't think of the answer.
public static $adminrules =
'email' => 'email|unique:users,email,null,id,email,NOT_EMPTY'
);
Edit It may be that using the rule without required is enough since a blank or null would pass validation in those cases. I might have a related bug that making it so I can't add more than 1 blank email, so I can't verify this.
public static $adminrules =
'email' => 'email|unique:users'
);
I tried this. Adding 'nullable' before 'sometimes'.
$validator = Validator::make($request->all(), [
'email' => 'nullable|sometimes|unique:users',
]);
You should try this:
$v->sometimes('email', 'email|unique:users,email', function($input)
{
return !empty($input->email);
});
$v is your validator object and you basically say that in case the email field is not empty it should also be unique (there shouldn't be a users table record with this value in email column).
In your Requests/UserRequest you'd have something like
public function rules()
{
return [
'email' => [
'nullable',Rule::unique((new User)->getTable())->ignore($this->route()->user->id ?? null)
]
];
}
The usage of nullable is what allows the field to be nullable. The other part is to check if the email is unique in the User model table.
If you wish to validate if the field is unique
between two fields please refer to this answer.
in another table, then add the following to your rules
'exists:'.(new ModelName)->getTable().',id'
You should try to change your structure of database to make the field email is nullable. And in the rules try this :
$this->validate($request,
[
'email' => 'email',
]
);
if(isset($request->address))
{
$this->validate($request,
[
'email' => 'email|unique:users'
]
);
}
By default, Laravel 'confirmed' validator adds the error message to the original field and not to the field which usually contains the confirmed value.
'password' => 'required|confirmed|min:8',
Is there any simple way to extend the validator or use some trick to force it to always show the error on the confirmation field instead of the original field?
If I fail to enter my password twice, the error seems more appropriate to belong the confirmation field and not to the original password field. Or maybe that's just our UX analyst getting nitpicky...
One way to go about it is to use same rule instead of confirmed
// ...
$input = Input::all();
$rules = [
'password' => 'required|min:8',
'password_confirmation' => 'required|min:8|same:password',
];
$messages = [
'password_confirmation.same' => 'Password Confirmation should match the Password',
];
$validator = Validator::make($input, $rules, $messages);
if ($validator->fails()) {
return back()->withInput()->withErrors($validator->messages());
}
// ...
You should design your form as below;
<input type="password" name="password">
<input type="password" name="password_confirmation">
Quote from Laravel: confirmed
"The field under validation must have a matching field of foo_confirmation. For example, if the field under validation is password, a matching password_confirmation field must be present in the input"
Now, you can design your validation as follow;
$request->validate([
"password" => 'required|confirmed'
]);
$rules=[
'username'=>'required|max:20',
'password1'=>'required|min:8',
'password2'=>'required|min:8|same:password1',
];
$error_messages=[
'password2.same'=>'password are not the same password must match same value',
'password1.min'=>'password length must be greater than 8 characters',
'password2.min'=>'confirm-password length must be greater than 8 characters',
];
$validator= validator($request->all(), $rules, $error_messages);
if ($validator->fails()) {
return redirect('control_pannel/change_password')
->withErrors($validator)
->withInput();
}
One solution that quickly comes to mind is to just display the password errors on the password_confirmation field.
If that won't work for you, just label the password_confirmation field as password and the password field as password confirmation so that if there are errors, it will show up near the password_confirmation label rather than the password label.
Otherwise, it's not difficult to add a your own custom validation method.