diff --git a/composer.json b/composer.json index 4588c09..1433edc 100644 --- a/composer.json +++ b/composer.json @@ -10,7 +10,7 @@ } ], "require": { - "esoko/soko-web": "0.1", + "esoko/soko-web": "0.3", "fzaninotto/faker": "^1.9" }, "require-dev": { diff --git a/composer.lock b/composer.lock index d494ac6..8f96e53 100644 --- a/composer.lock +++ b/composer.lock @@ -4,15 +4,15 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "4c91d823e927375028a2b3c9d974a8bd", + "content-hash": "0f82e530dedc414cfc0b04daf3c2085c", "packages": [ { "name": "esoko/soko-web", - "version": "v0.1", + "version": "v0.3", "source": { "type": "git", "url": "https://git.esoko.eu/esoko/soko-web.git", - "reference": "a17a88e9d44e42cef3d996fc66b4dd1134b85def" + "reference": "014a5480967c03c00dda5ee34c7eaf4be224b96e" }, "require": { "phpmailer/phpmailer": "^6.8", @@ -33,7 +33,7 @@ "GNU GPL 3.0" ], "description": "Lightweight web framework", - "time": "2023-04-07T17:32:15+00:00" + "time": "2023-04-16T14:54:22+00:00" }, { "name": "fzaninotto/faker", diff --git a/docker-compose.yml b/docker-compose.yml index 16a2306..b13127e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -29,6 +29,12 @@ services: MYSQL_DATABASE: 'mapguesser' MYSQL_USER: 'mapguesser' MYSQL_PASSWORD: 'mapguesser' + adminer: + image: adminer:4.8.1-standalone + ports: + - 9090:8080 + environment: + - ADMINER_DEFAULT_SERVER=mariadb mail: image: marcopas/docker-mailslurper:latest ports: diff --git a/public/index.php b/public/index.php index d48250f..3d7c84c 100644 --- a/public/index.php +++ b/public/index.php @@ -1,5 +1,13 @@ getHandler(); $controller = new $handler[0](Container::$request); - if ($controller instanceof SokoWeb\Interfaces\Authorization\ISecured) { - $authorized = $controller->authorize(); - } else { - $authorized = true; + if ( + $controller instanceof IAuthenticationRequired && + $controller->isAuthenticationRequired() && + Container::$request->user() === null + ) { + Container::$request->session()->set('redirect_after_login', substr($_SERVER['REQUEST_URI'], strlen('/'))); + $response = new Redirect(Container::$routeCollection->getRoute('login')->generateLink(), IRedirect::TEMPORARY); + header('Location: ' . $response->getUrl(), true, $response->getHttpCode()); + return; } if ($method === 'post' && Container::$request->post('anti_csrf_token') !== Container::$request->session()->get('anti_csrf_token')) { - $content = new SokoWeb\Response\JsonContent(['error' => 'no_valid_anti_csrf_token']); + $content = new JsonContent(['error' => 'no_valid_anti_csrf_token']); header('Content-Type: text/html; charset=UTF-8', true, 403); $content->render(); return; } - if ($authorized) { + if ( + !($controller instanceof ISecured) || + $controller->authorize() + ) { $response = call_user_func([$controller, $handler[1]]); - - if ($response instanceof SokoWeb\Interfaces\Response\IContent) { + if ($response instanceof IContent) { header('Content-Type: ' . $response->getContentType() . '; charset=UTF-8'); $response->render(); - return; - } elseif ($response instanceof SokoWeb\Interfaces\Response\IRedirect) { + } elseif ($response instanceof IRedirect) { header('Location: ' . $response->getUrl(), true, $response->getHttpCode()); - return; } } } -$content = new SokoWeb\Response\HtmlContent('error/404'); +$content = new HtmlContent('error/404'); header('Content-Type: text/html; charset=UTF-8', true, 404); $content->render(); diff --git a/public/static/js/maps.js b/public/static/js/maps.js index 35c4e33..d8f7a9f 100644 --- a/public/static/js/maps.js +++ b/public/static/js/maps.js @@ -69,7 +69,7 @@ printTimeForHuman: function (time) { const minutes = Math.floor(time / 60); const seconds = time % 60; - var time_str = ''; + var time_str = ''; if (minutes == 1) { time_str += '1 minute'; @@ -141,9 +141,7 @@ if (document.getElementById('challengeButton')) { document.getElementById('challengeButton').onclick = function () { MapGuesser.showModal('challenge'); - document.getElementById('createNewChallengeButton').href = '/challenge/new/' + this.dataset.mapId; document.getElementById('playMode').style.visibility = 'hidden'; - var timeLimit = document.getElementById('timeLimit').value; document.getElementById('timeLimitLabel').innerText = 'Time limit of ' + Util.printTimeForHuman(timeLimit); }; diff --git a/src/Controller/GameController.php b/src/Controller/GameController.php index 55caf22..db6b35c 100644 --- a/src/Controller/GameController.php +++ b/src/Controller/GameController.php @@ -2,7 +2,7 @@ use DateTime; use Faker\Factory; -use SokoWeb\Interfaces\Authorization\ISecured; +use SokoWeb\Interfaces\Authentication\IAuthenticationRequired; use SokoWeb\Interfaces\Request\IRequest; use SokoWeb\Response\HtmlContent; use SokoWeb\Response\JsonContent; @@ -21,7 +21,7 @@ use MapGuesser\Repository\PlaceRepository; use MapGuesser\Repository\UserInChallengeRepository; use SokoWeb\Response\Redirect; -class GameController implements ISecured +class GameController implements IAuthenticationRequired { const NUMBER_OF_ROUNDS = 5; @@ -53,9 +53,9 @@ class GameController implements ISecured $this->userInChallengeRepository = new UserInChallengeRepository(); } - public function authorize(): bool + public function isAuthenticationRequired(): bool { - return !empty($_ENV['ENABLE_GAME_FOR_GUESTS']) || $this->request->user() !== null; + return empty($_ENV['ENABLE_GAME_FOR_GUESTS']); } public function getGame(): IContent diff --git a/src/Controller/GameFlowController.php b/src/Controller/GameFlowController.php index 9ef5cb2..da076a8 100644 --- a/src/Controller/GameFlowController.php +++ b/src/Controller/GameFlowController.php @@ -1,7 +1,7 @@ guessRepository = new GuessRepository(); } - public function authorize(): bool + public function isAuthenticationRequired(): bool { - return !empty($_ENV['ENABLE_GAME_FOR_GUESTS']) || $this->request->user() !== null; + return empty($_ENV['ENABLE_GAME_FOR_GUESTS']); } public function initialData(): IContent diff --git a/src/Controller/LoginController.php b/src/Controller/LoginController.php index 5536d23..fcf24be 100644 --- a/src/Controller/LoginController.php +++ b/src/Controller/LoginController.php @@ -36,6 +36,8 @@ class LoginController private UserPlayedPlaceRepository $userPlayedPlaceRepository; + private string $redirectUrl; + public function __construct(IRequest $request) { $this->request = $request; @@ -44,15 +46,19 @@ class LoginController $this->userConfirmationRepository = new UserConfirmationRepository(); $this->userPasswordResetterRepository = new UserPasswordResetterRepository(); $this->userPlayedPlaceRepository = new UserPlayedPlaceRepository(); + $this->redirectUrl = $this->request->session()->has('redirect_after_login') ? + $this->request->session()->get('redirect_after_login') : + \Container::$routeCollection->getRoute('index')->generateLink(); } public function getLoginForm() { if ($this->request->user() !== null) { - return new Redirect(\Container::$routeCollection->getRoute('index')->generateLink(), IRedirect::TEMPORARY); + $this->deleteRedirectUrl(); + return new Redirect($this->redirectUrl, IRedirect::TEMPORARY); } - return new HtmlContent('login/login'); + return new HtmlContent('login/login', ['redirectUrl' => '/' . $this->redirectUrl]); } public function getGoogleLoginRedirect(): IRedirect @@ -76,7 +82,8 @@ class LoginController public function getSignupForm() { if ($this->request->user() !== null) { - return new Redirect(\Container::$routeCollection->getRoute('index')->generateLink(), IRedirect::TEMPORARY); + $this->deleteRedirectUrl(); + return new Redirect($this->redirectUrl, IRedirect::TEMPORARY); } if ($this->request->session()->has('tmp_user_data')) { @@ -98,7 +105,8 @@ class LoginController public function getSignupWithGoogleForm() { if ($this->request->user() !== null) { - return new Redirect(\Container::$routeCollection->getRoute('index')->generateLink(), IRedirect::TEMPORARY); + $this->deleteRedirectUrl(); + return new Redirect($this->redirectUrl, IRedirect::TEMPORARY); } if (!$this->request->session()->has('google_user_data')) { @@ -109,13 +117,14 @@ class LoginController $user = $this->userRepository->getByEmail($userData['email']); - return new HtmlContent('login/google_signup', ['found' => $user !== null, 'email' => $userData['email']]); + return new HtmlContent('login/google_signup', ['found' => $user !== null, 'email' => $userData['email'], 'redirectUrl' => '/' . $this->redirectUrl]); } public function getRequestPasswordResetForm() { if ($this->request->user() !== null) { - return new Redirect(\Container::$routeCollection->getRoute('index')->generateLink(), IRedirect::TEMPORARY); + $this->deleteRedirectUrl(); + return new Redirect($this->redirectUrl, IRedirect::TEMPORARY); } return new HtmlContent('login/password_reset_request', ['email' => $this->request->query('email')]); @@ -129,7 +138,8 @@ class LoginController public function getResetPasswordForm() { if ($this->request->user() !== null) { - return new Redirect(\Container::$routeCollection->getRoute('index')->generateLink(), IRedirect::TEMPORARY); + $this->deleteRedirectUrl(); + return new Redirect($this->redirectUrl, IRedirect::TEMPORARY); } $token = $this->request->query('token'); @@ -141,12 +151,13 @@ class LoginController $user = $this->userRepository->getById($resetter->getUserId()); - return new HtmlContent('login/reset_password', ['success' => true, 'token' => $token, 'email' => $user->getEmail()]); + return new HtmlContent('login/reset_password', ['success' => true, 'token' => $token, 'email' => $user->getEmail(), 'redirectUrl' => '/' . $this->redirectUrl]); } public function login(): IContent { if ($this->request->user() !== null) { + $this->deleteRedirectUrl(); return new JsonContent(['success' => true]); } @@ -198,13 +209,15 @@ class LoginController $this->request->setUser($user); + $this->deleteRedirectUrl(); return new JsonContent(['success' => true]); } public function loginWithGoogle() { if ($this->request->user() !== null) { - return new Redirect(\Container::$routeCollection->getRoute('index')->generateLink(), IRedirect::TEMPORARY); + $this->deleteRedirectUrl(); + return new Redirect($this->redirectUrl, IRedirect::TEMPORARY); } if ($this->request->query('state') !== $this->request->session()->get('oauth_state')) { @@ -242,7 +255,8 @@ class LoginController $this->request->setUser($user); - return new Redirect(\Container::$routeCollection->getRoute('index')->generateLink(), IRedirect::TEMPORARY); + $this->deleteRedirectUrl(); + return new Redirect($this->redirectUrl, IRedirect::TEMPORARY); } public function logout(): IRedirect @@ -255,7 +269,8 @@ class LoginController public function signup(): IContent { if ($this->request->user() !== null) { - return new JsonContent(['redirect' => ['target' => '/' . \Container::$routeCollection->getRoute('home')->generateLink()]]); + $this->deleteRedirectUrl(); + return new JsonContent(['redirect' => ['target' => '/' . $this->redirectUrl]]); } $user = $this->userRepository->getByEmail($this->request->post('email')); @@ -274,7 +289,8 @@ class LoginController $this->request->setUser($user); - $data = ['redirect' => ['target' => '/' . \Container::$routeCollection->getRoute('index')->generateLink()]]; + $this->deleteRedirectUrl(); + $data = ['redirect' => ['target' => '/' . $this->redirectUrl]]; } else { $data = [ 'error' => [ @@ -355,6 +371,7 @@ class LoginController public function signupWithGoogle(): IContent { if ($this->request->user() !== null) { + $this->deleteRedirectUrl(); return new JsonContent(['success' => true]); } @@ -384,6 +401,7 @@ class LoginController $this->request->session()->delete('google_user_data'); $this->request->setUser($user); + $this->deleteRedirectUrl(); return new JsonContent(['success' => true]); } @@ -404,7 +422,8 @@ class LoginController public function activate() { if ($this->request->user() !== null) { - return new Redirect(\Container::$routeCollection->getRoute('index')->generateLink(), IRedirect::TEMPORARY); + $this->deleteRedirectUrl(); + return new Redirect($this->redirectUrl, IRedirect::TEMPORARY); } $confirmation = $this->userConfirmationRepository->getByToken(substr($this->request->query('token'), 0, 32)); @@ -426,13 +445,15 @@ class LoginController $this->request->setUser($user); - return new Redirect(\Container::$routeCollection->getRoute('index')->generateLink(), IRedirect::TEMPORARY); + $this->deleteRedirectUrl(); + return new Redirect($this->redirectUrl, IRedirect::TEMPORARY); } public function cancel() { if ($this->request->user() !== null) { - return new Redirect(\Container::$routeCollection->getRoute('index')->generateLink(), IRedirect::TEMPORARY); + $this->deleteRedirectUrl(); + return new Redirect($this->redirectUrl, IRedirect::TEMPORARY); } $confirmation = $this->userConfirmationRepository->getByToken(substr($this->request->query('token'), 0, 32)); @@ -461,9 +482,10 @@ class LoginController public function requestPasswordReset(): IContent { if ($this->request->user() !== null) { + $this->deleteRedirectUrl(); return new JsonContent([ 'redirect' => [ - 'target' => '/' . \Container::$routeCollection->getRoute('home')->generateLink() + 'target' => '/' . $this->redirectUrl ] ]); } @@ -538,9 +560,10 @@ class LoginController public function resetPassword(): IContent { if ($this->request->user() !== null) { + $this->deleteRedirectUrl(); return new JsonContent([ 'redirect' => [ - 'target' => '/' . \Container::$routeCollection->getRoute('home')->generateLink() + 'target' => '/' . $this->redirectUrl ] ]); } @@ -581,6 +604,7 @@ class LoginController $this->request->setUser($user); + $this->deleteRedirectUrl(); return new JsonContent(['success' => true]); } @@ -641,4 +665,9 @@ class LoginController ]); $mail->send(); } + + private function deleteRedirectUrl(): void + { + $this->request->session()->delete('redirect_after_login'); + } } diff --git a/src/Controller/MapAdminController.php b/src/Controller/MapAdminController.php index 8d5656e..47a972f 100644 --- a/src/Controller/MapAdminController.php +++ b/src/Controller/MapAdminController.php @@ -2,6 +2,7 @@ use DateTime; use SokoWeb\Interfaces\Authentication\IUser; +use SokoWeb\Interfaces\Authentication\IAuthenticationRequired; use SokoWeb\Interfaces\Authorization\ISecured; use SokoWeb\Interfaces\Request\IRequest; use SokoWeb\Interfaces\Response\IContent; @@ -22,7 +23,7 @@ use SokoWeb\Response\JsonContent; use MapGuesser\Util\Geo\Bounds; use MapGuesser\Util\Panorama\Pov; -class MapAdminController implements ISecured +class MapAdminController implements IAuthenticationRequired, ISecured { private static string $unnamedMapName = '[unnamed map]'; @@ -57,11 +58,14 @@ class MapAdminController implements ISecured $this->userInChallengeRepository = new UserInChallengeRepository(); } + public function isAuthenticationRequired(): bool + { + return true; + } + public function authorize(): bool { - $user = $this->request->user(); - - return $user !== null && $user->hasPermission(IUser::PERMISSION_ADMIN); + return $this->request->user()->hasPermission(IUser::PERMISSION_ADMIN); } public function getMapEditor(): IContent diff --git a/src/Controller/UserController.php b/src/Controller/UserController.php index 9a7cdb6..4988797 100644 --- a/src/Controller/UserController.php +++ b/src/Controller/UserController.php @@ -2,7 +2,7 @@ use DateTime; use SokoWeb\Http\Request; -use SokoWeb\Interfaces\Authorization\ISecured; +use SokoWeb\Interfaces\Authentication\IAuthenticationRequired; use SokoWeb\Interfaces\Request\IRequest; use SokoWeb\Interfaces\Response\IContent; use SokoWeb\Interfaces\Response\IRedirect; @@ -19,7 +19,7 @@ use SokoWeb\Response\JsonContent; use SokoWeb\Response\Redirect; use SokoWeb\Util\JwtParser; -class UserController implements ISecured +class UserController implements IAuthenticationRequired { private IRequest $request; @@ -46,11 +46,9 @@ class UserController implements ISecured $this->guessRepository = new GuessRepository(); } - public function authorize(): bool + public function isAuthenticationRequired(): bool { - $user = $this->request->user(); - - return $user !== null; + return true; } public function getAccount(): IContent diff --git a/views/login/google_signup.php b/views/login/google_signup.php index 1cb0149..e056e58 100644 --- a/views/login/google_signup.php +++ b/views/login/google_signup.php @@ -5,7 +5,7 @@ @section(main)