]> BookStack Code Mirror - bookstack/blob - app/Http/Controllers/Auth/LoginController.php
Skip intermediate login page with single provider
[bookstack] / app / Http / Controllers / Auth / LoginController.php
1 <?php
2
3 namespace BookStack\Http\Controllers\Auth;
4
5 use BookStack\Auth\Access\LoginService;
6 use BookStack\Auth\Access\SocialAuthService;
7 use BookStack\Exceptions\LoginAttemptEmailNeededException;
8 use BookStack\Exceptions\LoginAttemptException;
9 use BookStack\Facades\Activity;
10 use BookStack\Http\Controllers\Controller;
11 use Illuminate\Foundation\Auth\AuthenticatesUsers;
12 use Illuminate\Http\Request;
13 use Illuminate\Validation\ValidationException;
14
15 class LoginController extends Controller
16 {
17     /*
18     |--------------------------------------------------------------------------
19     | Login Controller
20     |--------------------------------------------------------------------------
21     |
22     | This controller handles authenticating users for the application and
23     | redirecting them to your home screen. The controller uses a trait
24     | to conveniently provide its functionality to your applications.
25     |
26     */
27
28     use AuthenticatesUsers { logout as traitLogout; }
29
30     /**
31      * Redirection paths.
32      */
33     protected $redirectTo = '/';
34     protected $redirectPath = '/';
35     protected $redirectAfterLogout = '/';
36
37     protected $socialAuthService;
38     protected $loginService;
39
40     /**
41      * Create a new controller instance.
42      */
43     public function __construct(SocialAuthService $socialAuthService, LoginService $loginService)
44     {
45         $this->middleware('guest', ['only' => ['getLogin', 'login']]);
46         $this->middleware('guard:standard,ldap', ['only' => ['login']]);
47         $this->middleware('guard:standard,ldap,oidc', ['only' => ['logout']]);
48
49         $this->socialAuthService = $socialAuthService;
50         $this->loginService = $loginService;
51
52         $this->redirectPath = url('/');
53         $this->redirectAfterLogout = url(config('auth.auto_redirect') ? '/login?logout=1' : '/');
54     }
55
56     public function username()
57     {
58         return config('auth.method') === 'standard' ? 'email' : 'username';
59     }
60
61     /**
62      * Get the needed authorization credentials from the request.
63      */
64     protected function credentials(Request $request)
65     {
66         return $request->only('username', 'email', 'password');
67     }
68
69     /**
70      * Show the application login form.
71      */
72     public function getLogin(Request $request)
73     {
74         $socialDrivers = $this->socialAuthService->getActiveDrivers();
75         $authMethod = config('auth.method');
76         $autoRedirect = config('auth.auto_redirect');
77
78         if ($request->has('email')) {
79             session()->flashInput([
80                 'email'    => $request->get('email'),
81                 'password' => (config('app.env') === 'demo') ? $request->get('password', '') : '',
82             ]);
83         }
84
85         // Store the previous location for redirect after login
86         $this->updateIntendedFromPrevious();
87
88         if ($autoRedirect && !($request->has('logout') && $request->get('logout') == '1') && count($socialDrivers) == 0 && in_array($authMethod, ['oidc', 'saml2'])) {
89             return view('auth.login-redirect', [
90                 'authMethod'    => $authMethod,
91             ]);
92         }
93
94         return view('auth.login', [
95             'socialDrivers' => $socialDrivers,
96             'authMethod'    => $authMethod,
97         ]);
98     }
99
100     /**
101      * Handle a login request to the application.
102      *
103      * @param \Illuminate\Http\Request $request
104      *
105      * @throws \Illuminate\Validation\ValidationException
106      *
107      * @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\Response|\Illuminate\Http\JsonResponse
108      */
109     public function login(Request $request)
110     {
111         $this->validateLogin($request);
112         $username = $request->get($this->username());
113
114         // If the class is using the ThrottlesLogins trait, we can automatically throttle
115         // the login attempts for this application. We'll key this by the username and
116         // the IP address of the client making these requests into this application.
117         if (method_exists($this, 'hasTooManyLoginAttempts') &&
118             $this->hasTooManyLoginAttempts($request)) {
119             $this->fireLockoutEvent($request);
120
121             Activity::logFailedLogin($username);
122
123             return $this->sendLockoutResponse($request);
124         }
125
126         try {
127             if ($this->attemptLogin($request)) {
128                 return $this->sendLoginResponse($request);
129             }
130         } catch (LoginAttemptException $exception) {
131             Activity::logFailedLogin($username);
132
133             return $this->sendLoginAttemptExceptionResponse($exception, $request);
134         }
135
136         // If the login attempt was unsuccessful we will increment the number of attempts
137         // to login and redirect the user back to the login form. Of course, when this
138         // user surpasses their maximum number of attempts they will get locked out.
139         $this->incrementLoginAttempts($request);
140
141         Activity::logFailedLogin($username);
142
143         return $this->sendFailedLoginResponse($request);
144     }
145
146     /**
147      * Attempt to log the user into the application.
148      *
149      * @param \Illuminate\Http\Request $request
150      *
151      * @return bool
152      */
153     protected function attemptLogin(Request $request)
154     {
155         return $this->loginService->attempt(
156             $this->credentials($request),
157             auth()->getDefaultDriver(),
158             $request->filled('remember')
159         );
160     }
161
162     /**
163      * The user has been authenticated.
164      *
165      * @param \Illuminate\Http\Request $request
166      * @param mixed                    $user
167      *
168      * @return mixed
169      */
170     protected function authenticated(Request $request, $user)
171     {
172         return redirect()->intended($this->redirectPath());
173     }
174
175     /**
176      * Validate the user login request.
177      *
178      * @param \Illuminate\Http\Request $request
179      *
180      * @throws \Illuminate\Validation\ValidationException
181      *
182      * @return void
183      */
184     protected function validateLogin(Request $request)
185     {
186         $rules = ['password' => ['required', 'string']];
187         $authMethod = config('auth.method');
188
189         if ($authMethod === 'standard') {
190             $rules['email'] = ['required', 'email'];
191         }
192
193         if ($authMethod === 'ldap') {
194             $rules['username'] = ['required', 'string'];
195             $rules['email'] = ['email'];
196         }
197
198         $request->validate($rules);
199     }
200
201     /**
202      * Send a response when a login attempt exception occurs.
203      */
204     protected function sendLoginAttemptExceptionResponse(LoginAttemptException $exception, Request $request)
205     {
206         if ($exception instanceof LoginAttemptEmailNeededException) {
207             $request->flash();
208             session()->flash('request-email', true);
209         }
210
211         if ($message = $exception->getMessage()) {
212             $this->showWarningNotification($message);
213         }
214
215         return redirect('/login');
216     }
217
218     /**
219      * Get the failed login response instance.
220      *
221      * @param \Illuminate\Http\Request $request
222      *
223      * @throws \Illuminate\Validation\ValidationException
224      *
225      * @return \Symfony\Component\HttpFoundation\Response
226      */
227     protected function sendFailedLoginResponse(Request $request)
228     {
229         throw ValidationException::withMessages([
230             $this->username() => [trans('auth.failed')],
231         ])->redirectTo('/login');
232     }
233
234     /**
235      * Update the intended URL location from their previous URL.
236      * Ignores if not from the current app instance or if from certain
237      * login or authentication routes.
238      */
239     protected function updateIntendedFromPrevious(): void
240     {
241         // Store the previous location for redirect after login
242         $previous = url()->previous('');
243         $isPreviousFromInstance = (strpos($previous, url('/')) === 0);
244         if (!$previous || !setting('app-public') || !$isPreviousFromInstance) {
245             return;
246         }
247
248         $ignorePrefixList = [
249             '/login',
250             '/mfa',
251         ];
252
253         foreach ($ignorePrefixList as $ignorePrefix) {
254             if (strpos($previous, url($ignorePrefix)) === 0) {
255                 return;
256             }
257         }
258
259         redirect()->setIntendedUrl($previous);
260     }
261
262     /**
263      * Logout user and perform subsequent redirect.
264      *
265      * @param \Illuminate\Http\Request $request
266      *
267      * @return mixed
268      */
269     public function logout(Request $request)
270     {
271         $this->traitLogout($request);
272
273         return redirect($this->redirectAfterLogout);
274     }
275 }