Merged in feature/MAPG-142-resend-activation-link-functionality (pull request #172)
Feature/MAPG-142 resend activation link functionality
This commit is contained in:
commit
b58137acc9
@ -1,4 +1,4 @@
|
|||||||
APP_NAME=MapQuiz
|
APP_NAME=MapGuesser
|
||||||
DEV=1
|
DEV=1
|
||||||
DB_HOST=mariadb
|
DB_HOST=mariadb
|
||||||
DB_USER=mapguesser
|
DB_USER=mapguesser
|
||||||
|
@ -0,0 +1,5 @@
|
|||||||
|
UPDATE `user_confirmations` SET token=SUBSTRING(token, 1, 32);
|
||||||
|
|
||||||
|
ALTER TABLE `user_confirmations`
|
||||||
|
ADD `last_sent` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
MODIFY `token` varchar(32) CHARACTER SET ascii NOT NULL;
|
@ -0,0 +1,4 @@
|
|||||||
|
UPDATE `sessions` SET id=SUBSTRING(id, 1, 32);
|
||||||
|
|
||||||
|
ALTER TABLE `sessions`
|
||||||
|
MODIFY `id` varchar(32) CHARACTER SET ascii NOT NULL;
|
@ -0,0 +1,2 @@
|
|||||||
|
ALTER TABLE `users`
|
||||||
|
ADD `created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP;
|
2
main.php
2
main.php
@ -16,7 +16,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 MapGuesser\Interfaces\Session\ISessionHandler $sessionHandler;
|
||||||
static MapGuesser\Interfaces\Request\IRequest $request;
|
static MapGuesser\Interfaces\Request\IRequest $request;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
<?php namespace MapGuesser\Cli;
|
<?php namespace MapGuesser\Cli;
|
||||||
|
|
||||||
|
use DateTime;
|
||||||
use MapGuesser\PersistentData\PersistentDataManager;
|
use MapGuesser\PersistentData\PersistentDataManager;
|
||||||
use MapGuesser\PersistentData\Model\User;
|
use MapGuesser\PersistentData\Model\User;
|
||||||
use Symfony\Component\Console\Command\Command;
|
use Symfony\Component\Console\Command\Command;
|
||||||
@ -24,6 +25,7 @@ class AddUserCommand extends Command
|
|||||||
$user->setEmail($input->getArgument('email'));
|
$user->setEmail($input->getArgument('email'));
|
||||||
$user->setPlainPassword($input->getArgument('password'));
|
$user->setPlainPassword($input->getArgument('password'));
|
||||||
$user->setActive(true);
|
$user->setActive(true);
|
||||||
|
$user->setCreatedDate(new DateTime());
|
||||||
|
|
||||||
if ($input->hasArgument('type')) {
|
if ($input->hasArgument('type')) {
|
||||||
$user->setType($input->getArgument('type'));
|
$user->setType($input->getArgument('type'));
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
<?php namespace MapGuesser\Controller;
|
<?php namespace MapGuesser\Controller;
|
||||||
|
|
||||||
|
use DateInterval;
|
||||||
use DateTime;
|
use DateTime;
|
||||||
use MapGuesser\Http\Request;
|
use MapGuesser\Http\Request;
|
||||||
use MapGuesser\Interfaces\Request\IRequest;
|
use MapGuesser\Interfaces\Request\IRequest;
|
||||||
@ -52,13 +53,16 @@ class LoginController
|
|||||||
public function getGoogleLoginRedirect(): IRedirect
|
public function getGoogleLoginRedirect(): IRedirect
|
||||||
{
|
{
|
||||||
$state = bin2hex(random_bytes(16));
|
$state = bin2hex(random_bytes(16));
|
||||||
|
$nonce = bin2hex(random_bytes(16));
|
||||||
|
|
||||||
$this->request->session()->set('oauth_state', $state);
|
$this->request->session()->set('oauth_state', $state);
|
||||||
|
$this->request->session()->set('oauth_nonce', $nonce);
|
||||||
|
|
||||||
$oAuth = new GoogleOAuth(new Request());
|
$oAuth = new GoogleOAuth(new Request());
|
||||||
$url = $oAuth->getDialogUrl(
|
$url = $oAuth->getDialogUrl(
|
||||||
$state,
|
$state,
|
||||||
$this->request->getBase() . '/' . \Container::$routeCollection->getRoute('login-google-action')->generateLink()
|
$this->request->getBase() . '/' . \Container::$routeCollection->getRoute('login-google-action')->generateLink(),
|
||||||
|
$nonce
|
||||||
);
|
);
|
||||||
|
|
||||||
return new Redirect($url, IRedirect::TEMPORARY);
|
return new Redirect($url, IRedirect::TEMPORARY);
|
||||||
@ -168,6 +172,8 @@ class LoginController
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!$user->getActive()) {
|
if (!$user->getActive()) {
|
||||||
|
$this->resendConfirmationEmail($user);
|
||||||
|
|
||||||
return new JsonContent([
|
return new JsonContent([
|
||||||
'error' => [
|
'error' => [
|
||||||
'errorText' => 'User found with the given email address, but the account is not activated. ' .
|
'errorText' => 'User found with the given email address, but the account is not activated. ' .
|
||||||
@ -211,16 +217,20 @@ class LoginController
|
|||||||
}
|
}
|
||||||
|
|
||||||
$jwtParser = new JwtParser($tokenData['id_token']);
|
$jwtParser = new JwtParser($tokenData['id_token']);
|
||||||
$userData = $jwtParser->getPayload();
|
$idToken = $jwtParser->getPayload();
|
||||||
|
|
||||||
if (!$userData['email_verified']) {
|
if ($idToken['nonce'] !== $this->request->session()->get('oauth_nonce')) {
|
||||||
return new HtmlContent('login/google_login');
|
return new HtmlContent('login/google_login');
|
||||||
}
|
}
|
||||||
|
|
||||||
$user = $this->userRepository->getByGoogleSub($userData['sub']);
|
if (!$idToken['email_verified']) {
|
||||||
|
return new HtmlContent('login/google_login');
|
||||||
|
}
|
||||||
|
|
||||||
|
$user = $this->userRepository->getByGoogleSub($idToken['sub']);
|
||||||
|
|
||||||
if ($user === null) {
|
if ($user === null) {
|
||||||
$this->request->session()->set('google_user_data', $userData);
|
$this->request->session()->set('google_user_data', ['sub' => $idToken['sub'], 'email' => $idToken['email']]);
|
||||||
|
|
||||||
return new Redirect(\Container::$routeCollection->getRoute('signup-google')->generateLink(), IRedirect::TEMPORARY);
|
return new Redirect(\Container::$routeCollection->getRoute('signup-google')->generateLink(), IRedirect::TEMPORARY);
|
||||||
}
|
}
|
||||||
@ -301,16 +311,18 @@ class LoginController
|
|||||||
$user = new User();
|
$user = new User();
|
||||||
$user->setEmail($this->request->post('email'));
|
$user->setEmail($this->request->post('email'));
|
||||||
$user->setPlainPassword($this->request->post('password'));
|
$user->setPlainPassword($this->request->post('password'));
|
||||||
|
$user->setCreatedDate(new DateTime());
|
||||||
|
|
||||||
\Container::$dbConnection->startTransaction();
|
\Container::$dbConnection->startTransaction();
|
||||||
|
|
||||||
$this->pdm->saveToDb($user);
|
$this->pdm->saveToDb($user);
|
||||||
|
|
||||||
$token = hash('sha256', serialize($user) . random_bytes(10) . microtime());
|
$token = bin2hex(random_bytes(16));
|
||||||
|
|
||||||
$confirmation = new UserConfirmation();
|
$confirmation = new UserConfirmation();
|
||||||
$confirmation->setUser($user);
|
$confirmation->setUser($user);
|
||||||
$confirmation->setToken($token);
|
$confirmation->setToken($token);
|
||||||
|
$confirmation->setLastSentDate(new DateTime());
|
||||||
|
|
||||||
$this->pdm->saveToDb($confirmation);
|
$this->pdm->saveToDb($confirmation);
|
||||||
|
|
||||||
@ -338,6 +350,7 @@ class LoginController
|
|||||||
|
|
||||||
$user = new User();
|
$user = new User();
|
||||||
$user->setEmail($userData['email']);
|
$user->setEmail($userData['email']);
|
||||||
|
$user->setCreatedDate(new DateTime());
|
||||||
} else {
|
} else {
|
||||||
$sendWelcomeEmail = false;
|
$sendWelcomeEmail = false;
|
||||||
}
|
}
|
||||||
@ -377,7 +390,7 @@ class LoginController
|
|||||||
return new Redirect(\Container::$routeCollection->getRoute('index')->generateLink(), IRedirect::TEMPORARY);
|
return new Redirect(\Container::$routeCollection->getRoute('index')->generateLink(), IRedirect::TEMPORARY);
|
||||||
}
|
}
|
||||||
|
|
||||||
$confirmation = $this->userConfirmationRepository->getByToken($this->request->query('token'));
|
$confirmation = $this->userConfirmationRepository->getByToken(substr($this->request->query('token'), 0, 32));
|
||||||
|
|
||||||
if ($confirmation === null) {
|
if ($confirmation === null) {
|
||||||
return new HtmlContent('login/activate');
|
return new HtmlContent('login/activate');
|
||||||
@ -405,7 +418,7 @@ class LoginController
|
|||||||
return new Redirect(\Container::$routeCollection->getRoute('index')->generateLink(), IRedirect::TEMPORARY);
|
return new Redirect(\Container::$routeCollection->getRoute('index')->generateLink(), IRedirect::TEMPORARY);
|
||||||
}
|
}
|
||||||
|
|
||||||
$confirmation = $this->userConfirmationRepository->getByToken($this->request->query('token'));
|
$confirmation = $this->userConfirmationRepository->getByToken(substr($this->request->query('token'), 0, 32));
|
||||||
|
|
||||||
if ($confirmation === null) {
|
if ($confirmation === null) {
|
||||||
return new HtmlContent('login/cancel', ['success' => false]);
|
return new HtmlContent('login/cancel', ['success' => false]);
|
||||||
@ -445,6 +458,8 @@ class LoginController
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!$user->getActive()) {
|
if (!$user->getActive()) {
|
||||||
|
$this->resendConfirmationEmail($user);
|
||||||
|
|
||||||
return new JsonContent([
|
return new JsonContent([
|
||||||
'error' => [
|
'error' => [
|
||||||
'errorText' => 'User found with the given email address, but the account is not activated. ' .
|
'errorText' => 'User found with the given email address, but the account is not activated. ' .
|
||||||
@ -453,6 +468,16 @@ class LoginController
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$existingResetter = $this->userPasswordResetterRepository->getByUser($user);
|
||||||
|
|
||||||
|
if ($existingResetter !== null && $existingResetter->getExpiresDate() > new DateTime()) {
|
||||||
|
return new JsonContent([
|
||||||
|
'error' => [
|
||||||
|
'errorText' => 'Password reset was recently requested for this account. Please check your email, or try again later!'
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
$token = bin2hex(random_bytes(16));
|
$token = bin2hex(random_bytes(16));
|
||||||
$expires = new DateTime('+1 hour');
|
$expires = new DateTime('+1 hour');
|
||||||
|
|
||||||
@ -461,8 +486,16 @@ class LoginController
|
|||||||
$passwordResetter->setToken($token);
|
$passwordResetter->setToken($token);
|
||||||
$passwordResetter->setExpiresDate($expires);
|
$passwordResetter->setExpiresDate($expires);
|
||||||
|
|
||||||
|
\Container::$dbConnection->startTransaction();
|
||||||
|
|
||||||
|
if ($existingResetter !== null) {
|
||||||
|
$this->pdm->deleteFromDb($existingResetter);
|
||||||
|
}
|
||||||
|
|
||||||
$this->pdm->saveToDb($passwordResetter);
|
$this->pdm->saveToDb($passwordResetter);
|
||||||
|
|
||||||
|
\Container::$dbConnection->commit();
|
||||||
|
|
||||||
$this->sendPasswordResetEmail($user->getEmail(), $token, $expires);
|
$this->sendPasswordResetEmail($user->getEmail(), $token, $expires);
|
||||||
|
|
||||||
return new JsonContent(['success' => true]);
|
return new JsonContent(['success' => true]);
|
||||||
@ -533,6 +566,23 @@ class LoginController
|
|||||||
$mail->send();
|
$mail->send();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function resendConfirmationEmail(User $user): bool
|
||||||
|
{
|
||||||
|
$confirmation = $this->userConfirmationRepository->getByUser($user);
|
||||||
|
|
||||||
|
if ($confirmation === null || (clone $confirmation->getLastSentDate())->add(new DateInterval('PT1H')) > new DateTime()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$confirmation->setLastSentDate(new DateTime());
|
||||||
|
|
||||||
|
$this->pdm->saveToDb($confirmation);
|
||||||
|
|
||||||
|
$this->sendConfirmationEmail($user->getEmail(), $confirmation->getToken());
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
private function sendWelcomeEmail(string $email): void
|
private function sendWelcomeEmail(string $email): void
|
||||||
{
|
{
|
||||||
$mail = new Mail();
|
$mail = new Mail();
|
||||||
|
@ -59,14 +59,17 @@ class UserController implements ISecured
|
|||||||
$user = $this->request->user();
|
$user = $this->request->user();
|
||||||
|
|
||||||
$state = bin2hex(random_bytes(16));
|
$state = bin2hex(random_bytes(16));
|
||||||
|
$nonce = bin2hex(random_bytes(16));
|
||||||
|
|
||||||
$this->request->session()->set('oauth_state', $state);
|
$this->request->session()->set('oauth_state', $state);
|
||||||
|
$this->request->session()->set('oauth_nonce', $nonce);
|
||||||
|
|
||||||
$oAuth = new GoogleOAuth(new Request());
|
$oAuth = new GoogleOAuth(new Request());
|
||||||
|
|
||||||
$url = $oAuth->getDialogUrl(
|
$url = $oAuth->getDialogUrl(
|
||||||
$state,
|
$state,
|
||||||
$this->request->getBase() . '/' . \Container::$routeCollection->getRoute('account.googleAuthenticate-action')->generateLink(),
|
$this->request->getBase() . '/' . \Container::$routeCollection->getRoute('account.googleAuthenticate-action')->generateLink(),
|
||||||
|
$nonce,
|
||||||
$user->getEmail()
|
$user->getEmail()
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -95,9 +98,13 @@ class UserController implements ISecured
|
|||||||
}
|
}
|
||||||
|
|
||||||
$jwtParser = new JwtParser($tokenData['id_token']);
|
$jwtParser = new JwtParser($tokenData['id_token']);
|
||||||
$userData = $jwtParser->getPayload();
|
$idToken = $jwtParser->getPayload();
|
||||||
|
|
||||||
if ($userData['sub'] !== $user->getGoogleSub()) {
|
if ($idToken['nonce'] !== $this->request->session()->get('oauth_nonce')) {
|
||||||
|
return new HtmlContent('account/google_authenticate', ['success' => false]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($idToken['sub'] !== $user->getGoogleSub()) {
|
||||||
return new HtmlContent('account/google_authenticate', [
|
return new HtmlContent('account/google_authenticate', [
|
||||||
'success' => false,
|
'success' => false,
|
||||||
'errorText' => 'This Google account is not linked to your account.'
|
'errorText' => 'This Google account is not linked to your account.'
|
||||||
@ -184,11 +191,13 @@ class UserController implements ISecured
|
|||||||
|
|
||||||
\Container::$dbConnection->startTransaction();
|
\Container::$dbConnection->startTransaction();
|
||||||
|
|
||||||
foreach ($this->userConfirmationRepository->getByUser($user) as $userConfirmation) {
|
$userConfirmation = $this->userConfirmationRepository->getByUser($user);
|
||||||
|
if ($userConfirmation !== null) {
|
||||||
$this->pdm->deleteFromDb($userConfirmation);
|
$this->pdm->deleteFromDb($userConfirmation);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($this->userPasswordResetterRepository->getByUser($user) as $userPasswordResetter) {
|
$userPasswordResetter = $this->userPasswordResetterRepository->getByUser($user);
|
||||||
|
if ($userPasswordResetter !== null) {
|
||||||
$this->pdm->deleteFromDb($userPasswordResetter);
|
$this->pdm->deleteFromDb($userPasswordResetter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -135,6 +135,6 @@ class Modify
|
|||||||
|
|
||||||
private function generateKey(): string
|
private function generateKey(): string
|
||||||
{
|
{
|
||||||
return substr(hash('sha256', serialize($this->attributes) . random_bytes(10) . microtime()), 0, 7);
|
return substr(hash('sha256', serialize($this->attributes) . random_bytes(5) . microtime()), 0, 7);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
9
src/Interfaces/Session/ISessionHandler.php
Normal file
9
src/Interfaces/Session/ISessionHandler.php
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<?php namespace MapGuesser\Interfaces\Session;
|
||||||
|
|
||||||
|
use SessionHandlerInterface;
|
||||||
|
use SessionIdInterface;
|
||||||
|
use SessionUpdateTimestampHandlerInterface;
|
||||||
|
|
||||||
|
interface ISessionHandler extends SessionHandlerInterface, SessionIdInterface, SessionUpdateTimestampHandlerInterface
|
||||||
|
{
|
||||||
|
}
|
@ -15,7 +15,7 @@ class GoogleOAuth
|
|||||||
$this->request = $request;
|
$this->request = $request;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getDialogUrl(string $state, string $redirectUrl, ?string $loginHint = null): string
|
public function getDialogUrl(string $state, string $redirectUrl, ?string $nonce = null, ?string $loginHint = null): string
|
||||||
{
|
{
|
||||||
$oauthParams = [
|
$oauthParams = [
|
||||||
'response_type' => 'code',
|
'response_type' => 'code',
|
||||||
@ -23,9 +23,12 @@ class GoogleOAuth
|
|||||||
'scope' => 'openid email',
|
'scope' => 'openid email',
|
||||||
'redirect_uri' => $redirectUrl,
|
'redirect_uri' => $redirectUrl,
|
||||||
'state' => $state,
|
'state' => $state,
|
||||||
'nonce' => hash('sha256', random_bytes(10) . microtime()),
|
|
||||||
];
|
];
|
||||||
|
|
||||||
|
if ($nonce !== null) {
|
||||||
|
$oauthParams['nonce'] = $nonce;
|
||||||
|
}
|
||||||
|
|
||||||
if ($loginHint !== null) {
|
if ($loginHint !== null) {
|
||||||
$oauthParams['login_hint'] = $loginHint;
|
$oauthParams['login_hint'] = $loginHint;
|
||||||
}
|
}
|
||||||
|
@ -166,7 +166,7 @@ class Place extends Model
|
|||||||
}
|
}
|
||||||
|
|
||||||
$this->panoIdCached = $panoId;
|
$this->panoIdCached = $panoId;
|
||||||
$this->panoIdCachedTimestamp = new DateTime('now');
|
$this->panoIdCachedTimestamp = new DateTime();
|
||||||
|
|
||||||
(new PersistentDataManager())->saveToDb($this);
|
(new PersistentDataManager())->saveToDb($this);
|
||||||
|
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
<?php namespace MapGuesser\PersistentData\Model;
|
<?php namespace MapGuesser\PersistentData\Model;
|
||||||
|
|
||||||
|
use DateTime;
|
||||||
use MapGuesser\Interfaces\Authentication\IUser;
|
use MapGuesser\Interfaces\Authentication\IUser;
|
||||||
|
|
||||||
class User extends Model implements IUser
|
class User extends Model implements IUser
|
||||||
{
|
{
|
||||||
protected static string $table = 'users';
|
protected static string $table = 'users';
|
||||||
|
|
||||||
protected static array $fields = ['email', 'password', 'type', 'active', 'google_sub'];
|
protected static array $fields = ['email', 'password', 'type', 'active', 'google_sub', 'created'];
|
||||||
|
|
||||||
private static array $types = ['user', 'admin'];
|
private static array $types = ['user', 'admin'];
|
||||||
|
|
||||||
@ -20,6 +21,8 @@ class User extends Model implements IUser
|
|||||||
|
|
||||||
private ?string $googleSub = null;
|
private ?string $googleSub = null;
|
||||||
|
|
||||||
|
private DateTime $created;
|
||||||
|
|
||||||
public function setEmail(string $email): void
|
public function setEmail(string $email): void
|
||||||
{
|
{
|
||||||
$this->email = $email;
|
$this->email = $email;
|
||||||
@ -52,6 +55,16 @@ class User extends Model implements IUser
|
|||||||
$this->googleSub = $googleSub;
|
$this->googleSub = $googleSub;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function setCreatedDate(DateTime $created): void
|
||||||
|
{
|
||||||
|
$this->created = $created;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setCreated(string $created): void
|
||||||
|
{
|
||||||
|
$this->created = new DateTime($created);
|
||||||
|
}
|
||||||
|
|
||||||
public function getEmail(): string
|
public function getEmail(): string
|
||||||
{
|
{
|
||||||
return $this->email;
|
return $this->email;
|
||||||
@ -77,6 +90,16 @@ class User extends Model implements IUser
|
|||||||
return $this->googleSub;
|
return $this->googleSub;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getCreatedData(): DateTime
|
||||||
|
{
|
||||||
|
return $this->created;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCreated(): string
|
||||||
|
{
|
||||||
|
return $this->created->format('Y-m-d H:i:s');
|
||||||
|
}
|
||||||
|
|
||||||
public function hasPermission(int $permission): bool
|
public function hasPermission(int $permission): bool
|
||||||
{
|
{
|
||||||
switch ($permission) {
|
switch ($permission) {
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
<?php namespace MapGuesser\PersistentData\Model;
|
<?php namespace MapGuesser\PersistentData\Model;
|
||||||
|
|
||||||
|
use DateTime;
|
||||||
|
|
||||||
class UserConfirmation extends Model
|
class UserConfirmation extends Model
|
||||||
{
|
{
|
||||||
protected static string $table = 'user_confirmations';
|
protected static string $table = 'user_confirmations';
|
||||||
|
|
||||||
protected static array $fields = ['user_id', 'token'];
|
protected static array $fields = ['user_id', 'token', 'last_sent'];
|
||||||
|
|
||||||
protected static array $relations = ['user' => User::class];
|
protected static array $relations = ['user' => User::class];
|
||||||
|
|
||||||
@ -14,6 +16,8 @@ class UserConfirmation extends Model
|
|||||||
|
|
||||||
private string $token = '';
|
private string $token = '';
|
||||||
|
|
||||||
|
private DateTime $lastSent;
|
||||||
|
|
||||||
public function setUser(User $user): void
|
public function setUser(User $user): void
|
||||||
{
|
{
|
||||||
$this->user = $user;
|
$this->user = $user;
|
||||||
@ -29,6 +33,16 @@ class UserConfirmation extends Model
|
|||||||
$this->token = $token;
|
$this->token = $token;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function setLastSentDate(DateTime $lastSent): void
|
||||||
|
{
|
||||||
|
$this->lastSent = $lastSent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setLastSent(string $lastSent): void
|
||||||
|
{
|
||||||
|
$this->lastSent = new DateTime($lastSent);
|
||||||
|
}
|
||||||
|
|
||||||
public function getUser(): ?User
|
public function getUser(): ?User
|
||||||
{
|
{
|
||||||
return $this->user;
|
return $this->user;
|
||||||
@ -43,4 +57,14 @@ class UserConfirmation extends Model
|
|||||||
{
|
{
|
||||||
return $this->token;
|
return $this->token;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getLastSentDate(): DateTime
|
||||||
|
{
|
||||||
|
return $this->lastSent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getLastSent(): string
|
||||||
|
{
|
||||||
|
return $this->lastSent->format('Y-m-d H:i:s');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
<?php namespace MapGuesser\Repository;
|
<?php namespace MapGuesser\Repository;
|
||||||
|
|
||||||
use Generator;
|
|
||||||
use MapGuesser\Database\Query\Select;
|
use MapGuesser\Database\Query\Select;
|
||||||
use MapGuesser\Interfaces\Database\IResultSet;
|
|
||||||
use MapGuesser\PersistentData\Model\User;
|
use MapGuesser\PersistentData\Model\User;
|
||||||
use MapGuesser\PersistentData\Model\UserConfirmation;
|
use MapGuesser\PersistentData\Model\UserConfirmation;
|
||||||
use MapGuesser\PersistentData\PersistentDataManager;
|
use MapGuesser\PersistentData\PersistentDataManager;
|
||||||
@ -29,11 +27,11 @@ class UserConfirmationRepository
|
|||||||
return $this->pdm->selectFromDb($select, UserConfirmation::class);
|
return $this->pdm->selectFromDb($select, UserConfirmation::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getByUser(User $user): Generator
|
public function getByUser(User $user): ?UserConfirmation
|
||||||
{
|
{
|
||||||
$select = new Select(\Container::$dbConnection);
|
$select = new Select(\Container::$dbConnection);
|
||||||
$select->where('user_id', '=', $user->getId());
|
$select->where('user_id', '=', $user->getId());
|
||||||
|
|
||||||
yield from $this->pdm->selectMultipleFromDb($select, UserConfirmation::class);
|
return $this->pdm->selectFromDb($select, UserConfirmation::class);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
<?php namespace MapGuesser\Repository;
|
<?php namespace MapGuesser\Repository;
|
||||||
|
|
||||||
use Generator;
|
|
||||||
use MapGuesser\Database\Query\Select;
|
use MapGuesser\Database\Query\Select;
|
||||||
use MapGuesser\PersistentData\Model\User;
|
use MapGuesser\PersistentData\Model\User;
|
||||||
use MapGuesser\PersistentData\Model\UserPasswordResetter;
|
use MapGuesser\PersistentData\Model\UserPasswordResetter;
|
||||||
@ -28,11 +27,11 @@ class UserPasswordResetterRepository
|
|||||||
return $this->pdm->selectFromDb($select, UserPasswordResetter::class);
|
return $this->pdm->selectFromDb($select, UserPasswordResetter::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getByUser(User $user): Generator
|
public function getByUser(User $user): ?UserPasswordResetter
|
||||||
{
|
{
|
||||||
$select = new Select(\Container::$dbConnection);
|
$select = new Select(\Container::$dbConnection);
|
||||||
$select->where('user_id', '=', $user->getId());
|
$select->where('user_id', '=', $user->getId());
|
||||||
|
|
||||||
yield from $this->pdm->selectMultipleFromDb($select, UserPasswordResetter::class);
|
return $this->pdm->selectFromDb($select, UserPasswordResetter::class);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,11 +4,9 @@ use DateTime;
|
|||||||
use MapGuesser\Database\Query\Modify;
|
use MapGuesser\Database\Query\Modify;
|
||||||
use MapGuesser\Database\Query\Select;
|
use MapGuesser\Database\Query\Select;
|
||||||
use MapGuesser\Interfaces\Database\IResultSet;
|
use MapGuesser\Interfaces\Database\IResultSet;
|
||||||
use SessionHandlerInterface;
|
use MapGuesser\Interfaces\Session\ISessionHandler;
|
||||||
use SessionIdInterface;
|
|
||||||
use SessionUpdateTimestampHandlerInterface;
|
|
||||||
|
|
||||||
class DatabaseSessionHandler implements SessionHandlerInterface, SessionIdInterface, SessionUpdateTimestampHandlerInterface
|
class DatabaseSessionHandler implements ISessionHandler
|
||||||
{
|
{
|
||||||
private bool $exists = false;
|
private bool $exists = false;
|
||||||
|
|
||||||
@ -28,7 +26,7 @@ class DatabaseSessionHandler implements SessionHandlerInterface, SessionIdInterf
|
|||||||
{
|
{
|
||||||
$select = new Select(\Container::$dbConnection, 'sessions');
|
$select = new Select(\Container::$dbConnection, 'sessions');
|
||||||
$select->columns(['data']);
|
$select->columns(['data']);
|
||||||
$select->whereId($id);
|
$select->whereId(substr($id, 0, 32));
|
||||||
|
|
||||||
$result = $select->execute()->fetch(IResultSet::FETCH_ASSOC);
|
$result = $select->execute()->fetch(IResultSet::FETCH_ASSOC);
|
||||||
|
|
||||||
@ -46,16 +44,16 @@ class DatabaseSessionHandler implements SessionHandlerInterface, SessionIdInterf
|
|||||||
$modify = new Modify(\Container::$dbConnection, 'sessions');
|
$modify = new Modify(\Container::$dbConnection, 'sessions');
|
||||||
|
|
||||||
if ($this->exists) {
|
if ($this->exists) {
|
||||||
$modify->setId($id);
|
$modify->setId(substr($id, 0, 32));
|
||||||
} else {
|
} else {
|
||||||
$modify->setExternalId($id);
|
$modify->setExternalId(substr($id, 0, 32));
|
||||||
}
|
}
|
||||||
|
|
||||||
$modify->set('data', $data);
|
$modify->set('data', $data);
|
||||||
$modify->set('updated', (new DateTime())->format('Y-m-d H:i:s'));
|
$modify->set('updated', (new DateTime())->format('Y-m-d H:i:s'));
|
||||||
$modify->save();
|
$modify->save();
|
||||||
|
|
||||||
$written = true;
|
$this->written = true;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -63,9 +61,11 @@ class DatabaseSessionHandler implements SessionHandlerInterface, SessionIdInterf
|
|||||||
public function destroy($id): bool
|
public function destroy($id): bool
|
||||||
{
|
{
|
||||||
$modify = new Modify(\Container::$dbConnection, 'sessions');
|
$modify = new Modify(\Container::$dbConnection, 'sessions');
|
||||||
$modify->setId($id);
|
$modify->setId(substr($id, 0, 32));
|
||||||
$modify->delete();
|
$modify->delete();
|
||||||
|
|
||||||
|
$this->exists = false;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,12 +88,12 @@ class DatabaseSessionHandler implements SessionHandlerInterface, SessionIdInterf
|
|||||||
|
|
||||||
public function create_sid(): string
|
public function create_sid(): string
|
||||||
{
|
{
|
||||||
return hash('sha256', random_bytes(10) . microtime());
|
return bin2hex(random_bytes(16));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function validateId($id): bool
|
public function validateId($id): bool
|
||||||
{
|
{
|
||||||
return preg_match('/^[a-f0-9]{64}$/', $id);
|
return preg_match('/^[a-f0-9]{32}$/', $id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function updateTimestamp($id, $data): bool
|
public function updateTimestamp($id, $data): bool
|
||||||
@ -104,7 +104,7 @@ class DatabaseSessionHandler implements SessionHandlerInterface, SessionIdInterf
|
|||||||
|
|
||||||
$modify = new Modify(\Container::$dbConnection, 'sessions');
|
$modify = new Modify(\Container::$dbConnection, 'sessions');
|
||||||
|
|
||||||
$modify->setId($id);
|
$modify->setId(substr($id, 0, 32));
|
||||||
$modify->set('updated', (new DateTime())->format('Y-m-d H:i:s'));
|
$modify->set('updated', (new DateTime())->format('Y-m-d H:i:s'));
|
||||||
$modify->save();
|
$modify->save();
|
||||||
|
|
||||||
|
@ -13,6 +13,7 @@ final class GoogleOAuthTest extends TestCase
|
|||||||
{
|
{
|
||||||
$_ENV['GOOGLE_OAUTH_CLIENT_ID'] = 'xyz';
|
$_ENV['GOOGLE_OAUTH_CLIENT_ID'] = 'xyz';
|
||||||
$state = 'random_state_string';
|
$state = 'random_state_string';
|
||||||
|
$nonce = 'random_nonce_string';
|
||||||
$redirectUrl = 'http://example.com/oauth';
|
$redirectUrl = 'http://example.com/oauth';
|
||||||
|
|
||||||
$requestMock = $this->getMockBuilder(IRequest::class)
|
$requestMock = $this->getMockBuilder(IRequest::class)
|
||||||
@ -20,7 +21,7 @@ final class GoogleOAuthTest extends TestCase
|
|||||||
->getMock();
|
->getMock();
|
||||||
$googleOAuth = new GoogleOAuth($requestMock);
|
$googleOAuth = new GoogleOAuth($requestMock);
|
||||||
|
|
||||||
$dialogUrl = $googleOAuth->getDialogUrl($state, $redirectUrl);
|
$dialogUrl = $googleOAuth->getDialogUrl($state, $redirectUrl, $nonce);
|
||||||
$dialogUrlParsed = explode('?', $dialogUrl);
|
$dialogUrlParsed = explode('?', $dialogUrl);
|
||||||
|
|
||||||
$this->assertEquals('https://accounts.google.com/o/oauth2/v2/auth', $dialogUrlParsed[0]);
|
$this->assertEquals('https://accounts.google.com/o/oauth2/v2/auth', $dialogUrlParsed[0]);
|
||||||
@ -33,15 +34,10 @@ final class GoogleOAuthTest extends TestCase
|
|||||||
'scope' => 'openid email',
|
'scope' => 'openid email',
|
||||||
'redirect_uri' => $redirectUrl,
|
'redirect_uri' => $redirectUrl,
|
||||||
'state' => $state,
|
'state' => $state,
|
||||||
'nonce' => hash('sha256', random_bytes(10) . microtime()),
|
'nonce' => $nonce,
|
||||||
];
|
];
|
||||||
|
|
||||||
$this->assertEquals($expectedQueryParams['response_type'], $dialogUrlQueryParams['response_type']);
|
$this->assertEquals($expectedQueryParams, $dialogUrlQueryParams);
|
||||||
$this->assertEquals($expectedQueryParams['client_id'], $dialogUrlQueryParams['client_id']);
|
|
||||||
$this->assertEquals($expectedQueryParams['scope'], $dialogUrlQueryParams['scope']);
|
|
||||||
$this->assertEquals($expectedQueryParams['redirect_uri'], $dialogUrlQueryParams['redirect_uri']);
|
|
||||||
$this->assertEquals($expectedQueryParams['state'], $dialogUrlQueryParams['state']);
|
|
||||||
$this->assertMatchesRegularExpression('/^[a-f0-9]{64}$/', $dialogUrlQueryParams['nonce']);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testCanRequestToken(): void
|
public function testCanRequestToken(): void
|
||||||
|
7
web.php
7
web.php
@ -71,6 +71,11 @@ if (isset($_COOKIE['COOKIES_CONSENT'])) {
|
|||||||
'cookie_httponly' => true,
|
'cookie_httponly' => true,
|
||||||
'cookie_samesite' => 'Lax'
|
'cookie_samesite' => 'Lax'
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
// this is needed to handle old type of session IDs
|
||||||
|
if (!Container::$sessionHandler->validateId(session_id())) {
|
||||||
|
session_regenerate_id(true);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
$_SESSION = [];
|
$_SESSION = [];
|
||||||
}
|
}
|
||||||
@ -78,5 +83,5 @@ if (isset($_COOKIE['COOKIES_CONSENT'])) {
|
|||||||
Container::$request = new MapGuesser\Request\Request($_SERVER['REQUEST_SCHEME'] . '://' . $_SERVER['HTTP_HOST'], $_GET, $_POST, $_SESSION);
|
Container::$request = new MapGuesser\Request\Request($_SERVER['REQUEST_SCHEME'] . '://' . $_SERVER['HTTP_HOST'], $_GET, $_POST, $_SESSION);
|
||||||
|
|
||||||
if (!Container::$request->session()->has('anti_csrf_token')) {
|
if (!Container::$request->session()->has('anti_csrf_token')) {
|
||||||
Container::$request->session()->set('anti_csrf_token', hash('sha256', random_bytes(10) . microtime()));
|
Container::$request->session()->set('anti_csrf_token', bin2hex(random_bytes(16)));
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user