From 48eba75cf49f09935ecbdfbedabac006c880a6b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C5=91cze=20Bence?= Date: Sun, 28 Jun 2020 03:12:16 +0200 Subject: [PATCH 1/6] MAPG-181 fix and unify styles for buttons and inputs --- public/static/css/mapguesser.css | 66 ++++++++++++++++++++++++++------ 1 file changed, 55 insertions(+), 11 deletions(-) diff --git a/public/static/css/mapguesser.css b/public/static/css/mapguesser.css index c00b5ab..64b50bc 100644 --- a/public/static/css/mapguesser.css +++ b/public/static/css/mapguesser.css @@ -153,13 +153,27 @@ button, a.button { line-height: 35px; } -button.small { +button.small, div.inputWithButton>button { font-size: 14px; padding: 0 12px; height: 32px; line-height: 32px; } +button.small { + height: 32px; + line-height: 32px; +} + +div.inputWithButton>button { + border-radius: 2px; + height: 27px; + line-height: 27px; + width: 75px; + margin-left: -79px; + vertical-align: 2px; +} + button:enabled:hover, button:enabled:focus, a.button:hover, a.button:focus { background-color: #29457f; outline: none; @@ -191,7 +205,7 @@ button.gray, a.button.gray { background-color: #808080; } -button.gray:hover, button.gray:focus, a.button.gray:hover, a.button.gray:focus { +button.gray:enabled:hover, button.gray:enabled:focus, a.button.gray:hover, a.button.gray:focus { background-color: #555555; } @@ -199,7 +213,7 @@ button.red, a.button.red { background-color: #aa5e5e; } -button.red:hover, button.red:focus, a.button.red:hover, a.button.red:focus { +button.red:enabled:hover, button.red:enabled:focus, a.button.red:hover, a.button.red:focus { background-color: #7f2929; } @@ -207,7 +221,7 @@ button.yellow, a.button.yellow { background-color: #e8a349; } -button.yellow:hover, button.yellow:focus, a.button.yellow:hover, a.button.yellow:focus { +button.yellow:enabled:hover, button.yellow:enabled:focus, a.button.yellow:hover, a.button.yellow:focus { background-color: #c37713; } @@ -215,7 +229,7 @@ button.green, a.button.green { background-color: #28a745; } -button.green:hover, button.green:focus, a.button.green:hover, a.button.green:focus { +button.green:enabled:hover, button.green:enabled:focus, a.button.green:hover, a.button.green:focus { background-color: #1b7d31; } @@ -223,22 +237,36 @@ input, select, textarea { background-color: #f9fafb; border: solid #c8d2e1 1px; border-radius: 2px; - padding: 4px; box-sizing: border-box; font-size: 15px; font-weight: 300; } +input, select { + height: 30px; + line-height: 30px; + padding: 0 5px; +} + textarea { - font-size: 13px; + padding: 5px; resize: none; } -input.big, select.big, textarea.big { - padding: 5px; +input.big, select.big, textarea.big, div.inputWithButton>input { font-size: 18px; } +input.big, select.big, div.inputWithButton>input { + height: 35px; + line-height: 35px; + padding: 0 6px; +} + +textarea.big { + padding: 6px; +} + input.fullWidth, select.fullWidth, textarea.fullWidth { display: block; width: 100%; @@ -253,14 +281,30 @@ input:disabled, select:disabled, textarea:disabled { input:focus, select:focus, textarea:focus { background-color: #ffffff; border: solid #29457f 2px; - padding: 3px; outline: none; } -input.big:focus, select.big:focus, textarea.big:focus { +input:focus, select:focus { + padding: 0 4px; +} + +textarea:focus { padding: 4px; } +input.big:focus, select.big:focus { + padding: 0 5px; +} + +div.inputWithButton>input { + width: 100%; + padding: 0 83px 0 5px; +} + +textarea.big:focus { + padding: 5px; +} + div.modal { position: fixed; background-color: #ffffff; From f3225ae275ffd6c7f31bfd0014f1568b75820363 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C5=91cze=20Bence?= Date: Sun, 28 Jun 2020 03:15:20 +0200 Subject: [PATCH 2/6] MAPG-181 ability to add login_hint to dialog url --- src/OAuth/GoogleOAuth.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/OAuth/GoogleOAuth.php b/src/OAuth/GoogleOAuth.php index c339179..5f78ee2 100644 --- a/src/OAuth/GoogleOAuth.php +++ b/src/OAuth/GoogleOAuth.php @@ -15,7 +15,7 @@ class GoogleOAuth $this->request = $request; } - public function getDialogUrl(string $state, string $redirectUrl): string + public function getDialogUrl(string $state, string $redirectUrl, ?string $loginHint = null): string { $oauthParams = [ 'response_type' => 'code', @@ -26,6 +26,10 @@ class GoogleOAuth 'nonce' => hash('sha256', random_bytes(10) . microtime()), ]; + if ($loginHint !== null) { + $oauthParams['login_hint'] = $loginHint; + } + return self::$dialogUrlBase . '?' . http_build_query($oauthParams); } From 2fac82809805a1b33eb4ac21fdae8b7f1b6f80e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C5=91cze=20Bence?= Date: Sun, 28 Jun 2020 03:16:28 +0200 Subject: [PATCH 3/6] MAPG-181 text fixes --- views/login/google_login.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/views/login/google_login.php b/views/login/google_login.php index 65020f0..7cdd78d 100644 --- a/views/login/google_login.php +++ b/views/login/google_login.php @@ -3,6 +3,6 @@ @section('main')

Login up with Google

-

Authenticating with Google failed. Please retry!

+

Authentication with Google failed. Please try again!

@endsection From 4099f1d9628dce44bd8395d8885816213758f810 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C5=91cze=20Bence?= Date: Sun, 28 Jun 2020 03:16:48 +0200 Subject: [PATCH 4/6] MAPG-181 add new layout type (minimal) --- views/templates/layout_minimal.php | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 views/templates/layout_minimal.php diff --git a/views/templates/layout_minimal.php b/views/templates/layout_minimal.php new file mode 100644 index 0000000..62cec7d --- /dev/null +++ b/views/templates/layout_minimal.php @@ -0,0 +1,13 @@ +@extends('templates/mapguesser') + +@section('content') +
+

+ +

+
+
+@yields('main') +
+@endsection From 4333240acc7f2ee014a30df02e9d4bb9ad956845 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C5=91cze=20Bence?= Date: Sun, 28 Jun 2020 03:18:51 +0200 Subject: [PATCH 5/6] MAPG-181 add stuff to authenticate without password --- public/static/js/account/account.js | 69 ++++++++++++ .../static/js/account/google_authenticate.js | 10 ++ src/Controller/UserController.php | 104 +++++++++++++++++- views/account/account.php | 19 +++- views/account/delete.php | 24 +++- views/account/google_authenticate.php | 28 +++++ web.php | 2 + 7 files changed, 248 insertions(+), 8 deletions(-) create mode 100644 public/static/js/account/account.js create mode 100644 public/static/js/account/google_authenticate.js create mode 100644 views/account/google_authenticate.php diff --git a/public/static/js/account/account.js b/public/static/js/account/account.js new file mode 100644 index 0000000..d0bc3f6 --- /dev/null +++ b/public/static/js/account/account.js @@ -0,0 +1,69 @@ +var Account = { + original: null, + countdown: null, + + openGoogleAuthenticate: function () { + window.open('/account/googleAuthenticate', 'googleAuthenticate', 'height=600,width=600') + }, + + authenticatedWithGoogleCallback: function (authenticatedWithGoogleUntil) { + var password = document.getElementsByTagName('form')[0].elements.password; + var button = document.getElementById('authenticateWithGoogleButton'); + + Account.original = { + type: password.type, + placeholder: password.placeholder, + required: password.required, + disabled: password.disabled + }; + + password.type = 'text'; + password.placeholder = 'Authenticated!' + password.value = ''; + password.required = false; + password.disabled = true; + + button.disabled = true; + + Account.countdown = setInterval(function () { + var timeLeft = Math.ceil((authenticatedWithGoogleUntil.getTime() - new Date().getTime()) / 1000); + + if (timeLeft > 30) { + return; + } + + if (timeLeft <= 0) { + Account.resetGoogleAuthentication(); + return; + } + + password.placeholder = 'Authenticated! ' + timeLeft + ' seconds left...'; + }, 1000); + }, + + resetGoogleAuthentication: function () { + if (Account.countdown !== null) { + clearInterval(Account.countdown); + } + + var password = document.getElementsByTagName('form')[0].elements.password; + var button = document.getElementById('authenticateWithGoogleButton'); + + password.type = Account.original.type; + password.placeholder = Account.original.placeholder + password.required = Account.original.required; + password.disabled = Account.original.disabled; + + button.disabled = false; + } +}; + +(function () { + document.getElementById('authenticateWithGoogleButton').onclick = function () { + Account.openGoogleAuthenticate(); + }; + + document.getElementsByTagName('form')[0].onreset = function () { + Account.resetGoogleAuthentication(); + }; +})(); diff --git a/public/static/js/account/google_authenticate.js b/public/static/js/account/google_authenticate.js new file mode 100644 index 0000000..aff35b2 --- /dev/null +++ b/public/static/js/account/google_authenticate.js @@ -0,0 +1,10 @@ +(function () { + if (success) { + window.opener.Account.authenticatedWithGoogleCallback(authenticatedWithGoogleUntil); + window.close(); + } else { + document.getElementById('closeWindowButton').onclick = function () { + window.close(); + } + } +})(); diff --git a/src/Controller/UserController.php b/src/Controller/UserController.php index 06d3d35..25d14e9 100644 --- a/src/Controller/UserController.php +++ b/src/Controller/UserController.php @@ -1,16 +1,22 @@ request->user(); + + $state = bin2hex(random_bytes(16)); + + $this->request->session()->set('oauth_state', $state); + + $oAuth = new GoogleOAuth(new Request()); + + $url = $oAuth->getDialogUrl( + $state, + $this->request->getBase() . '/' . \Container::$routeCollection->getRoute('account.googleAuthenticate-action')->generateLink(), + $user->getEmail() + ); + + return new Redirect($url, IRedirect::TEMPORARY); + } + + public function authenticateWithGoogle(): IContent + { + /** + * @var User $user + */ + $user = $this->request->user(); + + if ($this->request->query('state') !== $this->request->session()->get('oauth_state')) { + $data = ['success' => false]; + return new HtmlContent('account/google_authenticate', $data); + } + + $oAuth = new GoogleOAuth(new Request()); + $tokenData = $oAuth->getToken($this->request->query('code'), $this->request->getBase() . '/' . \Container::$routeCollection->getRoute('account.googleAuthenticate-action')->generateLink()); + + if (!isset($tokenData['id_token'])) { + $data = ['success' => false]; + return new HtmlContent('account/google_authenticate', $data); + } + + $jwtParser = new JwtParser($tokenData['id_token']); + $userData = $jwtParser->getPayload(); + + if ($userData['sub'] !== $user->getGoogleSub()) { + $data = ['success' => false, 'errorText' => 'This Google account is not linked to your account.']; + return new HtmlContent('account/google_authenticate', $data); + } + + $authenticatedWithGoogleUntil = new DateTime('+45 seconds'); + $this->request->session()->set('authenticated_with_google_until', $authenticatedWithGoogleUntil); + + $data = ['success' => true, 'authenticatedWithGoogleUntil' => $authenticatedWithGoogleUntil]; + return new HtmlContent('account/google_authenticate', $data); + } + public function getDeleteAccount(): IContent { /** @@ -63,8 +126,13 @@ class UserController implements ISecured */ $user = $this->request->user(); - if (!$user->checkPassword($this->request->post('password'))) { - $data = ['error' => ['errorText' => 'The given current password is wrong.']]; + if (!$this->confirmUserIdentity( + $user, + $this->request->session()->get('authenticated_with_google_until'), + $this->request->post('password'), + $error + )) { + $data = ['error' => ['errorText' => $error]]; return new JsonContent($data); } @@ -84,6 +152,8 @@ class UserController implements ISecured $this->pdm->saveToDb($user); + $this->request->session()->delete('authenticated_with_google_until'); + $data = ['success' => true]; return new JsonContent($data); } @@ -95,8 +165,13 @@ class UserController implements ISecured */ $user = $this->request->user(); - if (!$user->checkPassword($this->request->post('password'))) { - $data = ['error' => ['errorText' => 'The given current password is wrong.']]; + if (!$this->confirmUserIdentity( + $user, + $this->request->session()->get('authenticated_with_google_until'), + $this->request->post('password'), + $error + )) { + $data = ['error' => ['errorText' => $error]]; return new JsonContent($data); } @@ -110,7 +185,28 @@ class UserController implements ISecured \Container::$dbConnection->commit(); + $this->request->session()->delete('authenticated_with_google_until'); + $data = ['success' => true]; return new JsonContent($data); } + + private function confirmUserIdentity(User $user, ?DateTime $authenticatedWithGoogleUntil, ?string $password, &$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; + } } diff --git a/views/account/account.php b/views/account/account.php index f6186b6..f1452f6 100644 --- a/views/account/account.php +++ b/views/account/account.php @@ -1,10 +1,27 @@ +@js('js/account/account.js') + @extends('templates/layout_normal') @section('main')

Account

- + +

Please confirm your identity with your password or with Google to modify your account.

+
+ +
+ +

Please confirm your identity with your password to modify your account.

+ + +

Please confirm your identity with Google to modify your account.

+
+ +
+
diff --git a/views/account/delete.php b/views/account/delete.php index 1b190e7..da22d90 100644 --- a/views/account/delete.php +++ b/views/account/delete.php @@ -1,14 +1,32 @@ +@js('js/account/account.js') + @extends('templates/layout_normal') @section('main')

Delete account

-

Are you sure you want to delete your account? This cannot be undone!

- +

Are you sure you want to delete your account? This cannot be undone!

+ +

Please confirm your identity with your password or with Google to delete your account.

+
+ +
+ +

Please confirm your identity with your password to delete your account.

+ + +

Please confirm your identity with Google to delete your account.

+
+ +
+

- + Cancel
diff --git a/views/account/google_authenticate.php b/views/account/google_authenticate.php new file mode 100644 index 0000000..12f889f --- /dev/null +++ b/views/account/google_authenticate.php @@ -0,0 +1,28 @@ +@js('js/account/google_authenticate.js') + +@extends('templates/layout_minimal') + +@section('main') +

Authenticate with Google

+ +
+

+ + + + Authentication with Google failed. + + Please close this window/tab and try again! +

+
+ +@endsection + +@section('pageScript') + +@endsection diff --git a/web.php b/web.php index 5eaffae..6df0859 100644 --- a/web.php +++ b/web.php @@ -37,6 +37,8 @@ Container::$routeCollection->group('account', function (MapGuesser\Routing\Route $routeCollection->post('account-action', '', [MapGuesser\Controller\UserController::class, 'saveAccount']); $routeCollection->get('account.delete', 'delete', [MapGuesser\Controller\UserController::class, 'getDeleteAccount']); $routeCollection->post('account.delete-action', 'delete', [MapGuesser\Controller\UserController::class, 'deleteAccount']); + $routeCollection->get('account.googleAuthenticate', 'googleAuthenticate', [MapGuesser\Controller\UserController::class, 'getGoogleAuthenticateRedirect']); + $routeCollection->get('account.googleAuthenticate-action', 'googleAuthenticate/code', [MapGuesser\Controller\UserController::class, 'authenticateWithGoogle']); }); //Container::$routeCollection->get('maps', 'maps', [MapGuesser\Controller\MapsController::class, 'getMaps']); Container::$routeCollection->group('game', function (MapGuesser\Routing\RouteCollection $routeCollection) { From bd5b49b09b44ef0ac4162205e2eeb5743d418640 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C5=91cze=20Bence?= Date: Sun, 28 Jun 2020 03:19:07 +0200 Subject: [PATCH 6/6] MAPG-181 set user active when added from CLI --- src/Cli/AddUserCommand.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Cli/AddUserCommand.php b/src/Cli/AddUserCommand.php index 90f3ba3..9ccebc8 100644 --- a/src/Cli/AddUserCommand.php +++ b/src/Cli/AddUserCommand.php @@ -23,6 +23,7 @@ class AddUserCommand extends Command $user = new User(); $user->setEmail($input->getArgument('email')); $user->setPlainPassword($input->getArgument('password')); + $user->setActive(true); if ($input->hasArgument('type')) { $user->setType($input->getArgument('type'));