Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Resolve TokenRepositoryInterface from Container rather than new DatabaseTokenRepository(...) #15071

Closed
AdenFraser opened this issue Aug 26, 2016 · 5 comments

Comments

@AdenFraser
Copy link
Contributor

AdenFraser commented Aug 26, 2016

I've been looking over a simple way of making a couple of minor amends to the Auth\Passwords in a project and have spotted that PasswordBrokerManager createTokenRepository() news a DatabaseTokenRepository() instance rather than resolving the \Illuminate\Auth\Passwords\TokenRepositoryInterface (which would be expected from the docblock).

Likewise the PasswordBrokerManager resolve() method new's an \Illuminate\Contracts\Auth\PasswordBroker instance (which is is in the IOC as auth.password).

I was looking at overriding a couple of methods and rebinding a new implementation of TokenRepositoryInterface before realising that this wasn't always used (only when type hinted in the construct).

What I'm asking is there a specific reason we are newing instances of contract implemenations which if were resolved, could be overridden? And if there is no reason, would a PR to change this behaviour be accepted.

I'd rather not create all the boilerplate of a new PasswordResetServiceProvider for what would be a couple of methods to override.

@cdave3
Copy link

cdave3 commented Sep 2, 2016

Funny, I just ended up in the exact same place looking for a way to do username based password resets.

The column email seems to be hard coded in DatabaseTokenRepository making this a bit more difficult than I was hoping for.

@AdenFraser
Copy link
Contributor Author

Ended up there initially because need to change the hash length (nothing
too difficult we thought!) And then discovered an awful lot which is quite
concrete!

On 2 Sep 2016 20:22, "Dave" notifications@github.com wrote:

Funny, I just ended up in the exact same place looking for a way to do
username based password resets.

The column email seems to be hard coded in DatabaseTokenRepository making
this a bit more difficult than I was hoping for.


You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub
#15071 (comment),
or mute the thread
https://github.com/notifications/unsubscribe-auth/AB0I4_z-41gNMfhrgVdFVieg3udmnYO0ks5qmHeSgaJpZM4JuIrM
.

@themsaid
Copy link
Member

themsaid commented Sep 8, 2016

Please feel free to open a PR with the suggested changes, I think you have a point here.

Closing this since it's not actually a bug :) please ping me if you think otherwise.

@themsaid themsaid closed this as completed Sep 8, 2016
@mstralka
Copy link

mstralka commented Feb 2, 2017

This is probably irrelevant once 5.5 is released because of #17499 but I wanted to modify the Reset Password functionality so it would not generate a new token each time a user clicks the link. If a user is impatient and clicks the Reset Password more than once, then the first email they receive is now invalid because the token was replaced. Instead, I just want the 2nd email to contain the same token as the first email did.

I subclassed DatabaseTokenRepository so it will only create a new token if a non-expired one doesn't already exist.

Here's what I had to do for this to work:

config/app.php: - comment out the PasswordResetServiceProvider and replace it with my subclass:

//Illuminate\Auth\Passwords\PasswordResetServiceProvider::class,
App\Auth\Passwords\MLPasswordResetServiceProvider::class,

MLPasswordResetServiceProvider.php - which registers my MLPasswordBrokerManager in the container under auth.password:

class MLPasswordResetServiceProvider extends PasswordResetServiceProvider
{
    /**
     * Register the password broker instance.
     *
     * @return void
     */
    protected function registerPasswordBroker()
    {
        $this->app->singleton('auth.password', function ($app) {
            return new MLPasswordBrokerManager($app);
        });

        $this->app->bind('auth.password.broker', function ($app) {
            return $app->make('auth.password')->broker();
        });
    }
}

MLPasswordBrokerManager.php - this class returns my MLDatabaseTokenRepository subclass:

class MLPasswordBrokerManager extends PasswordBrokerManager
{

    /**
     * Create a token repository instance based on the given configuration.
     *
     * @param  array $config
     * @return \Illuminate\Auth\Passwords\TokenRepositoryInterface
     */
    protected function createTokenRepository(array $config)
    {
        $key = $this->app['config']['app.key'];

        if (Str::startsWith($key, 'base64:')) {
            $key = base64_decode(substr($key, 7));
        }

        $connection = isset($config['connection']) ? $config['connection'] : null;

        return new MLDatabaseTokenRepository(
            $this->app['db']->connection($connection),
            $this->app['hash'],
            $config['table'],
            $key,
            $config['expire']
        );
    }
}

MLDatabaseTokenRepository.php - only creates a new token if a valid one does not already exist for the user:

class MLDatabaseTokenRepository extends DatabaseTokenRepository
{

    /**
     * Create a new token record.
     *
     * @param  \Illuminate\Contracts\Auth\CanResetPassword $user
     * @return string
     */
    public function create(CanResetPasswordContract $user)
    {
        $email = $user->getEmailForPasswordReset();

        // If the user already has a valid token, send it instead of creating a new one.
        // This will prevent the impatient user from being frustrated if they clicked
        // Reset Password more than once.
        $record = (array)$this->getTable()
            ->where('email', $email)
            ->first();
        $exists = $record && !$this->tokenExpired($record['created_at']);
        if ($exists) {
            return $record['token'];
        }

        $this->deleteExisting($user);

        // We will create a new, random token for the user so that we can e-mail them
        // a safe link to the password reset form. Then we will insert a record in
        // the database so that we can verify the token within the actual reset.
        $token = $this->createNewToken();

        $this->getTable()->insert($this->getPayload($email, $token));

        return $token;
    }
}

@Cyrille37
Copy link
Contributor

Hi,
Resolving TokenRepositoryInterface instance should permits to easily implements token management with Sentinel Reminder.
L5.4 has new DatabaseTokenRepository() hard coded.
Cheers.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants