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\Support\Facades\Auth;
14 use Illuminate\Validation\ValidationException;
16 class LoginController extends Controller
20 protected SocialDriverManager $socialDriverManager;
21 protected LoginService $loginService;
24 * Create a new controller instance.
26 public function __construct(SocialDriverManager $driverManager, LoginService $loginService)
28 $this->middleware('guest', ['only' => ['getLogin', 'login']]);
29 $this->middleware('guard:standard,ldap', ['only' => ['login']]);
30 $this->middleware('guard:standard,ldap,oidc', ['only' => ['logout']]);
32 $this->socialDriverManager = $driverManager;
33 $this->loginService = $loginService;
37 * Show the application login form.
39 public function getLogin(Request $request)
41 $socialDrivers = $this->socialDriverManager->getActive();
42 $authMethod = config('auth.method');
43 $preventInitiation = $request->get('prevent_auto_init') === 'true';
45 if ($request->has('email')) {
46 session()->flashInput([
47 'email' => $request->get('email'),
48 'password' => (config('app.env') === 'demo') ? $request->get('password', '') : '',
52 // Store the previous location for redirect after login
53 $this->updateIntendedFromPrevious();
55 if (!$preventInitiation && $this->shouldAutoInitiate()) {
56 return view('auth.login-initiate', [
57 'authMethod' => $authMethod,
61 return view('auth.login', [
62 'socialDrivers' => $socialDrivers,
63 'authMethod' => $authMethod,
68 * Handle a login request to the application.
70 public function login(Request $request)
72 $this->validateLogin($request);
73 $username = $request->get($this->username());
75 // Check login throttling attempts to see if they've gone over the limit
76 if ($this->hasTooManyLoginAttempts($request)) {
77 Activity::logFailedLogin($username);
78 return $this->sendLockoutResponse($request);
82 if ($this->attemptLogin($request)) {
83 return $this->sendLoginResponse($request);
85 } catch (LoginAttemptException $exception) {
86 Activity::logFailedLogin($username);
88 return $this->sendLoginAttemptExceptionResponse($exception, $request);
91 // On unsuccessful login attempt, Increment login attempts for throttling and log failed login.
92 $this->incrementLoginAttempts($request);
93 Activity::logFailedLogin($username);
95 // Throw validation failure for failed login
96 throw ValidationException::withMessages([
97 $this->username() => [trans('auth.failed')],
98 ])->redirectTo('/login');
102 * Logout user and perform subsequent redirect.
104 public function logout()
106 return redirect($this->loginService->logout());
110 * Get the expected username input based upon the current auth method.
112 protected function username(): string
114 return config('auth.method') === 'standard' ? 'email' : 'username';
118 * Get the needed authorization credentials from the request.
120 protected function credentials(Request $request): array
122 return $request->only('username', 'email', 'password');
126 * Send the response after the user was authenticated.
127 * @return RedirectResponse
129 protected function sendLoginResponse(Request $request)
131 $request->session()->regenerate();
132 $this->clearLoginAttempts($request);
134 return redirect()->intended('/');
138 * Attempt to log the user into the application.
140 protected function attemptLogin(Request $request): bool
142 return $this->loginService->attempt(
143 $this->credentials($request),
144 auth()->getDefaultDriver(),
145 $request->filled('remember')
151 * Validate the user login request.
152 * @throws ValidationException
154 protected function validateLogin(Request $request): void
156 $rules = ['password' => ['required', 'string']];
157 $authMethod = config('auth.method');
159 if ($authMethod === 'standard') {
160 $rules['email'] = ['required', 'email'];
163 if ($authMethod === 'ldap') {
164 $rules['username'] = ['required', 'string'];
165 $rules['email'] = ['email'];
168 $request->validate($rules);
172 * Send a response when a login attempt exception occurs.
174 protected function sendLoginAttemptExceptionResponse(LoginAttemptException $exception, Request $request)
176 if ($exception instanceof LoginAttemptEmailNeededException) {
178 session()->flash('request-email', true);
181 if ($message = $exception->getMessage()) {
182 $this->showWarningNotification($message);
185 return redirect('/login');
189 * Update the intended URL location from their previous URL.
190 * Ignores if not from the current app instance or if from certain
191 * login or authentication routes.
193 protected function updateIntendedFromPrevious(): void
195 // Store the previous location for redirect after login
196 $previous = url()->previous('');
197 $isPreviousFromInstance = (strpos($previous, url('/')) === 0);
198 if (!$previous || !setting('app-public') || !$isPreviousFromInstance) {
202 $ignorePrefixList = [
207 foreach ($ignorePrefixList as $ignorePrefix) {
208 if (strpos($previous, url($ignorePrefix)) === 0) {
213 redirect()->setIntendedUrl($previous);