Packy
Packy

Reputation: 3573

Laravel 5.2 send password reset after registration

I have altered my authController.php file to do a few things needed for my project. It works great, just need to make one more change. Right now the controller looks like this:

    <?php

namespace App\Http\Controllers\Auth;

use App\User;
use App\Role;
use Mail;
use Validator;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\ThrottlesLogins;
use Illuminate\Foundation\Auth\AuthenticatesAndRegistersUsers;
use Illuminate\Http\Request;

use Illuminate\Foundation\Auth\ResetsPasswords;

class AuthController extends Controller
{
    /*
    |--------------------------------------------------------------------------
    | Registration & Login Controller
    |--------------------------------------------------------------------------
    |
    | This controller handles the registration of new users, as well as the
    | authentication of existing users. By default, this controller uses
    | a simple trait to add these behaviors. Why don't you explore it?
    |
    */

    use AuthenticatesAndRegistersUsers, ThrottlesLogins, ResetsPasswords;

    /**
     * Where to redirect users after login / registration.
     *
     * @var string
     */
    protected $redirectTo = '/add';

    /**
     * Create a new authentication controller instance.
     *
     * @return void
     */
    public function __construct()
    {
        $this->middleware($this->guestMiddleware(), ['except' => 'logout']);


    }


     /**
     * Overwrite the Laravel 5.2 standard registration 
     * so user will not be logged in automatically.
     *
     * @param  array  $request
     * @return Register
     */
    public function register(Request $request)
    {
        $validator = $this->validator($request->all());

        if ($validator->fails()) {
            $this->throwValidationException(
                $request, $validator
            );
        }

        $this->create($request->all());

        return redirect($this->redirectPath());
    }

    /**
     * Extend password reset email to user for when registering
     */

    public function sendResetLinkEmail(Request $request)
    {
        $this->validateSendResetLinkEmail($request);

        $broker = $this->getBroker();

        $this->subject = "First Time User Setup";
        $broker->emailView = "auth.emails.password";

        $response = Password::broker($broker)->sendFirstTimeSetup(
            $this->getSendResetLinkEmailCredentials($request),
            $this->resetEmailBuilder()
        );

        switch ($response) {
            case Password::RESET_LINK_SENT:
                return $this->getSendResetLinkEmailSuccessResponse($response);
            case Password::FIRST_TIME_SETUP:
                return $this->getSendFirstTimeSetupEmailSuccessResponse($response);
            case Password::INVALID_USER:
            default:
                return $this->getSendResetLinkEmailFailureResponse($response);
        }
    }

    public function getSendFirstTimeSetupEmailSuccessResponse($response) 
    {
        return redirect()->back()->with('status', trans($response));
    }


    /**
     * Get a validator for an incoming registration request.
     *
     * @param  array  $data
     * @return \Illuminate\Contracts\Validation\Validator
     */
    protected function validator(array $data)
    {
        return Validator::make($data, [
            'first-name' => 'required|max:255',
            'last-name' => 'required|max:255',
            'phone' => 'required|max:255',
            'form' => 'max:255',
            'email' => 'required|email|max:255|unique:users',
            'password' => 'required|min:6|confirmed',
        ]);
    }

    /**
     * Create a new user instance after a valid registration.
     *
     * @param  array  $data
     * @return User
     */
    protected function create(Request $request,  array $data ) 
    {
        //Create the user
        $user = User::create([
            'first_name' => $data['first-name'],
            'last_name' => $data['last-name'],
            'phone' => $data['phone'],
            'email' => $data['email'],
            'password' => bcrypt($data['password']),
        ]);

        return $this->postEmail($request);

        /*
        Mail::send('auth.emails.registered', ['user' => $user], function ($m) use ($user) 
        {
            $m->to($user->email, $user->first_name)->subject('You Have Been Added');
        });*/

        //Is it a User? Then give them that role
        if ($data['user-role'] == 'user')
        {
            $role = Role::where('name', '=', 'user')->firstOrFail();
            $user = User::find($user->id);
            $user->roles()->attach($role->id);
        }

        //Is it an Admin? Then give them that role
        if ($data['user-role'] == 'admin')
        {
            $role = Role::where('name', '=', 'owner')->firstOrFail();
            $user = User::find($user->id);
            $user->roles()->attach($role->id);
        }

        return $user;
    }





}

It stops auto login on user creation, assigns a role to the user based on the form and sends a password reset email. The trouble is not I get the error Trait method guestMiddleware has not been applied, because there are collisions with other trait methods on App\Http\Controllers\Auth\AuthController

Upvotes: 1

Views: 3779

Answers (2)

eComEvo
eComEvo

Reputation: 12559

You can fix your trait collision issue by changing the use block to the following:

use ThrottlesLogins,
    ResetsPasswords,
    AuthenticatesAndRegistersUsers {
    AuthenticatesAndRegistersUsers::guestMiddleware insteadof ResetsPasswords;
    AuthenticatesAndRegistersUsers::getGuard insteadof ResetsPasswords;
    AuthenticatesAndRegistersUsers::redirectPath insteadof ResetsPasswords;
}

It's ugly, but it will fix all related collisions, thus allowing you to just use the pre-built Illuminate methods if you so choose.

Upvotes: 0

Ohgodwhy
Ohgodwhy

Reputation: 50787

