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;
* 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
{
use BookStack\Facades\Activity;
use BookStack\Uploads\UserAvatars;
use Exception;
+use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
$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'] ?? '';
}
if (!empty($data['password'])) {
- $user->password = bcrypt($data['password']);
+ $user->password = Hash::make($data['password']);
}
if (!empty($data['language'])) {
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.
*
$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.
*
// 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')
);
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 string $redirectTo = '/';
- protected string $redirectPath = '/';
+ use ThrottlesLogins;
protected SocialAuthService $socialAuthService;
protected LoginService $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');
}
/**
/**
* 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);
}
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),
);
}
- /**
- * 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');
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
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);
- }
}
use BookStack\Auth\Access\LoginService;
use BookStack\Auth\Access\RegistrationService;
use BookStack\Auth\Access\SocialAuthService;
-use BookStack\Auth\User;
use BookStack\Exceptions\StoppedAuthenticationException;
use BookStack\Exceptions\UserRegistrationException;
use BookStack\Http\Controllers\Controller;
-use Illuminate\Foundation\Auth\RegistersUsers;
+use Illuminate\Contracts\Validation\Validator as ValidatorContract;
use Illuminate\Http\Request;
-use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rules\Password;
class RegisterController extends Controller
{
- /*
- |--------------------------------------------------------------------------
- | Register Controller
- |--------------------------------------------------------------------------
- |
- | This controller handles the registration of new users as well as their
- | validation and creation. By default this controller uses a trait to
- | provide this functionality without requiring any additional code.
- |
- */
- use RegistersUsers;
-
protected SocialAuthService $socialAuthService;
protected RegistrationService $registrationService;
protected LoginService $loginService;
- /**
- * Where to redirect users after login / registration.
- */
- protected string $redirectTo = '/';
- protected string $redirectPath = '/';
-
/**
* Create a new controller instance.
*/
$this->socialAuthService = $socialAuthService;
$this->registrationService = $registrationService;
$this->loginService = $loginService;
-
- $this->redirectTo = url('/');
- $this->redirectPath = url('/');
- }
-
- /**
- * Get a validator for an incoming registration request.
- *
- * @return \Illuminate\Contracts\Validation\Validator
- */
- protected function validator(array $data)
- {
- return Validator::make($data, [
- 'name' => ['required', 'min:2', 'max:100'],
- 'email' => ['required', 'email', 'max:255', 'unique:users'],
- 'password' => ['required', Password::default()],
- ]);
}
/**
$this->showSuccessNotification(trans('auth.register_success'));
- return redirect($this->redirectPath());
+ return redirect('/');
}
/**
- * Create a new user instance after a valid registration.
- *
- * @param array $data
- *
- * @return User
+ * Get a validator for an incoming registration request.
*/
- protected function create(array $data)
+ protected function validator(array $data): ValidatorContract
{
- return User::create([
- 'name' => $data['name'],
- 'email' => $data['email'],
- 'password' => Hash::make($data['password']),
+ return Validator::make($data, [
+ 'name' => ['required', 'min:2', 'max:100'],
+ 'email' => ['required', 'email', 'max:255', 'unique:users'],
+ 'password' => ['required', Password::default()],
]);
}
}
namespace BookStack\Http\Controllers\Auth;
use BookStack\Actions\ActivityType;
+use BookStack\Auth\Access\LoginService;
+use BookStack\Auth\User;
use BookStack\Http\Controllers\Controller;
-use Illuminate\Foundation\Auth\ResetsPasswords;
+use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
+use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Password;
+use Illuminate\Support\Str;
+use Illuminate\Validation\Rules\Password as PasswordRule;
class ResetPasswordController extends Controller
{
- /*
- |--------------------------------------------------------------------------
- | Password Reset Controller
- |--------------------------------------------------------------------------
- |
- | This controller is responsible for handling password reset requests
- | and uses a simple trait to include this behavior. You're free to
- | explore this trait and override any methods you wish to tweak.
- |
- */
- use ResetsPasswords;
+ protected LoginService $loginService;
- protected $redirectTo = '/';
+ public function __construct(LoginService $loginService)
+ {
+ $this->middleware('guest');
+ $this->middleware('guard:standard');
+
+ $this->loginService = $loginService;
+ }
/**
- * Create a new controller instance.
- *
- * @return void
+ * Display the password reset view for the given token.
+ * If no token is present, display the link request form.
*/
- public function __construct()
+ public function showResetForm(Request $request)
{
- $this->middleware('guest');
- $this->middleware('guard:standard');
+ $token = $request->route()->parameter('token');
+
+ return view('auth.passwords.reset')->with(
+ ['token' => $token, 'email' => $request->email]
+ );
+ }
+
+ /**
+ * Reset the given user's password.
+ */
+ public function reset(Request $request)
+ {
+ $request->validate([
+ 'token' => 'required',
+ 'email' => 'required|email',
+ 'password' => ['required', 'confirmed', PasswordRule::defaults()],
+ ]);
+
+ // Here we will attempt to reset the user's password. If it is successful we
+ // will update the password on an actual user model and persist it to the
+ // database. Otherwise we will parse the error and return the response.
+ $credentials = $request->only('email', 'password', 'password_confirmation', 'token');
+ $response = Password::broker()->reset($credentials, function (User $user, string $password) {
+ $user->password = Hash::make($password);
+ $user->setRememberToken(Str::random(60));
+ $user->save();
+
+ $this->loginService->login($user, auth()->getDefaultDriver());
+ });
+
+ // If the password was successfully reset, we will redirect the user back to
+ // the application's home authenticated view. If there is an error we can
+ // redirect them back to where they came from with their error message.
+ return $response === Password::PASSWORD_RESET
+ ? $this->sendResetResponse()
+ : $this->sendResetFailedResponse($request, $response);
}
/**
* Get the response for a successful password reset.
- *
- * @param Request $request
- * @param string $response
- *
- * @return \Illuminate\Http\Response
*/
- protected function sendResetResponse(Request $request, $response)
+ protected function sendResetResponse(): RedirectResponse
{
- $message = trans('auth.reset_password_success');
- $this->showSuccessNotification($message);
+ $this->showSuccessNotification(trans('auth.reset_password_success'));
$this->logActivity(ActivityType::AUTH_PASSWORD_RESET_UPDATE, user());
- return redirect($this->redirectPath())
- ->with('status', trans($response));
+ return redirect('/');
}
/**
* 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)
+ protected function sendResetFailedResponse(Request $request, string $response): RedirectResponse
{
// We show invalid users as invalid tokens as to not leak what
// users may exist in the system.
class Saml2Controller extends Controller
{
- protected $samlService;
+ protected Saml2Service $samlService;
/**
* Saml2Controller constructor.
RegistrationService $registrationService,
LoginService $loginService
) {
- $this->middleware('guest')->only(['getRegister', 'postRegister']);
+ $this->middleware('guest')->only(['register']);
$this->socialAuthService = $socialAuthService;
$this->registrationService = $registrationService;
$this->loginService = $loginService;
--- /dev/null
+<?php
+
+namespace BookStack\Http\Controllers\Auth;
+
+use Illuminate\Cache\RateLimiter;
+use Illuminate\Http\Request;
+use Illuminate\Http\Response;
+use Illuminate\Support\Str;
+use Illuminate\Validation\ValidationException;
+
+trait ThrottlesLogins
+{
+ /**
+ * Determine if the user has too many failed login attempts.
+ */
+ protected function hasTooManyLoginAttempts(Request $request): bool
+ {
+ return $this->limiter()->tooManyAttempts(
+ $this->throttleKey($request),
+ $this->maxAttempts()
+ );
+ }
+
+ /**
+ * Increment the login attempts for the user.
+ */
+ protected function incrementLoginAttempts(Request $request): void
+ {
+ $this->limiter()->hit(
+ $this->throttleKey($request),
+ $this->decayMinutes() * 60
+ );
+ }
+
+ /**
+ * Redirect the user after determining they are locked out.
+ * @throws ValidationException
+ */
+ protected function sendLockoutResponse(Request $request): \Symfony\Component\HttpFoundation\Response
+ {
+ $seconds = $this->limiter()->availableIn(
+ $this->throttleKey($request)
+ );
+
+ throw ValidationException::withMessages([
+ $this->username() => [trans('auth.throttle', [
+ 'seconds' => $seconds,
+ 'minutes' => ceil($seconds / 60),
+ ])],
+ ])->status(Response::HTTP_TOO_MANY_REQUESTS);
+ }
+
+ /**
+ * Clear the login locks for the given user credentials.
+ */
+ protected function clearLoginAttempts(Request $request): void
+ {
+ $this->limiter()->clear($this->throttleKey($request));
+ }
+
+ /**
+ * Get the throttle key for the given request.
+ */
+ protected function throttleKey(Request $request): string
+ {
+ return Str::transliterate(Str::lower($request->input($this->username())) . '|' . $request->ip());
+ }
+
+ /**
+ * Get the rate limiter instance.
+ */
+ protected function limiter(): RateLimiter
+ {
+ return app(RateLimiter::class);
+ }
+
+ /**
+ * Get the maximum number of attempts to allow.
+ */
+ public function maxAttempts(): int
+ {
+ return 5;
+ }
+
+ /**
+ * Get the number of minutes to throttle for.
+ */
+ public function decayMinutes(): int
+ {
+ return 1;
+ }
+}
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Routing\Redirector;
+use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\Rules\Password;
class UserInviteController extends Controller
}
$user = $this->userRepo->getById($userId);
- $user->password = bcrypt($request->get('password'));
+ $user->password = Hash::make($request->get('password'));
$user->email_confirmed = true;
$user->save();
"laravel/framework": "^8.68",
"laravel/socialite": "^5.2",
"laravel/tinker": "^2.6",
- "laravel/ui": "^3.3",
"league/commonmark": "^1.6",
"league/flysystem-aws-s3-v3": "^1.0.29",
"league/html-to-markdown": "^5.0.0",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "1d3bd88b99d07b5410ee4b245bece28e",
+ "content-hash": "01795571047babf7ee6372b7f98843af",
"packages": [
{
"name": "aws/aws-crt-php",
},
"time": "2022-03-23T12:38:24+00:00"
},
- {
- "name": "laravel/ui",
- "version": "v3.4.6",
- "source": {
- "type": "git",
- "url": "https://github.com/laravel/ui.git",
- "reference": "65ec5c03f7fee2c8ecae785795b829a15be48c2c"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/laravel/ui/zipball/65ec5c03f7fee2c8ecae785795b829a15be48c2c",
- "reference": "65ec5c03f7fee2c8ecae785795b829a15be48c2c",
- "shasum": ""
- },
- "require": {
- "illuminate/console": "^8.42|^9.0",
- "illuminate/filesystem": "^8.42|^9.0",
- "illuminate/support": "^8.82|^9.0",
- "illuminate/validation": "^8.42|^9.0",
- "php": "^7.3|^8.0"
- },
- "require-dev": {
- "orchestra/testbench": "^6.23|^7.0"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "3.x-dev"
- },
- "laravel": {
- "providers": [
- "Laravel\\Ui\\UiServiceProvider"
- ]
- }
- },
- "autoload": {
- "psr-4": {
- "Laravel\\Ui\\": "src/",
- "Illuminate\\Foundation\\Auth\\": "auth-backend/"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Taylor Otwell",
- }
- ],
- "description": "Laravel UI utilities and presets.",
- "keywords": [
- "laravel",
- "ui"
- ],
- "support": {
- "source": "https://github.com/laravel/ui/tree/v3.4.6"
- },
- "time": "2022-05-20T13:38:08+00:00"
- },
{
"name": "league/commonmark",
"version": "1.6.7",