Merged in feature/MAPG-142-resend-activation-link-functionality (pull request #172)
Feature/MAPG-142 resend activation link functionality
This commit is contained in:
		
						commit
						b58137acc9
					
				| @ -1,4 +1,4 @@ | |||||||
| APP_NAME=MapQuiz | APP_NAME=MapGuesser | ||||||
| DEV=1 | DEV=1 | ||||||
| DB_HOST=mariadb | DB_HOST=mariadb | ||||||
| DB_USER=mapguesser | DB_USER=mapguesser | ||||||
|  | |||||||
| @ -0,0 +1,5 @@ | |||||||
|  | UPDATE `user_confirmations` SET token=SUBSTRING(token, 1, 32); | ||||||
|  | 
 | ||||||
|  | ALTER TABLE `user_confirmations` | ||||||
|  |   ADD `last_sent` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, | ||||||
|  |   MODIFY `token` varchar(32) CHARACTER SET ascii NOT NULL; | ||||||
| @ -0,0 +1,4 @@ | |||||||
|  | UPDATE `sessions` SET id=SUBSTRING(id, 1, 32); | ||||||
|  | 
 | ||||||
|  | ALTER TABLE `sessions` | ||||||
|  |   MODIFY `id` varchar(32) CHARACTER SET ascii NOT NULL; | ||||||
| @ -0,0 +1,2 @@ | |||||||
|  | ALTER TABLE `users` | ||||||
|  |   ADD `created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP; | ||||||
							
								
								
									
										2
									
								
								main.php
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								main.php
									
									
									
									
									
								
							| @ -16,7 +16,7 @@ class Container | |||||||
| { | { | ||||||
|     static MapGuesser\Interfaces\Database\IConnection $dbConnection; |     static MapGuesser\Interfaces\Database\IConnection $dbConnection; | ||||||
|     static MapGuesser\Routing\RouteCollection $routeCollection; |     static MapGuesser\Routing\RouteCollection $routeCollection; | ||||||
|     static \SessionHandlerInterface $sessionHandler; |     static MapGuesser\Interfaces\Session\ISessionHandler $sessionHandler; | ||||||
|     static MapGuesser\Interfaces\Request\IRequest $request; |     static MapGuesser\Interfaces\Request\IRequest $request; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,5 +1,6 @@ | |||||||
| <?php namespace MapGuesser\Cli; | <?php namespace MapGuesser\Cli; | ||||||
| 
 | 
 | ||||||
|  | use DateTime; | ||||||
| use MapGuesser\PersistentData\PersistentDataManager; | use MapGuesser\PersistentData\PersistentDataManager; | ||||||
| use MapGuesser\PersistentData\Model\User; | use MapGuesser\PersistentData\Model\User; | ||||||
| use Symfony\Component\Console\Command\Command; | use Symfony\Component\Console\Command\Command; | ||||||
| @ -24,6 +25,7 @@ class AddUserCommand extends Command | |||||||
|         $user->setEmail($input->getArgument('email')); |         $user->setEmail($input->getArgument('email')); | ||||||
|         $user->setPlainPassword($input->getArgument('password')); |         $user->setPlainPassword($input->getArgument('password')); | ||||||
|         $user->setActive(true); |         $user->setActive(true); | ||||||
|  |         $user->setCreatedDate(new DateTime()); | ||||||
| 
 | 
 | ||||||
|         if ($input->hasArgument('type')) { |         if ($input->hasArgument('type')) { | ||||||
|             $user->setType($input->getArgument('type')); |             $user->setType($input->getArgument('type')); | ||||||
|  | |||||||
| @ -1,5 +1,6 @@ | |||||||
| <?php namespace MapGuesser\Controller; | <?php namespace MapGuesser\Controller; | ||||||
| 
 | 
 | ||||||
|  | use DateInterval; | ||||||
| use DateTime; | use DateTime; | ||||||
| use MapGuesser\Http\Request; | use MapGuesser\Http\Request; | ||||||
| use MapGuesser\Interfaces\Request\IRequest; | use MapGuesser\Interfaces\Request\IRequest; | ||||||
| @ -52,13 +53,16 @@ class LoginController | |||||||
|     public function getGoogleLoginRedirect(): IRedirect |     public function getGoogleLoginRedirect(): IRedirect | ||||||
|     { |     { | ||||||
|         $state = bin2hex(random_bytes(16)); |         $state = bin2hex(random_bytes(16)); | ||||||
|  |         $nonce = bin2hex(random_bytes(16)); | ||||||
| 
 | 
 | ||||||
|         $this->request->session()->set('oauth_state', $state); |         $this->request->session()->set('oauth_state', $state); | ||||||
|  |         $this->request->session()->set('oauth_nonce', $nonce); | ||||||
| 
 | 
 | ||||||
|         $oAuth = new GoogleOAuth(new Request()); |         $oAuth = new GoogleOAuth(new Request()); | ||||||
|         $url = $oAuth->getDialogUrl( |         $url = $oAuth->getDialogUrl( | ||||||
|             $state, |             $state, | ||||||
|             $this->request->getBase() . '/' . \Container::$routeCollection->getRoute('login-google-action')->generateLink() |             $this->request->getBase() . '/' . \Container::$routeCollection->getRoute('login-google-action')->generateLink(), | ||||||
|  |             $nonce | ||||||
|         ); |         ); | ||||||
| 
 | 
 | ||||||
|         return new Redirect($url, IRedirect::TEMPORARY); |         return new Redirect($url, IRedirect::TEMPORARY); | ||||||
| @ -168,6 +172,8 @@ class LoginController | |||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         if (!$user->getActive()) { |         if (!$user->getActive()) { | ||||||
|  |             $this->resendConfirmationEmail($user); | ||||||
|  | 
 | ||||||
|             return new JsonContent([ |             return new JsonContent([ | ||||||
|                 'error' => [ |                 'error' => [ | ||||||
|                     'errorText' => 'User found with the given email address, but the account is not activated. ' . |                     'errorText' => 'User found with the given email address, but the account is not activated. ' . | ||||||
| @ -211,16 +217,20 @@ class LoginController | |||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         $jwtParser = new JwtParser($tokenData['id_token']); |         $jwtParser = new JwtParser($tokenData['id_token']); | ||||||
|         $userData = $jwtParser->getPayload(); |         $idToken = $jwtParser->getPayload(); | ||||||
| 
 | 
 | ||||||
|         if (!$userData['email_verified']) { |         if ($idToken['nonce'] !== $this->request->session()->get('oauth_nonce')) { | ||||||
|             return new HtmlContent('login/google_login'); |             return new HtmlContent('login/google_login'); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         $user = $this->userRepository->getByGoogleSub($userData['sub']); |         if (!$idToken['email_verified']) { | ||||||
|  |             return new HtmlContent('login/google_login'); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         $user = $this->userRepository->getByGoogleSub($idToken['sub']); | ||||||
| 
 | 
 | ||||||
|         if ($user === null) { |         if ($user === null) { | ||||||
|             $this->request->session()->set('google_user_data', $userData); |             $this->request->session()->set('google_user_data', ['sub' => $idToken['sub'], 'email' => $idToken['email']]); | ||||||
| 
 | 
 | ||||||
|             return new Redirect(\Container::$routeCollection->getRoute('signup-google')->generateLink(), IRedirect::TEMPORARY); |             return new Redirect(\Container::$routeCollection->getRoute('signup-google')->generateLink(), IRedirect::TEMPORARY); | ||||||
|         } |         } | ||||||
| @ -301,16 +311,18 @@ class LoginController | |||||||
|         $user = new User(); |         $user = new User(); | ||||||
|         $user->setEmail($this->request->post('email')); |         $user->setEmail($this->request->post('email')); | ||||||
|         $user->setPlainPassword($this->request->post('password')); |         $user->setPlainPassword($this->request->post('password')); | ||||||
|  |         $user->setCreatedDate(new DateTime()); | ||||||
| 
 | 
 | ||||||
|         \Container::$dbConnection->startTransaction(); |         \Container::$dbConnection->startTransaction(); | ||||||
| 
 | 
 | ||||||
|         $this->pdm->saveToDb($user); |         $this->pdm->saveToDb($user); | ||||||
| 
 | 
 | ||||||
|         $token = hash('sha256', serialize($user) . random_bytes(10) . microtime()); |         $token = bin2hex(random_bytes(16)); | ||||||
| 
 | 
 | ||||||
|         $confirmation = new UserConfirmation(); |         $confirmation = new UserConfirmation(); | ||||||
|         $confirmation->setUser($user); |         $confirmation->setUser($user); | ||||||
|         $confirmation->setToken($token); |         $confirmation->setToken($token); | ||||||
|  |         $confirmation->setLastSentDate(new DateTime()); | ||||||
| 
 | 
 | ||||||
|         $this->pdm->saveToDb($confirmation); |         $this->pdm->saveToDb($confirmation); | ||||||
| 
 | 
 | ||||||
| @ -338,6 +350,7 @@ class LoginController | |||||||
| 
 | 
 | ||||||
|             $user = new User(); |             $user = new User(); | ||||||
|             $user->setEmail($userData['email']); |             $user->setEmail($userData['email']); | ||||||
|  |             $user->setCreatedDate(new DateTime()); | ||||||
|         } else { |         } else { | ||||||
|             $sendWelcomeEmail = false; |             $sendWelcomeEmail = false; | ||||||
|         } |         } | ||||||
| @ -377,7 +390,7 @@ class LoginController | |||||||
|             return new Redirect(\Container::$routeCollection->getRoute('index')->generateLink(), IRedirect::TEMPORARY); |             return new Redirect(\Container::$routeCollection->getRoute('index')->generateLink(), IRedirect::TEMPORARY); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         $confirmation = $this->userConfirmationRepository->getByToken($this->request->query('token')); |         $confirmation = $this->userConfirmationRepository->getByToken(substr($this->request->query('token'), 0, 32)); | ||||||
| 
 | 
 | ||||||
|         if ($confirmation === null) { |         if ($confirmation === null) { | ||||||
|             return new HtmlContent('login/activate'); |             return new HtmlContent('login/activate'); | ||||||
| @ -405,7 +418,7 @@ class LoginController | |||||||
|             return new Redirect(\Container::$routeCollection->getRoute('index')->generateLink(), IRedirect::TEMPORARY); |             return new Redirect(\Container::$routeCollection->getRoute('index')->generateLink(), IRedirect::TEMPORARY); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         $confirmation = $this->userConfirmationRepository->getByToken($this->request->query('token')); |         $confirmation = $this->userConfirmationRepository->getByToken(substr($this->request->query('token'), 0, 32)); | ||||||
| 
 | 
 | ||||||
|         if ($confirmation === null) { |         if ($confirmation === null) { | ||||||
|             return new HtmlContent('login/cancel', ['success' => false]); |             return new HtmlContent('login/cancel', ['success' => false]); | ||||||
| @ -445,6 +458,8 @@ class LoginController | |||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         if (!$user->getActive()) { |         if (!$user->getActive()) { | ||||||
|  |             $this->resendConfirmationEmail($user); | ||||||
|  | 
 | ||||||
|             return new JsonContent([ |             return new JsonContent([ | ||||||
|                 'error' => [ |                 'error' => [ | ||||||
|                     'errorText' => 'User found with the given email address, but the account is not activated. ' . |                     'errorText' => 'User found with the given email address, but the account is not activated. ' . | ||||||
| @ -453,6 +468,16 @@ class LoginController | |||||||
|             ]); |             ]); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  |         $existingResetter = $this->userPasswordResetterRepository->getByUser($user); | ||||||
|  | 
 | ||||||
|  |         if ($existingResetter !== null && $existingResetter->getExpiresDate() > new DateTime()) { | ||||||
|  |             return new JsonContent([ | ||||||
|  |                 'error' => [ | ||||||
|  |                     'errorText' => 'Password reset was recently requested for this account. Please check your email, or try again later!' | ||||||
|  |                 ] | ||||||
|  |             ]); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|         $token = bin2hex(random_bytes(16)); |         $token = bin2hex(random_bytes(16)); | ||||||
|         $expires = new DateTime('+1 hour'); |         $expires = new DateTime('+1 hour'); | ||||||
| 
 | 
 | ||||||
| @ -461,8 +486,16 @@ class LoginController | |||||||
|         $passwordResetter->setToken($token); |         $passwordResetter->setToken($token); | ||||||
|         $passwordResetter->setExpiresDate($expires); |         $passwordResetter->setExpiresDate($expires); | ||||||
| 
 | 
 | ||||||
|  |         \Container::$dbConnection->startTransaction(); | ||||||
|  | 
 | ||||||
|  |         if ($existingResetter !== null) { | ||||||
|  |             $this->pdm->deleteFromDb($existingResetter); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|         $this->pdm->saveToDb($passwordResetter); |         $this->pdm->saveToDb($passwordResetter); | ||||||
| 
 | 
 | ||||||
|  |         \Container::$dbConnection->commit(); | ||||||
|  | 
 | ||||||
|         $this->sendPasswordResetEmail($user->getEmail(), $token, $expires); |         $this->sendPasswordResetEmail($user->getEmail(), $token, $expires); | ||||||
| 
 | 
 | ||||||
|         return new JsonContent(['success' => true]); |         return new JsonContent(['success' => true]); | ||||||
| @ -533,6 +566,23 @@ class LoginController | |||||||
|         $mail->send(); |         $mail->send(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     private function resendConfirmationEmail(User $user): bool | ||||||
|  |     { | ||||||
|  |         $confirmation = $this->userConfirmationRepository->getByUser($user); | ||||||
|  | 
 | ||||||
|  |         if ($confirmation === null || (clone $confirmation->getLastSentDate())->add(new DateInterval('PT1H')) > new DateTime()) { | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         $confirmation->setLastSentDate(new DateTime()); | ||||||
|  | 
 | ||||||
|  |         $this->pdm->saveToDb($confirmation); | ||||||
|  | 
 | ||||||
|  |         $this->sendConfirmationEmail($user->getEmail(), $confirmation->getToken()); | ||||||
|  | 
 | ||||||
|  |         return true; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     private function sendWelcomeEmail(string $email): void |     private function sendWelcomeEmail(string $email): void | ||||||
|     { |     { | ||||||
|         $mail = new Mail(); |         $mail = new Mail(); | ||||||
|  | |||||||
| @ -59,14 +59,17 @@ class UserController implements ISecured | |||||||
|         $user = $this->request->user(); |         $user = $this->request->user(); | ||||||
| 
 | 
 | ||||||
|         $state = bin2hex(random_bytes(16)); |         $state = bin2hex(random_bytes(16)); | ||||||
|  |         $nonce = bin2hex(random_bytes(16)); | ||||||
| 
 | 
 | ||||||
|         $this->request->session()->set('oauth_state', $state); |         $this->request->session()->set('oauth_state', $state); | ||||||
|  |         $this->request->session()->set('oauth_nonce', $nonce); | ||||||
| 
 | 
 | ||||||
|         $oAuth = new GoogleOAuth(new Request()); |         $oAuth = new GoogleOAuth(new Request()); | ||||||
| 
 | 
 | ||||||
|         $url = $oAuth->getDialogUrl( |         $url = $oAuth->getDialogUrl( | ||||||
|             $state, |             $state, | ||||||
|             $this->request->getBase() . '/' . \Container::$routeCollection->getRoute('account.googleAuthenticate-action')->generateLink(), |             $this->request->getBase() . '/' . \Container::$routeCollection->getRoute('account.googleAuthenticate-action')->generateLink(), | ||||||
|  |             $nonce, | ||||||
|             $user->getEmail() |             $user->getEmail() | ||||||
|         ); |         ); | ||||||
| 
 | 
 | ||||||
| @ -95,9 +98,13 @@ class UserController implements ISecured | |||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         $jwtParser = new JwtParser($tokenData['id_token']); |         $jwtParser = new JwtParser($tokenData['id_token']); | ||||||
|         $userData = $jwtParser->getPayload(); |         $idToken = $jwtParser->getPayload(); | ||||||
| 
 | 
 | ||||||
|         if ($userData['sub'] !== $user->getGoogleSub()) { |         if ($idToken['nonce'] !== $this->request->session()->get('oauth_nonce')) { | ||||||
|  |             return new HtmlContent('account/google_authenticate', ['success' => false]); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if ($idToken['sub'] !== $user->getGoogleSub()) { | ||||||
|             return new HtmlContent('account/google_authenticate', [ |             return new HtmlContent('account/google_authenticate', [ | ||||||
|                 'success' => false, |                 'success' => false, | ||||||
|                 'errorText' => 'This Google account is not linked to your account.' |                 'errorText' => 'This Google account is not linked to your account.' | ||||||
| @ -184,11 +191,13 @@ class UserController implements ISecured | |||||||
| 
 | 
 | ||||||
|         \Container::$dbConnection->startTransaction(); |         \Container::$dbConnection->startTransaction(); | ||||||
| 
 | 
 | ||||||
|         foreach ($this->userConfirmationRepository->getByUser($user) as $userConfirmation) { |         $userConfirmation = $this->userConfirmationRepository->getByUser($user); | ||||||
|  |         if ($userConfirmation !== null) { | ||||||
|             $this->pdm->deleteFromDb($userConfirmation); |             $this->pdm->deleteFromDb($userConfirmation); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         foreach ($this->userPasswordResetterRepository->getByUser($user) as $userPasswordResetter) { |         $userPasswordResetter = $this->userPasswordResetterRepository->getByUser($user); | ||||||
|  |         if ($userPasswordResetter !== null) { | ||||||
|             $this->pdm->deleteFromDb($userPasswordResetter); |             $this->pdm->deleteFromDb($userPasswordResetter); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -135,6 +135,6 @@ class Modify | |||||||
| 
 | 
 | ||||||
|     private function generateKey(): string |     private function generateKey(): string | ||||||
|     { |     { | ||||||
|         return substr(hash('sha256', serialize($this->attributes) . random_bytes(10) . microtime()), 0, 7); |         return substr(hash('sha256', serialize($this->attributes) . random_bytes(5) . microtime()), 0, 7); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
							
								
								
									
										9
									
								
								src/Interfaces/Session/ISessionHandler.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								src/Interfaces/Session/ISessionHandler.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,9 @@ | |||||||
|  | <?php namespace MapGuesser\Interfaces\Session; | ||||||
|  | 
 | ||||||
|  | use SessionHandlerInterface; | ||||||
|  | use SessionIdInterface; | ||||||
|  | use SessionUpdateTimestampHandlerInterface; | ||||||
|  | 
 | ||||||
|  | interface ISessionHandler extends SessionHandlerInterface, SessionIdInterface, SessionUpdateTimestampHandlerInterface | ||||||
|  | { | ||||||
|  | } | ||||||
| @ -15,7 +15,7 @@ class GoogleOAuth | |||||||
|         $this->request = $request; |         $this->request = $request; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function getDialogUrl(string $state, string $redirectUrl, ?string $loginHint = null): string |     public function getDialogUrl(string $state, string $redirectUrl, ?string $nonce = null, ?string $loginHint = null): string | ||||||
|     { |     { | ||||||
|         $oauthParams = [ |         $oauthParams = [ | ||||||
|             'response_type' => 'code', |             'response_type' => 'code', | ||||||
| @ -23,9 +23,12 @@ class GoogleOAuth | |||||||
|             'scope' => 'openid email', |             'scope' => 'openid email', | ||||||
|             'redirect_uri' => $redirectUrl, |             'redirect_uri' => $redirectUrl, | ||||||
|             'state' => $state, |             'state' => $state, | ||||||
|             'nonce' => hash('sha256', random_bytes(10) . microtime()), |  | ||||||
|         ]; |         ]; | ||||||
| 
 | 
 | ||||||
|  |         if ($nonce !== null) { | ||||||
|  |             $oauthParams['nonce'] = $nonce; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|         if ($loginHint !== null) { |         if ($loginHint !== null) { | ||||||
|             $oauthParams['login_hint'] = $loginHint; |             $oauthParams['login_hint'] = $loginHint; | ||||||
|         } |         } | ||||||
|  | |||||||
| @ -166,7 +166,7 @@ class Place extends Model | |||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         $this->panoIdCached = $panoId; |         $this->panoIdCached = $panoId; | ||||||
|         $this->panoIdCachedTimestamp = new DateTime('now'); |         $this->panoIdCachedTimestamp = new DateTime(); | ||||||
| 
 | 
 | ||||||
|         (new PersistentDataManager())->saveToDb($this); |         (new PersistentDataManager())->saveToDb($this); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,12 +1,13 @@ | |||||||
| <?php namespace MapGuesser\PersistentData\Model; | <?php namespace MapGuesser\PersistentData\Model; | ||||||
| 
 | 
 | ||||||
|  | use DateTime; | ||||||
| use MapGuesser\Interfaces\Authentication\IUser; | use MapGuesser\Interfaces\Authentication\IUser; | ||||||
| 
 | 
 | ||||||
| class User extends Model implements IUser | class User extends Model implements IUser | ||||||
| { | { | ||||||
|     protected static string $table = 'users'; |     protected static string $table = 'users'; | ||||||
| 
 | 
 | ||||||
|     protected static array $fields = ['email', 'password', 'type', 'active', 'google_sub']; |     protected static array $fields = ['email', 'password', 'type', 'active', 'google_sub', 'created']; | ||||||
| 
 | 
 | ||||||
|     private static array $types = ['user', 'admin']; |     private static array $types = ['user', 'admin']; | ||||||
| 
 | 
 | ||||||
| @ -20,6 +21,8 @@ class User extends Model implements IUser | |||||||
| 
 | 
 | ||||||
|     private ?string $googleSub = null; |     private ?string $googleSub = null; | ||||||
| 
 | 
 | ||||||
|  |     private DateTime $created; | ||||||
|  | 
 | ||||||
|     public function setEmail(string $email): void |     public function setEmail(string $email): void | ||||||
|     { |     { | ||||||
|         $this->email = $email; |         $this->email = $email; | ||||||
| @ -52,6 +55,16 @@ class User extends Model implements IUser | |||||||
|         $this->googleSub = $googleSub; |         $this->googleSub = $googleSub; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     public function setCreatedDate(DateTime $created): void | ||||||
|  |     { | ||||||
|  |         $this->created = $created; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public function setCreated(string $created): void | ||||||
|  |     { | ||||||
|  |         $this->created = new DateTime($created); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     public function getEmail(): string |     public function getEmail(): string | ||||||
|     { |     { | ||||||
|         return $this->email; |         return $this->email; | ||||||
| @ -77,6 +90,16 @@ class User extends Model implements IUser | |||||||
|         return $this->googleSub; |         return $this->googleSub; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     public function getCreatedData(): DateTime | ||||||
|  |     { | ||||||
|  |         return $this->created; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public function getCreated(): string | ||||||
|  |     { | ||||||
|  |         return $this->created->format('Y-m-d H:i:s'); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     public function hasPermission(int $permission): bool |     public function hasPermission(int $permission): bool | ||||||
|     { |     { | ||||||
|         switch ($permission) { |         switch ($permission) { | ||||||
|  | |||||||
| @ -1,10 +1,12 @@ | |||||||
| <?php namespace MapGuesser\PersistentData\Model; | <?php namespace MapGuesser\PersistentData\Model; | ||||||
| 
 | 
 | ||||||
|  | use DateTime; | ||||||
|  | 
 | ||||||
| class UserConfirmation extends Model | class UserConfirmation extends Model | ||||||
| { | { | ||||||
|     protected static string $table = 'user_confirmations'; |     protected static string $table = 'user_confirmations'; | ||||||
| 
 | 
 | ||||||
|     protected static array $fields = ['user_id', 'token']; |     protected static array $fields = ['user_id', 'token', 'last_sent']; | ||||||
| 
 | 
 | ||||||
|     protected static array $relations = ['user' => User::class]; |     protected static array $relations = ['user' => User::class]; | ||||||
| 
 | 
 | ||||||
| @ -14,6 +16,8 @@ class UserConfirmation extends Model | |||||||
| 
 | 
 | ||||||
|     private string $token = ''; |     private string $token = ''; | ||||||
| 
 | 
 | ||||||
|  |     private DateTime $lastSent; | ||||||
|  | 
 | ||||||
|     public function setUser(User $user): void |     public function setUser(User $user): void | ||||||
|     { |     { | ||||||
|         $this->user = $user; |         $this->user = $user; | ||||||
| @ -29,6 +33,16 @@ class UserConfirmation extends Model | |||||||
|         $this->token = $token; |         $this->token = $token; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     public function setLastSentDate(DateTime $lastSent): void | ||||||
|  |     { | ||||||
|  |         $this->lastSent = $lastSent; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public function setLastSent(string $lastSent): void | ||||||
|  |     { | ||||||
|  |         $this->lastSent = new DateTime($lastSent); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     public function getUser(): ?User |     public function getUser(): ?User | ||||||
|     { |     { | ||||||
|         return $this->user; |         return $this->user; | ||||||
| @ -43,4 +57,14 @@ class UserConfirmation extends Model | |||||||
|     { |     { | ||||||
|         return $this->token; |         return $this->token; | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     public function getLastSentDate(): DateTime | ||||||
|  |     { | ||||||
|  |         return $this->lastSent; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public function getLastSent(): string | ||||||
|  |     { | ||||||
|  |         return $this->lastSent->format('Y-m-d H:i:s'); | ||||||
|  |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,8 +1,6 @@ | |||||||
| <?php namespace MapGuesser\Repository; | <?php namespace MapGuesser\Repository; | ||||||
| 
 | 
 | ||||||
| use Generator; |  | ||||||
| use MapGuesser\Database\Query\Select; | use MapGuesser\Database\Query\Select; | ||||||
| use MapGuesser\Interfaces\Database\IResultSet; |  | ||||||
| use MapGuesser\PersistentData\Model\User; | use MapGuesser\PersistentData\Model\User; | ||||||
| use MapGuesser\PersistentData\Model\UserConfirmation; | use MapGuesser\PersistentData\Model\UserConfirmation; | ||||||
| use MapGuesser\PersistentData\PersistentDataManager; | use MapGuesser\PersistentData\PersistentDataManager; | ||||||
| @ -29,11 +27,11 @@ class UserConfirmationRepository | |||||||
|         return $this->pdm->selectFromDb($select, UserConfirmation::class); |         return $this->pdm->selectFromDb($select, UserConfirmation::class); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function getByUser(User $user): Generator |     public function getByUser(User $user): ?UserConfirmation | ||||||
|     { |     { | ||||||
|         $select = new Select(\Container::$dbConnection); |         $select = new Select(\Container::$dbConnection); | ||||||
|         $select->where('user_id', '=', $user->getId()); |         $select->where('user_id', '=', $user->getId()); | ||||||
| 
 | 
 | ||||||
|         yield from $this->pdm->selectMultipleFromDb($select, UserConfirmation::class); |         return $this->pdm->selectFromDb($select, UserConfirmation::class); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,6 +1,5 @@ | |||||||
| <?php namespace MapGuesser\Repository; | <?php namespace MapGuesser\Repository; | ||||||
| 
 | 
 | ||||||
| use Generator; |  | ||||||
| use MapGuesser\Database\Query\Select; | use MapGuesser\Database\Query\Select; | ||||||
| use MapGuesser\PersistentData\Model\User; | use MapGuesser\PersistentData\Model\User; | ||||||
| use MapGuesser\PersistentData\Model\UserPasswordResetter; | use MapGuesser\PersistentData\Model\UserPasswordResetter; | ||||||
| @ -28,11 +27,11 @@ class UserPasswordResetterRepository | |||||||
|         return $this->pdm->selectFromDb($select, UserPasswordResetter::class); |         return $this->pdm->selectFromDb($select, UserPasswordResetter::class); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function getByUser(User $user): Generator |     public function getByUser(User $user): ?UserPasswordResetter | ||||||
|     { |     { | ||||||
|         $select = new Select(\Container::$dbConnection); |         $select = new Select(\Container::$dbConnection); | ||||||
|         $select->where('user_id', '=', $user->getId()); |         $select->where('user_id', '=', $user->getId()); | ||||||
| 
 | 
 | ||||||
|         yield from $this->pdm->selectMultipleFromDb($select, UserPasswordResetter::class); |         return $this->pdm->selectFromDb($select, UserPasswordResetter::class); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -4,11 +4,9 @@ use DateTime; | |||||||
| use MapGuesser\Database\Query\Modify; | use MapGuesser\Database\Query\Modify; | ||||||
| use MapGuesser\Database\Query\Select; | use MapGuesser\Database\Query\Select; | ||||||
| use MapGuesser\Interfaces\Database\IResultSet; | use MapGuesser\Interfaces\Database\IResultSet; | ||||||
| use SessionHandlerInterface; | use MapGuesser\Interfaces\Session\ISessionHandler; | ||||||
| use SessionIdInterface; |  | ||||||
| use SessionUpdateTimestampHandlerInterface; |  | ||||||
| 
 | 
 | ||||||
| class DatabaseSessionHandler implements SessionHandlerInterface, SessionIdInterface, SessionUpdateTimestampHandlerInterface | class DatabaseSessionHandler implements ISessionHandler | ||||||
| { | { | ||||||
|     private bool $exists = false; |     private bool $exists = false; | ||||||
| 
 | 
 | ||||||
| @ -28,7 +26,7 @@ class DatabaseSessionHandler implements SessionHandlerInterface, SessionIdInterf | |||||||
|     { |     { | ||||||
|         $select = new Select(\Container::$dbConnection, 'sessions'); |         $select = new Select(\Container::$dbConnection, 'sessions'); | ||||||
|         $select->columns(['data']); |         $select->columns(['data']); | ||||||
|         $select->whereId($id); |         $select->whereId(substr($id, 0, 32)); | ||||||
| 
 | 
 | ||||||
|         $result = $select->execute()->fetch(IResultSet::FETCH_ASSOC); |         $result = $select->execute()->fetch(IResultSet::FETCH_ASSOC); | ||||||
| 
 | 
 | ||||||
| @ -46,16 +44,16 @@ class DatabaseSessionHandler implements SessionHandlerInterface, SessionIdInterf | |||||||
|         $modify = new Modify(\Container::$dbConnection, 'sessions'); |         $modify = new Modify(\Container::$dbConnection, 'sessions'); | ||||||
| 
 | 
 | ||||||
|         if ($this->exists) { |         if ($this->exists) { | ||||||
|             $modify->setId($id); |             $modify->setId(substr($id, 0, 32)); | ||||||
|         } else { |         } else { | ||||||
|             $modify->setExternalId($id); |             $modify->setExternalId(substr($id, 0, 32)); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         $modify->set('data', $data); |         $modify->set('data', $data); | ||||||
|         $modify->set('updated', (new DateTime())->format('Y-m-d H:i:s')); |         $modify->set('updated', (new DateTime())->format('Y-m-d H:i:s')); | ||||||
|         $modify->save(); |         $modify->save(); | ||||||
| 
 | 
 | ||||||
|         $written = true; |         $this->written = true; | ||||||
| 
 | 
 | ||||||
|         return true; |         return true; | ||||||
|     } |     } | ||||||
| @ -63,9 +61,11 @@ class DatabaseSessionHandler implements SessionHandlerInterface, SessionIdInterf | |||||||
|     public function destroy($id): bool |     public function destroy($id): bool | ||||||
|     { |     { | ||||||
|         $modify = new Modify(\Container::$dbConnection, 'sessions'); |         $modify = new Modify(\Container::$dbConnection, 'sessions'); | ||||||
|         $modify->setId($id); |         $modify->setId(substr($id, 0, 32)); | ||||||
|         $modify->delete(); |         $modify->delete(); | ||||||
| 
 | 
 | ||||||
|  |         $this->exists = false; | ||||||
|  | 
 | ||||||
|         return true; |         return true; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -88,12 +88,12 @@ class DatabaseSessionHandler implements SessionHandlerInterface, SessionIdInterf | |||||||
| 
 | 
 | ||||||
|     public function create_sid(): string |     public function create_sid(): string | ||||||
|     { |     { | ||||||
|         return hash('sha256', random_bytes(10) . microtime()); |         return bin2hex(random_bytes(16)); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function validateId($id): bool |     public function validateId($id): bool | ||||||
|     { |     { | ||||||
|         return preg_match('/^[a-f0-9]{64}$/', $id); |         return preg_match('/^[a-f0-9]{32}$/', $id); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function updateTimestamp($id, $data): bool |     public function updateTimestamp($id, $data): bool | ||||||
| @ -104,7 +104,7 @@ class DatabaseSessionHandler implements SessionHandlerInterface, SessionIdInterf | |||||||
| 
 | 
 | ||||||
|         $modify = new Modify(\Container::$dbConnection, 'sessions'); |         $modify = new Modify(\Container::$dbConnection, 'sessions'); | ||||||
| 
 | 
 | ||||||
|         $modify->setId($id); |         $modify->setId(substr($id, 0, 32)); | ||||||
|         $modify->set('updated', (new DateTime())->format('Y-m-d H:i:s')); |         $modify->set('updated', (new DateTime())->format('Y-m-d H:i:s')); | ||||||
|         $modify->save(); |         $modify->save(); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -13,6 +13,7 @@ final class GoogleOAuthTest extends TestCase | |||||||
|     { |     { | ||||||
|         $_ENV['GOOGLE_OAUTH_CLIENT_ID'] = 'xyz'; |         $_ENV['GOOGLE_OAUTH_CLIENT_ID'] = 'xyz'; | ||||||
|         $state = 'random_state_string'; |         $state = 'random_state_string'; | ||||||
|  |         $nonce = 'random_nonce_string'; | ||||||
|         $redirectUrl = 'http://example.com/oauth'; |         $redirectUrl = 'http://example.com/oauth'; | ||||||
| 
 | 
 | ||||||
|         $requestMock = $this->getMockBuilder(IRequest::class) |         $requestMock = $this->getMockBuilder(IRequest::class) | ||||||
| @ -20,7 +21,7 @@ final class GoogleOAuthTest extends TestCase | |||||||
|             ->getMock(); |             ->getMock(); | ||||||
|         $googleOAuth = new GoogleOAuth($requestMock); |         $googleOAuth = new GoogleOAuth($requestMock); | ||||||
| 
 | 
 | ||||||
|         $dialogUrl = $googleOAuth->getDialogUrl($state, $redirectUrl); |         $dialogUrl = $googleOAuth->getDialogUrl($state, $redirectUrl, $nonce); | ||||||
|         $dialogUrlParsed = explode('?', $dialogUrl); |         $dialogUrlParsed = explode('?', $dialogUrl); | ||||||
| 
 | 
 | ||||||
|         $this->assertEquals('https://accounts.google.com/o/oauth2/v2/auth', $dialogUrlParsed[0]); |         $this->assertEquals('https://accounts.google.com/o/oauth2/v2/auth', $dialogUrlParsed[0]); | ||||||
| @ -33,15 +34,10 @@ final class GoogleOAuthTest extends TestCase | |||||||
|             'scope' => 'openid email', |             'scope' => 'openid email', | ||||||
|             'redirect_uri' => $redirectUrl, |             'redirect_uri' => $redirectUrl, | ||||||
|             'state' => $state, |             'state' => $state, | ||||||
|             'nonce' => hash('sha256', random_bytes(10) . microtime()), |             'nonce' => $nonce, | ||||||
|         ]; |         ]; | ||||||
| 
 | 
 | ||||||
|         $this->assertEquals($expectedQueryParams['response_type'], $dialogUrlQueryParams['response_type']); |         $this->assertEquals($expectedQueryParams, $dialogUrlQueryParams); | ||||||
|         $this->assertEquals($expectedQueryParams['client_id'], $dialogUrlQueryParams['client_id']); |  | ||||||
|         $this->assertEquals($expectedQueryParams['scope'], $dialogUrlQueryParams['scope']); |  | ||||||
|         $this->assertEquals($expectedQueryParams['redirect_uri'], $dialogUrlQueryParams['redirect_uri']); |  | ||||||
|         $this->assertEquals($expectedQueryParams['state'], $dialogUrlQueryParams['state']); |  | ||||||
|         $this->assertMatchesRegularExpression('/^[a-f0-9]{64}$/', $dialogUrlQueryParams['nonce']); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function testCanRequestToken(): void |     public function testCanRequestToken(): void | ||||||
|  | |||||||
							
								
								
									
										7
									
								
								web.php
									
									
									
									
									
								
							
							
						
						
									
										7
									
								
								web.php
									
									
									
									
									
								
							| @ -71,6 +71,11 @@ if (isset($_COOKIE['COOKIES_CONSENT'])) { | |||||||
|         'cookie_httponly' => true, |         'cookie_httponly' => true, | ||||||
|         'cookie_samesite' => 'Lax' |         'cookie_samesite' => 'Lax' | ||||||
|     ]); |     ]); | ||||||
|  | 
 | ||||||
|  |     // this is needed to handle old type of session IDs
 | ||||||
|  |     if (!Container::$sessionHandler->validateId(session_id())) { | ||||||
|  |         session_regenerate_id(true); | ||||||
|  |     } | ||||||
| } else { | } else { | ||||||
|     $_SESSION = []; |     $_SESSION = []; | ||||||
| } | } | ||||||
| @ -78,5 +83,5 @@ if (isset($_COOKIE['COOKIES_CONSENT'])) { | |||||||
| Container::$request = new MapGuesser\Request\Request($_SERVER['REQUEST_SCHEME'] . '://' . $_SERVER['HTTP_HOST'], $_GET, $_POST, $_SESSION); | Container::$request = new MapGuesser\Request\Request($_SERVER['REQUEST_SCHEME'] . '://' . $_SERVER['HTTP_HOST'], $_GET, $_POST, $_SESSION); | ||||||
| 
 | 
 | ||||||
| if (!Container::$request->session()->has('anti_csrf_token')) { | if (!Container::$request->session()->has('anti_csrf_token')) { | ||||||
|     Container::$request->session()->set('anti_csrf_token', hash('sha256', random_bytes(10) . microtime())); |     Container::$request->session()->set('anti_csrf_token', bin2hex(random_bytes(16))); | ||||||
| } | } | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user