Fixed registations bugs and removed email confirmation model

This commit is contained in:
Dan Brown 2016-09-17 16:00:53 +01:00
parent 39e81d9a1f
commit fbfc25fc21
No known key found for this signature in database
GPG Key ID: 46D9F943C24A2EF9
11 changed files with 133 additions and 87 deletions

3
.gitignore vendored
View File

@ -10,4 +10,5 @@ Homestead.yaml
/public/bower
/storage/images
_ide_helper.php
/storage/debugbar
/storage/debugbar
.phpstorm.meta.php

View File

@ -1,29 +0,0 @@
<?php namespace BookStack;
use Illuminate\Notifications\Notifiable;
class EmailConfirmation extends Model
{
use Notifiable;
protected $fillable = ['user_id', 'token'];
/**
* Get the user that this confirmation is attached to.
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function user()
{
return $this->belongsTo(User::class);
}
/**
* Set the routing for mail notifications.
* @return mixed
*/
public function routeNotificationForMail()
{
return $this->user->email;
}
}

View File

@ -2,6 +2,8 @@
namespace BookStack\Http\Controllers\Auth;
use BookStack\Exceptions\ConfirmationEmailException;
use BookStack\Exceptions\UserRegistrationException;
use BookStack\Repos\UserRepo;
use BookStack\Services\EmailConfirmationService;
use BookStack\Services\SocialAuthService;
@ -158,7 +160,7 @@ class RegisterController extends Controller
* @param bool|false|SocialAccount $socialAccount
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
* @throws UserRegistrationException
* @throws \BookStack\Exceptions\ConfirmationEmailException
* @throws ConfirmationEmailException
*/
protected function registerUser(array $userData, $socialAccount = false)
{
@ -195,18 +197,6 @@ class RegisterController extends Controller
return view('auth/register-confirm');
}
/**
* View the confirmation email as a standard web page.
* @param $token
* @return \Illuminate\View\View
* @throws UserRegistrationException
*/
public function viewConfirmEmail($token)
{
$confirmation = $this->emailConfirmationService->getEmailConfirmationFromToken($token);
return view('emails/email-confirmation', ['token' => $confirmation->token]);
}
/**
* Confirms an email via a token and logs the user into the system.
* @param $token
@ -219,7 +209,7 @@ class RegisterController extends Controller
$user = $confirmation->user;
$user->email_confirmed = true;
$user->save();
auth()->login($confirmation->user);
auth()->login($user);
session()->flash('success', 'Your email has been confirmed!');
$this->emailConfirmationService->deleteConfirmationsByUser($user);
return redirect($this->redirectPath);

View File

@ -33,7 +33,7 @@ class Authenticate
public function handle($request, Closure $next)
{
if ($this->auth->check() && setting('registration-confirmation') && !$this->auth->user()->email_confirmed) {
return redirect()->guest(baseUrl('/register/confirm/awaiting'));
return redirect(baseUrl('/register/confirm/awaiting'));
}
if ($this->auth->guest() && !setting('app-public')) {

View File

@ -34,7 +34,8 @@ class RedirectIfAuthenticated
*/
public function handle($request, Closure $next)
{
if ($this->auth->check()) {
$requireConfirmation = setting('registration-confirmation');
if ($this->auth->check() && (!$requireConfirmation || ($requireConfirmation && $this->auth->user()->email_confirmed))) {
return redirect('/');
}

View File

@ -8,13 +8,15 @@ use Illuminate\Notifications\Messages\MailMessage;
class ConfirmEmail extends Notification
{
public $token;
/**
* Create a new notification instance.
*
* @param string $token
*/
public function __construct()
public function __construct($token)
{
//
$this->token = $token;
}
/**
@ -40,7 +42,7 @@ class ConfirmEmail extends Notification
->subject('Confirm your email on ' . session('app-name'))
->greeting('Thanks for joining ' . setting('app-name') . '!')
->line('Please confirm your email address by clicking the button below:')
->action('Confirm Email', baseUrl('/register/confirm/' . $notifiable->token));
->action('Confirm Email', baseUrl('/register/confirm/' . $this->token));
}
}

View File

@ -0,0 +1,50 @@
<?php
namespace BookStack\Notifications;
use Illuminate\Notifications\Notification;
use Illuminate\Notifications\Messages\MailMessage;
class ResetPassword extends Notification
{
/**
* The password reset token.
*
* @var string
*/
public $token;
/**
* Create a notification instance.
*
* @param string $token
*/
public function __construct($token)
{
$this->token = $token;
}
/**
* Get the notification's channels.
*
* @param mixed $notifiable
* @return array|string
*/
public function via($notifiable)
{
return ['mail'];
}
/**
* Build the mail representation of the notification.
*
* @return \Illuminate\Notifications\Messages\MailMessage
*/
public function toMail()
{
return (new MailMessage)
->line('You are receiving this email because we received a password reset request for your account.')
->action('Reset Password', baseUrl('password/reset/' . $this->token))
->line('If you did not request a password reset, no further action is required.');
}
}

View File

@ -1,23 +1,27 @@
<?php namespace BookStack\Services;
use BookStack\Notifications\ConfirmEmail;
use BookStack\Repos\UserRepo;
use Carbon\Carbon;
use BookStack\EmailConfirmation;
use BookStack\Exceptions\ConfirmationEmailException;
use BookStack\Exceptions\UserRegistrationException;
use BookStack\User;
use Illuminate\Database\Connection as Database;
class EmailConfirmationService
{
protected $emailConfirmation;
protected $db;
protected $users;
/**
* EmailConfirmationService constructor.
* @param EmailConfirmation $emailConfirmation
* @param Database $db
* @param UserRepo $users
*/
public function __construct(EmailConfirmation $emailConfirmation)
public function __construct(Database $db, UserRepo $users)
{
$this->emailConfirmation = $emailConfirmation;
$this->db = $db;
$this->users = $users;
}
/**
@ -33,13 +37,26 @@ class EmailConfirmationService
}
$this->deleteConfirmationsByUser($user);
$token = $this->getToken();
$confirmation = $this->emailConfirmation->create([
'user_id' => $user->id,
'token' => $token,
]);
$token = $this->createEmailConfirmation($user);
$confirmation->notify(new ConfirmEmail());
$user->notify(new ConfirmEmail($token));
}
/**
* Creates a new email confirmation in the database and returns the token.
* @param User $user
* @return string
*/
public function createEmailConfirmation(User $user)
{
$token = $this->getToken();
$this->db->table('email_confirmations')->insert([
'user_id' => $user->id,
'token' => $token,
'created_at' => Carbon::now(),
'updated_at' => Carbon::now()
]);
return $token;
}
/**
@ -51,22 +68,24 @@ class EmailConfirmationService
*/
public function getEmailConfirmationFromToken($token)
{
$emailConfirmation = $this->emailConfirmation->where('token', '=', $token)->first();
// If not found
$emailConfirmation = $this->db->table('email_confirmations')->where('token', '=', $token)->first();
// If not found show error
if ($emailConfirmation === null) {
throw new UserRegistrationException('This confirmation token is not valid or has already been used, Please try registering again.', '/register');
}
// If more than a day old
if (Carbon::now()->subDay()->gt($emailConfirmation->created_at)) {
$this->sendConfirmation($emailConfirmation->user);
if (Carbon::now()->subDay()->gt(new Carbon($emailConfirmation->created_at))) {
$user = $this->users->getById($emailConfirmation->user_id);
$this->sendConfirmation($user);
throw new UserRegistrationException('The confirmation token has expired, A new confirmation email has been sent.', '/register/confirm');
}
$emailConfirmation->user = $this->users->getById($emailConfirmation->user_id);
return $emailConfirmation;
}
/**
* Delete all email confirmations that belong to a user.
* @param User $user
@ -74,7 +93,7 @@ class EmailConfirmationService
*/
public function deleteConfirmationsByUser(User $user)
{
return $this->emailConfirmation->where('user_id', '=', $user->id)->delete();
return $this->db->table('email_confirmations')->where('user_id', '=', $user->id)->delete();
}
/**
@ -84,7 +103,7 @@ class EmailConfirmationService
protected function getToken()
{
$token = str_random(24);
while ($this->emailConfirmation->where('token', '=', $token)->exists()) {
while ($this->db->table('email_confirmations')->where('token', '=', $token)->exists()) {
$token = str_random(25);
}
return $token;

View File

@ -1,5 +1,6 @@
<?php namespace BookStack;
use BookStack\Notifications\ResetPassword;
use Illuminate\Auth\Authenticatable;
use Illuminate\Auth\Passwords\CanResetPassword;
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
@ -184,4 +185,14 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
return '';
}
/**
* Send the password reset notification.
* @param string $token
* @return void
*/
public function sendPasswordResetNotification($token)
{
$this->notify(new ResetPassword($token));
}
}

View File

@ -153,7 +153,6 @@ Route::get('/register/confirm', 'Auth\RegisterController@getRegisterConfirmation
Route::get('/register/confirm/awaiting', 'Auth\RegisterController@showAwaitingConfirmation');
Route::post('/register/confirm/resend', 'Auth\RegisterController@resendConfirmation');
Route::get('/register/confirm/{token}', 'Auth\RegisterController@confirmEmail');
Route::get('/register/confirm/{token}/email', 'Auth\RegisterController@viewConfirmEmail');
Route::get('/register/service/{socialDriver}', 'Auth\RegisterController@socialRegister');
Route::post('/register', 'Auth\RegisterController@postRegister');
@ -162,5 +161,5 @@ Route::get('/password/email', 'Auth\ForgotPasswordController@showLinkRequestForm
Route::post('/password/email', 'Auth\ForgotPasswordController@sendResetLinkEmail');
// Password reset routes...
Route::get('/password/reset/{token}', 'Auth\ResetPasswordController@getReset');
Route::post('/password/reset', 'Auth\ResetPasswordController@postReset');
Route::get('/password/reset/{token}', 'Auth\ResetPasswordController@showResetForm');
Route::post('/password/reset', 'Auth\ResetPasswordController@reset');

View File

@ -1,6 +1,7 @@
<?php
use BookStack\EmailConfirmation;
use BookStack\Notifications\ConfirmEmail;
use Illuminate\Support\Facades\Notification;
class AuthTest extends TestCase
{
@ -57,15 +58,13 @@ class AuthTest extends TestCase
public function test_confirmed_registration()
{
// Fake notifications
Notification::fake();
// Set settings and get user instance
$this->setSettings(['registration-enabled' => 'true', 'registration-confirmation' => 'true']);
$user = factory(\BookStack\User::class)->make();
// Mock Mailer to ensure mail is being sent
$mockMailer = Mockery::mock('Illuminate\Contracts\Mail\Mailer');
$mockMailer->shouldReceive('send')->with('emails/email-confirmation', Mockery::type('array'), Mockery::type('callable'))->twice();
$this->app->instance('mailer', $mockMailer);
// Go through registration process
$this->visit('/register')
->see('Sign Up')
@ -76,6 +75,10 @@ class AuthTest extends TestCase
->seePageIs('/register/confirm')
->seeInDatabase('users', ['name' => $user->name, 'email' => $user->email, 'email_confirmed' => false]);
// Ensure notification sent
$dbUser = \BookStack\User::where('email', '=', $user->email)->first();
Notification::assertSentTo($dbUser, ConfirmEmail::class);
// Test access and resend confirmation email
$this->login($user->email, $user->password)
->seePageIs('/register/confirm/awaiting')
@ -84,19 +87,18 @@ class AuthTest extends TestCase
->seePageIs('/register/confirm/awaiting')
->press('Resend Confirmation Email');
// Get confirmation
$user = $user->where('email', '=', $user->email)->first();
$emailConfirmation = EmailConfirmation::where('user_id', '=', $user->id)->first();
// Check confirmation email button and confirmation activation.
$this->visit('/register/confirm/' . $emailConfirmation->token . '/email')
->see('Email Confirmation')
->click('Confirm Email')
// Get confirmation and confirm notification matches
$emailConfirmation = DB::table('email_confirmations')->where('user_id', '=', $dbUser->id)->first();
Notification::assertSentTo($dbUser, ConfirmEmail::class, function($notification, $channels) use ($emailConfirmation) {
return $notification->token === $emailConfirmation->token;
});
// Check confirmation email confirmation activation.
$this->visit('/register/confirm/' . $emailConfirmation->token)
->seePageIs('/')
->see($user->name)
->notSeeInDatabase('email_confirmations', ['token' => $emailConfirmation->token])
->seeInDatabase('users', ['name' => $user->name, 'email' => $user->email, 'email_confirmed' => true]);
->seeInDatabase('users', ['name' => $dbUser->name, 'email' => $dbUser->email, 'email_confirmed' => true]);
}
public function test_restricted_registration()