Can't get user while testing auth in laravel - laravel

I'm writing automated tests for a legacy laravel project, 5.8.38.
I have this test method.
public function testUserReceivesAnEmailWithAPasswordResetLink()
{
Notification::fake();
$user = factory(User::class)->create([
'email' => 'john#example.com',
]);
$this->post($this->passwordEmailPostRoute(), [
'email' => 'john#example.com',
]);
$this->assertNull($token = DB::table('password_resets')->first());
Notification::assertSentTo($user, ResetPassword::class, function ($notification, $channels) use ($token) {
return Hash::check($notification->token, $token->token) === true;
});
}
This always fails because the user cannot be retrieved. The passwordEmailPostRoute() method goes to the src/Illuminate/Auth/Passwords/PasswordBroker.php sendResetLink() method, eventually ending up in src/Illuminate/Auth/EloquentUserProvider.php at retrieveByCredentials() method.
This always returns null.
I tried dumping data and queries, but everything failed. Any ideas appreciated.

This seems to be a very specific issue which I caused for myself.
My user factory generated wrong values for a morph connection field which prevented the return of a valid User object. I had to change the factory and the issue is now resolved.

Related

Laravel validation couldn't store value after validate and give error 500

I have a form that using ajax for update data client. In that form there is an input file. Everything is going fine except for updating the file. File is sent, it changed on storage too, but it gives error on validation and didn't change data on database.
Here is the code on the controller :
public function update(Request $request, Client $client)
{
$validatedData = Validator::make($request->all(), [
'name' => 'required|max:255',
'logo'=> 'image|file|max:100',
'level' => 'required|max:1'
]);
$validatedData['user_id'] = auth()->user()->id;
if ($validatedData->fails()){
return response()->json($validatedData->errors());
} else {
if($request->file('logo')){
if($request->oldLogo){
Storage::delete($request->oldLogo);
}
$validatedData['logo'] = $request->file('logo')->store('logo-clients');
}
$validateFix = $validatedData->validate();
Client::where('id', $client->id)->update($validateFix);
return response()->json([
'success' => 'Success!'
]);
}
}
It gives error on line :
$validatedData['logo'] = $request->file('logo')->store('logo-clients');
With message :
"Cannot use object of type Illuminate\Validation\Validator as array"
I use the same code that works on another case, the difference is the other not using ajax or I didn't use Validator::make on file input. I guess it's just wrong syntax but I don't really know where and what it is.
To retrieve the validated input of a Validator, use the validated() function like so:
$validated = $validator->validated();
Docs:
https://laravel.com/docs/9.x/validation#manually-creating-validators
https://laravel.com/api/9.x/Illuminate/Contracts/Validation/Validator.html
$validatedData is an object of type Illuminate\Validation\Validator.
I would say the error is earlier there as well as this line should give an error also:
$validatedData['user_id'] = auth()->user()->id;
As ericmp said, you first need to retrieve the validateddata to an array and then work with it.

Method Illuminate\Http\Request::first does not exist in laravel 6 error

I am trying to implement multiple column as unique(title,created_by). A user can not create duplicate title.
The validation give me error both in separate request class also.
The validation code is:
$created_by = auth()->user()->id;
$this->validate($request, [
'title' => 'required|max:50|unique:register_types,title,null,id,created_by,'.$created_by
]);
The code give error as "Method Illuminate\Http\Request::first does not exist"
But Validator method works successfully.
The code is:
$validator = \Validator::make($request->all(),[
'title' => 'required|max:50|unique:register_types,title,null,id,created_by,'.$created_by
]);
if ($validator->fails()) {
return $validator->errors();
}
I want to use first clean code pattern. How is it possible ?
I got my problem.
Some days ago, I have added a condition at render method in handler class:
elseif ($exception instanceof ValidationException) {
return $exception->first();
}
Solution:
At this time, just I blocked this condition.
Problem solved.

Disable hashing on Auth::attempt

I am working with an old database without hashed passwords, also this database needs to be unhashed since it is connected to a Runnable JAR.
I did everything to connect it with Laravel 5.3 and it worked, but.. When comes to login it always return false.
Here is the function code:
public function login(Request $request)
{
$this->validate($request, [
'account' => 'required|alpha_num|exists:accounts,account',
'password' => 'required|alpha_num|min:4',
]);
if(Auth::attempt(['account' => $request->account, 'password' => $request->password])){
return redirect()->route('account');
}
return redirect()->back()->withInput();
}
I came to the conclusion that Auth::attempt hashes the given password through the view and when comparing to the unhashed one in the database, returns false.
How can i fix this??
Thank you.
You will need to use another method of manual authentication.
$user = User::where('account', $request->account)
->where('password', $request->password)
->first();
if($user) {
Auth::loginUsingId($user->id);
// -- OR -- //
Auth::login($user);
return redirect()->route('account');
} else {
return redirect()->back()->withInput();
}
You just can add this to your App/User.
If you are using another driver in config/hashing.php - change bcrypt to argon/argon2i
public function getAuthPassword() {
return bcrypt($this->password);
}

What's the right way to work with MVC and Laravel

