Merged in feature/MAPG-69-implement-google-registration-login (pull request #123)
Feature/MAPG-69 implement google registration login
This commit is contained in:
commit
a56ce849c0
@ -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;
|
9
mail/signup-noconfirm.html
Normal file
9
mail/signup-noconfirm.html
Normal file
@ -0,0 +1,9 @@
|
||||
Hi,
|
||||
<br><br>
|
||||
You recently signed up on MapGuesser with this Google account ({{EMAIL}}).
|
||||
<br><br>
|
||||
Have fun on MapGuesser!
|
||||
<br><br>
|
||||
Regards,<br>
|
||||
MapGuesser<br>
|
||||
<a href="{{BASE_URL}}" title="MapGuesser">{{BASE_URL}}</a>
|
@ -10,4 +10,5 @@ However if you want to immediately delete it, please click on the following link
|
||||
Have fun on MapGuesser!
|
||||
<br><br>
|
||||
Regards,<br>
|
||||
MapGuesser
|
||||
MapGuesser<br>
|
||||
<a href="{{BASE_URL}}" title="MapGuesser">{{BASE_URL}}</a>
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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 <a href="/signup" title="Sign up">sign up here</a>!';
|
||||
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!';
|
||||
|
@ -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':
|
||||
|
@ -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 <a href="/login" title="Login">login here</a>!';
|
||||
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();
|
||||
});
|
||||
};
|
||||
}
|
||||
})();
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -1,170 +0,0 @@
|
||||
<?php namespace MapGuesser\Controller;
|
||||
|
||||
use MapGuesser\Interfaces\Request\IRequest;
|
||||
use MapGuesser\Interfaces\Response\IContent;
|
||||
use MapGuesser\Interfaces\Response\IRedirect;
|
||||
use MapGuesser\Mailing\Mail;
|
||||
use MapGuesser\PersistentData\PersistentDataManager;
|
||||
use MapGuesser\PersistentData\Model\User;
|
||||
use MapGuesser\PersistentData\Model\UserConfirmation;
|
||||
use MapGuesser\Repository\UserConfirmationRepository;
|
||||
use MapGuesser\Repository\UserRepository;
|
||||
use MapGuesser\Response\HtmlContent;
|
||||
use MapGuesser\Response\JsonContent;
|
||||
use MapGuesser\Response\Redirect;
|
||||
|
||||
class SignupController
|
||||
{
|
||||
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 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();
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
|
41
src/OAuth/GoogleOAuth.php
Normal file
41
src/OAuth/GoogleOAuth.php
Normal file
@ -0,0 +1,41 @@
|
||||
<?php namespace MapGuesser\OAuth;
|
||||
|
||||
use MapGuesser\Http\Request;
|
||||
|
||||
class GoogleOAuth
|
||||
{
|
||||
private static $dialogUrlBase = 'https://accounts.google.com/o/oauth2/v2/auth';
|
||||
|
||||
private static $tokenUrlBase = 'https://oauth2.googleapis.com/token';
|
||||
|
||||
public function getDialogUrl(string $state, string $redirectUrl): string
|
||||
{
|
||||
$oauthParams = [
|
||||
'response_type' => '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);
|
||||
}
|
||||
}
|
@ -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) {
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
33
src/Util/JwtParser.php
Normal file
33
src/Util/JwtParser.php
Normal file
@ -0,0 +1,33 @@
|
||||
<?php namespace MapGuesser\Util;
|
||||
|
||||
class JwtParser
|
||||
{
|
||||
private array $token;
|
||||
|
||||
public function __construct(?string $token = null)
|
||||
{
|
||||
if ($token !== null) {
|
||||
$this->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]);
|
||||
}
|
||||
}
|
9
views/login/google_login.php
Normal file
9
views/login/google_login.php
Normal file
@ -0,0 +1,9 @@
|
||||
<?php require ROOT . '/views/templates/main_header.php'; ?>
|
||||
<?php require ROOT . '/views/templates/header.php'; ?>
|
||||
<div class="main">
|
||||
<h2>Login up with Google</h2>
|
||||
<div class="box">
|
||||
<p class="error justify">Authenticating with Google failed. Please <a href="/login/google" title="Login with Google">retry</a>!</p>
|
||||
</div>
|
||||
</div>
|
||||
<?php require ROOT . '/views/templates/main_footer.php'; ?>
|
49
views/login/google_signup.php
Normal file
49
views/login/google_signup.php
Normal file
@ -0,0 +1,49 @@
|
||||
<?php require ROOT . '/views/templates/main_header.php'; ?>
|
||||
<?php require ROOT . '/views/templates/header.php'; ?>
|
||||
<div class="main">
|
||||
<h2>Sign up</h2>
|
||||
<div class="box">
|
||||
<form id="googleSignupForm" action="/signup/google" method="post">
|
||||
<?php if ($found): ?>
|
||||
<p class="justify">Please confirm that you link your account to your Google account.</p>
|
||||
<?php else: ?>
|
||||
<p class="justify">Please confirm your sign up request. Your account will be linked to your Google account.</p>
|
||||
<?php endif; ?>
|
||||
<input class="big fullWidth marginTop" type="email" name="email" placeholder="Email address" value="<?= $email ?>" disabled>
|
||||
<div class="right">
|
||||
<button class="marginTop marginRight" type="submit">
|
||||
<?php if ($found): ?>
|
||||
Link
|
||||
<?php else: ?>
|
||||
Sign up
|
||||
<?php endif; ?>
|
||||
</button><!--
|
||||
--><button id="cancelGoogleSignupButton" class="gray marginTop" type="button">Cancel</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
(function () {
|
||||
var form = document.getElementById('googleSignupForm');
|
||||
|
||||
form.onsubmit = function (e) {
|
||||
document.getElementById('loading').style.visibility = 'visible';
|
||||
|
||||
e.preventDefault();
|
||||
|
||||
MapGuesser.httpRequest('POST', form.action, function () {
|
||||
window.location.replace('/');
|
||||
});
|
||||
};
|
||||
|
||||
document.getElementById('cancelGoogleSignupButton').onclick = function () {
|
||||
document.getElementById('loading').style.visibility = 'visible';
|
||||
|
||||
MapGuesser.httpRequest('POST', '/signup/google/reset', function () {
|
||||
window.location.replace('/signup');
|
||||
});
|
||||
};
|
||||
})();
|
||||
</script>
|
||||
<?php require ROOT . '/views/templates/main_footer.php'; ?>
|
@ -15,6 +15,10 @@ $jsFiles = [
|
||||
<div class="right marginTop">
|
||||
<button type="submit">Login</button>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="center">
|
||||
<a class="button yellow" href="/login/google" title="Login with Google">Login with Google</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
35
views/login/signup.php
Normal file
35
views/login/signup.php
Normal file
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
$jsFiles = [
|
||||
'js/signup.js',
|
||||
];
|
||||
?>
|
||||
<?php require ROOT . '/views/templates/main_header.php'; ?>
|
||||
<?php require ROOT . '/views/templates/header.php'; ?>
|
||||
<div class="main">
|
||||
<h2>Sign up</h2>
|
||||
<div class="box">
|
||||
<form id="signupForm" action="/signup" method="post">
|
||||
<?php if (isset($email)): ?>
|
||||
<p class="justify">No user found with the given email address. Sign up with one click!</p>
|
||||
<input class="big fullWidth marginTop" type="email" name="email" placeholder="Email address" value="<?= $email ?>" required>
|
||||
<input class="big fullWidth marginTop" type="password" name="password" placeholder="Password confirmation" required minlength="6" autofocus>
|
||||
<?php else: ?>
|
||||
<input class="big fullWidth" type="email" name="email" placeholder="Email address" required autofocus>
|
||||
<input class="big fullWidth marginTop" type="password" name="password" placeholder="Password" required minlength="6">
|
||||
<input class="big fullWidth marginTop" type="password" name="password_confirm" placeholder="Password confirmation" minlength="6">
|
||||
<?php endif; ?>
|
||||
<p id="signupFormError" class="formError justify marginTop"></p>
|
||||
<div class="right">
|
||||
<button class="marginTop" type="submit">Sign up</button><!--
|
||||
--><?php if (isset($email)): ?><!--
|
||||
--><button id="resetSignupButton" class="gray marginTop marginLeft" type="button">Reset</button>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="center">
|
||||
<a class="button yellow" href="/login/google" title="Signup with Google">Signup with Google</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<?php require ROOT . '/views/templates/main_footer.php'; ?>
|
9
views/login/signup_success.php
Normal file
9
views/login/signup_success.php
Normal file
@ -0,0 +1,9 @@
|
||||
<?php require ROOT . '/views/templates/main_header.php'; ?>
|
||||
<?php require ROOT . '/views/templates/header.php'; ?>
|
||||
<div class="main">
|
||||
<h2>Sign up</h2>
|
||||
<div class="box">
|
||||
<p class="justify">Sign up was successful. Please check your email and click on the activation link to activate your account!</p>
|
||||
</div>
|
||||
</div>
|
||||
<?php require ROOT . '/views/templates/main_footer.php'; ?>
|
@ -1,22 +0,0 @@
|
||||
<?php
|
||||
$jsFiles = [
|
||||
'js/signup.js',
|
||||
];
|
||||
?>
|
||||
<?php require ROOT . '/views/templates/main_header.php'; ?>
|
||||
<?php require ROOT . '/views/templates/header.php'; ?>
|
||||
<div class="main">
|
||||
<h2>Sign up</h2>
|
||||
<div class="box">
|
||||
<form id="signupForm" action="/signup" method="post">
|
||||
<input class="big fullWidth" type="email" name="email" placeholder="Email address" required autofocus>
|
||||
<input class="big fullWidth marginTop" type="password" name="password" placeholder="Password" required minlength="6">
|
||||
<input class="big fullWidth marginTop" type="password" name="password_confirm" placeholder="Password confirmation" required minlength="6">
|
||||
<p id="signupFormError" class="formError justify marginTop"></p>
|
||||
<div class="right marginTop">
|
||||
<button type="submit">Sign up</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<?php require ROOT . '/views/templates/main_footer.php'; ?>
|
29
web.php
29
web.php
@ -13,15 +13,28 @@ if (!empty($_ENV['DEV'])) {
|
||||
Container::$routeCollection = new MapGuesser\Routing\RouteCollection();
|
||||
|
||||
Container::$routeCollection->get('index', '', [MapGuesser\Controller\HomeController::class, 'getIndex']);
|
||||
Container::$routeCollection->get('login', 'login', [MapGuesser\Controller\LoginController::class, 'getLoginForm']);
|
||||
Container::$routeCollection->post('login-action', 'login', [MapGuesser\Controller\LoginController::class, 'login']);
|
||||
Container::$routeCollection->get('signup', 'signup', [MapGuesser\Controller\SignupController::class, 'getSignupForm']);
|
||||
Container::$routeCollection->post('signup-action', 'signup', [MapGuesser\Controller\SignupController::class, 'signup']);
|
||||
Container::$routeCollection->get('signup.activate', 'signup/activate/{token}', [MapGuesser\Controller\SignupController::class, 'activate']);
|
||||
Container::$routeCollection->get('signup.cancel', 'signup/cancel/{token}', [MapGuesser\Controller\SignupController::class, 'cancel']);
|
||||
Container::$routeCollection->group('login', function (MapGuesser\Routing\RouteCollection $routeCollection) {
|
||||
$routeCollection->get('login', '', [MapGuesser\Controller\LoginController::class, 'getLoginForm']);
|
||||
$routeCollection->post('login-action', '', [MapGuesser\Controller\LoginController::class, 'login']);
|
||||
$routeCollection->get('login-google', 'google', [MapGuesser\Controller\LoginController::class, 'getGoogleLoginRedirect']);
|
||||
$routeCollection->get('login-google-action', 'google/code', [MapGuesser\Controller\LoginController::class, 'loginWithGoogle']);
|
||||
});
|
||||
Container::$routeCollection->group('signup', function (MapGuesser\Routing\RouteCollection $routeCollection) {
|
||||
$routeCollection->get('signup', '', [MapGuesser\Controller\LoginController::class, 'getSignupForm']);
|
||||
$routeCollection->post('signup-action', '', [MapGuesser\Controller\LoginController::class, 'signup']);
|
||||
$routeCollection->get('signup-google', 'google', [MapGuesser\Controller\LoginController::class, 'getSignupWithGoogleForm']);
|
||||
$routeCollection->post('signup-google-action', 'google', [MapGuesser\Controller\LoginController::class, 'signupWithGoogle']);
|
||||
$routeCollection->post('signup.reset', 'reset', [MapGuesser\Controller\LoginController::class, 'resetSignup']);
|
||||
$routeCollection->post('signup-google.reset', 'google/reset', [MapGuesser\Controller\LoginController::class, 'resetGoogleSignup']);
|
||||
$routeCollection->get('signup.success', 'success', [MapGuesser\Controller\LoginController::class, 'getSignupSuccess']);
|
||||
$routeCollection->get('signup.activate', 'activate/{token}', [MapGuesser\Controller\LoginController::class, 'activate']);
|
||||
$routeCollection->get('signup.cancel', 'cancel/{token}', [MapGuesser\Controller\LoginController::class, 'cancel']);
|
||||
});
|
||||
Container::$routeCollection->get('logout', 'logout', [MapGuesser\Controller\LoginController::class, 'logout']);
|
||||
Container::$routeCollection->get('profile', 'profile', [MapGuesser\Controller\UserController::class, 'getProfile']);
|
||||
Container::$routeCollection->post('profile-action', 'profile', [MapGuesser\Controller\UserController::class, 'saveProfile']);
|
||||
Container::$routeCollection->group('profile', function (MapGuesser\Routing\RouteCollection $routeCollection) {
|
||||
$routeCollection->get('profile', '', [MapGuesser\Controller\UserController::class, 'getProfile']);
|
||||
$routeCollection->post('profile-action', '', [MapGuesser\Controller\UserController::class, 'saveProfile']);
|
||||
});
|
||||
Container::$routeCollection->get('maps', 'maps', [MapGuesser\Controller\MapsController::class, 'getMaps']);
|
||||
Container::$routeCollection->group('game', function (MapGuesser\Routing\RouteCollection $routeCollection) {
|
||||
$routeCollection->get('game', '{mapId}', [MapGuesser\Controller\GameController::class, 'getGame']);
|
||||
|
Loading…
Reference in New Issue
Block a user