How to change laravel database value before compare? - laravel

I have emails on the users table with upper and lower case, e.g
MyName#email.com
On the login I use email and password field.
I want to success login when the user send the email no matter upper or lower case e.g
if the user send: myNAME#email.com
I think to change all to lower case before compare.
Now I send the email field using overload credentials method on Logincontroller class
protected function credentials(Request $request)
{
$credentials = [
$this->username() => strtolower($request->get($this->username())),
"password" => $request->get("password")
];
return $credentials;
}
This convert the email sended to lowercase.
But how can I change to lowercase, database email value before compare?

Related

Rule::notIn([]) - is not catching the duplicate entry?

Rule::notIn([]) - is not catching the duplicate entry? is not working for me?
I have a group of people that I don't want to enter the same email address. if a new member joins, they need to enter a unique email address for that group. [$this->familyEmails] holds the email already in the group. It is a one dimensional array. Here is the result of two dd() calls.
And the Rules function (notice that the dd() calls that produced the output in the above graphics is commented out:
public function rules(){
//dd('family emails',$this->familyEmails);
$w = [];
foreach($this->familyEmails as $femail){
if($femail != $this->email && array_search(strtolower($femail),$w)==null)
$w[] = strtolower($femail);
}
// dd(['rules_array'=>$w]);
return [
'email' => ['nullable','string', 'email', 'max:255',Rule::notIn($w)],
];
}
and the Custom Messages function
public function customMessages(){
return [
'email.string' => 'Email Address: The email you entered is not a proper email address; please change.',
'email.email' => 'Email Address: The email you entered is not a proper email address; please change.',
'email.max' => 'Email Address: The email you entered is too long; please change.',
'email.not_in' => 'This email address is already used in the family. Please enter another Email Address',
];
}
I also have an updated() function which validates data entered on the fly
public function updated($propertyName){
$data = $this->validateOnly($propertyName, $this->rules(), $this->customMessages());
$this->resetErrorBag($propertyName);
$this->changesMade = true;
//dd(['data'=>$data,'rules'=>$this->rules()]);
}
After I enter in an email address into the form, the dd() that you see commented out just above produces the following when i enter in an email that is in the notIn rule. The dd() seen just above should not be reached, but it is.
okay, i decided to answer my own question as it may help others.
I was creating the Rule:notIn([]) inside the Rules() function. But that caused problems.
As it seems that the Rules() function (of course) runs everytime you call the validate() helper. the answer was right in front of me.
When I put the loop in the Mount() function, I achieved my objective.
Before I did that, when creating the rules noIn array in the Rules() function it contained "sam#whistle.com". When I went to enter an email address for another person in the group, I entered "sam#whistle.com" .
So when I typed in the duplicate email, the rules() function ran, and recreated the rules array, and used the email that I typed in and it took it out of the rule. I used the $this->email which is the public property on the component. So I had to put it in the mount() function.
As I write this, I realize I need to put the array back in the Rules() function, and reference the model where the email address is stored, not reference the public property on the component that stores the typed in value.
$w = [];
foreach($this->familyEmails as $femail){
if($femail != $this->person->email && array_search(strtolower($femail),$w)==null)
$w[] = strtolower($femail);
}
instead of $this->email, I reference the model for the email address.

Unwanted validation rule being applied on password reset

I'm trying to use the password reset ability of Laravel's authentication. After running make:auth command, inside my ResetPasswordController, I have overridden rules function of Illuminate\Foundation\Auth\ResetsPasswords trait as the following:
protected function rules()
{
return [
'token' => 'required',
'email' => 'required|email',
'password' => 'required|confirmed|min:4',
];
}
So, I am trying to change the minimum length value to 4. But when I try to reset my password, a rule of minimum of 8 characters is still being applied instead of 4.
Here is the reset function of laravel in the same file:
public function reset(Request $request)
{
$request->validate($this->rules(), $this->validationErrorMessages());
// Here we will attempt to reset the user's password. If it is successful we
// will update the password on an actual user model and persist it to the
// database. Otherwise we will parse the error and return the response.
$response = $this->broker()->reset(
$this->credentials($request), function ($user, $password) {
$this->resetPassword($user, $password);
}
);
// If the password was successfully reset, we will redirect the user back to
// the application's home authenticated view. If there is an error we can
// redirect them back to where they came from with their error message.
return $response == Password::PASSWORD_RESET
? $this->sendResetResponse($request, $response)
: $this->sendResetFailedResponse($request, $response);
}
And the $response being returned is Illuminate\Support\Facades\Password::INVALID_PASSWORD. I don't understand where this rule is coming from.
Actually the validation behavior is like this: When I enter less than 4 characters, my own rule is applied (correctly). However, entering 4 to less than 8 characters is also an error by some other rule.
The reason that you're getting the error back is because the PasswordBroker expects a password with a minimum length of 8 characters so even though your form validation is passing, the validation in the PasswordBroker isn't.
One way to get around this would be to override the broker() method in your ResetPasswordController and pass your own validator to it:
public function broker()
{
$broker = Password::broker();
$broker->validator(function ($credentials) {
return $credentials['password'] === $credentials['password_confirmation'];
});
return $broker;
}
The above is essentially the same as what's going on in the PasswordBroker itself, just without the string length check as well.
Don't forget to import the Password facade into your controller:
use Illuminate\Support\Facades\Password;
This isn't essential, but for good measure I would then suggest updating the password error message in your resources/lang/en/passwords.php file as well.

Laravel password reset with mongodb

I am migrating old project (done in zend framework) to laravel 5.5. Database is mongo db. I am using laravel-mongodb to connect laravel and mongo.
I already override laravel login functionality because table fields are not same as default laravel fields. Login is working fine.
At present when I try to reset password I am getting error message "We can't find a user with that e-mail address". How can I override reset password functionality?
In user table the field name are usrEmail and usrPassword. Working code of login is given below.
At present when I try to reset password I am getting error message We can't find a user with that e-mail address. How can I override reset password functionality?
In user table the field name are usrEmail and usrPassword. Working code of login is given below.
LoginController.php
protected function attemptLogin(Request $request)
{
$authUser = User::where('usrEmail', $request->email)
->whereIn('usrlId', [1, 2, 5, 6])
->first();
if($authUser) {
$password = md5(env('MD5_Key'). $request->password. $authUser->usrPasswordSalt);
$user = User::where('usrEmail', $request->email)
->where('usrPassword', $password)
->where('usrActive', '1')
->where('usrEmailConfirmed', '1')
->where('is_delete', 0)
->where('usrlId', 2)
->first();
if ($user) {
$updateLoginTime = User::find($user->_id);
$updateLoginTime->lastlogin = date('Y-m-d H:i:s');
$updateLoginTime->save();
$this->guard()->login($user, $request->has('remember'));
return true;
}
else {
return false;
}
}
return false;
}
Try placing this in your Auth/ResetsPasswordController.php
protected function credentials(Request $request)
{
$data = $request->only(
'password', 'password_confirmation', 'token'
);
$data['usrEmail'] = $request->get('email');
return $data;
}
By default the ->only( also includes the email field, but since it is different in your database we needed to override this function, which is by default defined in the ResetsPasswords trait.
This should ensure that any email field in the password reset flow (both on requesting the email and the form once you click the emailed link) will point to the right field in your database.

Password resetting in laravel when email address is not unique

This might sound like an antipattern or a weak system design, but the client of my app has demanded that there can be multiple users with same email address.
So I added another unique column named username to the users table and removed ->unique() constraint from email column.
Registration, Login are working fine but the problem arises during the password reset.
Consider the scenario:
username - johndoe, email - john#example.com
username - janedoe, email - john#example.com
username - jimmydoe, email - john#example.com
If any one of them makes a request for a password reset link, they would have to use johndoe#example.com as their email. So which user's password is actually going to be reset when they click on reset link from mail? Turns out, the first user, in this case, johndoe. Even if the request was made by janedoe or jimmydoe.
So how do I reset password for a single username, rather than an email? What changes should I make in the ForgotPasswordController and/or ResetPasswordController controllers to solve this? Or, do I have to make changes in the core framework? If so, where and how?
Tested in Laravel 5.3 [This answer modifies some core files(you may override it if capable) and it's not a clean solution.]
Ask user for the unique username value instead of email on password forget form.
Override the sendResetLinkEmail() method in ForgotPasswordController.php as folows. [Originally written in SendsPasswordResetEmails.php].
public function sendResetLinkEmail(Request $request)
{
$this->validateEmail($request);
$response = $this->broker()->sendResetLink(
$request->only('username')
);
return $response == Password::RESET_LINK_SENT
? $this->sendResetLinkResponse($response)
: $this->sendResetLinkFailedResponse($request, $response);
}
you would also need to override the validateEmail() method.
protected function validateEmail(Request $request)
{
$this->validate($request, ['username' => 'required']);
}
Add username field instead of email on password reset form.
Override rules() in ResetPasswordController.php to over come the email field change.
protected function rules()
{
return [
'token' => 'required',
'username' => 'required',
'password' => 'required|confirmed|min:6',
];
}
Also override the credentials() in ResetPasswordController.php
protected function credentials(Request $request)
{
return $request->only(
'username', 'password', 'password_confirmation', 'token'
);
}
Update or override the getEmailForPasswordReset() method in Illuminate\Auth\Passwords\CanResetPassword.php to the folowing.
public function getEmailForPasswordReset()
{
return $this->username;
}
Laravel uses key-value pair to find the user and send email. If you pass 'username => 'xyz' it will look for the first record with value 'xyz' in username field.
Note: The unique column in users table is expected as username.
Illuminate\Auth\Passwords\CanResetPassword.php is a trait, and I was not able to overide the getEmailForPasswordReset method, so i just modified the core file itself.
This might sound like an antipattern or a weak system design, but the client of my app has demanded that there can be multiple users with same email address.
Then you need to rewrite this feature and ask user for some more unique information, no matter what it is going to be. Laravel provided password reset expects email to be unique and with your current design it won't work. There's no magic here. You you cannot disambiguate your user using non unique data.
You will need to rework some things for this, but I feel like the user experience is better. Generate a unique key for each user (for data hiding). There is a helper method for creating unique keys.
Then, when the email is sent out, link the button to a route that utilizes this key.
Then, modify or create that route that points to the reset password controller. You would then know which user it was referring to.
Remove the need for the user to insert their password because you'd already know who it was.

Cartalyst Sentry and registration user

It is possible to create user from Admin panel, by administrator without password? I imagine follow procedure:
Administrator create user without password
User get email with instruction for entering password and activation account
User can register with email and his password
I don't think so. That's why when I create my users I generate a random password.
$user->password = str_shuffle("Random_Password"); // generate random initial password
I have done this before by hacking the 'forgotten password' functionality of Laravel (rather that reinventing the wheel). I can't say how well this fits into Sentry but it was pretty trivial to do it in plain old Laravel:
Create user with blank password
Add an entry into the password reminders table (manually, don't use Auth::remind or whatever it is as it'll send an email, but do use the code from the class to generate the token)
Send welcome email to user with link to /user/confirm (or whatever, the point is that it doesn't have to be /user/forgotten-password) and hook that route up in the normal way for forgotten password with an added check for $user->password == '' if you wanna make sure only unconfirmed people can go to that page (not that it really matters).
You may also wish to extend the timeout on the forgotten passwords or, as I did (proper hacky I know), when the user's in the /user/confirm version of the forgotten password functionality, just refresh the timeout in the table before passing through to Laravel's auth system for checking.
Our code is something like this:
On register:
// however you register the user:
$user = new User;
$user->email = Input::get('email');
$user->password = '';
$user->save();
// create a reminder entry for the user
$reminderRepo = App::make('auth.reminder.repository');
$reminderRepo->create($user);
Mail::send(
'emails.registered',
[
'token' => $reminder->token,
],
function ($message) use ($user) {
$message->to($user->email)->setSubject(Lang::get('account.email.registered.subject', ['name' => $user->name]));
}
);
Now the confirm link:
class AccountController extends Controller
{
public function confirm($token)
{
$reminder = DB::table('password_reminders')->whereToken($token)->first();
if (! $reminder) {
App::abort(404);
}
// reset reminder date to now to keep it fresh
DB::table('password_reminders')->whereToken($token)->update(['created_at' => Carbon\Carbon::now()]);
// send token to view but also email so they don't have to type it in (with password reminders it's is a good thing to make users type it, but with confirm account it feels weird)
return View::make('account.confirm-account')->withToken($token)->withEmail($reminder->email);
}
public function postConfirm($token)
{
$credentials = Input::only('email', 'password', 'password_confirmation', 'token');
$response = Password::reset($credentials, function ($user, $password) {
$user->password = $password;
$user->save();
});
switch ($response) {
case Password::INVALID_PASSWORD:
case Password::INVALID_TOKEN:
case Password::INVALID_USER:
return Redirect::back()->withInput()->with('message-error', Lang::get($response));
case Password::PASSWORD_RESET:
Auth::login(User::whereEmail(Input::get('email'))->first());
return Redirect::route('account.home')->with('message-info', Lang::get('messages.confirm_account.succeeded'));
}
}

Resources