namespace BookStack\Http\Controllers\Auth;
+use Activity;
+use BookStack\Auth\Access\SocialAuthService;
+use BookStack\Exceptions\LoginAttemptEmailNeededException;
+use BookStack\Exceptions\LoginAttemptException;
+use BookStack\Exceptions\UserRegistrationException;
use BookStack\Http\Controllers\Controller;
-use BookStack\Repos\UserRepo;
-use BookStack\Services\SocialAuthService;
-use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Http\Request;
use AuthenticatesUsers;
/**
- * Where to redirect users after login.
- *
- * @var string
+ * Redirection paths
*/
protected $redirectTo = '/';
-
protected $redirectPath = '/';
protected $redirectAfterLogout = '/login';
protected $socialAuthService;
- protected $userRepo;
/**
* Create a new controller instance.
- *
- * @param SocialAuthService $socialAuthService
- * @param UserRepo $userRepo
*/
- public function __construct(SocialAuthService $socialAuthService, UserRepo $userRepo)
+ public function __construct(SocialAuthService $socialAuthService)
{
- $this->middleware('guest', ['only' => ['getLogin', 'postLogin']]);
+ $this->middleware('guest', ['only' => ['getLogin', 'login']]);
+ $this->middleware('guard:standard,ldap', ['only' => ['login', 'logout']]);
+
$this->socialAuthService = $socialAuthService;
- $this->userRepo = $userRepo;
- $this->redirectPath = baseUrl('/');
- $this->redirectAfterLogout = baseUrl('/login');
+ $this->redirectPath = url('/');
+ $this->redirectAfterLogout = url('/login');
parent::__construct();
}
}
/**
- * Overrides the action when a user is authenticated.
- * If the user authenticated but does not exist in the user table we create them.
- * @param Request $request
- * @param Authenticatable $user
- * @return \Illuminate\Http\RedirectResponse
- * @throws AuthException
+ * Get the needed authorization credentials from the request.
*/
- protected function authenticated(Request $request, Authenticatable $user)
+ protected function credentials(Request $request)
{
- // Explicitly log them out for now if they do no exist.
- if (!$user->exists) auth()->logout($user);
+ return $request->only('username', 'email', 'password');
+ }
- if (!$user->exists && $user->email === null && !$request->has('email')) {
- $request->flash();
- session()->flash('request-email', true);
- return redirect('/login');
+ /**
+ * Show the application login form.
+ */
+ public function getLogin(Request $request)
+ {
+ $socialDrivers = $this->socialAuthService->getActiveDrivers();
+ $authMethod = config('auth.method');
+
+ if ($request->has('email')) {
+ session()->flashInput([
+ 'email' => $request->get('email'),
+ 'password' => (config('app.env') === 'demo') ? $request->get('password', '') : ''
+ ]);
}
- if (!$user->exists && $user->email === null && $request->has('email')) {
- $user->email = $request->get('email');
+ // Store the previous location for redirect after login
+ $previous = url()->previous('');
+ if ($previous && $previous !== url('/login') && setting('app-public')) {
+ $isPreviousFromInstance = (strpos($previous, url('/')) === 0);
+ if ($isPreviousFromInstance) {
+ redirect()->setIntendedUrl($previous);
+ }
}
- if (!$user->exists) {
+ return view('auth.login', [
+ 'socialDrivers' => $socialDrivers,
+ 'authMethod' => $authMethod,
+ ]);
+ }
+
+ /**
+ * Handle a login request to the application.
+ *
+ * @param \Illuminate\Http\Request $request
+ * @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\Response|\Illuminate\Http\JsonResponse
+ *
+ * @throws \Illuminate\Validation\ValidationException
+ */
+ 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);
+
+ Activity::logFailedLogin($username);
+ return $this->sendLockoutResponse($request);
+ }
- // Check for users with same email already
- $alreadyUser = $user->newQuery()->where('email', '=', $user->email)->count() > 0;
- if ($alreadyUser) {
- throw new AuthException('A user with the email ' . $user->email . ' already exists but with different credentials.');
+ try {
+ if ($this->attemptLogin($request)) {
+ return $this->sendLoginResponse($request);
}
+ } catch (LoginAttemptException $exception) {
+ Activity::logFailedLogin($username);
+ return $this->sendLoginAttemptExceptionResponse($exception, $request);
+ }
- $user->save();
- $this->userRepo->attachDefaultRole($user);
- auth()->login($user);
+ // 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.
+ $this->incrementLoginAttempts($request);
+
+ Activity::logFailedLogin($username);
+ return $this->sendFailedLoginResponse($request);
+ }
+
+ /**
+ * The user has been authenticated.
+ *
+ * @param \Illuminate\Http\Request $request
+ * @param mixed $user
+ * @return mixed
+ */
+ protected function authenticated(Request $request, $user)
+ {
+ // Authenticate on all session guards if a likely admin
+ if ($user->can('users-manage') && $user->can('user-roles-manage')) {
+ $guards = ['standard', 'ldap', 'saml2'];
+ foreach ($guards as $guard) {
+ auth($guard)->login($user);
+ }
}
- $path = session()->pull('url.intended', '/');
- $path = baseUrl($path, true);
- return redirect($path);
+ return redirect()->intended($this->redirectPath());
}
/**
- * Show the application login form.
- * @return \Illuminate\Http\Response
+ * Validate the user login request.
+ *
+ * @param \Illuminate\Http\Request $request
+ * @return void
+ *
+ * @throws \Illuminate\Validation\ValidationException
*/
- public function getLogin()
+ protected function validateLogin(Request $request)
{
- $socialDrivers = $this->socialAuthService->getActiveDrivers();
+ $rules = ['password' => 'required|string'];
$authMethod = config('auth.method');
- return view('auth/login', ['socialDrivers' => $socialDrivers, 'authMethod' => $authMethod]);
+
+ if ($authMethod === 'standard') {
+ $rules['email'] = 'required|email';
+ }
+
+ if ($authMethod === 'ldap') {
+ $rules['username'] = 'required|string';
+ $rules['email'] = 'email';
+ }
+
+ $request->validate($rules);
}
/**
- * Redirect to the relevant social site.
- * @param $socialDriver
- * @return \Symfony\Component\HttpFoundation\RedirectResponse
+ * Send a response when a login attempt exception occurs.
*/
- public function getSocialLogin($socialDriver)
+ protected function sendLoginAttemptExceptionResponse(LoginAttemptException $exception, Request $request)
{
- session()->put('social-callback', 'login');
- return $this->socialAuthService->startLogIn($socialDriver);
+ if ($exception instanceof LoginAttemptEmailNeededException) {
+ $request->flash();
+ session()->flash('request-email', true);
+ }
+
+ if ($message = $exception->getMessage()) {
+ $this->showWarningNotification($message);
+ }
+
+ return redirect('/login');
}
-}
\ No newline at end of file
+
+}