I've got a simple CRUD application in which is possible to add users, through a form, the ideal flow is
an admin fills the form with user name and password
an email is sent to the user with a link to set the password
the user is logged in
Now at the end of the day what I really want is that the PasswordBroker does, but instead to send the emails.passsowrd view I want a different one, and the rest of the logic can be the same
Is there an easy way so create a valid isntace of the passwordBroker and passing a different view?
the property emailWiew is protected so I cannot override it, I tried to create a new instance out of the IoC
$tokens = $this->app['auth.password.tokens'];
$users = $this->app['auth']->driver()->getProvider();
$view = "emails.createPassword";
$password = new PasswordBroker(
$tokens, $users, $this->app['mailer'], $view
);
dd($users->retrieveByCredentials(["email#user.com"])); //i get null and the user email is valid
$response = $password->sendResetLink(["email#user.com"], function (Message $message) {
$message->subject("Set your password");
});
dd($response); // I get "passwords.user" wich is an error
but when I pass the email address I get an invalid user error
any idea?
The problem is that you're not providing the key to retrieve the credentials, so it's trying to get a field in the users table by the name of "0". use the following and it will work:
$response = $password->sendResetLink(
["email" => "email#user.com"],
function (Message $message) {
$message->subject("Set your password");
});
Related
I am using the Laravel framework to work with my MySQL database, and currently want to update my database from a JSON object, that will be sent from somewhere else.
Currently, I have it the same as my 'Store' function, which is obviously not going to work, because it will update everything, or refuse to work because it is missing information.
This is the for each I have currently, it does not work, but I am not experienced with how it is best to parse a JSON with a for-each, then store it.
public function update(Request $request,$student)
{
$storeData = User::find($student);
foreach ($request as $value) {
$storeData-> username = $value;
}
Here is my store function, with all the info that the front-end team may send in a JSON format.
$storeData->username=$request->input('username');
$storeData->password=$request->input('password');
$storeData->email=$request->input('email');
$storeData->location=$request->input('location');
$storeData->role=DB::table('users')->where('user_id', $student)->value('role');
$storeData->devotional_id=$request->input('devotional_id');
$storeData->gift_id=$request->input('gift_id');
$storeData->save();
return dd("Info Recieved");
You can write the method like the below snippet.
Also, assume you are working with laravel API, so you don't need to parse the incoming JSON input, but you will receive these values as items in the request object.
However, you should use the filled method in order to determine if the field is existing and has a value, the update function will override with empty values otherwise.
I just added this method to the first input, but you have to use it each and every input if you are not sure what the front end will pass.
public function update(Request $request, $student)
{
$storeData = User::find($student); // should be id
if ($request->filled('username')) { // use this for other items also
$storeData->username = $request->input('username');
}
$storeData->password = $request->input('password');
$storeData->email = $request->input('email');
$storeData->location = $request->input('location');
$storeData->role = DB::table('users')->where('user_id', $student)->value('role');
$storeData->devotional_id = $request->input('devotional_id');
$storeData->gift_id = $request->input('gift_id');
$storeData->update();
dd("Info Recieved");
}
Why would they send json data from the front in a post?
Really it would be from a form input. Like Rinto said it would be request object.
$user->username = $request->user_name;
I'm gathering this is a form on the front to create a new user. Why not use the built in auth scaffolding that has this set up for you in the register area?
So I'd personally use...
//look up user that matches the email or create a new user
$user = User::firstOrNew(['email' => request('email')]);
//add other input values here
$user->name = request('name');
//save
$user->save();
Hard to give an exact answer to this when the question is a bit vague in what you're doing. There are many methods in Laravel to accomplish things. From your code it just looks like registration. Also, the big gotcha I see in your code is you are passing a text only password and then adding that password in plain text to your database. That is a big security flaw.
you can convert your JSON object to an array and then do your foreach loop on the new array. To update a table in Laravel it's update ($storeData->update();) not save. Save is to insert.
$Arr = json_decode($request, true);
I want to send some data or even just a message to my front end when I redirect a user on email verification click.
The click works fine and the verified_at field gets updated and then using redirect('/login') I get back to the correct react component. What I would like to do is display a message here saying something like "Cool, email verified, you can now login"
public function verify(Request $request)
{
$userID = $request['id'];
$user = User::findOrFail($userID);
$date = date('Y-m-d g:i:s');
// update email verified_at field
$user->email_verified_at = $date;
$user->save();
return redirect('/login');
}
I have also tried doing something like
return redirect('/login')->with('blah blah blah');
Any ideas on how I can go about this would be great.
What I have done is to add the id with the redirect('/login/' . $id). This way I can use the id to get the user info again from the front end.
Probably not the best solution, but it works for now until I can work out something better
(answered by questioner)
I've been searching the internet and have yet to find a solution to the following problem...
We currently have a website developed using Laravel which the user table is a remote Microsoft SQL database. The driver in config/auth.php has been set to "database". All is working fine except for the password reset functionality, which we get the following error:
UnexpectedValueException in PasswordBroker.php line 238: User must implement CanResetPassword interface.
From my limited understanding of Laravel (this is my first experiance with Laravel), the Eloquent driver has support for the CanResetPassword functionality, however, this has not been implemented in the Database User Provider by Laravel, hence the error.
So my question is thus, has anyone had a configuration where they have the driver to “Database” and implemented a reset password functionality? All the examples I have seen to date relate to using the Eloquent model, which from my understanding of Laravel is not an option since during the initial development we had to change the driver from Eloquent to database to get the remote Microsoft SQL server working in the first place. Moving the Microsoft SQL database to a local database is not an option I’m afraid.
Alternatively, if anyone has implemented another method of a user resetting their password using an email address I would be open to suggestions.
To write your own password reset logic, you can still use the default migration that comes out of the box or simply create yours. The most important part is the token. Because you are making your own password reset, you have a couple of decisions to make:
Will the token expire?
Can a user use the same token multiple times?
You will need 2 pages, 4 different routes and 4 different functions in the same controller. The 'I forgot my password' page and the 'Reset password' page. In the first page, display a form where you take the user email. And post to the following controller.
//to be added on top as use statements
use DB;
use Auth;
use Hash;
use Carbon;
use App\User;
public function sendPasswordResetToken(Request $request)
{
$user = User::where ('email', $request->email)-first();
if ( !$user ) return redirect()->back()->withErrors(['error' => '404']);
//create a new token to be sent to the user.
DB::table('password_resets')->insert([
'email' => $request->email,
'token' => str_random(60), //change 60 to any length you want
'created_at' => Carbon::now()
]);
$tokenData = DB::table('password_resets')
->where('email', $request->email)->first();
$token = $tokenData->token;
$email = $request->email; // or $email = $tokenData->email;
/**
* Send email to the email above with a link to your password reset
* something like url('password-reset/' . $token)
* Sending email varies according to your Laravel version. Very easy to implement
*/
}
Second part, when the user clicks on the link
/**
* Assuming the URL looks like this
* http://localhost/password-reset/random-string-here
* You check if the user and the token exist and display a page
*/
public function showPasswordResetForm($token)
{
$tokenData = DB::table('password_resets')
->where('token', $token)->first();
if ( !$tokenData ) return redirect()->to('home'); //redirect them anywhere you want if the token does not exist.
return view('passwords.show');
}
Display a page with a form containing 2 inputs
- New password password or whateveer you want
- New password confirmation password_confirm or whatever you want
The form should post to the same URL mapped to the following controller. Why? because we still need to use the token to find the actual user.
public function resetPassword(Request $request, $token)
{
//some validation
...
$password = $request->password;
$tokenData = DB::table('password_resets')
->where('token', $token)->first();
$user = User::where('email', $tokenData->email)->first();
if ( !$user ) return redirect()->to('home'); //or wherever you want
$user->password = Hash::make($password);
$user->update(); //or $user->save();
//do we log the user directly or let them login and try their password for the first time ? if yes
Auth::login($user);
// If the user shouldn't reuse the token later, delete the token
DB::table('password_resets')->where('email', $user->email')->delete();
//redirect where we want according to whether they are logged in or not.
}
Don't forget to add routes
Route::get('password-reset', 'PasswordController#showForm'); //I did not create this controller. it simply displays a view with a form to take the email
Route::post('password-reset', 'PasswordController#sendPasswordResetToken');
Route::get('reset-password/{token}', 'PasswordController#showPasswordResetForm');
Route::post('reset-password/{token}', 'PasswordController#resetPassword');
Note: There might be typos or syntax errors because I did not test this and wrote it here directly from the top of my head. If you see an error/exception, don't panick, read the error and search google.
Just to add to what #eddythedove said.
Instead of str_random(60) I used the Laravel way of creating a token:
private function generateToken()
{
// This is set in the .env file
$key = config('app.key');
// Illuminate\Support\Str;
if (Str::startsWith($key, 'base64:')) {
$key = base64_decode(substr($key, 7));
}
return hash_hmac('sha256', Str::random(40), $key);
}
If you find an error in str_random, make sure you import the module first:
use Illuminate\Support\Str;
Then call with Str::random (60).
$key = config('app.key');
if (Str::startsWith($key, 'base64:')) {
$key = base64_decode(substr($key, 7));
}
$token = hash_hmac('sha256', Str::random(40), $key);
$dbToken = app(Hasher::class)->make($token);
DB::insert('password_resets', [
'email' => 'email#mail.com',
'token' => $dbToken,
]);
This should work in Laravel 8
The default way Laravel handles the Reset Password has a few security issues.
No track record reset password attempts (Delete the token in the table after success attempt is not acceptable)
No expiry date
No token used time
We always better keep track of these security functions.
I have altered the default table like this on my db migration:
public function up()
{
Schema::table('password_resets', function (Blueprint $table) {
$table->bigIncrements('id');
$table->enum('is_used', ['t', 'f'])->default('f');
$table->dateTime('updated_at')->nullable();
});
}
Instead of deleting the record I simply update the table 'is_used' to 't' and updated_at column.
I use following query to filter is_used = 'f' and created on the same day to gather with token.
$data = PasswordReset::where('token', $token)->where('is_used', 'f')
->whereDate('created_at', '>=', Carbon::today()->toDateString())->first();
i try some code after that i get some solution that will work in laravel 8+.
$key = config('app.key');
if (Str::startsWith($key, 'base64:')) {
$key = base64_decode(substr($key, 7));
}
$token = hash_hmac('sha256', Str::random(40), $key);
$dbToken =Hash::make($token);
Within my project, I have given the administrator role the privilege to add users to the site. For security reasons, I hash a random string, to store as the temporary password, I then want to send the user an email with the standard Laravel reset password template.
I have the following:
$user = new User();
$user->name = Input::get('name');
$user->email = Input::get('email');
$user->password = Hash::make(str_random(8));
$user->save();
$response = Password::sendResetLink(Input::get('email'), function (Message $message) {
$message->subject('Password Reset');
});
The error I'm getting is
Argument 1 passed to
Illuminate\Auth\Passwords\PasswordBroker::sendResetLink() must be of
the type array, string given
How can I trigger this function within Laravel, so the user is sent a password reset email? Thank you.
The problem here is that you are sending string email, and you should send array (this is what error says).
You should in this case use:
Request::only('email')
instead of
Input::get('email')
When my users log into the website their first name, last name and ID are missing from the session data because my session data is coded to take post data and submit into the session table in my database.
Because user logs in with email and password in my session data only email appears and nothing else does.
How can I make first name, last name and id appear in my session table in my db? I want to some how grab these details from the database when user is logging in and provide it in my $u_data array so it get's posted upload login success.
Here is my code:
<?php
class Login_Model extends CI_Model {
public function checkLogin() {
$this->db->where('email', $this->input->post('email')); //compare db email to email entered in form
$this->db->where('password', $this->hashed()); //compare db password to hashed user password
$query = $this->db->get('users'); //get the above info from 'user' table
if ($query->num_rows() == 1) { //if number of rows returned is 1
$u_data = array( //new variable with session data
'user_id' => $this->db->insert_id(),
'email' => $this->input->post('email'),
'first_name' => $this->input->post('first_name'),
'last_name' => $this->input->post('last_name'),
'logged_in' => TRUE
);
$this->session->set_userdata($u_data); //send data from variable to db session
return TRUE;
} else {
return FALSE;
}
}
public function hashed() { //hashing method
// sha1 and salt password
$password = $this->encrypt->sha1($this->input->post('password')); //encrypt user password
$salt = $this->config->item('encryption_key'); //grab static salt from config file
$start_hash = sha1($salt . $password);
$end_hash = sha1($password . $salt);
$hashed = sha1($start_hash . $password . $end_hash);
return $hashed;
}
}
If you're tracking sessions in your DB, two solutions come to mind.
First, you could select the first/last from the user table and insert it into the session table. This requires changes to your application.
Second, you could set up a view for your application, in which the session table is automatically joined with the appropriate user, but that assumes you already have some unique identifier for which user it is in the session (was that the email address?*). This solution would not require any changes to the application code, but would require changes to the DB, which may be the preferred method depending upon your deployment requirements (or it may not :) ).
* as a side note, if you're using email addresses for unique identifiers, be aware that some people share email addresses as you decide if this is the right solution for you.
It'd be as simple as doing something like:
session_start();
$_SESSION['userdata'] = $u_data;
within your CheckLogin method. The session is just a regular PHP array that happens to be automatically preserved for you. You can put anything you want into it, but you do have do put things into it yourself - PHP won't do it for you.
comment followup:
gotcha. So, you simply modify you class to fetch that information from the DB once the login's authenticated. I don't know how your DB class works, but instead of merely checking if there's a matching row, fetch the first/last name, using a query something like this:
select firstname, lastname
from users
where email=$email and password=$password
If you get a result row, you know it's a valid login, and then you just retrieve the name data. I have no idea how your db class works, but it shouldn't be too hard to get it to do that.
When I'm working with Auth systems in CodeIgniter I have made it a practice to include the "user" object globally in views, and also globally in my controllers, by fetching the userdata in the constructor, like so...
<?php
class My_Controller extends Controller {
private $the_user; //global var to store current user data
function My_Controller() {
parent::Controller();
$data->the_user = $this->ion_auth->get_user(); //get user data
$this->load->vars($data); //load into all views as $the_user "$the_user"
$this->the_user=$data->the_user; //load into private class variable "$this->the_user"
}
At that point $the_user variable object is available in all views by default AND $this->the_user is always available to controller functions. It always represents the user currently logged in.
I am using Ion_auth for authentication and fetching the user, so that piece you would have to fill in.
I actually just constructed a "How-to" to implement extended Controller classes so all the Auth logic is automatically inherited to all "protected" Controllers.
The following solved this issue for me. I looked at one of my old questions on here and used my common sense.
I made this edit to my code.
if ($query->num_rows() == 1) { //if number of rows returned is 1
$user = $query->result();
$u_data = array( //new variable with session data
'user_id' => $user[0]->id,
'email' => $this->input->post('email'),
'first_name' => $user[0]->first_name,
'last_name' => $user[0]->last_name,
'logged_in' => TRUE
);