One of the tricks to this is understanding how the traits inside of the Auth factory work.

First, we'll need to use the use Illuminate\Foundation\Auth\ResetsPasswords; Trait as well as the AuthenticatesAndRegistersUsers and the ThrottlesLogins trait.

use AuthenticatesAndRegistersUsers, ThrottlesLogins, ResetsPassword;

Next, we need to make sure that we have a $request instance being passed to our create method:

protected function create(Illuminate\Http\Request $request, array $data)

Sidenote - I would recommend moving away from passing in a $data object as an argument and instead working with $request. You can get your data like this: $request->get('first-name'), etc.

Finally, we pass our $request to the postEmail() function:

return $this->postEmail($request);

This will pipe the $request to the ResetsPasswords\postEmail function:

public function postEmail(Request $request)
{
    return $this->sendResetLinkEmail($request);
}

Which will inturn pipe it to the ResetsPasswords\sendResetLinkEmail function:

public function sendResetLinkEmail(Request $request)
{
    $this->validateSendResetLinkEmail($request);

    $broker = $this->getBroker();

    $response = Password::broker($broker)->sendResetLink(
        $this->getSendResetLinkEmailCredentials($request),
        $this->resetEmailBuilder()
    );

    switch ($response) {
        case Password::RESET_LINK_SENT:
            return $this->getSendResetLinkEmailSuccessResponse($response);
        case Password::INVALID_USER:
        default:
            return $this->getSendResetLinkEmailFailureResponse($response);
    }
}

Which will ultimately send out an email.

The key to making this entire thing work is that the instance of Illuminate\Http\Request always contains an email field.

Note

The response that will be returned from the sendResetLinkEmail function may not be the same response that you sent out. Perhaps Users are confused by a Password Reset request when they've just created their account. Instead, maybe you want to send a First Time Setup. In order to do this, you will need to create your own Password Broker and Password Facade.

Next, lets make 2 new directories:

App\Brokers
App\Facades 

Next, make a new file inside of App\Facades and call it Password.php. Namespace the file accordingly and extend the existing Password Facade. Also, we'll add another const as an observable response type for our FIRST_TIME_SETUP.

<?php

namespace App\Facades;

class Password extends \Illuminate\Support\Facades\Password {
    /**
     * Constant representing a successfully sent reminder.
     *
     * @var string
     */
    const FIRST_TIME_SETUP = 'passwords.first_time_setup';
}

Now we have added another response type that we can switch on which will help dictate how we send out our email.

Next, we need to make our Password Broker and establish our sendFirstTimeSetup functionality.

namespace App\Brokers;

class PasswordBroker extends Illuminate\Auth\Passwords\PasswordBroker {
    public function sendFirstTimeSetup(array $credentials, Closure $callback = null) 
    {
        // First we will check to see if we found a user at the given credentials and
        // if we did not we will redirect back to this current URI with a piece of
        // "flash" data in the session to indicate to the developers the errors.
        $user = $this->getUser($credentials);

        if (is_null($user)) {
            return static::INVALID_USER;
        }

        // Once we have the reset token, we are ready to send the message out to this
        // user with a link to reset their password. We will then redirect back to
        // the current URI having nothing set in the session to indicate errors.
        $token = $this->tokens->create($user);

        $this->emailResetLink($user, $token, $callback);

        return static::FIRST_TIME_SETUP;
    }        
}

Now we need to copy the sendResetLinkEmail function that we saw earlier, and move it into our AuthController. This will allow us to modify the Password Facade that we use, and modify our switch statement to support our First Time Setup

public function sendResetLinkEmail(Request $request)
{
    $this->validateSendResetLinkEmail($request);

    $broker = $this->getBroker();

    $response = Password::broker($broker)->sendFirstTimeSetup(
        $this->getSendResetLinkEmailCredentials($request),
        $this->resetEmailBuilder()
    );

    switch ($response) {
        case Password::RESET_LINK_SENT:
            return $this->getSendResetLinkEmailSuccessResponse($response);
        case Password::FIRST_TIME_SETUP:
            return $this->getSendFirstTimeSetupEmailSuccessResponse($response);
        case Password::INVALID_USER:
        default:
            return $this->getSendResetLinkEmailFailureResponse($response);
    }
}

Then, also in the AuthController, we'll make another function to return our response:

public function getSendFirstTimeSetupEmailSuccessResponse($response) 
{
    return redirect()->back()->with('status', trans($response));
}

Finally, if you want to override the view that is used by the function when being sent out, simply override the $broker->emailView property before invoking the ->sendFirstTimeSetup() function:

//...

$broker->emailView = "emails.user.first_time_setup";
$response = Password::broker($broker)->sendFirstTimeSetup(
    $this->getSendResetLinkEmailCredentials($request),
    $this->resetEmailBuilder()
);

//...

If you want to change the subject of the email, override the ->subject() property inside of your AuthController prior to firing your ->sendFirstTimeSetup() function:

//...
$this->subject = "First Time User Setup";
$broker->emailView = "emails.user.first_time_setup";
$response = Password::broker($broker)->sendFirstTimeSetup(
    $this->getSendResetLinkEmailCredentials($request),
    $this->resetEmailBuilder()
);

//...

I could go on and on, but I think this will get you going in the right direction. Hopefully it will help a few others as well.

Upvotes: 6

Related Questions