Got standard form-based registration working

This commit is contained in:
Dan Brown 2015-09-05 20:25:57 +01:00
parent 2c3fb557d6
commit dec0cbb1b2
17 changed files with 370 additions and 10 deletions

15
app/EmailConfirmation.php Normal file
View File

@ -0,0 +1,15 @@
<?php
namespace Oxbow;
use Illuminate\Database\Eloquent\Model;
class EmailConfirmation extends Model
{
protected $fillable = ['user_id', 'token'];
public function user()
{
return $this->belongsTo('Oxbow\User');
}
}

View File

@ -0,0 +1,7 @@
<?php namespace Oxbow\Exceptions;
class ConfirmationEmailException extends NotifyException
{
}

View File

@ -0,0 +1,7 @@
<?php namespace Oxbow\Exceptions;
class UserRegistrationException extends NotifyException
{
}

View File

@ -2,7 +2,12 @@
namespace Oxbow\Http\Controllers\Auth;
use Illuminate\Http\Request;
use Oxbow\Exceptions\SocialSignInException;
use Oxbow\Exceptions\UserRegistrationException;
use Oxbow\Repos\UserRepo;
use Oxbow\Services\EmailConfirmationService;
use Oxbow\Services\Facades\Setting;
use Oxbow\Services\SocialAuthService;
use Oxbow\User;
use Validator;
@ -30,15 +35,22 @@ class AuthController extends Controller
protected $redirectAfterLogout = '/login';
protected $socialAuthService;
protected $emailConfirmationService;
protected $userRepo;
/**
* Create a new authentication controller instance.
* @param SocialAuthService $socialAuthService
* @param SocialAuthService $socialAuthService
* @param EmailConfirmationService $emailConfirmationService
* @param UserRepo $userRepo
*/
public function __construct(SocialAuthService $socialAuthService)
public function __construct(SocialAuthService $socialAuthService, EmailConfirmationService $emailConfirmationService, UserRepo $userRepo)
{
$this->middleware('guest', ['only' => ['getLogin', 'postLogin', 'getRegister']]);
$this->socialAuthService = $socialAuthService;
$this->emailConfirmationService = $emailConfirmationService;
$this->userRepo = $userRepo;
parent::__construct();
}
/**
@ -52,7 +64,7 @@ class AuthController extends Controller
return Validator::make($data, [
'name' => 'required|max:255',
'email' => 'required|email|max:255|unique:users',
'password' => 'required|confirmed|min:6',
'password' => 'required|min:6',
]);
}
@ -71,6 +83,13 @@ class AuthController extends Controller
]);
}
protected function checkRegistrationAllowed()
{
if(!\Setting::get('registration-enabled')) {
throw new UserRegistrationException('Registrations are currently disabled.', '/login');
}
}
/**
* Show the application registration form.
*
@ -78,10 +97,104 @@ class AuthController extends Controller
*/
public function getRegister()
{
$this->checkRegistrationAllowed();
$socialDrivers = $this->socialAuthService->getActiveDrivers();
return view('auth.register', ['socialDrivers' => $socialDrivers]);
}
/**
* Handle a registration request for the application.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
* @throws UserRegistrationException
*/
public function postRegister(Request $request)
{
$this->checkRegistrationAllowed();
$validator = $this->validator($request->all());
if ($validator->fails()) {
$this->throwValidationException(
$request, $validator
);
}
if(\Setting::get('registration-restrict')) {
$restrictedEmailDomains = explode(',', str_replace(' ', '', \Setting::get('registration-restrict')));
$userEmailDomain = $domain = substr(strrchr($request->get('email'), "@"), 1);
if(!in_array($userEmailDomain, $restrictedEmailDomains)) {
throw new UserRegistrationException('That email domain does not have access to this application', '/register');
}
}
$newUser = $this->create($request->all());
$newUser->attachRoleId(\Setting::get('registration-role'), 1);
if(\Setting::get('registration-confirmation') || \Setting::get('registration-restrict')) {
$newUser->email_confirmed = false;
$newUser->save();
$this->emailConfirmationService->sendConfirmation($newUser);
return redirect('/register/confirm');
}
auth()->login($newUser);
return redirect($this->redirectPath());
}
/**
* Show the page to tell the user to check thier email
* and confirm their address.
*/
public function getRegisterConfirmation()
{
return view('auth/register-confirm');
}
/**
* Confirms an email via a token and logs the user into the system.
* @param $token
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
* @throws UserRegistrationException
*/
public function confirmEmail($token)
{
$confirmation = $this->emailConfirmationService->getEmailConfirmationFromToken($token);
$user = $confirmation->user;
$user->email_confirmed = true;
$user->save();
auth()->login($confirmation->user);
session()->flash('success', 'Your email has been confirmed!');
$this->emailConfirmationService->deleteConfirmationsByUser($user);
return redirect($this->redirectPath);
}
/**
* Shows a notice that a user's email address has not been confirmed,
* Also has the option to re-send the confirmation email.
* @return \Illuminate\View\View
*/
public function showAwaitingConfirmation()
{
return view('auth/user-unconfirmed');
}
/**
* Resend the confirmation email
* @param Request $request
* @return \Illuminate\View\View
*/
public function resendConfirmation(Request $request)
{
$this->validate($request, [
'email' => 'required|email|exists:users,email'
]);
$user = $this->userRepo->getByEmail($request->get('email'));
$this->emailConfirmationService->sendConfirmation($user);
\Session::flash('success', 'Confirmation email resent, Please check your inbox.');
return redirect('/register/confirm');
}
/**
* Show the application login form.
*

View File

@ -38,6 +38,7 @@ class SettingController extends Controller
$key = str_replace('setting-', '', trim($name));
Setting::put($key, $value);
}
session()->flash('success', 'Settings Saved');
return redirect('/settings');
}

View File

@ -118,7 +118,6 @@ class UserController extends Controller
}
if ($request->has('password') && $request->get('password') != '') {
//dd('cat');
$password = $request->get('password');
$user->password = bcrypt($password);
}

View File

@ -4,6 +4,7 @@ namespace Oxbow\Http\Middleware;
use Closure;
use Illuminate\Contracts\Auth\Guard;
use Oxbow\Exceptions\UserRegistrationException;
use Setting;
class Authenticate
@ -34,6 +35,9 @@ class Authenticate
*/
public function handle($request, Closure $next)
{
if(auth()->check() && auth()->user()->email_confirmed == false) {
return redirect()->guest('/register/confirm/awaiting');
}
if ($this->auth->guest() && !Setting::get('app-public')) {
if ($request->ajax()) {
return response('Unauthorized.', 401);

View File

@ -88,6 +88,11 @@ Route::get('/login', 'Auth\AuthController@getLogin');
Route::post('/login', 'Auth\AuthController@postLogin');
Route::get('/logout', 'Auth\AuthController@getLogout');
Route::get('/register', 'Auth\AuthController@getRegister');
Route::get('/register/confirm', 'Auth\AuthController@getRegisterConfirmation');
Route::get('/register/confirm/awaiting', 'Auth\AuthController@showAwaitingConfirmation');
Route::post('/register/confirm/resend', 'Auth\AuthController@resendConfirmation');
Route::get('/register/confirm/{token}', 'Auth\AuthController@confirmEmail');
Route::post('/register', 'Auth\AuthController@postRegister');
// Password reset link request routes...
Route::get('/password/email', 'Auth\PasswordController@getEmail');

View File

@ -21,4 +21,9 @@ class UserRepo
public function getByEmail($email) {
return $this->user->where('email', '=', $email)->first();
}
public function getById($id)
{
return $this->user->findOrFail($id);
}
}

View File

@ -0,0 +1,102 @@
<?php namespace Oxbow\Services;
use Carbon\Carbon;
use Illuminate\Contracts\Mail\Mailer;
use Illuminate\Mail\Message;
use Oxbow\EmailConfirmation;
use Oxbow\Exceptions\ConfirmationEmailException;
use Oxbow\Exceptions\UserRegistrationException;
use Oxbow\Repos\UserRepo;
use Oxbow\Setting;
use Oxbow\User;
class EmailConfirmationService
{
protected $mailer;
protected $emailConfirmation;
/**
* EmailConfirmationService constructor.
* @param Mailer $mailer
* @param EmailConfirmation $emailConfirmation
*/
public function __construct(Mailer $mailer, EmailConfirmation $emailConfirmation)
{
$this->mailer = $mailer;
$this->emailConfirmation = $emailConfirmation;
}
/**
* Create new confirmation for a user,
* Also removes any existing old ones.
* @param User $user
* @throws ConfirmationEmailException
*/
public function sendConfirmation(User $user)
{
if($user->email_confirmed) {
throw new ConfirmationEmailException('Email has already been confirmed, Try logging in.', '/login');
}
$this->deleteConfirmationsByUser($user);
$token = $this->getToken();
$this->emailConfirmation->create([
'user_id' => $user->id,
'token' => $token,
]);
$this->mailer->send('emails/email-confirmation', ['token' => $token], function (Message $message) use ($user) {
$appName = \Setting::get('app-name', 'BookStack');
$message->to($user->email, $user->name)->subject('Confirm your email on ' . $appName . '.');
});
}
/**
* Gets an email confirmation by looking up the token,
* Ensures the token has not expired.
* @param string $token
* @return EmailConfirmation
* @throws UserRegistrationException
*/
public function getEmailConfirmationFromToken($token)
{
$emailConfirmation = $this->emailConfirmation->where('token', '=', $token)->first();
// If not found
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);
throw new UserRegistrationException('The confirmation token has expired, A new confirmation email has been sent.', '/register/confirm');
}
return $emailConfirmation;
}
/**
* Delete all email confirmations that belong to a user.
* @param User $user
* @return mixed
*/
public function deleteConfirmationsByUser(User $user)
{
return $this->emailConfirmation->where('user_id', '=', $user->id)->delete();
}
/**
* Creates a unique token within the email confirmation database.
* @return string
*/
protected function getToken()
{
$token = str_random(24);
while ($this->emailConfirmation->where('token', '=', $token)->exists()) {
$token = str_random(25);
}
return $token;
}
}

