Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions app/Auth/Access/LoginService.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use BookStack\Actions\ActivityType;
use BookStack\Auth\Access\Mfa\MfaSession;
use BookStack\Auth\User;
use BookStack\Exceptions\LoginAttemptException;
use BookStack\Exceptions\StoppedAuthenticationException;
use BookStack\Facades\Activity;
use BookStack\Facades\Theme;
Expand Down Expand Up @@ -149,6 +150,7 @@ public function awaitingEmailConfirmation(User $user): bool
* May interrupt the flow if extra authentication requirements are imposed.
*
* @throws StoppedAuthenticationException
* @throws LoginAttemptException
*/
public function attempt(array $credentials, string $method, bool $remember = false): bool
{
Expand Down
5 changes: 3 additions & 2 deletions app/Auth/UserRepo.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use BookStack\Facades\Activity;
use BookStack\Uploads\UserAvatars;
use Exception;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;

Expand Down Expand Up @@ -61,7 +62,7 @@ public function createWithoutActivity(array $data, bool $emailConfirmed = false)
$user = new User();
$user->name = $data['name'];
$user->email = $data['email'];
$user->password = bcrypt(empty($data['password']) ? Str::random(32) : $data['password']);
$user->password = Hash::make(empty($data['password']) ? Str::random(32) : $data['password']);
$user->email_confirmed = $emailConfirmed;
$user->external_auth_id = $data['external_auth_id'] ?? '';

Expand Down Expand Up @@ -126,7 +127,7 @@ public function update(User $user, array $data, bool $manageUsersAllowed): User
}

if (!empty($data['password'])) {
$user->password = bcrypt($data['password']);
$user->password = Hash::make($data['password']);
}

