mirror of
https://github.com/BookStackApp/BookStack.git
synced 2024-08-26 20:22:18 +00:00
Updated password reset process not to indicate if email exists
- Intended to prevent enumeration to check if a user exists. - Updated messages on both the reqest-reset and set-password elements. - Also updated notification auto-hide to be dynamic based upon the amount of words within the notification. - Added tests to cover. For #2016
This commit is contained in:
parent
053cbbd5b6
commit
ba1be9d710
@ -5,7 +5,7 @@ namespace BookStack\Http\Controllers\Auth;
|
|||||||
use BookStack\Http\Controllers\Controller;
|
use BookStack\Http\Controllers\Controller;
|
||||||
use Illuminate\Foundation\Auth\SendsPasswordResetEmails;
|
use Illuminate\Foundation\Auth\SendsPasswordResetEmails;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Password;
|
use Illuminate\Support\Facades\Password;
|
||||||
|
|
||||||
class ForgotPasswordController extends Controller
|
class ForgotPasswordController extends Controller
|
||||||
{
|
{
|
||||||
@ -52,8 +52,8 @@ class ForgotPasswordController extends Controller
|
|||||||
$request->only('email')
|
$request->only('email')
|
||||||
);
|
);
|
||||||
|
|
||||||
if ($response === Password::RESET_LINK_SENT) {
|
if ($response === Password::RESET_LINK_SENT || $response === Password::INVALID_USER) {
|
||||||
$message = trans('auth.reset_password_sent_success', ['email' => $request->get('email')]);
|
$message = trans('auth.reset_password_sent', ['email' => $request->get('email')]);
|
||||||
$this->showSuccessNotification($message);
|
$this->showSuccessNotification($message);
|
||||||
return back()->with('status', trans($response));
|
return back()->with('status', trans($response));
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ namespace BookStack\Http\Controllers\Auth;
|
|||||||
use BookStack\Http\Controllers\Controller;
|
use BookStack\Http\Controllers\Controller;
|
||||||
use Illuminate\Foundation\Auth\ResetsPasswords;
|
use Illuminate\Foundation\Auth\ResetsPasswords;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Password;
|
||||||
|
|
||||||
class ResetPasswordController extends Controller
|
class ResetPasswordController extends Controller
|
||||||
{
|
{
|
||||||
@ -49,4 +50,24 @@ class ResetPasswordController extends Controller
|
|||||||
return redirect($this->redirectPath())
|
return redirect($this->redirectPath())
|
||||||
->with('status', trans($response));
|
->with('status', trans($response));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the response for a failed password reset.
|
||||||
|
*
|
||||||
|
* @param \Illuminate\Http\Request $request
|
||||||
|
* @param string $response
|
||||||
|
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\JsonResponse
|
||||||
|
*/
|
||||||
|
protected function sendResetFailedResponse(Request $request, $response)
|
||||||
|
{
|
||||||
|
// We show invalid users as invalid tokens as to not leak what
|
||||||
|
// users may exist in the system.
|
||||||
|
if ($response === Password::INVALID_USER) {
|
||||||
|
$response = Password::INVALID_TOKEN;
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirect()->back()
|
||||||
|
->withInput($request->only('email'))
|
||||||
|
->withErrors(['email' => trans($response)]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,7 +28,11 @@ class Notification {
|
|||||||
this.elem.classList.add('showing');
|
this.elem.classList.add('showing');
|
||||||
}, 1);
|
}, 1);
|
||||||
|
|
||||||
if (this.autohide) setTimeout(this.hide.bind(this), 2000);
|
if (this.autohide) {
|
||||||
|
const words = textToShow.split(' ').length;
|
||||||
|
const timeToShow = Math.max(2000, 1000 + (250 * words));
|
||||||
|
setTimeout(this.hide.bind(this), timeToShow);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
hide() {
|
hide() {
|
||||||
|
@ -43,7 +43,7 @@ return [
|
|||||||
'reset_password' => 'Reset Password',
|
'reset_password' => 'Reset Password',
|
||||||
'reset_password_send_instructions' => 'Enter your email below and you will be sent an email with a password reset link.',
|
'reset_password_send_instructions' => 'Enter your email below and you will be sent an email with a password reset link.',
|
||||||
'reset_password_send_button' => 'Send Reset Link',
|
'reset_password_send_button' => 'Send Reset Link',
|
||||||
'reset_password_sent_success' => 'A password reset link has been sent to :email.',
|
'reset_password_sent' => 'A password reset link will be sent to :email if that email address is found in the system.',
|
||||||
'reset_password_success' => 'Your password has been successfully reset.',
|
'reset_password_success' => 'Your password has been successfully reset.',
|
||||||
'email_reset_subject' => 'Reset your :appName password',
|
'email_reset_subject' => 'Reset your :appName password',
|
||||||
'email_reset_text' => 'You are receiving this email because we received a password reset request for your account.',
|
'email_reset_text' => 'You are receiving this email because we received a password reset request for your account.',
|
||||||
|
@ -8,7 +8,7 @@ return [
|
|||||||
|
|
||||||
'password' => 'Passwords must be at least eight characters and match the confirmation.',
|
'password' => 'Passwords must be at least eight characters and match the confirmation.',
|
||||||
'user' => "We can't find a user with that e-mail address.",
|
'user' => "We can't find a user with that e-mail address.",
|
||||||
'token' => 'This password reset token is invalid.',
|
'token' => 'The password reset token is invalid for this email address.',
|
||||||
'sent' => 'We have e-mailed your password reset link!',
|
'sent' => 'We have e-mailed your password reset link!',
|
||||||
'reset' => 'Your password has been reset!',
|
'reset' => 'Your password has been reset!',
|
||||||
|
|
||||||
|
@ -1,9 +1,13 @@
|
|||||||
<?php namespace Tests\Auth;
|
<?php namespace Tests\Auth;
|
||||||
|
|
||||||
|
use BookStack\Auth\Role;
|
||||||
use BookStack\Auth\User;
|
use BookStack\Auth\User;
|
||||||
use BookStack\Entities\Page;
|
use BookStack\Entities\Page;
|
||||||
use BookStack\Notifications\ConfirmEmail;
|
use BookStack\Notifications\ConfirmEmail;
|
||||||
|
use BookStack\Notifications\ResetPassword;
|
||||||
use BookStack\Settings\SettingService;
|
use BookStack\Settings\SettingService;
|
||||||
|
use DB;
|
||||||
|
use Hash;
|
||||||
use Illuminate\Support\Facades\Notification;
|
use Illuminate\Support\Facades\Notification;
|
||||||
use Tests\BrowserKitTest;
|
use Tests\BrowserKitTest;
|
||||||
|
|
||||||
@ -129,7 +133,7 @@ class AuthTest extends BrowserKitTest
|
|||||||
->press('Resend Confirmation Email');
|
->press('Resend Confirmation Email');
|
||||||
|
|
||||||
// Get confirmation and confirm notification matches
|
// Get confirmation and confirm notification matches
|
||||||
$emailConfirmation = \DB::table('email_confirmations')->where('user_id', '=', $dbUser->id)->first();
|
$emailConfirmation = DB::table('email_confirmations')->where('user_id', '=', $dbUser->id)->first();
|
||||||
Notification::assertSentTo($dbUser, ConfirmEmail::class, function($notification, $channels) use ($emailConfirmation) {
|
Notification::assertSentTo($dbUser, ConfirmEmail::class, function($notification, $channels) use ($emailConfirmation) {
|
||||||
return $notification->token === $emailConfirmation->token;
|
return $notification->token === $emailConfirmation->token;
|
||||||
});
|
});
|
||||||
@ -257,7 +261,7 @@ class AuthTest extends BrowserKitTest
|
|||||||
->seePageIs('/settings/users');
|
->seePageIs('/settings/users');
|
||||||
|
|
||||||
$userPassword = User::find($user->id)->password;
|
$userPassword = User::find($user->id)->password;
|
||||||
$this->assertTrue(\Hash::check('newpassword', $userPassword));
|
$this->assertTrue(Hash::check('newpassword', $userPassword));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_user_deletion()
|
public function test_user_deletion()
|
||||||
@ -276,7 +280,7 @@ class AuthTest extends BrowserKitTest
|
|||||||
|
|
||||||
public function test_user_cannot_be_deleted_if_last_admin()
|
public function test_user_cannot_be_deleted_if_last_admin()
|
||||||
{
|
{
|
||||||
$adminRole = \BookStack\Auth\Role::getRole('admin');
|
$adminRole = Role::getRole('admin');
|
||||||
|
|
||||||
// Delete all but one admin user if there are more than one
|
// Delete all but one admin user if there are more than one
|
||||||
$adminUsers = $adminRole->users;
|
$adminUsers = $adminRole->users;
|
||||||
@ -309,14 +313,13 @@ class AuthTest extends BrowserKitTest
|
|||||||
|
|
||||||
public function test_reset_password_flow()
|
public function test_reset_password_flow()
|
||||||
{
|
{
|
||||||
|
|
||||||
Notification::fake();
|
Notification::fake();
|
||||||
|
|
||||||
$this->visit('/login')->click('Forgot Password?')
|
$this->visit('/login')->click('Forgot Password?')
|
||||||
->seePageIs('/password/email')
|
->seePageIs('/password/email')
|
||||||
->type('admin@admin.com', 'email')
|
->type('admin@admin.com', 'email')
|
||||||
->press('Send Reset Link')
|
->press('Send Reset Link')
|
||||||
->see('A password reset link has been sent to admin@admin.com');
|
->see('A password reset link will be sent to admin@admin.com if that email address is found in the system.');
|
||||||
|
|
||||||
$this->seeInDatabase('password_resets', [
|
$this->seeInDatabase('password_resets', [
|
||||||
'email' => 'admin@admin.com'
|
'email' => 'admin@admin.com'
|
||||||
@ -324,8 +327,8 @@ class AuthTest extends BrowserKitTest
|
|||||||
|
|
||||||
$user = User::where('email', '=', 'admin@admin.com')->first();
|
$user = User::where('email', '=', 'admin@admin.com')->first();
|
||||||
|
|
||||||
Notification::assertSentTo($user, \BookStack\Notifications\ResetPassword::class);
|
Notification::assertSentTo($user, ResetPassword::class);
|
||||||
$n = Notification::sent($user, \BookStack\Notifications\ResetPassword::class);
|
$n = Notification::sent($user, ResetPassword::class);
|
||||||
|
|
||||||
$this->visit('/password/reset/' . $n->first()->token)
|
$this->visit('/password/reset/' . $n->first()->token)
|
||||||
->see('Reset Password')
|
->see('Reset Password')
|
||||||
@ -337,6 +340,28 @@ class AuthTest extends BrowserKitTest
|
|||||||
->see('Your password has been successfully reset');
|
->see('Your password has been successfully reset');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function test_reset_password_flow_shows_success_message_even_if_wrong_password_to_prevent_user_discovery()
|
||||||
|
{
|
||||||
|
$this->visit('/login')->click('Forgot Password?')
|
||||||
|
->seePageIs('/password/email')
|
||||||
|
->type('barry@admin.com', 'email')
|
||||||
|
->press('Send Reset Link')
|
||||||
|
->see('A password reset link will be sent to barry@admin.com if that email address is found in the system.')
|
||||||
|
->dontSee('We can\'t find a user');
|
||||||
|
|
||||||
|
|
||||||
|
$this->visit('/password/reset/arandometokenvalue')
|
||||||
|
->see('Reset Password')
|
||||||
|
->submitForm('Reset Password', [
|
||||||
|
'email' => 'barry@admin.com',
|
||||||
|
'password' => 'randompass',
|
||||||
|
'password_confirmation' => 'randompass'
|
||||||
|
])->followRedirects()
|
||||||
|
->seePageIs('/password/reset/arandometokenvalue')
|
||||||
|
->dontSee('We can\'t find a user')
|
||||||
|
->see('The password reset token is invalid for this email address.');
|
||||||
|
}
|
||||||
|
|
||||||
public function test_reset_password_page_shows_sign_links()
|
public function test_reset_password_page_shows_sign_links()
|
||||||
{
|
{
|
||||||
$this->setSettings(['registration-enabled' => 'true']);
|
$this->setSettings(['registration-enabled' => 'true']);
|
||||||
|
Loading…
Reference in New Issue
Block a user