Merged in feature/MAPG-69-implement-google-registration (pull request #112)
Feature/MAPG-69 implement google registration
This commit is contained in:
commit
afbd0b4e20
@ -8,3 +8,6 @@ GOOGLE_MAPS_JS_API_KEY=your_google_maps_js_api_key
|
|||||||
LEAFLET_TILESERVER_URL=a_leaflet_compatible_tileserver_url
|
LEAFLET_TILESERVER_URL=a_leaflet_compatible_tileserver_url
|
||||||
LEAFLET_TILESERVER_ATTRIBUTION=attribution_to_be_shown_for_tiles
|
LEAFLET_TILESERVER_ATTRIBUTION=attribution_to_be_shown_for_tiles
|
||||||
STATIC_ROOT=/static
|
STATIC_ROOT=/static
|
||||||
|
MAIL_FROM=mapguesser@mapguesser-dev.ch
|
||||||
|
MAIL_HOST=mail
|
||||||
|
MAIL_PORT=2500
|
||||||
|
@ -5,7 +5,8 @@
|
|||||||
"license": "GNU GPL 3.0",
|
"license": "GNU GPL 3.0",
|
||||||
"require": {
|
"require": {
|
||||||
"vlucas/phpdotenv": "^4.1",
|
"vlucas/phpdotenv": "^4.1",
|
||||||
"symfony/console": "^5.1"
|
"symfony/console": "^5.1",
|
||||||
|
"phpmailer/phpmailer": "^6.1"
|
||||||
},
|
},
|
||||||
"require-dev": {},
|
"require-dev": {},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
|
70
composer.lock
generated
70
composer.lock
generated
@ -4,8 +4,76 @@
|
|||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "13a0eaff2786786caff2be86ac704fc7",
|
"content-hash": "67a75c3149ef859545476427e7f2f686",
|
||||||
"packages": [
|
"packages": [
|
||||||
|
{
|
||||||
|
"name": "phpmailer/phpmailer",
|
||||||
|
"version": "v6.1.6",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/PHPMailer/PHPMailer.git",
|
||||||
|
"reference": "c2796cb1cb99d7717290b48c4e6f32cb6c60b7b3"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/c2796cb1cb99d7717290b48c4e6f32cb6c60b7b3",
|
||||||
|
"reference": "c2796cb1cb99d7717290b48c4e6f32cb6c60b7b3",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"ext-ctype": "*",
|
||||||
|
"ext-filter": "*",
|
||||||
|
"php": ">=5.5.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"doctrine/annotations": "^1.2",
|
||||||
|
"friendsofphp/php-cs-fixer": "^2.2",
|
||||||
|
"phpunit/phpunit": "^4.8 || ^5.7"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"ext-mbstring": "Needed to send email in multibyte encoding charset",
|
||||||
|
"hayageek/oauth2-yahoo": "Needed for Yahoo XOAUTH2 authentication",
|
||||||
|
"league/oauth2-google": "Needed for Google XOAUTH2 authentication",
|
||||||
|
"psr/log": "For optional PSR-3 debug logging",
|
||||||
|
"stevenmaguire/oauth2-microsoft": "Needed for Microsoft XOAUTH2 authentication",
|
||||||
|
"symfony/polyfill-mbstring": "To support UTF-8 if the Mbstring PHP extension is not enabled (^1.2)"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"PHPMailer\\PHPMailer\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"LGPL-2.1-only"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Marcus Bointon",
|
||||||
|
"email": "phpmailer@synchromedia.co.uk"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Jim Jagielski",
|
||||||
|
"email": "jimjag@gmail.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Andy Prevost",
|
||||||
|
"email": "codeworxtech@users.sourceforge.net"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Brent R. Matzelle"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "PHPMailer is a full-featured email creation and transfer class for PHP",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://github.com/synchro",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2020-05-27T12:24:03+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "phpoption/phpoption",
|
"name": "phpoption/phpoption",
|
||||||
"version": "1.7.3",
|
"version": "1.7.3",
|
||||||
|
@ -10,8 +10,6 @@ $select->columns(['id', 'bound_south_lat', 'bound_west_lng', 'bound_north_lat',
|
|||||||
|
|
||||||
$result = $select->execute();
|
$result = $select->execute();
|
||||||
|
|
||||||
\Container::$dbConnection->startTransaction();
|
|
||||||
|
|
||||||
while ($map = $result->fetch(IResultSet::FETCH_ASSOC)) {
|
while ($map = $result->fetch(IResultSet::FETCH_ASSOC)) {
|
||||||
$bounds = Bounds::createDirectly($map['bound_south_lat'], $map['bound_west_lng'], $map['bound_north_lat'], $map['bound_east_lng']);
|
$bounds = Bounds::createDirectly($map['bound_south_lat'], $map['bound_west_lng'], $map['bound_north_lat'], $map['bound_east_lng']);
|
||||||
|
|
||||||
@ -20,5 +18,3 @@ while ($map = $result->fetch(IResultSet::FETCH_ASSOC)) {
|
|||||||
$modify->set('area', $bounds->calculateApproximateArea());
|
$modify->set('area', $bounds->calculateApproximateArea());
|
||||||
$modify->save();
|
$modify->save();
|
||||||
}
|
}
|
||||||
|
|
||||||
\Container::$dbConnection->commit();
|
|
||||||
|
17
database/migrations/data/20200614_1328_user_confirmation.php
Normal file
17
database/migrations/data/20200614_1328_user_confirmation.php
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use MapGuesser\Database\Query\Modify;
|
||||||
|
use MapGuesser\Database\Query\Select;
|
||||||
|
use MapGuesser\Interfaces\Database\IResultSet;
|
||||||
|
|
||||||
|
$select = new Select(\Container::$dbConnection, 'users');
|
||||||
|
$select->columns(['id']);
|
||||||
|
|
||||||
|
$result = $select->execute();
|
||||||
|
|
||||||
|
while ($map = $result->fetch(IResultSet::FETCH_ASSOC)) {
|
||||||
|
$modify = new Modify(\Container::$dbConnection, 'users');
|
||||||
|
$modify->setId($map['id']);
|
||||||
|
$modify->set('active', true);
|
||||||
|
$modify->save();
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
CREATE TABLE `user_confirmations` (
|
||||||
|
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||||
|
`user_id` int(10) unsigned NOT NULL,
|
||||||
|
`token` varchar(64) NOT NULL,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
KEY `user_id` (`user_id`),
|
||||||
|
KEY `token` (`token`),
|
||||||
|
CONSTRAINT `user_confirmations_user_id` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`)
|
||||||
|
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;
|
||||||
|
|
||||||
|
ALTER TABLE
|
||||||
|
`users`
|
||||||
|
ADD
|
||||||
|
`active` tinyint(1) NOT NULL DEFAULT 0;
|
@ -8,6 +8,7 @@ services:
|
|||||||
- .:/var/www/mapguesser
|
- .:/var/www/mapguesser
|
||||||
links:
|
links:
|
||||||
- 'mariadb'
|
- 'mariadb'
|
||||||
|
- 'mail'
|
||||||
mariadb:
|
mariadb:
|
||||||
image: mariadb:10.1
|
image: mariadb:10.1
|
||||||
volumes:
|
volumes:
|
||||||
@ -17,5 +18,10 @@ services:
|
|||||||
MYSQL_DATABASE: 'mapguesser'
|
MYSQL_DATABASE: 'mapguesser'
|
||||||
MYSQL_USER: 'mapguesser'
|
MYSQL_USER: 'mapguesser'
|
||||||
MYSQL_PASSWORD: 'mapguesser'
|
MYSQL_PASSWORD: 'mapguesser'
|
||||||
|
mail:
|
||||||
|
image: marcopas/docker-mailslurper:latest
|
||||||
|
ports:
|
||||||
|
- 8080:8080
|
||||||
|
- 8085:8085
|
||||||
volumes:
|
volumes:
|
||||||
mysql:
|
mysql:
|
||||||
|
13
mail/signup.tpl
Normal file
13
mail/signup.tpl
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
Hi,
|
||||||
|
|
||||||
|
You recently signed up on MapGuesser with this email address ({{EMAIL}}). To activate your account, please click on the following link:
|
||||||
|
<a href="{{ACTIVATE_LINK}}" title="Account activation">{{ACTIVATE_LINK}}</a>
|
||||||
|
|
||||||
|
If you did not sign up on MapGuesser or changed your mind, no further action is required, your email address will be deleted soon.
|
||||||
|
However if you want to immediately delete it, please click on the following link:
|
||||||
|
<a href="{{CANCEL_LINK}}" title="Sign up cancellation">{{CANCEL_LINK}}</a>
|
||||||
|
|
||||||
|
Have fun on MapGuesser!
|
||||||
|
|
||||||
|
Regards,
|
||||||
|
MapGuesser
|
1
main.php
1
main.php
@ -15,6 +15,7 @@ class Container
|
|||||||
static MapGuesser\Interfaces\Database\IConnection $dbConnection;
|
static MapGuesser\Interfaces\Database\IConnection $dbConnection;
|
||||||
static MapGuesser\Routing\RouteCollection $routeCollection;
|
static MapGuesser\Routing\RouteCollection $routeCollection;
|
||||||
static \SessionHandlerInterface $sessionHandler;
|
static \SessionHandlerInterface $sessionHandler;
|
||||||
|
static MapGuesser\Interfaces\Request\IRequest $request;
|
||||||
}
|
}
|
||||||
|
|
||||||
Container::$dbConnection = new MapGuesser\Database\Mysql\Connection($_ENV['DB_HOST'], $_ENV['DB_USER'], $_ENV['DB_PASSWORD'], $_ENV['DB_NAME']);
|
Container::$dbConnection = new MapGuesser\Database\Mysql\Connection($_ENV['DB_HOST'], $_ENV['DB_USER'], $_ENV['DB_PASSWORD'], $_ENV['DB_NAME']);
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
require '../web.php';
|
require '../web.php';
|
||||||
|
|
||||||
$host = $_SERVER['REQUEST_SCHEME'] . '://' . $_SERVER['HTTP_HOST'];
|
|
||||||
$method = strtolower($_SERVER['REQUEST_METHOD']);
|
$method = strtolower($_SERVER['REQUEST_METHOD']);
|
||||||
$url = substr($_SERVER['REQUEST_URI'], strlen('/'));
|
$url = substr($_SERVER['REQUEST_URI'], strlen('/'));
|
||||||
if (($pos = strpos($url, '?')) !== false) {
|
if (($pos = strpos($url, '?')) !== false) {
|
||||||
@ -15,10 +14,10 @@ $match = Container::$routeCollection->match($method, explode('/', $url));
|
|||||||
if ($match !== null) {
|
if ($match !== null) {
|
||||||
list($route, $params) = $match;
|
list($route, $params) = $match;
|
||||||
|
|
||||||
$request = new MapGuesser\Request\Request($_GET, $params, $_POST, $_SESSION);
|
Container::$request->setParsedRouteParams($params);
|
||||||
|
|
||||||
$handler = $route->getHandler();
|
$handler = $route->getHandler();
|
||||||
$controller = new $handler[0]($request);
|
$controller = new $handler[0](Container::$request);
|
||||||
|
|
||||||
if ($controller instanceof MapGuesser\Interfaces\Authorization\ISecured) {
|
if ($controller instanceof MapGuesser\Interfaces\Authorization\ISecured) {
|
||||||
$authorized = $controller->authorize();
|
$authorized = $controller->authorize();
|
||||||
@ -26,7 +25,7 @@ if ($match !== null) {
|
|||||||
$authorized = true;
|
$authorized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($method === 'post' && $request->post('anti_csrf_token') !== $request->session()->get('anti_csrf_token')) {
|
if ($method === 'post' && Container::$request->post('anti_csrf_token') !== Container::$request->session()->get('anti_csrf_token')) {
|
||||||
header('Content-Type: text/html; charset=UTF-8', true, 403);
|
header('Content-Type: text/html; charset=UTF-8', true, 403);
|
||||||
echo json_encode(['error' => 'no_valid_anti_csrf_token']);
|
echo json_encode(['error' => 'no_valid_anti_csrf_token']);
|
||||||
return;
|
return;
|
||||||
@ -41,7 +40,7 @@ if ($match !== null) {
|
|||||||
|
|
||||||
return;
|
return;
|
||||||
} elseif ($response instanceof MapGuesser\Interfaces\Response\IRedirect) {
|
} elseif ($response instanceof MapGuesser\Interfaces\Response\IRedirect) {
|
||||||
header('Location: ' . $host . '/' . $response->getUrl(), true, $response->getHttpCode());
|
header('Location: ' . Container::$request->getBase() . '/' . $response->getUrl(), true, $response->getHttpCode());
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -69,8 +69,9 @@ sub {
|
|||||||
bottom: -0.4em;
|
bottom: -0.4em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mono {
|
hr {
|
||||||
font-family: 'Roboto Mono', monospace;
|
border: solid #bbbbbb 1px;
|
||||||
|
margin: 10px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bold {
|
.bold {
|
||||||
@ -261,13 +262,18 @@ div.modal {
|
|||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
p.formError {
|
p.error, p.formError {
|
||||||
color: #7f2929;
|
color: #7f2929;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
p.formError {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.header {
|
div.header {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: auto auto;
|
||||||
background-color: #333333;
|
background-color: #333333;
|
||||||
height: 50px;
|
height: 50px;
|
||||||
line-height: 50px;
|
line-height: 50px;
|
||||||
@ -275,30 +281,25 @@ div.header {
|
|||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.header>div.grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: auto auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.header.small {
|
div.header.small {
|
||||||
height: 40px;
|
height: 40px;
|
||||||
line-height: 40px;
|
line-height: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.header>div.grid>:nth-child(2) {
|
div.header>p.header {
|
||||||
line-height: inherit;
|
line-height: inherit;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.header>div.grid>:nth-child(2)>span {
|
div.header>p.header>span {
|
||||||
padding-left: 6px;
|
padding-left: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.header>div.grid>:nth-child(2)>span>a:link, div.header>div.grid>:nth-child(2)>span>a:visited {
|
div.header>p.header>span>a:link, div.header>p.header>span>a:visited {
|
||||||
color: inherit;
|
color: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.header>div.grid>:nth-child(2)>span:not(:last-child) {
|
div.header>p.header>span:not(:last-child) {
|
||||||
border-right: solid white 1px;
|
border-right: solid white 1px;
|
||||||
padding-right: 6px;
|
padding-right: 6px;
|
||||||
}
|
}
|
||||||
@ -337,7 +338,7 @@ div.box {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 599px) {
|
@media screen and (max-width: 599px) {
|
||||||
div.header.small h1 span {
|
div.header h1 span {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
button, a.button {
|
button, a.button {
|
||||||
|
@ -13,7 +13,10 @@
|
|||||||
var errorText;
|
var errorText;
|
||||||
switch (this.response.error) {
|
switch (this.response.error) {
|
||||||
case 'user_not_found':
|
case 'user_not_found':
|
||||||
errorText = 'No user found with the given email address.';
|
errorText = 'No user found with the given email address. You can <a href="/signup" title="Sign up">sign up here</a>!';
|
||||||
|
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!';
|
||||||
break;
|
break;
|
||||||
case 'password_not_match':
|
case 'password_not_match':
|
||||||
errorText = 'The given password is wrong.'
|
errorText = 'The given password is wrong.'
|
||||||
|
@ -72,7 +72,7 @@ var MapGuesser = {
|
|||||||
|
|
||||||
closeButton.classList.add('gray');
|
closeButton.classList.add('gray');
|
||||||
closeButton.classList.add('marginTop');
|
closeButton.classList.add('marginTop');
|
||||||
closeButton.textContent = 'Cancel';
|
closeButton.textContent = 'Close';
|
||||||
closeButton.onclick = function () {
|
closeButton.onclick = function () {
|
||||||
MapGuesser.hideModal();
|
MapGuesser.hideModal();
|
||||||
};
|
};
|
||||||
@ -88,6 +88,14 @@ var MapGuesser = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
document.getElementById('cover').style.visibility = 'hidden';
|
document.getElementById('cover').style.visibility = 'hidden';
|
||||||
|
},
|
||||||
|
|
||||||
|
toggleDisableOnChange: function (input, button) {
|
||||||
|
if (input.defaultValue !== input.value) {
|
||||||
|
button.disabled = false;
|
||||||
|
} else {
|
||||||
|
button.disabled = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
51
public/static/js/profile.js
Normal file
51
public/static/js/profile.js
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
(function () {
|
||||||
|
var form = document.getElementById('profileForm');
|
||||||
|
|
||||||
|
form.elements.password_new.onkeyup = function () {
|
||||||
|
MapGuesser.toggleDisableOnChange(this, form.elements.save);
|
||||||
|
};
|
||||||
|
|
||||||
|
form.elements.password_new_confirm.onkeyup = function () {
|
||||||
|
MapGuesser.toggleDisableOnChange(this, form.elements.save);
|
||||||
|
};
|
||||||
|
|
||||||
|
form.onsubmit = function (e) {
|
||||||
|
document.getElementById('loading').style.visibility = 'visible';
|
||||||
|
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
var formData = new FormData(form);
|
||||||
|
|
||||||
|
MapGuesser.httpRequest('POST', form.action, function () {
|
||||||
|
document.getElementById('loading').style.visibility = 'hidden';
|
||||||
|
|
||||||
|
if (this.response.error) {
|
||||||
|
var errorText;
|
||||||
|
switch (this.response.error) {
|
||||||
|
case 'password_not_match':
|
||||||
|
errorText = 'The given current password is wrong.'
|
||||||
|
break;
|
||||||
|
case 'passwords_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':
|
||||||
|
errorText = 'The given new passwords do not match.'
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
var profileFormError = document.getElementById('profileFormError');
|
||||||
|
profileFormError.style.display = 'block';
|
||||||
|
profileFormError.innerHTML = errorText;
|
||||||
|
|
||||||
|
form.elements.password_new.select();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('profileFormError').style.display = 'none';
|
||||||
|
form.reset();
|
||||||
|
form.elements.save.disabled = true;
|
||||||
|
form.elements.password_new.focus();
|
||||||
|
}, formData);
|
||||||
|
};
|
||||||
|
})();
|
47
public/static/js/signup.js
Normal file
47
public/static/js/signup.js
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
(function () {
|
||||||
|
var form = document.getElementById('signupForm');
|
||||||
|
|
||||||
|
form.onsubmit = function (e) {
|
||||||
|
document.getElementById('loading').style.visibility = 'visible';
|
||||||
|
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
var formData = new FormData(form);
|
||||||
|
|
||||||
|
MapGuesser.httpRequest('POST', form.action, function () {
|
||||||
|
document.getElementById('loading').style.visibility = 'hidden';
|
||||||
|
|
||||||
|
if (this.response.error) {
|
||||||
|
var errorText;
|
||||||
|
switch (this.response.error) {
|
||||||
|
case 'passwords_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':
|
||||||
|
errorText = 'There is a user already registered with the given email address. Please check your email and click on the activation link!';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
var signupFormError = document.getElementById('signupFormError');
|
||||||
|
signupFormError.style.display = 'block';
|
||||||
|
signupFormError.innerHTML = errorText;
|
||||||
|
|
||||||
|
form.elements.email.select();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('signupFormError').style.display = 'none';
|
||||||
|
form.reset();
|
||||||
|
form.elements.email.focus();
|
||||||
|
|
||||||
|
MapGuesser.showModalWithContent('Sign up successful', 'Sign up was successful. Please check your email and click on the activation link to activate your account!');
|
||||||
|
}, formData);
|
||||||
|
};
|
||||||
|
})();
|
@ -53,6 +53,11 @@ class LoginController
|
|||||||
|
|
||||||
$user = new User($userData);
|
$user = new User($userData);
|
||||||
|
|
||||||
|
if (!$user->getActive()) {
|
||||||
|
$data = ['error' => 'user_not_active'];
|
||||||
|
return new JsonContent($data);
|
||||||
|
}
|
||||||
|
|
||||||
if (!$user->checkPassword($this->request->post('password'))) {
|
if (!$user->checkPassword($this->request->post('password'))) {
|
||||||
$data = ['error' => 'password_not_match'];
|
$data = ['error' => 'password_not_match'];
|
||||||
return new JsonContent($data);
|
return new JsonContent($data);
|
||||||
@ -68,6 +73,6 @@ class LoginController
|
|||||||
{
|
{
|
||||||
$this->request->session()->delete('user');
|
$this->request->session()->delete('user');
|
||||||
|
|
||||||
return new Redirect([\Container::$routeCollection->getRoute('login'), []], IRedirect::TEMPORARY);
|
return new Redirect([\Container::$routeCollection->getRoute('index'), []], IRedirect::TEMPORARY);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
192
src/Controller/SignupController.php
Normal file
192
src/Controller/SignupController.php
Normal file
@ -0,0 +1,192 @@
|
|||||||
|
<?php namespace MapGuesser\Controller;
|
||||||
|
|
||||||
|
use MapGuesser\Database\Query\Modify;
|
||||||
|
use MapGuesser\Database\Query\Select;
|
||||||
|
use MapGuesser\Interfaces\Database\IResultSet;
|
||||||
|
use MapGuesser\Interfaces\Request\IRequest;
|
||||||
|
use MapGuesser\Interfaces\Response\IContent;
|
||||||
|
use MapGuesser\Interfaces\Response\IRedirect;
|
||||||
|
use MapGuesser\Mailing\Mail;
|
||||||
|
use MapGuesser\Model\User;
|
||||||
|
use MapGuesser\Response\HtmlContent;
|
||||||
|
use MapGuesser\Response\JsonContent;
|
||||||
|
use MapGuesser\Response\Redirect;
|
||||||
|
|
||||||
|
class SignupController
|
||||||
|
{
|
||||||
|
private IRequest $request;
|
||||||
|
|
||||||
|
public function __construct(IRequest $request)
|
||||||
|
{
|
||||||
|
$this->request = $request;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSignupForm()
|
||||||
|
{
|
||||||
|
$session = $this->request->session();
|
||||||
|
|
||||||
|
if ($session->get('user')) {
|
||||||
|
return new Redirect([\Container::$routeCollection->getRoute('index'), []], IRedirect::TEMPORARY);
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = [];
|
||||||
|
return new HtmlContent('signup/signup', $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function signup(): IContent
|
||||||
|
{
|
||||||
|
$session = $this->request->session();
|
||||||
|
|
||||||
|
if ($session->get('user')) {
|
||||||
|
//TODO: return with some error
|
||||||
|
$data = ['success' => true];
|
||||||
|
return new JsonContent($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
$select = new Select(\Container::$dbConnection, 'users');
|
||||||
|
$select->columns(User::getFields());
|
||||||
|
$select->where('email', '=', $this->request->post('email'));
|
||||||
|
|
||||||
|
$userData = $select->execute()->fetch(IResultSet::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if ($userData !== null) {
|
||||||
|
$user = new User($userData);
|
||||||
|
|
||||||
|
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([
|
||||||
|
'email' => $this->request->post('email'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$user->setPlainPassword($this->request->post('password'));
|
||||||
|
|
||||||
|
\Container::$dbConnection->startTransaction();
|
||||||
|
|
||||||
|
$modify = new Modify(\Container::$dbConnection, 'users');
|
||||||
|
$modify->fill($user->toArray());
|
||||||
|
$modify->save();
|
||||||
|
$userId = $modify->getId();
|
||||||
|
|
||||||
|
$token = hash('sha256', serialize($user) . random_bytes(10) . microtime());
|
||||||
|
|
||||||
|
$modify = new Modify(\Container::$dbConnection, 'user_confirmations');
|
||||||
|
$modify->set('user_id', $userId);
|
||||||
|
$modify->set('token', $token);
|
||||||
|
$modify->save();
|
||||||
|
|
||||||
|
\Container::$dbConnection->commit();
|
||||||
|
|
||||||
|
$this->sendConfirmationEmail($user->getEmail(), $token);
|
||||||
|
|
||||||
|
$data = ['success' => true];
|
||||||
|
return new JsonContent($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function activate()
|
||||||
|
{
|
||||||
|
$session = $this->request->session();
|
||||||
|
|
||||||
|
if ($session->get('user')) {
|
||||||
|
return new Redirect([\Container::$routeCollection->getRoute('index'), []], IRedirect::TEMPORARY);
|
||||||
|
}
|
||||||
|
|
||||||
|
$select = new Select(\Container::$dbConnection, 'user_confirmations');
|
||||||
|
$select->columns(['id', 'user_id']);
|
||||||
|
$select->where('token', '=', $this->request->query('token'));
|
||||||
|
|
||||||
|
$confirmation = $select->execute()->fetch(IResultSet::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if ($confirmation === null) {
|
||||||
|
$data = [];
|
||||||
|
return new HtmlContent('signup/activate', $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
\Container::$dbConnection->startTransaction();
|
||||||
|
|
||||||
|
$modify = new Modify(\Container::$dbConnection, 'user_confirmations');
|
||||||
|
$modify->setId($confirmation['id']);
|
||||||
|
$modify->delete();
|
||||||
|
|
||||||
|
$modify = new Modify(\Container::$dbConnection, 'users');
|
||||||
|
$modify->setId($confirmation['user_id']);
|
||||||
|
$modify->set('active', true);
|
||||||
|
$modify->save();
|
||||||
|
|
||||||
|
\Container::$dbConnection->commit();
|
||||||
|
|
||||||
|
$select = new Select(\Container::$dbConnection, 'users');
|
||||||
|
$select->columns(User::getFields());
|
||||||
|
$select->whereId($confirmation['user_id']);
|
||||||
|
|
||||||
|
$userData = $select->execute()->fetch(IResultSet::FETCH_ASSOC);
|
||||||
|
$user = new User($userData);
|
||||||
|
|
||||||
|
$session->set('user', $user);
|
||||||
|
|
||||||
|
return new Redirect([\Container::$routeCollection->getRoute('index'), []], IRedirect::TEMPORARY);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function cancel()
|
||||||
|
{
|
||||||
|
$session = $this->request->session();
|
||||||
|
|
||||||
|
if ($session->get('user')) {
|
||||||
|
return new Redirect([\Container::$routeCollection->getRoute('index'), []], IRedirect::TEMPORARY);
|
||||||
|
}
|
||||||
|
|
||||||
|
$select = new Select(\Container::$dbConnection, 'user_confirmations');
|
||||||
|
$select->columns(['id', 'user_id']);
|
||||||
|
$select->where('token', '=', $this->request->query('token'));
|
||||||
|
|
||||||
|
$confirmation = $select->execute()->fetch(IResultSet::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if ($confirmation === null) {
|
||||||
|
$data = ['success' => false];
|
||||||
|
return new HtmlContent('signup/cancel', $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
\Container::$dbConnection->startTransaction();
|
||||||
|
|
||||||
|
$modify = new Modify(\Container::$dbConnection, 'user_confirmations');
|
||||||
|
$modify->setId($confirmation['id']);
|
||||||
|
$modify->delete();
|
||||||
|
|
||||||
|
$modify = new Modify(\Container::$dbConnection, 'users');
|
||||||
|
$modify->setId($confirmation['user_id']);
|
||||||
|
$modify->delete();
|
||||||
|
|
||||||
|
\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();
|
||||||
|
}
|
||||||
|
}
|
56
src/Controller/UserController.php
Normal file
56
src/Controller/UserController.php
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
<?php namespace MapGuesser\Controller;
|
||||||
|
|
||||||
|
use MapGuesser\Database\Query\Modify;
|
||||||
|
use MapGuesser\Interfaces\Request\IRequest;
|
||||||
|
use MapGuesser\Interfaces\Response\IContent;
|
||||||
|
use MapGuesser\Response\HtmlContent;
|
||||||
|
use MapGuesser\Response\JsonContent;
|
||||||
|
|
||||||
|
class UserController
|
||||||
|
{
|
||||||
|
private IRequest $request;
|
||||||
|
|
||||||
|
public function __construct(IRequest $request)
|
||||||
|
{
|
||||||
|
$this->request = $request;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getProfile(): IContent
|
||||||
|
{
|
||||||
|
$user = $this->request->user();
|
||||||
|
|
||||||
|
$data = ['user' => $user->toArray()];
|
||||||
|
return new HtmlContent('profile', $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function saveProfile(): IContent
|
||||||
|
{
|
||||||
|
$user = $this->request->user();
|
||||||
|
|
||||||
|
if (!$user->checkPassword($this->request->post('password'))) {
|
||||||
|
$data = ['error' => 'password_not_match'];
|
||||||
|
return new JsonContent($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strlen($this->request->post('password_new')) > 0) {
|
||||||
|
if (strlen($this->request->post('password_new')) < 6) {
|
||||||
|
$data = ['error' => 'passwords_too_short'];
|
||||||
|
return new JsonContent($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->request->post('password_new') !== $this->request->post('password_new_confirm')) {
|
||||||
|
$data = ['error' => 'passwords_not_match'];
|
||||||
|
return new JsonContent($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
$user->setPlainPassword($this->request->post('password_new'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$modify = new Modify(\Container::$dbConnection, 'users');
|
||||||
|
$modify->fill($user->toArray());
|
||||||
|
$modify->save();
|
||||||
|
|
||||||
|
$data = ['success' => true];
|
||||||
|
return new JsonContent($data);
|
||||||
|
}
|
||||||
|
}
|
@ -7,4 +7,6 @@ interface IUser
|
|||||||
const PERMISSION_ADMIN = 1;
|
const PERMISSION_ADMIN = 1;
|
||||||
|
|
||||||
public function hasPermission(int $permission): bool;
|
public function hasPermission(int $permission): bool;
|
||||||
|
|
||||||
|
public function getDisplayName(): string;
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,10 @@ use MapGuesser\Interfaces\Authentication\IUser;
|
|||||||
|
|
||||||
interface IRequest
|
interface IRequest
|
||||||
{
|
{
|
||||||
|
public function setParsedRouteParams(array &$routeParams);
|
||||||
|
|
||||||
|
public function getBase(): string;
|
||||||
|
|
||||||
public function query(string $key);
|
public function query(string $key);
|
||||||
|
|
||||||
public function post(string $key);
|
public function post(string $key);
|
||||||
|
81
src/Mailing/Mail.php
Normal file
81
src/Mailing/Mail.php
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
<?php namespace MapGuesser\Mailing;
|
||||||
|
|
||||||
|
use PHPMailer\PHPMailer\PHPMailer;
|
||||||
|
|
||||||
|
class Mail
|
||||||
|
{
|
||||||
|
private array $recipients = [];
|
||||||
|
|
||||||
|
public string $subject = '';
|
||||||
|
|
||||||
|
public string $body = '';
|
||||||
|
|
||||||
|
public function addRecipient(string $mail, ?string $name = null): void
|
||||||
|
{
|
||||||
|
$this->recipients[] = [$mail, $name];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setSubject(string $subject): void
|
||||||
|
{
|
||||||
|
$this->subject = $subject;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setBody(string $body): void
|
||||||
|
{
|
||||||
|
$this->body = $body;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setBodyFromTemplate(string $template, array $params = []): void
|
||||||
|
{
|
||||||
|
$this->body = file_get_contents(ROOT . '/mail/' . $template . '.tpl');
|
||||||
|
|
||||||
|
foreach ($params as $key => $param) {
|
||||||
|
$this->body = str_replace('{{' . $key . '}}', $param, $this->body);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function send(): void
|
||||||
|
{
|
||||||
|
$mailer = new PHPMailer(true);
|
||||||
|
|
||||||
|
$mailer->CharSet = 'utf-8';
|
||||||
|
$mailer->Hostname = substr($_ENV['MAIL_FROM'], strpos($_ENV['MAIL_FROM'], '@') + 1);
|
||||||
|
|
||||||
|
if (!empty($_ENV['MAIL_HOST'])) {
|
||||||
|
$mailer->Mailer = 'smtp';
|
||||||
|
$mailer->Host = $_ENV['MAIL_HOST'];
|
||||||
|
$mailer->Port = !empty($_ENV['MAIL_PORT']) ? $_ENV['MAIL_PORT'] : 25;
|
||||||
|
$mailer->SMTPSecure = !empty($_ENV['MAIL_SECURE']) ? $_ENV['MAIL_SECURE'] : '';
|
||||||
|
|
||||||
|
if (!empty($_ENV['MAIL_USER'])) {
|
||||||
|
$mailer->SMTPAuth = true;
|
||||||
|
$mailer->Username = $_ENV['MAIL_USER'];
|
||||||
|
$mailer->Password = $_ENV['MAIL_PASSWORD'];
|
||||||
|
} else {
|
||||||
|
$mailer->SMTPAuth = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$mailer->Mailer = 'mail';
|
||||||
|
}
|
||||||
|
|
||||||
|
$mailer->setFrom($_ENV['MAIL_FROM'], 'MapGuesser');
|
||||||
|
$mailer->addReplyTo($_ENV['MAIL_FROM'], 'MapGuesser');
|
||||||
|
|
||||||
|
$mailer->Sender = !empty($_ENV['MAIL_BOUNCE']) ? $_ENV['MAIL_BOUNCE'] : $_ENV['MAIL_FROM'];
|
||||||
|
|
||||||
|
$mailer->Subject = $this->subject;
|
||||||
|
$mailer->Body = $this->body;
|
||||||
|
|
||||||
|
foreach ($this->recipients as $recipient) {
|
||||||
|
$this->sendMail($mailer, $recipient);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function sendMail(PHPMailer $mailer, array $recipient)
|
||||||
|
{
|
||||||
|
$mailer->clearAddresses();
|
||||||
|
$mailer->addAddress($recipient[0], $recipient[1]);
|
||||||
|
|
||||||
|
$mailer->send();
|
||||||
|
}
|
||||||
|
}
|
@ -6,7 +6,7 @@ class User extends BaseModel implements IUser
|
|||||||
{
|
{
|
||||||
private static array $types = ['user', 'admin'];
|
private static array $types = ['user', 'admin'];
|
||||||
|
|
||||||
protected static array $fields = ['email', 'password', 'type'];
|
protected static array $fields = ['email', 'password', 'type', 'active'];
|
||||||
|
|
||||||
private string $email;
|
private string $email;
|
||||||
|
|
||||||
@ -14,6 +14,8 @@ class User extends BaseModel implements IUser
|
|||||||
|
|
||||||
private string $type = 'user';
|
private string $type = 'user';
|
||||||
|
|
||||||
|
private bool $active = false;
|
||||||
|
|
||||||
public function setEmail(string $email): void
|
public function setEmail(string $email): void
|
||||||
{
|
{
|
||||||
$this->email = $email;
|
$this->email = $email;
|
||||||
@ -36,6 +38,11 @@ class User extends BaseModel implements IUser
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function setActive($active): void
|
||||||
|
{
|
||||||
|
$this->active = (bool) $active;
|
||||||
|
}
|
||||||
|
|
||||||
public function getEmail(): string
|
public function getEmail(): string
|
||||||
{
|
{
|
||||||
return $this->email;
|
return $this->email;
|
||||||
@ -51,6 +58,11 @@ class User extends BaseModel implements IUser
|
|||||||
return $this->type;
|
return $this->type;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getActive(): bool
|
||||||
|
{
|
||||||
|
return $this->active;
|
||||||
|
}
|
||||||
|
|
||||||
public function hasPermission(int $permission): bool
|
public function hasPermission(int $permission): bool
|
||||||
{
|
{
|
||||||
switch ($permission) {
|
switch ($permission) {
|
||||||
@ -63,6 +75,11 @@ class User extends BaseModel implements IUser
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getDisplayName(): string
|
||||||
|
{
|
||||||
|
return $this->email;
|
||||||
|
}
|
||||||
|
|
||||||
public function checkPassword(string $password): bool
|
public function checkPassword(string $password): bool
|
||||||
{
|
{
|
||||||
return password_verify($password, $this->password);
|
return password_verify($password, $this->password);
|
||||||
|
@ -3,26 +3,37 @@
|
|||||||
use MapGuesser\Interfaces\Authentication\IUser;
|
use MapGuesser\Interfaces\Authentication\IUser;
|
||||||
use MapGuesser\Interfaces\Request\IRequest;
|
use MapGuesser\Interfaces\Request\IRequest;
|
||||||
use MapGuesser\Interfaces\Request\ISession;
|
use MapGuesser\Interfaces\Request\ISession;
|
||||||
use MapGuesser\Model\User;
|
|
||||||
|
|
||||||
class Request implements IRequest
|
class Request implements IRequest
|
||||||
{
|
{
|
||||||
|
private string $base;
|
||||||
|
|
||||||
private array $get;
|
private array $get;
|
||||||
|
|
||||||
private array $routeParams;
|
private array $routeParams = [];
|
||||||
|
|
||||||
private array $post;
|
private array $post;
|
||||||
|
|
||||||
private Session $session;
|
private Session $session;
|
||||||
|
|
||||||
public function __construct(array &$get, array &$routeParams, array &$post, array &$session)
|
public function __construct(string $base, array &$get, array &$post, array &$session)
|
||||||
{
|
{
|
||||||
|
$this->base = $base;
|
||||||
$this->get = &$get;
|
$this->get = &$get;
|
||||||
$this->routeParams = &$routeParams;
|
|
||||||
$this->post = &$post;
|
$this->post = &$post;
|
||||||
$this->session = new Session($session);
|
$this->session = new Session($session);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function setParsedRouteParams(array &$routeParams)
|
||||||
|
{
|
||||||
|
$this->routeParams = &$routeParams;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getBase(): string
|
||||||
|
{
|
||||||
|
return $this->base;
|
||||||
|
}
|
||||||
|
|
||||||
public function query($key)
|
public function query($key)
|
||||||
{
|
{
|
||||||
if (isset($this->get[$key])) {
|
if (isset($this->get[$key])) {
|
||||||
|
@ -14,15 +14,14 @@ $jsFiles = [
|
|||||||
?>
|
?>
|
||||||
<?php require ROOT . '/views/templates/main_header.php'; ?>
|
<?php require ROOT . '/views/templates/main_header.php'; ?>
|
||||||
<div class="header small">
|
<div class="header small">
|
||||||
<div class="grid">
|
|
||||||
<h1>
|
<h1>
|
||||||
<a href="/maps" title="Back to playable maps">
|
<a href="/maps" title="Back to playable maps">
|
||||||
<img class="inline" width="1em" height="1em" src="<?= $_ENV['STATIC_ROOT'] ?>/img/icon.svg?rev=<?= REVISION ?>">
|
<img class="inline" width="1em" height="1em" src="<?= $_ENV['STATIC_ROOT'] ?>/img/icon.svg?rev=<?= REVISION ?>">
|
||||||
<span>MapGuesser</span>
|
<span>MapGuesser</span>
|
||||||
</a>
|
</a>
|
||||||
</h1>
|
</h1>
|
||||||
<p>
|
<p class="header">
|
||||||
<span class="bold"><a href="javascript:;" id="mapName" title="Edit map data"><?= $mapName ?></a></span><!--
|
<span><a href="javascript:;" id="mapName" title="Edit map data"><?= $mapName ?></a></span><!--
|
||||||
--><span><!--
|
--><span><!--
|
||||||
<?php /* Copyright (c) 2019 The Bootstrap Authors. License can be found in 'USED_SOFTWARE' in section 'Bootstrap Icons'. */ ?>
|
<?php /* Copyright (c) 2019 The Bootstrap Authors. License can be found in 'USED_SOFTWARE' in section 'Bootstrap Icons'. */ ?>
|
||||||
--><svg class="inline" width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
|
--><svg class="inline" width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
|
||||||
@ -48,7 +47,6 @@ $jsFiles = [
|
|||||||
<span id="deleted" class="bold">0</span><!--
|
<span id="deleted" class="bold">0</span><!--
|
||||||
--></span>
|
--></span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div id="metadata" class="modal">
|
<div id="metadata" class="modal">
|
||||||
<h2>Edit map data</h2>
|
<h2>Edit map data</h2>
|
||||||
|
@ -9,19 +9,17 @@ $jsFiles = [
|
|||||||
?>
|
?>
|
||||||
<?php require ROOT . '/views/templates/main_header.php'; ?>
|
<?php require ROOT . '/views/templates/main_header.php'; ?>
|
||||||
<div class="header small">
|
<div class="header small">
|
||||||
<div class="grid">
|
|
||||||
<h1>
|
<h1>
|
||||||
<a href="/maps" title="Back to playable maps">
|
<a href="/maps" title="Back to playable maps">
|
||||||
<img class="inline" width="1em" height="1em" src="<?= $_ENV['STATIC_ROOT'] ?>/img/icon.svg?rev=<?= REVISION ?>">
|
<img class="inline" width="1em" height="1em" src="<?= $_ENV['STATIC_ROOT'] ?>/img/icon.svg?rev=<?= REVISION ?>">
|
||||||
<span>MapGuesser</span>
|
<span>MapGuesser</span>
|
||||||
</a>
|
</a>
|
||||||
</h1>
|
</h1>
|
||||||
<p>
|
<p class="header">
|
||||||
<span id="mapName" class="bold"><?= $mapName ?></span><!--
|
<span id="mapName" class="bold"><?= $mapName ?></span><!--
|
||||||
--><span>Round <span id="currentRound" class="bold"></span></span><!--
|
--><span>Round <span id="currentRound" class="bold"></span></span><!--
|
||||||
--><span>Score <span id="currentScoreSum" class="bold"></span></span>
|
--><span>Score <span id="currentScoreSum" class="bold"></span></span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div id="guessCover"></div>
|
<div id="guessCover"></div>
|
||||||
<div id="panorama"></div>
|
<div id="panorama"></div>
|
||||||
|
@ -11,7 +11,7 @@ $jsFiles = [
|
|||||||
<form id="loginForm" action="/login" method="post">
|
<form id="loginForm" action="/login" method="post">
|
||||||
<input class="big fullWidth" type="email" name="email" placeholder="Email address" autofocus>
|
<input class="big fullWidth" type="email" name="email" placeholder="Email address" autofocus>
|
||||||
<input class="big fullWidth marginTop" type="password" name="password" placeholder="Password">
|
<input class="big fullWidth marginTop" type="password" name="password" placeholder="Password">
|
||||||
<p id="loginFormError" class="formError marginTop"></p>
|
<p id="loginFormError" class="formError justify marginTop"></p>
|
||||||
<div class="right marginTop">
|
<div class="right marginTop">
|
||||||
<button type="submit">Login</button>
|
<button type="submit">Login</button>
|
||||||
</div>
|
</div>
|
||||||
|
25
views/profile.php
Normal file
25
views/profile.php
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
$jsFiles = [
|
||||||
|
'js/profile.js',
|
||||||
|
];
|
||||||
|
?>
|
||||||
|
<?php require ROOT . '/views/templates/main_header.php'; ?>
|
||||||
|
<?php require ROOT . '/views/templates/header.php'; ?>
|
||||||
|
<div class="main">
|
||||||
|
<h2>Profile</h2>
|
||||||
|
<div class="box">
|
||||||
|
<form id="profileForm" action="/profile" method="post">
|
||||||
|
<?php /* TODO: disabled for the time being, email modification should be implemented */ ?>
|
||||||
|
<input class="big fullWidth" type="email" name="email" placeholder="Email address" value="<?= $user['email'] ?>" disabled>
|
||||||
|
<input class="big fullWidth marginTop" type="password" name="password_new" placeholder="New password" autofocus>
|
||||||
|
<input class="big fullWidth marginTop" type="password" name="password_new_confirm" placeholder="New password confirmation">
|
||||||
|
<hr>
|
||||||
|
<input class="big fullWidth" type="password" name="password" placeholder="Current password">
|
||||||
|
<p id="profileFormError" class="formError justify marginTop"></p>
|
||||||
|
<div class="right marginTop">
|
||||||
|
<button type="submit" name="save" disabled>Save</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php require ROOT . '/views/templates/main_footer.php'; ?>
|
9
views/signup/activate.php
Normal file
9
views/signup/activate.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>Account activation</h2>
|
||||||
|
<div class="box">
|
||||||
|
<p class="error justify">Activation failed. Please check the link you entered or retry <a href="/signup" title="Sign up">sign up</a>!</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php require ROOT . '/views/templates/main_footer.php'; ?>
|
13
views/signup/cancel.php
Normal file
13
views/signup/cancel.php
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<?php require ROOT . '/views/templates/main_header.php'; ?>
|
||||||
|
<?php require ROOT . '/views/templates/header.php'; ?>
|
||||||
|
<div class="main">
|
||||||
|
<h2>Account cancellation</h2>
|
||||||
|
<div class="box">
|
||||||
|
<?php if ($success) : ?>
|
||||||
|
<p class="justify">Cancellation was successfull. You can <a href="/signup" title="Sign up">sign up</a> any time if you want!</p>
|
||||||
|
<?php else: ?>
|
||||||
|
<p class="error justify">Cancellation failed. Please check the link you entered! Maybe the account was already deleted, in this case no further action is required.</p>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php require ROOT . '/views/templates/main_footer.php'; ?>
|
22
views/signup/signup.php
Normal file
22
views/signup/signup.php
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
<?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" autofocus>
|
||||||
|
<input class="big fullWidth marginTop" type="password" name="password" placeholder="Password">
|
||||||
|
<input class="big fullWidth marginTop" type="password" name="password_confirm" placeholder="Password confirmation">
|
||||||
|
<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'; ?>
|
@ -2,7 +2,22 @@
|
|||||||
<h1>
|
<h1>
|
||||||
<a href="/" title="MapGuesser">
|
<a href="/" title="MapGuesser">
|
||||||
<img class="inline" width="1em" height="1em" src="<?= $_ENV['STATIC_ROOT'] ?>/img/icon.svg?rev=<?= REVISION ?>">
|
<img class="inline" width="1em" height="1em" src="<?= $_ENV['STATIC_ROOT'] ?>/img/icon.svg?rev=<?= REVISION ?>">
|
||||||
MapGuesser
|
<span>MapGuesser</span>
|
||||||
</a>
|
</a>
|
||||||
</h1>
|
</h1>
|
||||||
|
<p class="header">
|
||||||
|
<?php if (Container::$request->user()) : ?>
|
||||||
|
<span><a href="/profile" title="Profile">
|
||||||
|
<?php /* Copyright (c) 2019 The Bootstrap Authors. License can be found in 'USED_SOFTWARE' in section 'Bootstrap Icons'. */ ?>
|
||||||
|
<svg class="inline" width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path fill-rule="evenodd" d="M3 14s-1 0-1-1 1-4 6-4 6 3 6 4-1 1-1 1H3zm5-6a3 3 0 1 0 0-6 3 3 0 0 0 0 6z"/>
|
||||||
|
</svg>
|
||||||
|
<?= Container::$request->user()->getDisplayName() ?><!--
|
||||||
|
--></a></span><!--
|
||||||
|
--><span><a href="/logout" title="Logout">Logout</a></span>
|
||||||
|
<?php else : ?>
|
||||||
|
<span><a href="/signup" title="Login">Sign up</a></span><!--
|
||||||
|
--><span><a href="/login" title="Login">Login</a></span>
|
||||||
|
<?php endif; ?>
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
@ -28,6 +28,6 @@
|
|||||||
<div id="cover"></div>
|
<div id="cover"></div>
|
||||||
<div id="modal" class="modal">
|
<div id="modal" class="modal">
|
||||||
<h2 id="modalTitle"></h2>
|
<h2 id="modalTitle"></h2>
|
||||||
<p id="modalText" class="marginTop"></p>
|
<p id="modalText" class="justify marginTop"></p>
|
||||||
<div id="modalButtons" class="right"></div>
|
<div id="modalButtons" class="right"></div>
|
||||||
</div>
|
</div>
|
12
web.php
12
web.php
@ -15,7 +15,13 @@ Container::$routeCollection = new MapGuesser\Routing\RouteCollection();
|
|||||||
Container::$routeCollection->get('index', '', [MapGuesser\Controller\HomeController::class, 'getIndex']);
|
Container::$routeCollection->get('index', '', [MapGuesser\Controller\HomeController::class, 'getIndex']);
|
||||||
Container::$routeCollection->get('login', 'login', [MapGuesser\Controller\LoginController::class, 'getLoginForm']);
|
Container::$routeCollection->get('login', 'login', [MapGuesser\Controller\LoginController::class, 'getLoginForm']);
|
||||||
Container::$routeCollection->post('login-action', 'login', [MapGuesser\Controller\LoginController::class, 'login']);
|
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->get('logout', 'logout', [MapGuesser\Controller\LoginController::class, 'logout']);
|
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->get('maps', 'maps', [MapGuesser\Controller\MapsController::class, 'getMaps']);
|
Container::$routeCollection->get('maps', 'maps', [MapGuesser\Controller\MapsController::class, 'getMaps']);
|
||||||
Container::$routeCollection->group('game', function (MapGuesser\Routing\RouteCollection $routeCollection) {
|
Container::$routeCollection->group('game', function (MapGuesser\Routing\RouteCollection $routeCollection) {
|
||||||
$routeCollection->get('game', '{mapId}', [MapGuesser\Controller\GameController::class, 'getGame']);
|
$routeCollection->get('game', '{mapId}', [MapGuesser\Controller\GameController::class, 'getGame']);
|
||||||
@ -40,6 +46,8 @@ session_start([
|
|||||||
'cookie_samesite' => 'Lax'
|
'cookie_samesite' => 'Lax'
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (!isset($_SESSION['anti_csrf_token'])) {
|
Container::$request = new MapGuesser\Request\Request($_SERVER['REQUEST_SCHEME'] . '://' . $_SERVER['HTTP_HOST'], $_GET, $_POST, $_SESSION);
|
||||||
$_SESSION['anti_csrf_token'] = hash('sha256', random_bytes(10) . microtime());
|
|
||||||
|
if (!Container::$request->session()->has('anti_csrf_token')) {
|
||||||
|
Container::$request->session()->set('anti_csrf_token', hash('sha256', random_bytes(10) . microtime()));
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user