Improved password reset flow with notifications.
Also added links to sign-in/register. Fixed links in emails sent out. Fixes #210 and #218.
Showing
8 changed files
with
98 additions
and
3 deletions
| ... | @@ -4,6 +4,8 @@ namespace BookStack\Http\Controllers\Auth; | ... | @@ -4,6 +4,8 @@ namespace BookStack\Http\Controllers\Auth; |
| 4 | 4 | ||
| 5 | use BookStack\Http\Controllers\Controller; | 5 | use BookStack\Http\Controllers\Controller; |
| 6 | use Illuminate\Foundation\Auth\ResetsPasswords; | 6 | use Illuminate\Foundation\Auth\ResetsPasswords; |
| 7 | +use Illuminate\Http\Request; | ||
| 8 | +use Password; | ||
| 7 | 9 | ||
| 8 | class PasswordController extends Controller | 10 | class PasswordController extends Controller |
| 9 | { | 11 | { |
| ... | @@ -29,4 +31,46 @@ class PasswordController extends Controller | ... | @@ -29,4 +31,46 @@ class PasswordController extends Controller |
| 29 | { | 31 | { |
| 30 | $this->middleware('guest'); | 32 | $this->middleware('guest'); |
| 31 | } | 33 | } |
| 34 | + | ||
| 35 | + | ||
| 36 | + /** | ||
| 37 | + * Send a reset link to the given user. | ||
| 38 | + * | ||
| 39 | + * @param \Illuminate\Http\Request $request | ||
| 40 | + * @return \Illuminate\Http\Response | ||
| 41 | + */ | ||
| 42 | + public function sendResetLinkEmail(Request $request) | ||
| 43 | + { | ||
| 44 | + $this->validate($request, ['email' => 'required|email']); | ||
| 45 | + | ||
| 46 | + $broker = $this->getBroker(); | ||
| 47 | + | ||
| 48 | + $response = Password::broker($broker)->sendResetLink( | ||
| 49 | + $request->only('email'), $this->resetEmailBuilder() | ||
| 50 | + ); | ||
| 51 | + | ||
| 52 | + switch ($response) { | ||
| 53 | + case Password::RESET_LINK_SENT: | ||
| 54 | + $message = 'A password reset link has been sent to ' . $request->get('email') . '.'; | ||
| 55 | + session()->flash('success', $message); | ||
| 56 | + return $this->getSendResetLinkEmailSuccessResponse($response); | ||
| 57 | + | ||
| 58 | + case Password::INVALID_USER: | ||
| 59 | + default: | ||
| 60 | + return $this->getSendResetLinkEmailFailureResponse($response); | ||
| 61 | + } | ||
| 62 | + } | ||
| 63 | + | ||
| 64 | + /** | ||
| 65 | + * Get the response for after a successful password reset. | ||
| 66 | + * | ||
| 67 | + * @param string $response | ||
| 68 | + * @return \Symfony\Component\HttpFoundation\Response | ||
| 69 | + */ | ||
| 70 | + protected function getResetSuccessResponse($response) | ||
| 71 | + { | ||
| 72 | + $message = 'Your password has been successfully reset.'; | ||
| 73 | + session()->flash('success', $message); | ||
| 74 | + return redirect($this->redirectPath())->with('status', trans($response)); | ||
| 75 | + } | ||
| 32 | } | 76 | } | ... | ... |
| ... | @@ -84,6 +84,11 @@ function baseUrl($path, $forceAppDomain = false) | ... | @@ -84,6 +84,11 @@ function baseUrl($path, $forceAppDomain = false) |
| 84 | $path = implode('/', array_splice($explodedPath, 3)); | 84 | $path = implode('/', array_splice($explodedPath, 3)); |
| 85 | } | 85 | } |
| 86 | 86 | ||
| 87 | + // Return normal url path if not specified in config | ||
| 88 | + if (config('app.url') === '') { | ||
| 89 | + return url($path); | ||
| 90 | + } | ||
| 91 | + | ||
| 87 | return rtrim(config('app.url'), '/') . '/' . $path; | 92 | return rtrim(config('app.url'), '/') . '/' . $path; |
| 88 | } | 93 | } |
| 89 | 94 | ... | ... |
| ... | @@ -8,6 +8,7 @@ return [ | ... | @@ -8,6 +8,7 @@ return [ |
| 8 | 'app-name' => 'BookStack', | 8 | 'app-name' => 'BookStack', |
| 9 | 'app-editor' => 'wysiwyg', | 9 | 'app-editor' => 'wysiwyg', |
| 10 | 'app-color' => '#0288D1', | 10 | 'app-color' => '#0288D1', |
| 11 | - 'app-color-light' => 'rgba(21, 101, 192, 0.15)' | 11 | + 'app-color-light' => 'rgba(21, 101, 192, 0.15)', |
| 12 | + 'registration-enabled' => false, | ||
| 12 | 13 | ||
| 13 | ]; | 14 | ]; |
| ... | \ No newline at end of file | ... | \ No newline at end of file | ... | ... |
| 1 | @extends('public') | 1 | @extends('public') |
| 2 | 2 | ||
| 3 | +@section('header-buttons') | ||
| 4 | + <a href="{{ baseUrl("/login") }}"><i class="zmdi zmdi-sign-in"></i>Sign in</a> | ||
| 5 | + @if(setting('registration-enabled')) | ||
| 6 | + <a href="{{ baseUrl("/register") }}"><i class="zmdi zmdi-account-add"></i>Sign up</a> | ||
| 7 | + @endif | ||
| 8 | +@stop | ||
| 9 | + | ||
| 3 | @section('content') | 10 | @section('content') |
| 4 | 11 | ||
| 5 | 12 | ... | ... |
| 1 | @extends('public') | 1 | @extends('public') |
| 2 | 2 | ||
| 3 | +@section('header-buttons') | ||
| 4 | + <a href="{{ baseUrl("/login") }}"><i class="zmdi zmdi-sign-in"></i>Sign in</a> | ||
| 5 | + @if(setting('registration-enabled')) | ||
| 6 | + <a href="{{ baseUrl("/register") }}"><i class="zmdi zmdi-account-add"></i>Sign up</a> | ||
| 7 | + @endif | ||
| 8 | +@stop | ||
| 9 | + | ||
| 3 | @section('body-class', 'image-cover login') | 10 | @section('body-class', 'image-cover login') |
| 4 | 11 | ||
| 5 | @section('content') | 12 | @section('content') | ... | ... |
| ... | @@ -162,14 +162,14 @@ | ... | @@ -162,14 +162,14 @@ |
| 162 | <h1 style="padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-family:'Helvetica Neue', Helvetica, Arial, 'Lucida Grande', sans-serif;color:#444;margin-top:10px;margin-bottom:10px;margin-right:0;margin-left:0;line-height:1.2;font-weight:200;font-size:36px;"> | 162 | <h1 style="padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-family:'Helvetica Neue', Helvetica, Arial, 'Lucida Grande', sans-serif;color:#444;margin-top:10px;margin-bottom:10px;margin-right:0;margin-left:0;line-height:1.2;font-weight:200;font-size:36px;"> |
| 163 | Email Confirmation</h1> | 163 | Email Confirmation</h1> |
| 164 | <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;"> | 164 | <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;"> |
| 165 | - Thanks for joining <a href="{{ baseUrl('/') }}">{{ setting('app-name')}}</a>. <br/> | 165 | + Thanks for joining <a href="{{ baseUrl('/', true) }}">{{ setting('app-name')}}</a>. <br/> |
| 166 | Please confirm your email address by clicking the button below.</p> | 166 | Please confirm your email address by clicking the button below.</p> |
| 167 | <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%;"> | 167 | <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%;"> |
| 168 | <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;"> | 168 | <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;"> |
| 169 | <td class="padding" | 169 | <td class="padding" |
| 170 | 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;"> | 170 | 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;"> |
| 171 | <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;"> | 171 | <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;"> |
| 172 | - <a class="btn-primary" href="{{ baseUrl('/register/confirm/' . $token) }}" | 172 | + <a class="btn-primary" href="{{ baseUrl('/register/confirm/' . $token, true) }}" |
| 173 | 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 | 173 | 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 |
| 174 | Email</a></p> | 174 | Email</a></p> |
| 175 | </td> | 175 | </td> | ... | ... |
This diff is collapsed.
Click to expand it.
| ... | @@ -216,6 +216,37 @@ class AuthTest extends TestCase | ... | @@ -216,6 +216,37 @@ class AuthTest extends TestCase |
| 216 | ->seePageIs('/login'); | 216 | ->seePageIs('/login'); |
| 217 | } | 217 | } |
| 218 | 218 | ||
| 219 | + public function test_reset_password_flow() | ||
| 220 | + { | ||
| 221 | + $this->visit('/login')->click('Forgot Password?') | ||
| 222 | + ->seePageIs('/password/email') | ||
| 223 | + ->type('admin@admin.com', 'email') | ||
| 224 | + ->press('Send Reset Link') | ||
| 225 | + ->see('A password reset link has been sent to admin@admin.com'); | ||
| 226 | + | ||
| 227 | + $this->seeInDatabase('password_resets', [ | ||
| 228 | + 'email' => 'admin@admin.com' | ||
| 229 | + ]); | ||
| 230 | + | ||
| 231 | + $reset = DB::table('password_resets')->where('email', '=', 'admin@admin.com')->first(); | ||
| 232 | + $this->visit('/password/reset/' . $reset->token) | ||
| 233 | + ->see('Reset Password') | ||
| 234 | + ->submitForm('Reset Password', [ | ||
| 235 | + 'email' => 'admin@admin.com', | ||
| 236 | + 'password' => 'randompass', | ||
| 237 | + 'password_confirmation' => 'randompass' | ||
| 238 | + ])->seePageIs('/') | ||
| 239 | + ->see('Your password has been successfully reset'); | ||
| 240 | + } | ||
| 241 | + | ||
| 242 | + public function test_reset_password_page_shows_sign_links() | ||
| 243 | + { | ||
| 244 | + $this->setSettings(['registration-enabled' => 'true']); | ||
| 245 | + $this->visit('/password/email') | ||
| 246 | + ->seeLink('Sign in') | ||
| 247 | + ->seeLink('Sign up'); | ||
| 248 | + } | ||
| 249 | + | ||
| 219 | /** | 250 | /** |
| 220 | * Perform a login | 251 | * Perform a login |
| 221 | * @param string $email | 252 | * @param string $email | ... | ... |
-
Please register or sign in to post a comment