View File

@ -14,8 +14,8 @@ class CreateSocialAccountsTable extends Migration
{
Schema::create('social_accounts', function (Blueprint $table) {
$table->increments('id');
$table->integer('user_id')->indexed();
$table->string('driver')->indexed();
$table->integer('user_id')->index();
$table->string('driver')->index();
$table->string('driver_id');
$table->string('avatar');
$table->timestamps();

View File

@ -0,0 +1,39 @@
<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class AddEmailConfirmationTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('users', function (Blueprint $table) {
$table->boolean('email_confirmed')->default(true);
});
Schema::create('email_confirmations', function (Blueprint $table) {
$table->increments('id');
$table->integer('user_id')->index();
$table->string('token')->index();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('email_confirmed');
});
Schema::drop('email_confirmations');
}
}

View File

@ -0,0 +1,19 @@
@extends('public')
@section('header-buttons')
@if(!$signedIn)
<a href="/login"><i class="zmdi zmdi-sign-in"></i>Sign in</a>
@endif
@stop
@section('content')
<div class="text-center">
<div class="center-box">
<h2>Thanks for registering!</h2>
<p>Please check your email and click the confirmation button to access {{ \Setting::get('app-name') }}.</p>
</div>
</div>
@stop

View File

@ -8,9 +8,9 @@
<div class="text-center">
<div class="center-box">
<h1>Register</h1>
<h1>Sign Up</h1>
<form action="/login" method="POST">
<form action="/register" method="POST">
{!! csrf_field() !!}
<div class="form-group">
@ -29,13 +29,14 @@
</div>
<div class="from-group">
<button class="button block pos">Sign In</button>
<button class="button block pos">Create Account</button>
</div>
</form>
@if(count($socialDrivers) > 0)
<hr class="margin-top">
<h3 class="text-muted">Social Registration</h3>
<p class="text-small">Register and sign in using another service.</p>
@if(isset($socialDrivers['google']))
<a href="/register/service/google" style="color: #DC4E41;"><i class="zmdi zmdi-google-plus-box zmdi-hc-4x"></i></a>
@endif

View File

@ -0,0 +1,30 @@
@extends('public')
@section('content')
<div class="row">
<div class="col-md-6 col-md-offset-3">
<h2>Email Address not confirmed</h2>
<p class="text-muted">Your email address has not yet been confirmed. <br>
Please click the link in the email that was sent shortly after you registered. <br>
If you cannot find the email you can re-send the confirmation email by submitting the form below.
</p>
<hr>
<form action="/register/confirm/resend" method="POST">
{!! csrf_field() !!}
<div class="form-group">
<label for="email">Email Address</label>
@if(auth()->check())
@include('form/text', ['name' => 'email', 'model' => auth()->user()])
@else
@include('form/text', ['name' => 'email'])
@endif
</div>
<div class="form-group">
<button type="submit" class="button pos">Resend Confirmation Email</button>
</div>
</form>
</div>
</div>
@stop

View File

@ -157,7 +157,7 @@
<table style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-family:'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif;font-size:100%;line-height:1.6;width:100%;">
<tr style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-family:'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif;font-size:100%;line-height:1.6;">
<td class="padding" style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;font-family:'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif;font-size:100%;line-height:1.6;padding-top:10px;padding-bottom:10px;padding-right:0;padding-left:0;">
<p style="margin-top:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-family:'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif;line-height:1.6;margin-bottom:10px;font-weight:normal;font-size:14px;color:#888888;"><a class="btn-primary" href="{{ url('user/confirm/'.$token) }}" style="margin-top:0;margin-bottom:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-family:'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif;font-size:100%;text-decoration:none;color:#FFF;background-color:#348eda;border-style:solid;border-color:#348eda;border-width:10px 20px;line-height:2;font-weight:bold;margin-right:10px;text-align:center;cursor:pointer;display:inline-block;border-radius:4px;">Confirm Email</a></p>
<p style="margin-top:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-family:'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif;line-height:1.6;margin-bottom:10px;font-weight:normal;font-size:14px;color:#888888;"><a class="btn-primary" href="{{ url('/register/confirm/'.$token) }}" style="margin-top:0;margin-bottom:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-family:'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif;font-size:100%;text-decoration:none;color:#FFF;background-color:#348eda;border-style:solid;border-color:#348eda;border-width:10px 20px;line-height:2;font-weight:bold;margin-right:10px;text-align:center;cursor:pointer;display:inline-block;border-radius:4px;">Confirm Email</a></p>
</td>
</tr>
</table>

View File

@ -37,6 +37,19 @@
<div class="links text-center">
@yield('header-buttons')
</div>
@if($signedIn)
<img class="avatar" src="{{$currentUser->getAvatar(30)}}" alt="{{ $currentUser->name }}">
<div class="dropdown-container" data-dropdown>
<span class="user-name" data-dropdown-toggle>
{{ $currentUser->name }} <i class="zmdi zmdi-caret-down"></i>
</span>
<ul>
<li>
<a href="/logout" class="text-neg"><i class="zmdi zmdi-run zmdi-hc-lg"></i>Logout</a>
</li>
</ul>
</div>
@endif
</div>
</div>
</div>