if (!empty($data['language'])) {
Expand Down
6 changes: 3 additions & 3 deletions app/Http/Controllers/Auth/ConfirmEmailController.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@

class ConfirmEmailController extends Controller
{
protected $emailConfirmationService;
protected $loginService;
protected $userRepo;
protected EmailConfirmationService $emailConfirmationService;
protected LoginService $loginService;
protected UserRepo $userRepo;

/**
* Create a new controller instance.
Expand Down
23 changes: 9 additions & 14 deletions app/Http/Controllers/Auth/ForgotPasswordController.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,11 @@

use BookStack\Actions\ActivityType;
use BookStack\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\SendsPasswordResetEmails;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Password;

class ForgotPasswordController extends Controller
{
/*
|--------------------------------------------------------------------------
| Password Reset Controller
|--------------------------------------------------------------------------
|
| This controller is responsible for handling password reset emails and
| includes a trait which assists in sending these notifications from
| your application to your users. Feel free to explore this trait.
|
*/
use SendsPasswordResetEmails;

/**
* Create a new controller instance.
*
Expand All @@ -33,6 +20,14 @@ public function __construct()
$this->middleware('guard:standard');
}

/**
* Display the form to request a password reset link.
*/
public function showLinkRequestForm()
{
return view('auth.passwords.email');
}

/**
* Send a reset link to the given user.
*
Expand All @@ -49,7 +44,7 @@ public function sendResetLinkEmail(Request $request)
// We will send the password reset link to this user. Once we have attempted
// to send the link, we will examine the response then see the message we
// need to show to the user. Finally, we'll send out a proper response.
$response = $this->broker()->sendResetLink(
$response = Password::broker()->sendResetLink(
$request->only('email')
);

Expand Down
167 changes: 55 additions & 112 deletions app/Http/Controllers/Auth/LoginController.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,31 +8,14 @@
use BookStack\Exceptions\LoginAttemptException;
use BookStack\Facades\Activity;
use BookStack\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Validation\ValidationException;

class LoginController extends Controller
{
/*
|--------------------------------------------------------------------------
| Login Controller
|--------------------------------------------------------------------------
|
| This controller handles authenticating users for the application and
| redirecting them to your home screen. The controller uses a trait
| to conveniently provide its functionality to your applications.
|
*/
use AuthenticatesUsers {
logout as traitLogout;
}

/**
* Redirection paths.
*/
protected $redirectTo = '/';
protected $redirectPath = '/';
use ThrottlesLogins;

protected SocialAuthService $socialAuthService;
protected LoginService $loginService;
Expand All @@ -48,21 +31,6 @@ public function __construct(SocialAuthService $socialAuthService, LoginService $

$this->socialAuthService = $socialAuthService;
$this->loginService = $loginService;

$this->redirectPath = url('/');
}

public function username()
{
return config('auth.method') === 'standard' ? 'email' : 'username';
}

/**
* Get the needed authorization credentials from the request.
*/
protected function credentials(Request $request)
{
return $request->only('username', 'email', 'password');
}

/**
Expand Down Expand Up @@ -98,29 +66,15 @@ public function getLogin(Request $request)

/**
* Handle a login request to the application.
*
* @param \Illuminate\Http\Request $request
*
* @throws \Illuminate\Validation\ValidationException
*
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\Response|\Illuminate\Http\JsonResponse
*/
public function login(Request $request)
{
$this->validateLogin($request);
$username = $request->get($this->username());

// If the class is using the ThrottlesLogins trait, we can automatically throttle
// the login attempts for this application. We'll key this by the username and
// the IP address of the client making these requests into this application.
if (
method_exists($this, 'hasTooManyLoginAttempts') &&
$this->hasTooManyLoginAttempts($request)
) {
$this->fireLockoutEvent($request);

// Check login throttling attempts to see if they've gone over the limit
if ($this->hasTooManyLoginAttempts($request)) {
Activity::logFailedLogin($username);

return $this->sendLockoutResponse($request);
}

Expand All @@ -134,24 +88,62 @@ public function login(Request $request)
return $this->sendLoginAttemptExceptionResponse($exception, $request);
}

// If the login attempt was unsuccessful we will increment the number of attempts
// to login and redirect the user back to the login form. Of course, when this
// user surpasses their maximum number of attempts they will get locked out.
// On unsuccessful login attempt, Increment login attempts for throttling and log failed login.
$this->incrementLoginAttempts($request);

Activity::logFailedLogin($username);

return $this->sendFailedLoginResponse($request);
// Throw validation failure for failed login
throw ValidationException::withMessages([
$this->username() => [trans('auth.failed')],
])->redirectTo('/login');
}

/**
* Logout user and perform subsequent redirect.
*/
public function logout(Request $request)
{
Auth::guard()->logout();
$request->session()->invalidate();
$request->session()->regenerateToken();

$redirectUri = $this->shouldAutoInitiate() ? '/login?prevent_auto_init=true' : '/';

return redirect($redirectUri);
}

/**
* Get the expected username input based upon the current auth method.
*/
protected function username(): string
{
return config('auth.method') === 'standard' ? 'email' : 'username';
}

/**
* Get the needed authorization credentials from the request.
*/
protected function credentials(Request $request): array
{
return $request->only('username', 'email', 'password');
}

/**
* Send the response after the user was authenticated.
* @return RedirectResponse
*/
protected function sendLoginResponse(Request $request)
{
$request->session()->regenerate();
$this->clearLoginAttempts($request);

return redirect()->intended('/');
}

/**
* Attempt to log the user into the application.
*
* @param \Illuminate\Http\Request $request
*
* @return bool
*/
protected function attemptLogin(Request $request)
protected function attemptLogin(Request $request): bool
{
return $this->loginService->attempt(
$this->credentials($request),
Expand All @@ -160,29 +152,12 @@ protected function attemptLogin(Request $request)
);
}

/**
* The user has been authenticated.
*
* @param \Illuminate\Http\Request $request
* @param mixed $user
*
* @return mixed
*/
protected function authenticated(Request $request, $user)
{
return redirect()->intended($this->redirectPath());
}

/**
* Validate the user login request.
*
* @param \Illuminate\Http\Request $request
*
* @throws \Illuminate\Validation\ValidationException
*
* @return void
* @throws ValidationException
*/
protected function validateLogin(Request $request)
protected function validateLogin(Request $request): void
{
$rules = ['password' => ['required', 'string']];
$authMethod = config('auth.method');
Expand Down Expand Up @@ -216,22 +191,6 @@ protected function sendLoginAttemptExceptionResponse(LoginAttemptException $exce
return redirect('/login');
}

/**
* Get the failed login response instance.
*
* @param \Illuminate\Http\Request $request
*
* @throws \Illuminate\Validation\ValidationException
*
* @return \Symfony\Component\HttpFoundation\Response
*/
protected function sendFailedLoginResponse(Request $request)
{
throw ValidationException::withMessages([
$this->username() => [trans('auth.failed')],
])->redirectTo('/login');
}

/**
* Update the intended URL location from their previous URL.
* Ignores if not from the current app instance or if from certain
Expand Down Expand Up @@ -271,20 +230,4 @@ protected function shouldAutoInitiate(): bool

return $autoRedirect && count($socialDrivers) === 0 && in_array($authMethod, ['oidc', 'saml2']);
}

/**
* Logout user and perform subsequent redirect.
*
* @param \Illuminate\Http\Request $request
*
* @return mixed
*/
public function logout(Request $request)
{
$this->traitLogout($request);

$redirectUri = $this->shouldAutoInitiate() ? '/login?prevent_auto_init=true' : '/';

return redirect($redirectUri);
}
}
Loading