--- /dev/null
+<?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');
+ }
+}
--- /dev/null
+<?php namespace Oxbow\Exceptions;
+
+
+class ConfirmationEmailException extends NotifyException
+{
+
+}
\ No newline at end of file
--- /dev/null
+<?php namespace Oxbow\Exceptions;
+
+
+class UserRegistrationException extends NotifyException
+{
+
+}
\ No newline at end of file
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;
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();
}
/**
return Validator::make($data, [
'name' => 'required|max:255',
'email' => 'required|email|max:255|unique:users',
- 'password' => 'required|confirmed|min:6',
+ 'password' => 'required|min:6',
]);
}
]);
}
+ protected function checkRegistrationAllowed()
+ {
+ if(!\Setting::get('registration-enabled')) {
+ throw new UserRegistrationException('Registrations are currently disabled.', '/login');
+ }
+ }
+
/**
* Show the application registration form.
*
*/
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.
*
$key = str_replace('setting-', '', trim($name));
Setting::put($key, $value);
}
+ session()->flash('success', 'Settings Saved');
return redirect('/settings');
}
}
if ($request->has('password') && $request->get('password') != '') {
- //dd('cat');
$password = $request->get('password');
$user->password = bcrypt($password);
}
use Closure;
use Illuminate\Contracts\Auth\Guard;
+use Oxbow\Exceptions\UserRegistrationException;
use Setting;
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);
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');
public function getByEmail($email) {
return $this->user->where('email', '=', $email)->first();
}
+
+ public function getById($id)
+ {
+ return $this->user->findOrFail($id);
+ }
}
\ No newline at end of file
--- /dev/null
+<?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;
+ }
+
+
+}
\ No newline at end of file
{
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();
--- /dev/null
+<?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');
+ }
+}
--- /dev/null
+@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
<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">
</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
--- /dev/null
+@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
<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>
<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>