userRepository = new UserRepository(); } public function isAuthenticationRequired(): bool { return true; } public function getAccount(): IContent { /** * @var User $user */ $user = \Container::$request->user(); return new HtmlContent('account/account', ['user' => $user->toArray()]); } public function getGoogleConnectRedirect(): IRedirect { /** * @var User $user */ $user = \Container::$request->user(); $state = bin2hex(random_bytes(16)); $nonce = bin2hex(random_bytes(16)); \Container::$request->session()->set('oauth_state', $state); \Container::$request->session()->set('oauth_nonce', $nonce); $oAuth = new GoogleOAuth(new Request()); $url = $oAuth->getDialogUrl( $state, \Container::$request->getBase() . \Container::$routeCollection->getRoute('account.googleConnect-confirm')->generateLink(), $nonce, $user->getEmail() ); return new Redirect($url, IRedirect::TEMPORARY); } public function getGoogleConnectConfirm(): IContent { $defaultError = 'Authentication with Google failed. Please try again!'; if (\Container::$request->query('state') !== \Container::$request->session()->get('oauth_state')) { return new HtmlContent('account/google_connect', ['success' => false, 'error' => $defaultError]); } $oAuth = new GoogleOAuth(new Request()); $tokenData = $oAuth->getToken( \Container::$request->query('code'), \Container::$request->getBase() . \Container::$routeCollection->getRoute('account.googleConnect-confirm')->generateLink() ); if (!isset($tokenData['id_token'])) { return new HtmlContent('account/google_connect', ['success' => false, 'error' => $defaultError]); } $jwtParser = new JwtParser($tokenData['id_token']); $idToken = $jwtParser->getPayload(); if ($idToken['nonce'] !== \Container::$request->session()->get('oauth_nonce')) { return new HtmlContent('account/google_connect', ['success' => false, 'error' => $defaultError]); } $anotherUser = $this->userRepository->getByGoogleSub($idToken['sub']); if ($anotherUser !== null) { return new HtmlContent('account/google_connect', [ 'success' => false, 'error' => 'This Google account is linked to another account.' ]); } \Container::$request->session()->set('google_user_data', $idToken); /** * @var User $user */ $user = \Container::$request->user(); return new HtmlContent('account/google_connect', [ 'success' => true, 'googleAccount' => $idToken['email'], 'userEmail' => $user->getEmail() ]); } public function connectGoogle(): IContent { /** * @var User $user */ $user = \Container::$request->user(); if (!$user->checkPassword(\Container::$request->post('password'))) { return new JsonContent([ 'error' => [ 'errorText' => 'The given password is wrong.' ] ]); } $googleUserData = \Container::$request->session()->get('google_user_data'); $user->setGoogleSub($googleUserData['sub']); \Container::$persistentDataManager->saveToDb($user); return new JsonContent(['success' => true]); } public function getGoogleDisconnectConfirm(): IContent { /** * @var User $user */ $user = \Container::$request->user(); return new HtmlContent('account/google_disconnect', [ 'success' => true, 'userEmail' => $user->getEmail() ]); } public function disconnectGoogle(): IContent { /** * @var User $user */ $user = \Container::$request->user(); if (!$user->checkPassword(\Container::$request->post('password'))) { return new JsonContent([ 'error' => [ 'errorText' => 'The given password is wrong.' ] ]); } $user->setGoogleSub(null); \Container::$persistentDataManager->saveToDb($user); return new JsonContent(['success' => true]); } public function getGoogleAuthenticateRedirect(): IRedirect { /** * @var User $user */ $user = \Container::$request->user(); $state = bin2hex(random_bytes(16)); $nonce = bin2hex(random_bytes(16)); \Container::$request->session()->set('oauth_state', $state); \Container::$request->session()->set('oauth_nonce', $nonce); $oAuth = new GoogleOAuth(new Request()); $url = $oAuth->getDialogUrl( $state, \Container::$request->getBase() . \Container::$routeCollection->getRoute('account.googleAuthenticate-action')->generateLink(), $nonce, $user->getEmail() ); return new Redirect($url, IRedirect::TEMPORARY); } public function authenticateWithGoogle(): IContent { /** * @var User $user */ $user = \Container::$request->user(); if (\Container::$request->query('state') !== \Container::$request->session()->get('oauth_state')) { return new HtmlContent('account/google_authenticate', ['success' => false]); } $oAuth = new GoogleOAuth(new Request()); $tokenData = $oAuth->getToken( \Container::$request->query('code'), \Container::$request->getBase() . \Container::$routeCollection->getRoute('account.googleAuthenticate-action')->generateLink() ); if (!isset($tokenData['id_token'])) { return new HtmlContent('account/google_authenticate', ['success' => false]); } $jwtParser = new JwtParser($tokenData['id_token']); $idToken = $jwtParser->getPayload(); if ($idToken['nonce'] !== \Container::$request->session()->get('oauth_nonce')) { return new HtmlContent('account/google_authenticate', ['success' => false]); } if ($idToken['sub'] !== $user->getGoogleSub()) { return new HtmlContent('account/google_authenticate', [ 'success' => false, 'errorText' => 'This Google account is not linked to your account.' ]); } $authenticatedWithGoogleUntil = new DateTime('+45 seconds'); \Container::$request->session()->set('authenticated_with_google_until', $authenticatedWithGoogleUntil); return new HtmlContent('account/google_authenticate', [ 'success' => true, 'authenticatedWithGoogleUntil' => $authenticatedWithGoogleUntil ]); } public function saveAccount(): IContent { /** * @var User $user */ $user = \Container::$request->user(); if (!$this->confirmUserIdentity( $user, \Container::$request->session()->get('authenticated_with_google_until'), \Container::$request->post('password'), $error )) { return new JsonContent(['error' => ['errorText' => $error]]); } $newEmail = \Container::$request->post('email'); if ($newEmail !== $user->getEmail()) { if (!filter_var($newEmail, FILTER_VALIDATE_EMAIL)) { return new JsonContent(['error' => ['errorText' => 'Please provide a valid email address.']]); } if ($this->userRepository->getByEmail($newEmail) !== null) { return new JsonContent(['error' => ['errorText' => 'The given email address belongs to another account.']]); } $user->setEmail($newEmail); } $newUsername = \Container::$request->post('username'); if ($newUsername !== $user->getUsername()) { if (strlen($newUsername) > 0) { if (filter_var($newUsername, FILTER_VALIDATE_EMAIL)) { return new JsonContent(['error' => ['errorText' => 'Please select a username that is not a valid email address.']]); } if ($this->userRepository->getByUsername($newUsername) !== null) { return new JsonContent(['error' => ['errorText' => 'The given username is already taken.']]); } $user->setUsername($newUsername); } else { $user->setUsername(null); } } $newPassword = \Container::$request->post('password_new'); if (strlen($newPassword) > 0) { if (strlen($newPassword) < 6) { return new JsonContent([ 'error' => [ 'errorText' => 'The given new password is too short. Please choose a password that is at least 6 characters long!' ] ]); } if ($newPassword !== \Container::$request->post('password_new_confirm')) { return new JsonContent([ 'error' => [ 'errorText' => 'The given new passwords do not match.' ] ]); } $user->setPlainPassword($newPassword); } $user->setNickname(\Container::$request->post('nickname')); $user->setPhone(\Container::$request->post('phone')); $user->setIdNumber(\Container::$request->post('id_number')); \Container::$persistentDataManager->saveToDb($user); \Container::$request->session()->delete('authenticated_with_google_until'); return new JsonContent(['success' => true]); } private function confirmUserIdentity(User $user, ?DateTime $authenticatedWithGoogleUntil, ?string $password, ?string &$error): bool { if ($authenticatedWithGoogleUntil !== null && $authenticatedWithGoogleUntil > new DateTime()) { return true; } if ($password !== null) { if ($user->checkPassword($password)) { return true; } $error = 'The given current password is wrong.'; return false; } $error = 'Could not confirm your identity. Please try again!'; return false; } }