X-Git-Url: http://source.bookstackapp.com/bookstack/blobdiff_plain/2dcc5105ad0b4f7701099b57f89f45de0d0ff8e8..refs/pull/166/head:/app/Http/Controllers/Auth/AuthController.php diff --git a/app/Http/Controllers/Auth/AuthController.php b/app/Http/Controllers/Auth/AuthController.php index eacbe2bb9..2cbc047ce 100644 --- a/app/Http/Controllers/Auth/AuthController.php +++ b/app/Http/Controllers/Auth/AuthController.php @@ -1,16 +1,18 @@ -middleware('guest', ['except' => 'getLogout']); - $this->socialite = $socialite; + $this->middleware('guest', ['only' => ['getLogin', 'postLogin', 'getRegister', 'postRegister']]); + $this->socialAuthService = $socialAuthService; + $this->emailConfirmationService = $emailConfirmationService; $this->userRepo = $userRepo; + $this->redirectPath = baseUrl('/'); + $this->redirectAfterLogout = baseUrl('/login'); + $this->username = config('auth.method') === 'standard' ? 'email' : 'username'; + parent::__construct(); } /** * Get a validator for an incoming registration request. - * * @param array $data * @return \Illuminate\Contracts\Validation\Validator */ protected function validator(array $data) { return Validator::make($data, [ - 'name' => 'required|max:255', - 'email' => 'required|email|max:255|unique:users', - 'password' => 'required|confirmed|min:6', + 'name' => 'required|max:255', + 'email' => 'required|email|max:255|unique:users', + 'password' => 'required|min:6', ]); } + protected function checkRegistrationAllowed() + { + if (!setting('registration-enabled')) { + throw new UserRegistrationException('Registrations are currently disabled.', '/login'); + } + } + /** - * Create a new user instance after a valid registration. - * - * @param array $data - * @return User + * Show the application registration form. + * @return \Illuminate\Http\Response */ - protected function create(array $data) + public function getRegister() { - return User::create([ - 'name' => $data['name'], - 'email' => $data['email'], - 'password' => bcrypt($data['password']), - ]); + $this->checkRegistrationAllowed(); + $socialDrivers = $this->socialAuthService->getActiveDrivers(); + return view('auth.register', ['socialDrivers' => $socialDrivers]); } /** - * Show the application login form. - * + * Handle a registration request for the application. + * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\Response + * @throws UserRegistrationException */ - public function getLogin() + public function postRegister(Request $request) { + $this->checkRegistrationAllowed(); + $validator = $this->validator($request->all()); - if (view()->exists('auth.authenticate')) { - return view('auth.authenticate'); + if ($validator->fails()) { + $this->throwValidationException( + $request, $validator + ); } - $socialDrivers = $this->getActiveSocialDrivers(); + $userData = $request->all(); + return $this->registerUser($userData); + } - return view('auth.login', ['socialDrivers' => $socialDrivers]); + + /** + * 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 + */ + protected function authenticated(Request $request, Authenticatable $user) + { + // Explicitly log them out for now if they do no exist. + if (!$user->exists) auth()->logout($user); + + if (!$user->exists && $user->email === null && !$request->has('email')) { + $request->flash(); + session()->flash('request-email', true); + return redirect('/login'); + } + + if (!$user->exists && $user->email === null && $request->has('email')) { + $user->email = $request->get('email'); + } + + if (!$user->exists) { + + // 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.'); + } + + $user->save(); + $this->userRepo->attachDefaultRole($user); + auth()->login($user); + } + + return redirect()->intended($this->redirectPath()); } /** - * Redirect to the relevant social site. + * Register a new user after a registration callback. * @param $socialDriver - * @return \Symfony\Component\HttpFoundation\RedirectResponse + * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector + * @throws UserRegistrationException */ - public function getSocialLogin($socialDriver) + protected function socialRegisterCallback($socialDriver) { - $driver = $this->validateSocialDriver($socialDriver); - return $this->socialite->driver($driver)->redirect(); + $socialUser = $this->socialAuthService->handleRegistrationCallback($socialDriver); + $socialAccount = $this->socialAuthService->fillSocialAccount($socialDriver, $socialUser); + + // Create an array of the user data to create a new user instance + $userData = [ + 'name' => $socialUser->getName(), + 'email' => $socialUser->getEmail(), + 'password' => str_random(30) + ]; + return $this->registerUser($userData, $socialAccount); } /** - * The callback for social login services. - * - * @param $socialDriver + * The registrations flow for all users. + * @param array $userData + * @param bool|false|SocialAccount $socialAccount * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector - * @throws UserNotFound + * @throws UserRegistrationException + * @throws \BookStack\Exceptions\ConfirmationEmailException */ - public function socialCallback($socialDriver) + protected function registerUser(array $userData, $socialAccount = false) { - $driver = $this->validateSocialDriver($socialDriver); - // Get user details from social driver - $socialUser = $this->socialite->driver($driver)->user(); - $user = $this->userRepo->getByEmail($socialUser->getEmail()); + if (setting('registration-restrict')) { + $restrictedEmailDomains = explode(',', str_replace(' ', '', setting('registration-restrict'))); + $userEmailDomain = $domain = substr(strrchr($userData['email'], "@"), 1); + if (!in_array($userEmailDomain, $restrictedEmailDomains)) { + throw new UserRegistrationException('That email domain does not have access to this application', '/register'); + } + } + + $newUser = $this->userRepo->registerNew($userData); + if ($socialAccount) { + $newUser->socialAccounts()->save($socialAccount); + } - // Redirect if the email is not a current user. - if ($user === null) { - throw new UserNotFound('A user with the email ' . $socialUser->getEmail() . ' was not found.', '/login'); + if (setting('registration-confirmation') || setting('registration-restrict')) { + $newUser->save(); + $this->emailConfirmationService->sendConfirmation($newUser); + return redirect('/register/confirm'); } - \Auth::login($user, true); + auth()->login($newUser); + session()->flash('success', 'Thanks for signing up! You are now registered and signed in.'); + return redirect($this->redirectPath()); + } + + /** + * Show the page to tell the user to check their email + * and confirm their address. + */ + public function getRegisterConfirmation() + { + return view('auth/register-confirm'); + } + + /** + * View the confirmation email as a standard web page. + * @param $token + * @return \Illuminate\View\View + * @throws UserRegistrationException + */ + public function viewConfirmEmail($token) + { + $confirmation = $this->emailConfirmationService->getEmailConfirmationFromToken($token); + return view('emails/email-confirmation', ['token' => $confirmation->token]); + } + + /** + * Confirms an email via a token and logs the user into the system. + * @param $token + * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector + * @throws UserRegistrationException + */ + public function confirmEmail($token) + { + $confirmation = $this->emailConfirmationService->getEmailConfirmationFromToken($token); + $user = $confirmation->user; + $user->email_confirmed = true; + $user->save(); + auth()->login($confirmation->user); + session()->flash('success', 'Your email has been confirmed!'); + $this->emailConfirmationService->deleteConfirmationsByUser($user); return redirect($this->redirectPath); } /** - * Ensure the social driver is correct and supported. - * - * @param $socialDriver - * @return string - * @throws SocialDriverNotConfigured + * Shows a notice that a user's email address has not been confirmed, + * Also has the option to re-send the confirmation email. + * @return \Illuminate\View\View + */ + public function showAwaitingConfirmation() + { + return view('auth/user-unconfirmed'); + } + + /** + * Resend the confirmation email + * @param Request $request + * @return \Illuminate\View\View */ - protected function validateSocialDriver($socialDriver) + public function resendConfirmation(Request $request) { - $driver = trim(strtolower($socialDriver)); + $this->validate($request, [ + 'email' => 'required|email|exists:users,email' + ]); + $user = $this->userRepo->getByEmail($request->get('email')); + $this->emailConfirmationService->sendConfirmation($user); + session()->flash('success', 'Confirmation email resent, Please check your inbox.'); + return redirect('/register/confirm'); + } - if (!in_array($driver, $this->validSocialDrivers)) abort(404, 'Social Driver Not Found'); - if(!$this->checkSocialDriverConfigured($driver)) throw new SocialDriverNotConfigured; + /** + * Show the application login form. + * @return \Illuminate\Http\Response + */ + public function getLogin() + { + $socialDrivers = $this->socialAuthService->getActiveDrivers(); + $authMethod = config('auth.method'); + return view('auth/login', ['socialDrivers' => $socialDrivers, 'authMethod' => $authMethod]); + } - return $driver; + /** + * Redirect to the relevant social site. + * @param $socialDriver + * @return \Symfony\Component\HttpFoundation\RedirectResponse + */ + public function getSocialLogin($socialDriver) + { + session()->put('social-callback', 'login'); + return $this->socialAuthService->startLogIn($socialDriver); } /** - * Check a social driver has been configured correctly. - * @param $driver - * @return bool + * Redirect to the social site for authentication intended to register. + * @param $socialDriver + * @return mixed */ - protected function checkSocialDriverConfigured($driver) + public function socialRegister($socialDriver) { - $upperName = strtoupper($driver); - $config = [env($upperName . '_APP_ID', false), env($upperName . '_APP_SECRET', false), env('APP_URL', false)]; - return (!in_array(false, $config) && !in_array(null, $config)); + $this->checkRegistrationAllowed(); + session()->put('social-callback', 'register'); + return $this->socialAuthService->startRegister($socialDriver); } /** - * Gets the names of the active social drivers. - * @return array + * The callback for social login services. + * @param $socialDriver + * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector + * @throws SocialSignInException */ - protected function getActiveSocialDrivers() + public function socialCallback($socialDriver) { - $activeDrivers = []; - foreach($this->validSocialDrivers as $driverName) { - if($this->checkSocialDriverConfigured($driverName)) { - $activeDrivers[$driverName] = true; + if (session()->has('social-callback')) { + $action = session()->pull('social-callback'); + if ($action == 'login') { + return $this->socialAuthService->handleLoginCallback($socialDriver); + } elseif ($action == 'register') { + return $this->socialRegisterCallback($socialDriver); } + } else { + throw new SocialSignInException('No action defined', '/login'); } - return $activeDrivers; + return redirect()->back(); } + + /** + * Detach a social account from a user. + * @param $socialDriver + * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector + */ + public function detachSocialAccount($socialDriver) + { + return $this->socialAuthService->detachSocialAccount($socialDriver); + } + }