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
9375e92c12
commit
330bff0ec8
|
@ -32,6 +32,7 @@ class CoreServiceProvider extends ServiceProvider
|
|||
Event::listen('Flarum.Core.*', 'Flarum\Core\Listeners\DiscussionMetadataUpdater');
|
||||
Event::listen('Flarum.Core.*', 'Flarum\Core\Listeners\UserMetadataUpdater');
|
||||
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('renamed', 'Flarum\Core\Posts\RenamedPost');
|
||||
|
|
36
framework/core/src/Flarum/Core/Listeners/EmailConfirmationMailer.php
Executable file
36
framework/core/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++) {
|
||||
$user = User::create([
|
||||
'username' => $faker->userName,
|
||||
'email' => $faker->safeEmail,
|
||||
'password' => 'password',
|
||||
'join_time' => $faker->dateTimeThisYear,
|
||||
'time_zone' => $faker->timezone
|
||||
'username' => $faker->userName,
|
||||
'email' => $faker->safeEmail,
|
||||
'is_confirmed' => true,
|
||||
'password' => 'password',
|
||||
'join_time' => $faker->dateTimeThisYear
|
||||
]);
|
||||
|
||||
// Assign the users to the 'Member' group, and possibly some others.
|
||||
|
@ -49,6 +49,7 @@ class UserTableSeeder extends Seeder
|
|||
|
||||
// Guests can view the forum
|
||||
['group.2' , 'forum' , 'view'],
|
||||
['group.2' , 'forum' , 'register'],
|
||||
|
||||
// Members can create and reply to discussions + edit their own stuff
|
||||
['group.3' , 'forum' , 'startDiscussion'],
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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]);
|
||||
|
||||
|
||||
$this->userRepo->save($user);
|
||||
$this->userRepo->syncGroups($user, [3]); // default groups
|
||||
$this->dispatchEventsFor($user);
|
||||
|
||||
return $user;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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\Groups\Group;
|
||||
use Flarum\Core\Support\Exceptions\PermissionDeniedException;
|
||||
use Flarum\Core\Support\Exceptions\InvalidConfirmationTokenException;
|
||||
|
||||
class User extends Entity implements UserInterface, RemindableInterface
|
||||
{
|
||||
use EventGenerator;
|
||||
use Permissible;
|
||||
|
||||
|
||||
use UserTrait, RemindableTrait;
|
||||
|
||||
protected static $rules = [
|
||||
|
@ -35,7 +36,7 @@ class User extends Entity implements UserInterface, RemindableInterface
|
|||
protected $table = 'users';
|
||||
|
||||
protected $hidden = ['password'];
|
||||
|
||||
|
||||
public static function boot()
|
||||
{
|
||||
parent::boot();
|
||||
|
@ -61,12 +62,20 @@ class User extends Entity implements UserInterface, RemindableInterface
|
|||
|
||||
public function setUsernameAttribute($username)
|
||||
{
|
||||
if ($username === $this->username) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->attributes['username'] = $username;
|
||||
$this->raise(new Events\UserWasRenamed($this));
|
||||
}
|
||||
|
||||
public function setEmailAttribute($email)
|
||||
{
|
||||
if ($email === $this->email) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->attributes['email'] = $email;
|
||||
$this->raise(new Events\EmailWasChanged($this));
|
||||
}
|
||||
|
@ -77,6 +86,14 @@ class User extends Entity implements UserInterface, RemindableInterface
|
|||
$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)
|
||||
{
|
||||
$user = new static;
|
||||
|
@ -84,13 +101,39 @@ class User extends Entity implements UserInterface, RemindableInterface
|
|||
$user->username = $username;
|
||||
$user->email = $email;
|
||||
$user->password = $password;
|
||||
$user->join_time = time();
|
||||
|
||||
$user->refreshConfirmationToken();
|
||||
|
||||
$user->raise(new Events\UserWasRegistered($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()
|
||||
{
|
||||
return ['join_time', 'last_seen_time', 'read_time'];
|
||||
|
|
|
@ -5,37 +5,38 @@ use Illuminate\Database\Migrations\Migration;
|
|||
|
||||
class CreateUsersTable extends Migration {
|
||||
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('users', function(Blueprint $table)
|
||||
{
|
||||
$table->increments('id');
|
||||
$table->string('username');
|
||||
$table->string('email');
|
||||
$table->string('password');
|
||||
$table->string('token');
|
||||
$table->dateTime('join_time');
|
||||
$table->string('time_zone');
|
||||
$table->dateTime('last_seen_time')->nullable();
|
||||
$table->dateTime('read_time')->nullable();
|
||||
$table->integer('discussions_count')->unsigned()->default(0);
|
||||
$table->integer('posts_count')->unsigned()->default(0);
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('users', function(Blueprint $table)
|
||||
{
|
||||
$table->increments('id');
|
||||
$table->string('username');
|
||||
$table->string('email');
|
||||
$table->boolean('is_confirmed')->default(0);
|
||||
$table->string('confirmation_token')->nullable();
|
||||
$table->string('password');
|
||||
$table->string('token');
|
||||
$table->dateTime('join_time')->nullable();
|
||||
$table->dateTime('last_seen_time')->nullable();
|
||||
$table->dateTime('read_time')->nullable();
|
||||
$table->integer('discussions_count')->unsigned()->default(0);
|
||||
$table->integer('posts_count')->unsigned()->default(0);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::drop('users');
|
||||
}
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::drop('users');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,7 +1,13 @@
|
|||
<?php
|
||||
|
||||
Route::get('/', function()
|
||||
{
|
||||
return View::make('flarum.web::index')
|
||||
->with('title', Config::get('flarum::forum_title', 'Flarum Demo Forum'));
|
||||
Route::get('/', function () {
|
||||
return View::make('flarum.web::index')
|
||||
->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
framework/core/src/views/emails/confirm.blade.php
Normal file
13
framework/core/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