1
0
mirror of https://github.com/flarum/core.git synced 2025-08-05 07:57:46 +02:00

Improve email changing/confirmation stuff

This commit is contained in:
Toby Zerner
2015-05-27 16:24:54 +09:30
parent f4dc1b5d04
commit b6a8416daf
18 changed files with 262 additions and 94 deletions

View File

@@ -4,6 +4,7 @@ use Flarum\Api\Request;
use Flarum\Core\Commands\GenerateAccessTokenCommand;
use Flarum\Core\Repositories\UserRepositoryInterface;
use Flarum\Core\Exceptions\PermissionDeniedException;
use Flarum\Core\Events\UserEmailChangeWasRequested;
use Illuminate\Http\JsonResponse;
use Illuminate\Contracts\Bus\Dispatcher;
@@ -36,6 +37,11 @@ class TokenAction extends JsonApiAction
throw new PermissionDeniedException;
}
if (! $user->is_activated) {
event(new UserEmailChangeWasRequested($user, $user->email));
return new JsonResponse(['code' => 'confirm_email', 'email' => $user->email], 401);
}
$token = $this->bus->dispatch(
new GenerateAccessTokenCommand($user->id)
);

View File

@@ -2,13 +2,10 @@
class ConfirmEmailCommand
{
public $userId;
public $token;
public function __construct($userId, $token)
public function __construct($token)
{
$this->userId = $userId;
$this->token = $token;
}
}

View File

@@ -0,0 +1,16 @@
<?php namespace Flarum\Core\Events;
use Flarum\Core\Models\User;
class UserEmailChangeWasRequested
{
public $user;
public $email;
public function __construct(User $user, $email)
{
$this->user = $user;
$this->email = $email;
}
}

View File

