mirror of
https://github.com/flarum/framework.git
synced 2024-11-26 18:33:40 +08:00
Preliminary email confirmation implementation
Whenever a user registers or changes their email, they are sent an email containing a link which they must click to confirm it. Upon registering, a user won’t be assigned to any groups and therefore won’t have permission to do anything (but they can still log in!) Upon confirming their email for the first time, their account will be assigned to the Member group and thus “activated”.
This commit is contained in:
parent
b6ef1f296e
commit
0e4e44c358
|
@ -32,6 +32,7 @@ class CoreServiceProvider extends ServiceProvider
|
||||||
Event::listen('Flarum.Core.*', 'Flarum\Core\Listeners\DiscussionMetadataUpdater');
|
Event::listen('Flarum.Core.*', 'Flarum\Core\Listeners\DiscussionMetadataUpdater');
|
||||||
Event::listen('Flarum.Core.*', 'Flarum\Core\Listeners\UserMetadataUpdater');
|
Event::listen('Flarum.Core.*', 'Flarum\Core\Listeners\UserMetadataUpdater');
|
||||||
Event::listen('Flarum.Core.*', 'Flarum\Core\Listeners\RenamedPostCreator');
|
Event::listen('Flarum.Core.*', 'Flarum\Core\Listeners\RenamedPostCreator');
|
||||||
|
Event::listen('Flarum.Core.*', 'Flarum\Core\Listeners\EmailConfirmationMailer');
|
||||||
|
|
||||||
Post::addType('comment', 'Flarum\Core\Posts\CommentPost');
|
Post::addType('comment', 'Flarum\Core\Posts\CommentPost');
|
||||||
Post::addType('renamed', 'Flarum\Core\Posts\RenamedPost');
|
Post::addType('renamed', 'Flarum\Core\Posts\RenamedPost');
|
||||||
|
|
36
src/Flarum/Core/Listeners/EmailConfirmationMailer.php
Executable file
36
src/Flarum/Core/Listeners/EmailConfirmationMailer.php
Executable file
|
@ -0,0 +1,36 @@
|
||||||
|
<?php namespace Flarum\Core\Listeners;
|
||||||
|
|
||||||
|
use Illuminate\Mail\Mailer;
|
||||||
|
use Laracasts\Commander\Events\EventListener;
|
||||||
|
|
||||||
|
use Flarum\Core\Users\Events\UserWasRegistered;
|
||||||
|
use Flarum\Core\Users\Events\EmailWasChanged;
|
||||||
|
|
||||||
|
class EmailConfirmationMailer extends EventListener
|
||||||
|
{
|
||||||
|
protected $mailer;
|
||||||
|
|
||||||
|
public function __construct(Mailer $mailer)
|
||||||
|
{
|
||||||
|
$this->mailer = $mailer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function whenUserWasRegistered(UserWasRegistered $event)
|
||||||
|
{
|
||||||
|
$user = $event->user;
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
'user' => $user,
|
||||||
|
'url' => route('flarum.confirm', ['id' => $user->id, 'token' => $user->confirmation_token])
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->mailer->send('flarum::emails.confirm', $data, function ($message) use ($user) {
|
||||||
|
$message->to($user->email)->subject('Welcome!');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function whenEmailWasChanged(EmailWasChanged $event)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
<?php namespace Flarum\Core\Support\Exceptions;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
|
||||||
|
class InvalidConfirmationTokenException extends Exception
|
||||||
|
{
|
||||||
|
}
|
|
@ -24,11 +24,11 @@ class UserTableSeeder extends Seeder
|
||||||
|
|
||||||
for ($i = 0; $i < 100; $i++) {
|
for ($i = 0; $i < 100; $i++) {
|
||||||
$user = User::create([
|
$user = User::create([
|
||||||
'username' => $faker->userName,
|
'username' => $faker->userName,
|
||||||
'email' => $faker->safeEmail,
|
'email' => $faker->safeEmail,
|
||||||
'password' => 'password',
|
'is_confirmed' => true,
|
||||||
'join_time' => $faker->dateTimeThisYear,
|
'password' => 'password',
|
||||||
'time_zone' => $faker->timezone
|
'join_time' => $faker->dateTimeThisYear
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Assign the users to the 'Member' group, and possibly some others.
|
// Assign the users to the 'Member' group, and possibly some others.
|
||||||
|
@ -49,6 +49,7 @@ class UserTableSeeder extends Seeder
|
||||||
|
|
||||||
// Guests can view the forum
|
// Guests can view the forum
|
||||||
['group.2' , 'forum' , 'view'],
|
['group.2' , 'forum' , 'view'],
|
||||||
|
['group.2' , 'forum' , 'register'],
|
||||||
|
|
||||||
// Members can create and reply to discussions + edit their own stuff
|
// Members can create and reply to discussions + edit their own stuff
|
||||||
['group.3' , 'forum' , 'startDiscussion'],
|
['group.3' , 'forum' , 'startDiscussion'],
|
||||||
|
|
14
src/Flarum/Core/Users/Commands/ConfirmEmailCommand.php
Normal file
14
src/Flarum/Core/Users/Commands/ConfirmEmailCommand.php
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
<?php namespace Flarum\Core\Users\Commands;
|
||||||
|
|
||||||
|
class ConfirmEmailCommand
|
||||||
|
{
|
||||||
|
public $userId;
|
||||||
|
|
||||||
|
public $token;
|
||||||
|
|
||||||
|
public function __construct($userId, $token)
|
||||||
|
{
|
||||||
|
$this->userId = $userId;
|
||||||
|
$this->token = $token;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
<?php namespace Flarum\Core\Users\Commands;
|
||||||
|
|
||||||
|
use Laracasts\Commander\CommandHandler;
|
||||||
|
use Laracasts\Commander\Events\DispatchableTrait;
|
||||||
|
use Event;
|
||||||
|
|
||||||
|
use Flarum\Core\Users\UserRepository;
|
||||||
|
|
||||||
|
class ConfirmEmailCommandHandler implements CommandHandler
|
||||||
|
{
|
||||||
|
use DispatchableTrait;
|
||||||
|
|
||||||
|
protected $userRepo;
|
||||||
|
|
||||||
|
public function __construct(UserRepository $userRepo)
|
||||||
|
{
|
||||||
|
$this->userRepo = $userRepo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handle($command)
|
||||||
|
{
|
||||||
|
$user = $this->userRepo->findOrFail($command->userId);
|
||||||
|
|
||||||
|
$user->confirmEmail($command->token);
|
||||||
|
|
||||||
|
// If the user hasn't yet had their account activated,
|
||||||
|
if (! $user->join_time) {
|
||||||
|
$user->activate();
|
||||||
|
}
|
||||||
|
|
||||||
|
Event::fire('Flarum.Core.Users.Commands.ConfirmEmail.UserWillBeSaved', [$user, $command]);
|
||||||
|
|
||||||
|
$this->userRepo->save($user);
|
||||||
|
$this->dispatchEventsFor($user);
|
||||||
|
|
||||||
|
return $user;
|
||||||
|
}
|
||||||
|
}
|
11
src/Flarum/Core/Users/Commands/ConfirmEmailValidator.php
Normal file
11
src/Flarum/Core/Users/Commands/ConfirmEmailValidator.php
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
<?php namespace Flarum\Core\Users\Commands;
|
||||||
|
|
||||||
|
use Flarum\Core\Support\CommandValidator;
|
||||||
|
|
||||||
|
class ConfirmEmailValidator extends CommandValidator
|
||||||
|
{
|
||||||
|
public function validate($command)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -39,9 +39,8 @@ class RegisterUserCommandHandler implements CommandHandler
|
||||||
);
|
);
|
||||||
|
|
||||||
Event::fire('Flarum.Core.Users.Commands.RegisterUser.UserWillBeSaved', [$user, $command]);
|
Event::fire('Flarum.Core.Users.Commands.RegisterUser.UserWillBeSaved', [$user, $command]);
|
||||||
|
|
||||||
$this->userRepo->save($user);
|
$this->userRepo->save($user);
|
||||||
$this->userRepo->syncGroups($user, [3]); // default groups
|
|
||||||
$this->dispatchEventsFor($user);
|
$this->dispatchEventsFor($user);
|
||||||
|
|
||||||
return $user;
|
return $user;
|
||||||
|
|
13
src/Flarum/Core/Users/Events/EmailWasConfirmed.php
Normal file
13
src/Flarum/Core/Users/Events/EmailWasConfirmed.php
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
<?php namespace Flarum\Core\Users\Events;
|
||||||
|
|
||||||
|
use Flarum\Core\Users\User;
|
||||||
|
|
||||||
|
class EmailWasConfirmed
|
||||||
|
{
|
||||||
|
public $user;
|
||||||
|
|
||||||
|
public function __construct(User $user)
|
||||||
|
{
|
||||||
|
$this->user = $user;
|
||||||
|
}
|
||||||
|
}
|
13
src/Flarum/Core/Users/Events/UserWasActivated.php
Normal file
13
src/Flarum/Core/Users/Events/UserWasActivated.php
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
<?php namespace Flarum\Core\Users\Events;
|
||||||
|
|
||||||
|
use Flarum\Core\Users\User;
|
||||||
|
|
||||||
|
class UserWasActivated
|
||||||
|
{
|
||||||
|
public $user;
|
||||||
|
|
||||||
|
public function __construct(User $user)
|
||||||
|
{
|
||||||
|
$this->user = $user;
|
||||||
|
}
|
||||||
|
}
|
|
@ -14,12 +14,13 @@ use Laracasts\Commander\Events\EventGenerator;
|
||||||
use Flarum\Core\Entity;
|
use Flarum\Core\Entity;
|
||||||
use Flarum\Core\Groups\Group;
|
use Flarum\Core\Groups\Group;
|
||||||
use Flarum\Core\Support\Exceptions\PermissionDeniedException;
|
use Flarum\Core\Support\Exceptions\PermissionDeniedException;
|
||||||
|
use Flarum\Core\Support\Exceptions\InvalidConfirmationTokenException;
|
||||||
|
|
||||||
class User extends Entity implements UserInterface, RemindableInterface
|
class User extends Entity implements UserInterface, RemindableInterface
|
||||||
{
|
{
|
||||||
use EventGenerator;
|
use EventGenerator;
|
||||||
use Permissible;
|
use Permissible;
|
||||||
|
|
||||||
use UserTrait, RemindableTrait;
|
use UserTrait, RemindableTrait;
|
||||||
|
|
||||||
protected static $rules = [
|
protected static $rules = [
|
||||||
|
@ -35,7 +36,7 @@ class User extends Entity implements UserInterface, RemindableInterface
|
||||||
protected $table = 'users';
|
protected $table = 'users';
|
||||||
|
|
||||||
protected $hidden = ['password'];
|
protected $hidden = ['password'];
|
||||||
|
|
||||||
public static function boot()
|
public static function boot()
|
||||||
{
|
{
|
||||||
parent::boot();
|
parent::boot();
|
||||||
|
@ -61,12 +62,20 @@ class User extends Entity implements UserInterface, RemindableInterface
|
||||||
|
|
||||||
public function setUsernameAttribute($username)
|
public function setUsernameAttribute($username)
|
||||||
{
|
{
|
||||||
|
if ($username === $this->username) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
$this->attributes['username'] = $username;
|
$this->attributes['username'] = $username;
|
||||||
$this->raise(new Events\UserWasRenamed($this));
|
$this->raise(new Events\UserWasRenamed($this));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setEmailAttribute($email)
|
public function setEmailAttribute($email)
|
||||||
{
|
{
|
||||||
|
if ($email === $this->email) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
$this->attributes['email'] = $email;
|
$this->attributes['email'] = $email;
|
||||||
$this->raise(new Events\EmailWasChanged($this));
|
$this->raise(new Events\EmailWasChanged($this));
|
||||||
}
|
}
|
||||||
|
@ -77,6 +86,14 @@ class User extends Entity implements UserInterface, RemindableInterface
|
||||||
$this->raise(new Events\PasswordWasChanged($this));
|
$this->raise(new Events\PasswordWasChanged($this));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function activate()
|
||||||
|
{
|
||||||
|
$this->join_time = time();
|
||||||
|
$this->groups()->sync([3]);
|
||||||
|
|
||||||
|
$this->raise(new Events\UserWasActivated($this));
|
||||||
|
}
|
||||||
|
|
||||||
public static function register($username, $email, $password)
|
public static function register($username, $email, $password)
|
||||||
{
|
{
|
||||||
$user = new static;
|
$user = new static;
|
||||||
|
@ -84,13 +101,39 @@ class User extends Entity implements UserInterface, RemindableInterface
|
||||||
$user->username = $username;
|
$user->username = $username;
|
||||||
$user->email = $email;
|
$user->email = $email;
|
||||||
$user->password = $password;
|
$user->password = $password;
|
||||||
$user->join_time = time();
|
|
||||||
|
$user->refreshConfirmationToken();
|
||||||
|
|
||||||
$user->raise(new Events\UserWasRegistered($user));
|
$user->raise(new Events\UserWasRegistered($user));
|
||||||
|
|
||||||
return $user;
|
return $user;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function validateConfirmationToken($token)
|
||||||
|
{
|
||||||
|
return ! $this->is_confirmed
|
||||||
|
&& $token
|
||||||
|
&& $this->confirmation_token === $token;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function refreshConfirmationToken()
|
||||||
|
{
|
||||||
|
$this->is_confirmed = false;
|
||||||
|
$this->confirmation_token = str_random(30);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function confirmEmail($token)
|
||||||
|
{
|
||||||
|
if (! $this->validateConfirmationToken($token)) {
|
||||||
|
throw new InvalidConfirmationTokenException;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->is_confirmed = true;
|
||||||
|
$this->confirmation_token = null;
|
||||||
|
|
||||||
|
$this->raise(new Events\EmailWasConfirmed($this));
|
||||||
|
}
|
||||||
|
|
||||||
public function getDates()
|
public function getDates()
|
||||||
{
|
{
|
||||||
return ['join_time', 'last_seen_time', 'read_time'];
|
return ['join_time', 'last_seen_time', 'read_time'];
|
||||||
|
|
|
@ -5,37 +5,38 @@ use Illuminate\Database\Migrations\Migration;
|
||||||
|
|
||||||
class CreateUsersTable extends Migration {
|
class CreateUsersTable extends Migration {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Run the migrations.
|
* Run the migrations.
|
||||||
*
|
*
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function up()
|
public function up()
|
||||||
{
|
{
|
||||||
Schema::create('users', function(Blueprint $table)
|
Schema::create('users', function(Blueprint $table)
|
||||||
{
|
{
|
||||||
$table->increments('id');
|
$table->increments('id');
|
||||||
$table->string('username');
|
$table->string('username');
|
||||||
$table->string('email');
|
$table->string('email');
|
||||||
$table->string('password');
|
$table->boolean('is_confirmed')->default(0);
|
||||||
$table->string('token');
|
$table->string('confirmation_token')->nullable();
|
||||||
$table->dateTime('join_time');
|
$table->string('password');
|
||||||
$table->string('time_zone');
|
$table->string('token');
|
||||||
$table->dateTime('last_seen_time')->nullable();
|
$table->dateTime('join_time')->nullable();
|
||||||
$table->dateTime('read_time')->nullable();
|
$table->dateTime('last_seen_time')->nullable();
|
||||||
$table->integer('discussions_count')->unsigned()->default(0);
|
$table->dateTime('read_time')->nullable();
|
||||||
$table->integer('posts_count')->unsigned()->default(0);
|
$table->integer('discussions_count')->unsigned()->default(0);
|
||||||
});
|
$table->integer('posts_count')->unsigned()->default(0);
|
||||||
}
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reverse the migrations.
|
* Reverse the migrations.
|
||||||
*
|
*
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function down()
|
public function down()
|
||||||
{
|
{
|
||||||
Schema::drop('users');
|
Schema::drop('users');
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,13 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
Route::get('/', function()
|
Route::get('/', function () {
|
||||||
{
|
return View::make('flarum.web::index')
|
||||||
return View::make('flarum.web::index')
|
->with('title', Config::get('flarum::forum_title', 'Flarum Demo Forum'));
|
||||||
->with('title', Config::get('flarum::forum_title', 'Flarum Demo Forum'));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Route::get('confirm/{id}/{token}', ['as' => 'flarum.confirm', function ($userId, $token) {
|
||||||
|
$command = new Flarum\Core\Users\Commands\ConfirmEmailCommand($userId, $token);
|
||||||
|
|
||||||
|
$commandBus = App::make('Laracasts\Commander\CommandBus');
|
||||||
|
$commandBus->execute($command);
|
||||||
|
}]);
|
||||||
|
|
13
src/views/emails/confirm.blade.php
Normal file
13
src/views/emails/confirm.blade.php
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en-US">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h2>Welcome, {{ $user->username }}</h2>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
To confirm your email, click here: {{ $url }}
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
Loading…
Reference in New Issue
Block a user