3 namespace BookStack\Access\Controllers;
5 use BookStack\Access\LoginService;
6 use BookStack\Access\SocialDriverManager;
7 use BookStack\Exceptions\LoginAttemptEmailNeededException;
8 use BookStack\Exceptions\LoginAttemptException;
9 use BookStack\Facades\Activity;
10 use BookStack\Http\Controller;
11 use Illuminate\Http\RedirectResponse;
12 use Illuminate\Http\Request;
13 use Illuminate\Validation\ValidationException;
15 class LoginController extends Controller
19 public function __construct(
20 protected SocialDriverManager $socialDriverManager,
21 protected LoginService $loginService,
23 $this->middleware('guest', ['only' => ['getLogin', 'login']]);
24 $this->middleware('guard:standard,ldap', ['only' => ['login']]);
25 $this->middleware('guard:standard,ldap,oidc', ['only' => ['logout']]);
29 * Show the application login form.
31 public function getLogin(Request $request)
33 $socialDrivers = $this->socialDriverManager->getActive();
34 $authMethod = config('auth.method');
35 $preventInitiation = $request->get('prevent_auto_init') === 'true';
37 if ($request->has('email')) {
38 session()->flashInput([
39 'email' => $request->get('email'),
40 'password' => (config('app.env') === 'demo') ? $request->get('password', '') : '',
44 // Store the previous location for redirect after login
45 $this->updateIntendedFromPrevious();
47 if (!$preventInitiation && $this->loginService->shouldAutoInitiate()) {
48 return view('auth.login-initiate', [
49 'authMethod' => $authMethod,
53 return view('auth.login', [
54 'socialDrivers' => $socialDrivers,
55 'authMethod' => $authMethod,
60 * Handle a login request to the application.
62 public function login(Request $request)
64 $this->validateLogin($request);
65 $username = $request->get($this->username());
67 // Check login throttling attempts to see if they've gone over the limit
68 if ($this->hasTooManyLoginAttempts($request)) {
69 Activity::logFailedLogin($username);
70 return $this->sendLockoutResponse($request);
74 if ($this->attemptLogin($request)) {
75 return $this->sendLoginResponse($request);
77 } catch (LoginAttemptException $exception) {
78 Activity::logFailedLogin($username);
80 return $this->sendLoginAttemptExceptionResponse($exception, $request);
83 // On unsuccessful login attempt, Increment login attempts for throttling and log failed login.
84 $this->incrementLoginAttempts($request);
85 Activity::logFailedLogin($username);
87 // Throw validation failure for failed login
88 throw ValidationException::withMessages([
89 $this->username() => [trans('auth.failed')],
90 ])->redirectTo('/login');
94 * Logout user and perform subsequent redirect.
96 public function logout()
98 return redirect($this->loginService->logout());
102 * Get the expected username input based upon the current auth method.
104 protected function username(): string
106 return config('auth.method') === 'standard' ? 'email' : 'username';
110 * Get the needed authorization credentials from the request.
112 protected function credentials(Request $request): array
114 return $request->only('username', 'email', 'password');
118 * Send the response after the user was authenticated.
119 * @return RedirectResponse
121 protected function sendLoginResponse(Request $request)
123 $request->session()->regenerate();
124 $this->clearLoginAttempts($request);
126 return redirect()->intended('/');
130 * Attempt to log the user into the application.
132 protected function attemptLogin(Request $request): bool
134 return $this->loginService->attempt(
135 $this->credentials($request),
136 auth()->getDefaultDriver(),
137 $request->filled('remember')
143 * Validate the user login request.
144 * @throws ValidationException
146 protected function validateLogin(Request $request): void
148 $rules = ['password' => ['required', 'string']];
149 $authMethod = config('auth.method');
151 if ($authMethod === 'standard') {
152 $rules['email'] = ['required', 'email'];
155 if ($authMethod === 'ldap') {
156 $rules['username'] = ['required', 'string'];
157 $rules['email'] = ['email'];
160 $request->validate($rules);
164 * Send a response when a login attempt exception occurs.
166 protected function sendLoginAttemptExceptionResponse(LoginAttemptException $exception, Request $request)
168 if ($exception instanceof LoginAttemptEmailNeededException) {
170 session()->flash('request-email', true);
173 if ($message = $exception->getMessage()) {
174 $this->showWarningNotification($message);
177 return redirect('/login');
181 * Update the intended URL location from their previous URL.
182 * Ignores if not from the current app instance or if from certain
183 * login or authentication routes.
185 protected function updateIntendedFromPrevious(): void
187 // Store the previous location for redirect after login
188 $previous = url()->previous('');
189 $isPreviousFromInstance = str_starts_with($previous, url('/'));
190 if (!$previous || !setting('app-public') || !$isPreviousFromInstance) {
194 $ignorePrefixList = [
199 foreach ($ignorePrefixList as $ignorePrefix) {
200 if (str_starts_with($previous, url($ignorePrefix))) {
205 redirect()->setIntendedUrl($previous);