@@ -3,6 +3,8 @@
use Flarum\Core\Repositories\UserRepositoryInterface as UserRepository;
use Flarum\Core\Events\UserWillBeSaved;
use Flarum\Core\Support\DispatchesEvents;
use Flarum\Core\Exceptions\InvalidConfirmationTokenException;
use Flarum\Core\Models\EmailToken;
class ConfirmEmailCommandHandler
{
@@ -17,10 +19,14 @@ class ConfirmEmailCommandHandler
public function handle($command)
{
$user = $this->users->findOrFail($command->userId);
$token = EmailToken::find($command->token)->first();
$user->assertConfirmationTokenValid($command->token);
$user->confirmEmail();
if (! $token) {
throw new InvalidConfirmationTokenException;
}
$user = $token->user;
$user->changeEmail($token->email);
if (! $user->is_activated) {
$user->activate();
@@ -31,6 +37,8 @@ class ConfirmEmailCommandHandler
$user->save();
$this->dispatchEventsFor($user);
$token->delete();
return $user;
}
}

View File

@@ -23,11 +23,12 @@ class EditUserCommandHandler
$userToEdit->assertCan($user, 'edit');
if (isset($command->data['username'])) {
$userToEdit->assertCan($user, 'rename');
$userToEdit->rename($command->data['username']);
}
if (isset($command->data['email'])) {
$userToEdit->changeEmail($command->data['email']);
$userToEdit->requestEmailChange($command->data['email']);
}
if (isset($command->data['password'])) {

View File

@@ -2,8 +2,9 @@
use Illuminate\Mail\Mailer;
use Flarum\Core\Events\UserWasRegistered;
use Flarum\Core\Events\EmailWasChanged;
use Config;
use Flarum\Core\Events\UserEmailChangeWasRequested;
use Flarum\Core;
use Flarum\Core\Models\EmailToken;
use Illuminate\Contracts\Events\Dispatcher;
class EmailConfirmationMailer
@@ -23,28 +24,47 @@ class EmailConfirmationMailer
public function subscribe(Dispatcher $events)
{
$events->listen('Flarum\Core\Events\UserWasRegistered', __CLASS__.'@whenUserWasRegistered');
$events->listen('Flarum\Core\Events\EmailWasChanged', __CLASS__.'@whenEmailWasChanged');
$events->listen('Flarum\Core\Events\UserEmailChangeWasRequested', __CLASS__.'@whenUserEmailChangeWasRequested');
}
public function whenUserWasRegistered(UserWasRegistered $event)
{
$user = $event->user;
$data = $this->getPayload($user, $user->email);
$forumTitle = Config::get('flarum::forum_title');
$data = [
'username' => $user->username,
'forumTitle' => $forumTitle,
'url' => route('flarum.forum.confirm', ['id' => $user->id, 'token' => $user->confirmation_token])
];
$this->mailer->send(['text' => 'flarum::emails.confirm'], $data, function ($message) use ($user) {
$message->to($user->email);
$message->subject('Confirm Your Email Address');
$this->mailer->send(['text' => 'flarum::emails.activateAccount'], $data, function ($message) use ($email) {
$message->to($email);
$message->subject('Activate Your New Account');
});
}
public function whenEmailWasChanged(EmailWasChanged $event)
public function whenUserEmailChangeWasRequested(UserEmailChangeWasRequested $event)
{
$email = $event->email;
$data = $this->getPayload($event->user, $email);
$this->mailer->send(['text' => 'flarum::emails.confirmEmail'], $data, function ($message) use ($email) {
$message->to($email);
$message->subject('Confirm Your New Email Address');
});
}
protected function generateToken($user, $email)
{
$token = EmailToken::generate($user->id, $email);
$token->save();
return $token;
}
protected function getPayload($user, $email)
{
$token = $this->generateToken($user, $email);
return [
'username' => $user->username,
'url' => route('flarum.forum.confirmEmail', ['token' => $token->id]),
'forumTitle' => Core::config('forum_title')
];
}
}

View File

@@ -0,0 +1,46 @@
<?php namespace Flarum\Core\Models;
class EmailToken extends Model
{
/**
* The table associated with the model.
*
* @var string
*/
protected $table = 'email_tokens';
/**
* Use a custom primary key for this model.
*
* @var boolean
*/
public $incrementing = false;
/**
* Generate a reset token for the specified user.
*
* @param int $userId
* @return static
*/
public static function generate($userId, $email)
{
$token = new static;
$token->id = str_random(40);
$token->user_id = $userId;
$token->email = $email;
$token->created_at = time();
return $token;
}
/**
* Define the relationship with the owner of this reset token.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function user()
{
return $this->belongsTo('Flarum\Core\Models\User');
}
}

View File

@@ -110,14 +110,19 @@ class Model extends Eloquent
*/
public function assertValid()
{
$validation = $this->makeValidator();
if ($validation->fails()) {
throw (new ValidationFailureException)
->setErrors($validation->errors())
->setInput($validation->getData());
$validator = $this->makeValidator();
if ($validator->fails()) {
$this->throwValidationFailureException($validator);
}
}
protected function throwValidationFailureException($validator)
{
throw (new ValidationFailureException)
->setErrors($validator->errors())
->setInput($validator->getData());
}
/**
* Make a new validator instance for this model.
*

View File

@@ -13,6 +13,7 @@ use Flarum\Core\Events\UserBioWasChanged;
use Flarum\Core\Events\UserAvatarWasChanged;
use Flarum\Core\Events\UserWasActivated;
use Flarum\Core\Events\UserEmailWasConfirmed;
use Flarum\Core\Events\UserEmailChangeWasRequested;
class User extends Model
{
@@ -94,8 +95,6 @@ class User extends Model
$user->password = $password;
$user->join_time = time();
$user->refreshConfirmationToken();
$user->raise(new UserWasRegistered($user));
return $user;
@@ -111,6 +110,7 @@ class User extends Model
{
if ($username !== $this->username) {
$this->username = $username;
$this->raise(new UserWasRenamed($this));
}
@@ -127,12 +127,31 @@ class User extends Model
{
if ($email !== $this->email) {
$this->email = $email;
$this->raise(new UserEmailWasChanged($this));
}
return $this;
}
public function requestEmailChange($email)
{
if ($email !== $this->email) {
$validator = static::$validator->make(
compact('email'),
$this->expandUniqueRules(array_only(static::$rules, 'email'))
);
if ($validator->fails()) {
$this->throwValidationFailureException($validator);
}
$this->raise(new UserEmailChangeWasRequested($this, $email));
}
return $this;
}
/**
* Change the user's password.
*
@@ -257,41 +276,12 @@ class User extends Model
public function activate()
{
$this->is_activated = true;
$this->groups()->sync([3]);
$this->raise(new UserWasActivated($this));
return $this;
}
/**
* Check if a given confirmation token is valid for this user.
*
* @param string $token
* @return boolean
*/
public function assertConfirmationTokenValid($token)
{
if ($this->is_confirmed ||
! $token ||
$this->confirmation_token !== $token) {
throw new InvalidConfirmationTokenException;
}
}
/**
* Generate a new confirmation token for the user.
*
* @return $this
*/
public function refreshConfirmationToken()
{
$this->is_confirmed = false;
$this->confirmation_token = str_random(30);
return $this;
}
/**
* Confirm the user's email.
*
@@ -461,7 +451,13 @@ class User extends Model
*/
public function permissions()
{
return Permission::whereIn('group_id', array_merge([Group::GUEST_ID], $this->groups->lists('id')));
$groupIds = [Group::GUEST_ID];
if ($this->is_activated) {
$groupIds = array_merge($groupIds, [Group::MEMBER_ID], $this->groups->lists('id'));
}
return Permission::whereIn('group_id', $groupIds);
}
/**

View File

@@ -5,16 +5,15 @@ use Flarum\Core\Commands\ConfirmEmailCommand;
use Flarum\Core\Commands\GenerateAccessTokenCommand;
use Flarum\Core\Exceptions\InvalidConfirmationTokenException;
class ConfirmAction extends BaseAction
class ConfirmEmailAction extends BaseAction
{
use MakesRememberCookie;
public function handle(Request $request, $routeParams = [])
{
try {
$userId = array_get($routeParams, 'id');
$token = array_get($routeParams, 'token');
$command = new ConfirmEmailCommand($userId, $token);
$command = new ConfirmEmailCommand($token);
$user = $this->dispatch($command);
} catch (InvalidConfirmationTokenException $e) {
return 'Invalid confirmation token';
@@ -24,7 +23,6 @@ class ConfirmAction extends BaseAction
$token = $this->dispatch($command);
return redirect('/')
->withCookie($this->makeRememberCookie($token->id))
->with('alert', ['type' => 'success', 'message' => 'Thanks for confirming!']);
->withCookie($this->makeRememberCookie($token->id));
}
}

View File

@@ -28,9 +28,9 @@ Route::post('login', [
'uses' => $action('Flarum\Forum\Actions\LoginAction')
]);
Route::get('confirm/{id}/{token}', [
'as' => 'flarum.forum.confirm',
'uses' => $action('Flarum\Forum\Actions\ConfirmAction')
Route::get('confirm/{token}', [
'as' => 'flarum.forum.confirmEmail',
'uses' => $action('Flarum\Forum\Actions\ConfirmEmailAction')
]);
Route::get('reset/{token}', [