Login up with Google
+Authenticating with Google failed. Please retry!
+diff --git a/database/migrations/structure/20200620_2113_google_login.sql b/database/migrations/structure/20200620_2113_google_login.sql
new file mode 100644
index 0000000..152f328
--- /dev/null
+++ b/database/migrations/structure/20200620_2113_google_login.sql
@@ -0,0 +1,8 @@
+ALTER TABLE
+ `users`
+ADD
+ `google_sub` varchar(255) CHARACTER SET ascii COLLATE ascii_bin NULL DEFAULT NULL,
+ADD
+ UNIQUE `google_sub` (`google_sub`),
+MODIFY
+ `password` varchar(60) NULL DEFAULT NULL;
diff --git a/mail/signup-noconfirm.html b/mail/signup-noconfirm.html
new file mode 100644
index 0000000..8c00ae0
--- /dev/null
+++ b/mail/signup-noconfirm.html
@@ -0,0 +1,9 @@
+Hi,
+
+You recently signed up on MapGuesser with this Google account ({{EMAIL}}).
+
+Have fun on MapGuesser!
+
+Regards,
+MapGuesser
+{{BASE_URL}}
diff --git a/mail/signup.html b/mail/signup.html
index 480e0c1..629d774 100644
--- a/mail/signup.html
+++ b/mail/signup.html
@@ -10,4 +10,5 @@ However if you want to immediately delete it, please click on the following link
Have fun on MapGuesser!
Regards,
-MapGuesser
+MapGuesser
+{{BASE_URL}}
diff --git a/public/index.php b/public/index.php
index ad425a0..24595aa 100644
--- a/public/index.php
+++ b/public/index.php
@@ -9,7 +9,7 @@ if (($pos = strpos($url, '?')) !== false) {
}
$url = rawurldecode($url);
-$match = Container::$routeCollection->match($method, explode('/', $url));
+$match = Container::$routeCollection->match($method, $url == '' ? [] : explode('/', $url));
if ($match !== null) {
list($route, $params) = $match;
@@ -40,7 +40,7 @@ if ($match !== null) {
return;
} elseif ($response instanceof MapGuesser\Interfaces\Response\IRedirect) {
- header('Location: ' . Container::$request->getBase() . '/' . $response->getUrl(), true, $response->getHttpCode());
+ header('Location: ' . $response->getUrl(), true, $response->getHttpCode());
return;
}
diff --git a/public/static/css/mapguesser.css b/public/static/css/mapguesser.css
index c492c19..0d2cf8c 100644
--- a/public/static/css/mapguesser.css
+++ b/public/static/css/mapguesser.css
@@ -107,6 +107,10 @@ hr {
margin-right: 10px;
}
+.center {
+ text-align: center;
+}
+
.right {
text-align: right;
}
@@ -350,6 +354,12 @@ div.box {
padding: 0;
width: 100%;
}
+ button.marginLeft, a.button.marginLeft {
+ margin-left: 0;
+ }
+ button.marginRight, a.button.marginRight {
+ margin-right: 0;
+ }
div.modal {
left: 20px;
right: 20px;
diff --git a/public/static/js/login.js b/public/static/js/login.js
index 0044488..cb97b3c 100644
--- a/public/static/js/login.js
+++ b/public/static/js/login.js
@@ -10,10 +10,15 @@
MapGuesser.httpRequest('POST', form.action, function () {
if (this.response.error) {
+ if (this.response.error === 'user_not_found') {
+ window.location.replace('/signup');
+ return;
+ }
+
var errorText;
switch (this.response.error) {
- case 'user_not_found':
- errorText = 'No user found with the given email address. You can sign up here!';
+ case 'password_too_short':
+ errorText = 'The given password is too short. Please choose a password that is at least 6 characters long!'
break;
case 'user_not_active':
errorText = 'User found with the given email address, but the account is not activated. Please check your email and click on the activation link!';
diff --git a/public/static/js/profile.js b/public/static/js/profile.js
index aff3137..0c2a2d3 100644
--- a/public/static/js/profile.js
+++ b/public/static/js/profile.js
@@ -25,7 +25,7 @@
case 'password_not_match':
errorText = 'The given current password is wrong.'
break;
- case 'passwords_too_short':
+ case 'password_too_short':
errorText = 'The given new password is too short. Please choose a password that is at least 6 characters long!'
break;
case 'passwords_not_match':
diff --git a/public/static/js/signup.js b/public/static/js/signup.js
index d4674f2..89e4209 100644
--- a/public/static/js/signup.js
+++ b/public/static/js/signup.js
@@ -9,28 +9,33 @@
var formData = new FormData(form);
MapGuesser.httpRequest('POST', form.action, function () {
- document.getElementById('loading').style.visibility = 'hidden';
-
if (this.response.error) {
+ if (this.response.error === 'user_found') {
+ window.location.replace('/');
+ return;
+ }
+
var errorText;
switch (this.response.error) {
case 'email_not_valid':
errorText = 'The given email address is not valid.'
break;
- case 'passwords_too_short':
+ case 'password_too_short':
errorText = 'The given password is too short. Please choose a password that is at least 6 characters long!'
break;
case 'passwords_not_match':
errorText = 'The given passwords do not match.'
break;
- case 'user_found':
- errorText = 'There is a user already registered with the given email address. Please login here!';
- break;
- case 'not_active_user_found':
+ case 'user_found_user_not_active':
errorText = 'There is a user already registered with the given email address. Please check your email and click on the activation link!';
break;
+ case 'user_found_password_not_match':
+ errorText = 'There is a user already registered with the given email address, but the given password is wrong.'
+ break;
}
+ document.getElementById('loading').style.visibility = 'hidden';
+
var signupFormError = document.getElementById('signupFormError');
signupFormError.style.display = 'block';
signupFormError.innerHTML = errorText;
@@ -38,10 +43,18 @@
return;
}
- document.getElementById('signupFormError').style.display = 'none';
- form.reset();
-
- MapGuesser.showModalWithContent('Sign up successful', 'Sign up was successful. Please check your email and click on the activation link to activate your account!');
+ window.location.replace('/signup/success');
}, formData);
};
+
+ var resetSignupButton = document.getElementById('resetSignupButton');
+ if (resetSignupButton) {
+ resetSignupButton.onclick = function () {
+ document.getElementById('loading').style.visibility = 'visible';
+
+ MapGuesser.httpRequest('POST', '/signup/reset', function () {
+ window.location.reload();
+ });
+ };
+ }
})();
diff --git a/src/Controller/HomeController.php b/src/Controller/HomeController.php
index ae2fa61..b5a180e 100644
--- a/src/Controller/HomeController.php
+++ b/src/Controller/HomeController.php
@@ -7,6 +7,6 @@ class HomeController
{
public function getIndex(): IRedirect
{
- return new Redirect([\Container::$routeCollection->getRoute('maps'), []], IRedirect::TEMPORARY);
+ return new Redirect(\Container::$routeCollection->getRoute('maps')->generateLink(), IRedirect::TEMPORARY);
}
}
diff --git a/src/Controller/LoginController.php b/src/Controller/LoginController.php
index f83633d..400f95d 100644
--- a/src/Controller/LoginController.php
+++ b/src/Controller/LoginController.php
@@ -3,31 +3,97 @@
use MapGuesser\Interfaces\Request\IRequest;
use MapGuesser\Interfaces\Response\IContent;
use MapGuesser\Interfaces\Response\IRedirect;
+use MapGuesser\Mailing\Mail;
+use MapGuesser\OAuth\GoogleOAuth;
+use MapGuesser\PersistentData\Model\User;
+use MapGuesser\PersistentData\Model\UserConfirmation;
+use MapGuesser\PersistentData\PersistentDataManager;
+use MapGuesser\Repository\UserConfirmationRepository;
use MapGuesser\Repository\UserRepository;
use MapGuesser\Response\HtmlContent;
use MapGuesser\Response\JsonContent;
use MapGuesser\Response\Redirect;
+use MapGuesser\Util\JwtParser;
class LoginController
{
private IRequest $request;
+ private PersistentDataManager $pdm;
+
private UserRepository $userRepository;
+ private UserConfirmationRepository $userConfirmationRepository;
+
public function __construct(IRequest $request)
{
$this->request = $request;
+ $this->pdm = new PersistentDataManager();
$this->userRepository = new UserRepository();
+ $this->userConfirmationRepository = new UserConfirmationRepository();
}
public function getLoginForm()
{
if ($this->request->user() !== null) {
- return new Redirect([\Container::$routeCollection->getRoute('index'), []], IRedirect::TEMPORARY);
+ return new Redirect(\Container::$routeCollection->getRoute('index')->generateLink(), IRedirect::TEMPORARY);
}
$data = [];
- return new HtmlContent('login', $data);
+ return new HtmlContent('login/login', $data);
+ }
+
+ public function getGoogleLoginRedirect(): IRedirect
+ {
+ $state = bin2hex(random_bytes(16));
+
+ $this->request->session()->set('oauth_state', $state);
+
+ $oAuth = new GoogleOAuth();
+ $url = $oAuth->getDialogUrl($state, $this->request->getBase() . '/' . \Container::$routeCollection->getRoute('login-google-action')->generateLink());
+
+ return new Redirect($url, IRedirect::TEMPORARY);
+ }
+
+ public function getSignupForm()
+ {
+ if ($this->request->user() !== null) {
+ return new Redirect(\Container::$routeCollection->getRoute('index')->generateLink(), IRedirect::TEMPORARY);
+ }
+
+ if ($this->request->session()->has('tmp_user_data')) {
+ $tmpUserData = $this->request->session()->get('tmp_user_data');
+
+ $data = ['email' => $tmpUserData['email']];
+ } else {
+ $data = [];
+ }
+
+ return new HtmlContent('login/signup', $data);
+ }
+
+ public function getSignupSuccess(): IContent
+ {
+ $data = [];
+ return new HtmlContent('login/signup_success', $data);
+ }
+
+ public function getSignupWithGoogleForm()
+ {
+ if ($this->request->user() !== null) {
+ return new Redirect(\Container::$routeCollection->getRoute('index')->generateLink(), IRedirect::TEMPORARY);
+ }
+
+ if (!$this->request->session()->has('google_user_data')) {
+ return new Redirect(\Container::$routeCollection->getRoute('login-google')->generateLink(), IRedirect::TEMPORARY);
+ }
+
+ $userData = $this->request->session()->get('google_user_data');
+
+ $user = $this->userRepository->getByEmail($userData['email']);
+
+ $data = ['found' => $user !== null, 'email' => $userData['email']];
+ return new HtmlContent('login/google_signup', $data);
}
public function login(): IContent
@@ -40,6 +106,16 @@ class LoginController
$user = $this->userRepository->getByEmail($this->request->post('email'));
if ($user === null) {
+ if (strlen($this->request->post('password')) < 6) {
+ $data = ['error' => 'password_too_short'];
+ return new JsonContent($data);
+ }
+
+ $tmpUser = new User();
+ $tmpUser->setPlainPassword($this->request->post('password'));
+
+ $this->request->session()->set('tmp_user_data', ['email' => $this->request->post('email'), 'password_hashed' => $tmpUser->getPassword()]);
+
$data = ['error' => 'user_not_found'];
return new JsonContent($data);
}
@@ -60,10 +136,256 @@ class LoginController
return new JsonContent($data);
}
+ public function loginWithGoogle()
+ {
+ if ($this->request->user() !== null) {
+ return new Redirect(\Container::$routeCollection->getRoute('index')->generateLink(), IRedirect::TEMPORARY);
+ }
+
+ if ($this->request->query('state') !== $this->request->session()->get('oauth_state')) {
+ $data = [];
+ return new HtmlContent('login/google_login', $data);
+ }
+
+ $oAuth = new GoogleOAuth();
+ $tokenData = $oAuth->getToken($this->request->query('code'), $this->request->getBase() . '/' . \Container::$routeCollection->getRoute('login-google-action')->generateLink());
+
+ if (!isset($tokenData['id_token'])) {
+ $data = [];
+ return new HtmlContent('login/google_login', $data);
+ }
+
+ $jwtParser = new JwtParser($tokenData['id_token']);
+ $userData = $jwtParser->getPayload();
+
+ if (!$userData['email_verified']) {
+ $data = [];
+ return new HtmlContent('login/google_login', $data);
+ }
+
+ $user = $this->userRepository->getByGoogleSub($userData['sub']);
+
+ if ($user === null) {
+ $this->request->session()->set('google_user_data', $userData);
+
+ return new Redirect(\Container::$routeCollection->getRoute('signup-google')->generateLink(), IRedirect::TEMPORARY);
+ }
+
+ $this->request->setUser($user);
+
+ return new Redirect(\Container::$routeCollection->getRoute('index')->generateLink(), IRedirect::TEMPORARY);
+ }
+
public function logout(): IRedirect
{
$this->request->setUser(null);
- return new Redirect([\Container::$routeCollection->getRoute('index'), []], IRedirect::TEMPORARY);
+ return new Redirect(\Container::$routeCollection->getRoute('index')->generateLink(), IRedirect::TEMPORARY);
+ }
+
+ public function signup(): IContent
+ {
+ if ($this->request->user() !== null) {
+ $data = ['error' => 'logged_in'];
+ return new JsonContent($data);
+ }
+
+ $user = $this->userRepository->getByEmail($this->request->post('email'));
+
+ if ($user !== null) {
+ if ($user->getActive()) {
+ if (!$user->checkPassword($this->request->post('password'))) {
+ $data = ['error' => 'user_found_password_not_match'];
+ return new JsonContent($data);
+ }
+
+ $this->request->setUser($user);
+
+ $data = ['error' => 'user_found'];
+ } else {
+ $data = ['error' => 'user_found_user_not_active'];
+ }
+ return new JsonContent($data);
+ }
+
+ if (filter_var($this->request->post('email'), FILTER_VALIDATE_EMAIL) === false) {
+ $data = ['error' => 'email_not_valid'];
+ return new JsonContent($data);
+ }
+
+ if ($this->request->session()->has('tmp_user_data')) {
+ $tmpUserData = $this->request->session()->get('tmp_user_data');
+
+ $tmpUser = new User();
+ $tmpUser->setPassword($tmpUserData['password_hashed']);
+
+ if (!$tmpUser->checkPassword($this->request->post('password'))) {
+ $data = ['error' => 'passwords_not_match'];
+ return new JsonContent($data);
+ }
+ } else {
+ if (strlen($this->request->post('password')) < 6) {
+ $data = ['error' => 'password_too_short'];
+ return new JsonContent($data);
+ }
+
+ if ($this->request->post('password') !== $this->request->post('password_confirm')) {
+ $data = ['error' => 'passwords_not_match'];
+ return new JsonContent($data);
+ }
+ }
+
+ $user = new User();
+ $user->setEmail($this->request->post('email'));
+ $user->setPlainPassword($this->request->post('password'));
+
+ \Container::$dbConnection->startTransaction();
+
+ $this->pdm->saveToDb($user);
+
+ $token = hash('sha256', serialize($user) . random_bytes(10) . microtime());
+
+ $confirmation = new UserConfirmation();
+ $confirmation->setUser($user);
+ $confirmation->setToken($token);
+
+ $this->pdm->saveToDb($confirmation);
+
+ \Container::$dbConnection->commit();
+
+ $this->sendConfirmationEmail($user->getEmail(), $token);
+
+ $this->request->session()->delete('tmp_user_data');
+
+ $data = ['success' => true];
+ return new JsonContent($data);
+ }
+
+ public function signupWithGoogle(): IContent
+ {
+ if ($this->request->user() !== null) {
+ $data = ['success' => true];
+ return new JsonContent($data);
+ }
+
+ $userData = $this->request->session()->get('google_user_data');
+
+ $user = $this->userRepository->getByEmail($userData['email']);
+
+ if ($user === null) {
+ $user = new User();
+ $user->setEmail($userData['email']);
+ }
+
+ $user->setActive(true);
+ $user->setGoogleSub($userData['sub']);
+
+ $this->pdm->saveToDb($user);
+
+ $this->sendWelcomeEmail($user->getEmail());
+
+ $this->request->session()->delete('google_user_data');
+ $this->request->setUser($user);
+
+ $data = ['success' => true];
+ return new JsonContent($data);
+ }
+
+ public function resetSignup(): IContent
+ {
+ $this->request->session()->delete('tmp_user_data');
+
+ $data = ['success' => true];
+ return new JsonContent($data);
+ }
+
+ public function resetGoogleSignup(): IContent
+ {
+ $this->request->session()->delete('google_user_data');
+
+ $data = ['success' => true];
+ return new JsonContent($data);
+ }
+
+ public function activate()
+ {
+ if ($this->request->user() !== null) {
+ return new Redirect(\Container::$routeCollection->getRoute('index')->generateLink(), IRedirect::TEMPORARY);
+ }
+
+ $confirmation = $this->userConfirmationRepository->getByToken($this->request->query('token'));
+
+ if ($confirmation === null) {
+ $data = [];
+ return new HtmlContent('login/activate', $data);
+ }
+
+ \Container::$dbConnection->startTransaction();
+
+ $this->pdm->deleteFromDb($confirmation);
+
+ $user = $this->userRepository->getById($confirmation->getUserId());
+ $user->setActive(true);
+
+ $this->pdm->saveToDb($user);
+
+ \Container::$dbConnection->commit();
+
+ $this->request->setUser($user);
+
+ return new Redirect(\Container::$routeCollection->getRoute('index')->generateLink(), IRedirect::TEMPORARY);
+ }
+
+ public function cancel()
+ {
+ if ($this->request->user() !== null) {
+ return new Redirect(\Container::$routeCollection->getRoute('index')->generateLink(), IRedirect::TEMPORARY);
+ }
+
+ $confirmation = $this->userConfirmationRepository->getByToken($this->request->query('token'));
+
+ if ($confirmation === null) {
+ $data = ['success' => false];
+ return new HtmlContent('login/cancel', $data);
+ }
+
+ \Container::$dbConnection->startTransaction();
+
+ $this->pdm->deleteFromDb($confirmation);
+
+ $user = $this->userRepository->getById($confirmation->getUserId());
+
+ $this->pdm->deleteFromDb($user);
+
+ \Container::$dbConnection->commit();
+
+ $data = ['success' => true];
+ return new HtmlContent('login/cancel', $data);
+ }
+
+ private function sendConfirmationEmail(string $email, string $token): void
+ {
+ $mail = new Mail();
+ $mail->addRecipient($email);
+ $mail->setSubject('Welcome to MapGuesser - Activate your account');
+ $mail->setBodyFromTemplate('signup', [
+ 'EMAIL' => $email,
+ 'ACTIVATE_LINK' => $this->request->getBase() . '/'. \Container::$routeCollection->getRoute('signup.activate')->generateLink(['token' => $token]),
+ 'CANCEL_LINK' => $this->request->getBase() . '/' . \Container::$routeCollection->getRoute('signup.cancel')->generateLink(['token' => $token]),
+ 'BASE_URL' => $this->request->getBase(),
+ ]);
+ $mail->send();
+ }
+
+ private function sendWelcomeEmail(string $email): void
+ {
+ $mail = new Mail();
+ $mail->addRecipient($email);
+ $mail->setSubject('Welcome to MapGuesser');
+ $mail->setBodyFromTemplate('signup-noconfirm', [
+ 'EMAIL' => $email,
+ 'BASE_URL' => $this->request->getBase(),
+ ]);
+ $mail->send();
}
}
diff --git a/src/Controller/SignupController.php b/src/Controller/SignupController.php
deleted file mode 100644
index 77b3197..0000000
--- a/src/Controller/SignupController.php
+++ /dev/null
@@ -1,170 +0,0 @@
-request = $request;
- $this->pdm = new PersistentDataManager();
- $this->userRepository = new UserRepository();
- $this->userConfirmationRepository = new UserConfirmationRepository();
- }
-
- public function getSignupForm()
- {
- if ($this->request->user() !== null) {
- return new Redirect([\Container::$routeCollection->getRoute('index'), []], IRedirect::TEMPORARY);
- }
-
- $data = [];
- return new HtmlContent('signup/signup', $data);
- }
-
- public function signup(): IContent
- {
- if ($this->request->user() !== null) {
- //TODO: return with some error
- $data = ['success' => true];
- return new JsonContent($data);
- }
-
- if (filter_var($this->request->post('email'), FILTER_VALIDATE_EMAIL) === false) {
- $data = ['error' => 'email_not_valid'];
- return new JsonContent($data);
- }
-
- $user = $this->userRepository->getByEmail($this->request->post('email'));
-
- if ($user !== null) {
- if ($user->getActive()) {
- $data = ['error' => 'user_found'];
- } else {
- $data = ['error' => 'not_active_user_found'];
- }
- return new JsonContent($data);
- }
-
- if (strlen($this->request->post('password')) < 6) {
- $data = ['error' => 'passwords_too_short'];
- return new JsonContent($data);
- }
-
- if ($this->request->post('password') !== $this->request->post('password_confirm')) {
- $data = ['error' => 'passwords_not_match'];
- return new JsonContent($data);
- }
-
- $user = new User();
- $user->setEmail($this->request->post('email'));
- $user->setPlainPassword($this->request->post('password'));
-
- \Container::$dbConnection->startTransaction();
-
- $this->pdm->saveToDb($user);
-
- $token = hash('sha256', serialize($user) . random_bytes(10) . microtime());
-
- $confirmation = new UserConfirmation();
- $confirmation->setUser($user);
- $confirmation->setToken($token);
-
- $this->pdm->saveToDb($confirmation);
-
- \Container::$dbConnection->commit();
-
- $this->sendConfirmationEmail($user->getEmail(), $token);
-
- $data = ['success' => true];
- return new JsonContent($data);
- }
-
- public function activate()
- {
- if ($this->request->user() !== null) {
- return new Redirect([\Container::$routeCollection->getRoute('index'), []], IRedirect::TEMPORARY);
- }
-
- $confirmation = $this->userConfirmationRepository->getByToken($this->request->query('token'));
-
- if ($confirmation === null) {
- $data = [];
- return new HtmlContent('signup/activate', $data);
- }
-
- \Container::$dbConnection->startTransaction();
-
- $this->pdm->deleteFromDb($confirmation);
-
- $user = $this->userRepository->getById($confirmation->getUserId());
- $user->setActive(true);
-
- $this->pdm->saveToDb($user);
-
- \Container::$dbConnection->commit();
-
- $this->request->setUser($user);
-
- return new Redirect([\Container::$routeCollection->getRoute('index'), []], IRedirect::TEMPORARY);
- }
-
- public function cancel()
- {
- if ($this->request->user() !== null) {
- return new Redirect([\Container::$routeCollection->getRoute('index'), []], IRedirect::TEMPORARY);
- }
-
- $confirmation = $this->userConfirmationRepository->getByToken($this->request->query('token'));
-
- if ($confirmation === null) {
- $data = ['success' => false];
- return new HtmlContent('signup/cancel', $data);
- }
-
- \Container::$dbConnection->startTransaction();
-
- $this->pdm->deleteFromDb($confirmation);
-
- $user = $this->userRepository->getById($confirmation->getUserId());
-
- $this->pdm->deleteFromDb($user);
-
- \Container::$dbConnection->commit();
-
- $data = ['success' => true];
- return new HtmlContent('signup/cancel', $data);
- }
-
- private function sendConfirmationEmail($email, $token): void
- {
- $mail = new Mail();
- $mail->addRecipient($email);
- $mail->setSubject('Welcome to MapGuesser - Activate your account');
- $mail->setBodyFromTemplate('signup', [
- 'EMAIL' => $email,
- 'ACTIVATE_LINK' => $this->request->getBase() . '/signup/activate/' . $token,
- 'CANCEL_LINK' => $this->request->getBase() . '/signup/cancel/' . $token,
- ]);
- $mail->send();
- }
-}
diff --git a/src/Controller/UserController.php b/src/Controller/UserController.php
index 778217a..14ed237 100644
--- a/src/Controller/UserController.php
+++ b/src/Controller/UserController.php
@@ -52,7 +52,7 @@ class UserController implements ISecured
if (strlen($this->request->post('password_new')) > 0) {
if (strlen($this->request->post('password_new')) < 6) {
- $data = ['error' => 'passwords_too_short'];
+ $data = ['error' => 'password_too_short'];
return new JsonContent($data);
}
diff --git a/src/OAuth/GoogleOAuth.php b/src/OAuth/GoogleOAuth.php
new file mode 100644
index 0000000..07c2107
--- /dev/null
+++ b/src/OAuth/GoogleOAuth.php
@@ -0,0 +1,41 @@
+ 'code',
+ 'client_id' => $_ENV['GOOGLE_OAUTH_CLIENT_ID'],
+ 'scope' => 'openid email',
+ 'redirect_uri' => $redirectUrl,
+ 'state' => $state,
+ 'nonce' => hash('sha256', random_bytes(10) . microtime()),
+ ];
+
+ return self::$dialogUrlBase . '?' . http_build_query($oauthParams);
+ }
+
+ public function getToken(string $code, string $redirectUrl)
+ {
+ $tokenParams = [
+ 'code' => $code,
+ 'client_id' => $_ENV['GOOGLE_OAUTH_CLIENT_ID'],
+ 'client_secret' => $_ENV['GOOGLE_OAUTH_CLIENT_SECRET'],
+ 'redirect_uri' => $redirectUrl,
+ 'grant_type' => 'authorization_code',
+ ];
+
+ $request = new Request(self::$tokenUrlBase, Request::HTTP_POST);
+ $request->setQuery($tokenParams);
+ $response = $request->send();
+
+ return json_decode($response->getBody(), true);
+ }
+}
diff --git a/src/PersistentData/Model/User.php b/src/PersistentData/Model/User.php
index fc3b185..e13adfd 100644
--- a/src/PersistentData/Model/User.php
+++ b/src/PersistentData/Model/User.php
@@ -6,24 +6,26 @@ class User extends Model implements IUser
{
protected static string $table = 'users';
- protected static array $fields = ['email', 'password', 'type', 'active'];
+ protected static array $fields = ['email', 'password', 'type', 'active', 'google_sub'];
private static array $types = ['user', 'admin'];
private string $email = '';
- private string $password = '';
+ private ?string $password = null;
private string $type = 'user';
private bool $active = false;
+ private ?string $googleSub = null;
+
public function setEmail(string $email): void
{
$this->email = $email;
}
- public function setPassword(string $hashedPassword): void
+ public function setPassword(?string $hashedPassword): void
{
$this->password = $hashedPassword;
}
@@ -45,12 +47,17 @@ class User extends Model implements IUser
$this->active = (bool) $active;
}
+ public function setGoogleSub(?string $googleSub): void
+ {
+ $this->googleSub = $googleSub;
+ }
+
public function getEmail(): string
{
return $this->email;
}
- public function getPassword(): string
+ public function getPassword(): ?string
{
return $this->password;
}
@@ -65,6 +72,11 @@ class User extends Model implements IUser
return $this->active;
}
+ public function getGoogleSub(): ?string
+ {
+ return $this->googleSub;
+ }
+
public function hasPermission(int $permission): bool
{
switch ($permission) {
diff --git a/src/PersistentData/Model/UserConfirmation.php b/src/PersistentData/Model/UserConfirmation.php
index 70a76d6..fac2b2d 100644
--- a/src/PersistentData/Model/UserConfirmation.php
+++ b/src/PersistentData/Model/UserConfirmation.php
@@ -6,6 +6,8 @@ class UserConfirmation extends Model
protected static array $fields = ['user_id', 'token'];
+ protected static array $relations = ['user' => User::class];
+
private ?User $user = null;
private ?int $userId = null;
diff --git a/src/Repository/UserRepository.php b/src/Repository/UserRepository.php
index b67771d..c7ddcaf 100644
--- a/src/Repository/UserRepository.php
+++ b/src/Repository/UserRepository.php
@@ -25,4 +25,12 @@ class UserRepository
return $this->pdm->selectFromDb($select, User::class);
}
+
+ public function getByGoogleSub(string $sub): ?User
+ {
+ $select = new Select(\Container::$dbConnection);
+ $select->where('google_sub', '=', $sub);
+
+ return $this->pdm->selectFromDb($select, User::class);
+ }
}
diff --git a/src/Response/Redirect.php b/src/Response/Redirect.php
index de5ab39..ed78183 100644
--- a/src/Response/Redirect.php
+++ b/src/Response/Redirect.php
@@ -4,11 +4,11 @@ use MapGuesser\Interfaces\Response\IRedirect;
class Redirect implements IRedirect
{
- private $target;
+ private string $target;
private int $type;
- public function __construct($target, int $type = IRedirect::TEMPORARY)
+ public function __construct(string $target, int $type = IRedirect::TEMPORARY)
{
$this->target = $target;
$this->type = $type;
@@ -16,10 +16,10 @@ class Redirect implements IRedirect
public function getUrl(): string
{
- if (is_array($this->target)) {
- $link = $this->target[0]->generateLink($this->target[1]);
- } else {
+ if (preg_match('/^http(s)?/', $this->target)) {
$link = $this->target;
+ } else {
+ $link = \Container::$request->getBase() . '/' . $this->target;
}
return $link;
diff --git a/src/Routing/RouteCollection.php b/src/Routing/RouteCollection.php
index c729c3e..d93007a 100644
--- a/src/Routing/RouteCollection.php
+++ b/src/Routing/RouteCollection.php
@@ -64,7 +64,7 @@ class RouteCollection
throw new \Exception('Route already exists: ' . $id);
}
- $pattern = array_merge($this->groupStack, explode('/', $pattern));
+ $pattern = array_merge($this->groupStack, $pattern === '' ? [] : explode('/', $pattern));
$route = new Route($id, $pattern, $handler);
$groupNumber = count($pattern);
diff --git a/src/Util/JwtParser.php b/src/Util/JwtParser.php
new file mode 100644
index 0000000..b6e57a1
--- /dev/null
+++ b/src/Util/JwtParser.php
@@ -0,0 +1,33 @@
+setToken($token);
+ }
+ }
+
+ public function setToken(string $token)
+ {
+ $this->token = explode('.', str_replace(['_', '-'], ['/', '+'], $token));
+ }
+
+ public function getHeader(): array
+ {
+ return json_decode(base64_decode($this->token[0]), true);
+ }
+
+ public function getPayload(): array
+ {
+ return json_decode(base64_decode($this->token[1]), true);
+ }
+
+ public function getSignature(): string
+ {
+ return base64_decode($this->token[2]);
+ }
+}
diff --git a/views/signup/activate.php b/views/login/activate.php
similarity index 100%
rename from views/signup/activate.php
rename to views/login/activate.php
diff --git a/views/signup/cancel.php b/views/login/cancel.php
similarity index 100%
rename from views/signup/cancel.php
rename to views/login/cancel.php
diff --git a/views/login/google_login.php b/views/login/google_login.php
new file mode 100644
index 0000000..334857b
--- /dev/null
+++ b/views/login/google_login.php
@@ -0,0 +1,9 @@
+
+
+
Authenticating with Google failed. Please retry!
+Sign up was successful. Please check your email and click on the activation link to activate your account!
+