I'm building a CRUD using laravel and I'm not sure about the MVC rules.
I thought that all the functions related to database (Crud) should be done inside the model and not the controller. But I found this inside User's Controller:
protected function create(array $data)
{
return User::create([
'name' => $data['name'],
'email' => $data['email'],
'password' => bcrypt($data['password']),
]);
}
I know it's not persisting to the database, just returning a new instance of the class User. Should I call this function inside the model so then persist it ?
Doesn't make much sense calling this to just make a ->save().
Your example is okay, but if you think your controller's doing too much work that it should not be doing, you can refactor your code to transfer the work.
For example, in your code, the password is being bcrypted, you can create a new function in User model (or another helper class if you want, like UserHelper or UserQuery)
class User ...
{
public static function registerUser($data)
{
$data['password'] = bcrypt($data['password']);
$user = self::create($data);
return $user;
}
}
You can now use this to directly pass user data, it will shoulder the bcrypting of the password.
$new_user = User::registerUser(['username' => 'helloworld', 'password' => 'worldhello']);
I think we should always put in mind if a class/method/any other point of control is doing something that is beyond its purpose, then that's the time we should think of refactoring it to another point of control.

L4.2 Event: know where event got fired from?

I have a question regarding Events with Laravel 4.2...
I currently have an event listener on "auth.login"... some code lines are executed when user logins on web version... however I would like to execute a different action if the user logged via the API controller, example: ApiController#postLogin (my mobile version).
Code in my home controller:
if (Auth::attempt(['email' => Input::get('login'), 'password' => Input::get('password')]) OR Auth::attempt(['username' => Input::get('login'), 'password' => Input::get('password')]))
{
return Redirect::intended(URL::route('dashboard.index'));
}
else
{
return Redirect::action('HomeController#getIndex')->with('poplogin', true)->with('badcredentials',true)->withInput();
}
Code in global.php (event listener)
Event::listen('auth.login', function($user)
{
//Put Login_attemp in Database for Last activity, etc
$user->login_attemp()->create(['login_ip'=>$_SERVER['REMOTE_ADDR'],'login_time'=> date('Y-m-d H:i:s',time())]);
$user->last_logged = date('Y-m-d H:i:s',time());
$user->save();
Session::flash('justlogged',true);
//other code that I didnt include..........
});
Code in my ApiController
public function getRefreshData() {
//check the token
$token = Input::get('token');
$username = Input::get('username');
$user = User::where('api_token', $token)
->where('username', $username)
->first();
if(!$user || !$token) {
return Response::json([
'error' => true,
'message' => 'Invalid Token, please re login',
'code' => 401],
401
);
}
Auth::login($user);
//5 last Timesheets + tslines, for pre-load at log-in in phone memory
//Not inserting possible creation dates between, to keep phone app 100% independent
$timesheets = $user->timesheets()->orderBy('startdate', 'DESC')->take(10)->with('tslines')->get();
//Other code that I didnt include
);
return $response;
}
I cannot control the execution of the event "auth.login" myself.. firing it manually with parameter would just double-fire the event (i think?)
Is there a way to detect where the event got fired from in the Event:listen and do not insert a "log-in attemp" (my code in event listener) each time I use the getRefreshData() function in my API? Yes, I need to log the user in my API function (for other code that isn't included)
Edit: It seems to me that the most straightforward way to handle this is to check for the token in the Event listener.
Event::listen('auth.login', function($user)
{
if (Input::has('token') && Input::has('username')) {
//Put Login_attemp in Database for Last activity, etc
$user->login_attemp()->create(['login_ip'=>$_SERVER['REMOTE_ADDR'],'login_time'=> date('Y-m-d H:i:s',time())]);
$user->last_logged = date('Y-m-d H:i:s',time());
$user->save();
Session::flash('justlogged',true);
//other code that I didnt include..........
}
});
I really would suggest, long term, looking at using the functionality demonstrated in the docs under Accessing the Logged In User, it's just going to make life easier.
Original response: It might be helpful if you posted more code, because I feel like maybe this is an instance where if we zoom out a little bit maybe there is a better way to deal with this situation. Possibly you need multiple actions, different listeners, etc.
For solving this issue though, it's easy, just pass in whatever additional data you need to via a parameter:
$response = Event::fire('auth.login', array($user, 'source' => 'ApiController#postLogin', 'mobile' => true));
Then you can set those parameters to the $event object that is passed to your listener.
Let me know if you have any further questions!
After some research, I found how I could 'bypass' the execution of the event listener when the event is fired from the ApiController, using the Request::is() function
From L4.2 Docs: http://laravel.com/docs/4.2/requests#request-information )..
My routes.php file is like so:
Route::controller('api/v1', 'ApiV1Controller');
And in my global.php (where I declare my event listener)
Event::listen('auth.login', function($user)
{
if (!Request::is('api/*'))
{
//Code that is always executed at firing of event, except when from my API controllers
//Put Login_attemp in Database for Last activity, etc
$user->login_attemp()->create(['login_ip'=>$_SERVER['REMOTE_ADDR'],'login_time'=> date('Y-m-d H:i:s',time())]);
$user->last_logged = date('Y-m-d H:i:s',time());
$user->save();
}
}

Resources