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

Add better base password reset functionality #111

Draft
wants to merge 6 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 67 additions & 1 deletion src/Auth/Manager.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@
use Illuminate\Support\Facades\Session;
use Illuminate\Support\Facades\Request;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Contracts\Auth\StatefulGuard;
use Illuminate\Contracts\Auth\UserProvider;
use Illuminate\Session\SessionManager;

/**
* Authentication manager
*/
class Manager implements \Illuminate\Contracts\Auth\StatefulGuard
class Manager implements StatefulGuard, UserProvider
{
use \Winter\Storm\Support\Traits\Singleton;

Expand Down Expand Up @@ -44,6 +46,11 @@ class Manager implements \Illuminate\Contracts\Auth\StatefulGuard
*/
protected $throttleModel = Models\Throttle::class;

/**
* @var string Password Reset Model Class
*/
protected $passwordResetModel = Models\PasswordReset::class;

/**
* @var bool Flag to enable login throttling
*/
Expand Down Expand Up @@ -366,6 +373,21 @@ public function findThrottleByUserId($userId, $ipAddress = null)
return $this->throttle[$cacheKey] = $throttle;
}

//
// Password Reset
//

/**
* Creates a new password reset model instance.
*
* @return Models\PasswordReset
*/
public function createPasswordResetModel()
{
$class = '\\' . ltrim($this->passwordResetModel, '\\');
return new $class();
}

//
// Business Logic
//
Expand Down Expand Up @@ -895,4 +917,48 @@ public function getRealUser()
return $this->getUser();
}
}

/**
* @inheritDoc
*/
public function retrieveById($identifier)
{
return $this->findUserById($identifier);
}

/**
* @inheritDoc
*/
public function retrieveByCredentials(array $credentials)
{
return $this->validateInternal($credentials);
}

/**
* @inheritDoc
*
* Not implemented.
*/
public function retrieveByToken($identifier, $token)
{
return null;
}

/**
* @inheritDoc
*
* Not implemented.
*/
public function updateRememberToken(Authenticatable $user, $token)
{
return;
}

/**
* @inheritDoc
*/
public function validateCredentials(Authenticatable $user, array $credentials)
{
return ($user === $this->validateInternal($credentials));
}
}
22 changes: 22 additions & 0 deletions src/Auth/Migrations/2022_09_07_000007_Db_Password_Resets.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

use Winter\Storm\Database\Schema\Blueprint;
use Winter\Storm\Database\Updates\Migration;
use Winter\Storm\Support\Facades\Schema;

class DbPasswordResets extends Migration
{
public function up()
{
Schema::create('password_resets', function (Blueprint $table) {
$table->string('email')->index();
$table->string('token');
$table->timestamp('created_at')->nullable();
});
}

public function down()
{
Schema::dropIfExists('password_resets');
}
}
26 changes: 26 additions & 0 deletions src/Auth/Models/PasswordReset.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

namespace Winter\Storm\Auth\Models;

use Winter\Storm\Database\Model;

/**
* Password Reset model
*
* Represents a single password reset request.
*
* @author Winter CMS
* @method \Winter\Storm\Database\Relations\BelongsToMany users() Users relation.
*/
class PasswordReset extends Model
{
/**
* @var string The table associated with the model.
*/
protected $table = 'password_resets';

/**
* @var string[]|bool The attributes that aren't mass assignable.
*/
protected $guarded = ['*'];
}
3 changes: 2 additions & 1 deletion src/Auth/Models/User.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,12 @@
* @method \Winter\Storm\Database\Relations\BelongsToMany groups() Group relation.
* @method \Winter\Storm\Database\Relations\BelongsTo role() Role relation.
*/
class User extends Model implements \Illuminate\Contracts\Auth\Authenticatable
class User extends Model implements \Illuminate\Contracts\Auth\Authenticatable, \Illuminate\Contracts\Auth\CanResetPassword
{
use \Winter\Storm\Database\Traits\Hashable;
use \Winter\Storm\Database\Traits\Purgeable;
use \Winter\Storm\Database\Traits\Validation;
use \Winter\Storm\Auth\Passwords\CanResetPassword;

/**
* @var string The table associated with the model.
Expand Down
42 changes: 42 additions & 0 deletions src/Auth/Passwords/CanResetPassword.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php

namespace Winter\Storm\Auth\Passwords;

use Illuminate\Auth\Passwords\CanResetPassword as BaseCanResetPassword;
use Winter\Storm\Support\Facades\Config;
use Winter\Storm\Support\Facades\Mail;

trait CanResetPassword
{
use BaseCanResetPassword;

/**
* {@inheritDoc}
*/
public function sendPasswordResetNotification($token)
{
Mail::rawTo(
[$this->getEmailForPasswordReset()],
$this->defaultPasswordResetEmail($token),
function ($message) {
$message->subject('Password reset request');
}
);
}

/**
* The default password reset email content.
*
* @param string $token
* @return string
*/
protected function defaultPasswordResetEmail($token)
{
$url = Config::get('app.url') . '/reset-password/' . $token;

return 'Hi,' . "\n\n"
. 'Someone has requested a password reset for your account. If this was you, please go to the following URL to reset your password.' . "\n\n"
. $url . "\n\n"
. 'If this was not you, please feel free to disregard this email.';
}
}
92 changes: 92 additions & 0 deletions src/Auth/Passwords/PasswordBrokerManager.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
<?php

namespace Winter\Storm\Auth\Passwords;

use InvalidArgumentException;
use Illuminate\Auth\Passwords\DatabaseTokenRepository;
use Illuminate\Auth\Passwords\PasswordBroker;
use Illuminate\Auth\Passwords\PasswordBrokerManager as BasePasswordBrokerManager;

class PasswordBrokerManager extends BasePasswordBrokerManager
{
/**
* {@inheritDoc}
*/
protected function resolve($name)
{
$config = $this->getConfig($name);

if (is_null($config)) {
throw new InvalidArgumentException("Password resetter [{$name}] is not defined.");
}

// The password broker uses a token repository to validate tokens and send user
// password e-mails, as well as validating that password reset process as an
// aggregate service of sorts providing a convenient interface for resets.
return new PasswordBroker(
$this->createTokenRepository($config),
$this->getAuthInstance(),
);
}

/**
* {@inheritDoc}
*/
protected function createTokenRepository(array $config)
{
$key = $this->app['config']['app.key'];

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

$passwordResetModel = $this->getAuthInstance()->createPasswordResetModel();

if (isset($config['connection'])) {
$connection = $config['connection'];
} else {
$connection = $passwordResetModel->getConnectionName();
}

return new DatabaseTokenRepository(
$this->app['db']->connection($connection),
$this->app['hash'],
$config['table'] ?? $passwordResetModel->getTable(),
$key,
$config['expire'] ?? 180,
$config['throttle'] ?? 30,
);
}

/**
* {@inheritDoc}
*
* @return array|null
*/
protected function getConfig($name)
{
if ($name === 'winter') {
return [];
}

return $this->app['config']["auth.passwords.{$name}"];
}

/**
* {@inheritDoc}
*/
public function getDefaultDriver()
{
return $this->app['config']['auth.defaults.passwords'] ?? 'winter';
}

/**
* Returns the active auth instance.
*
* @return \Winter\Storm\Auth\Manager
*/
protected function getAuthInstance()
{
return $this->app['auth'];
}
}
27 changes: 27 additions & 0 deletions src/Auth/Passwords/PasswordResetServiceProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

namespace Winter\Storm\Auth\Passwords;

use Illuminate\Auth\Passwords\PasswordResetServiceProvider as BaseProvider;

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

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