Merged in feature/MAPG-141-password-forgotten-functionality (pull request #170)
Feature/MAPG-141 password forgotten functionality
This commit is contained in:
		
						commit
						091afb0aab
					
				@ -0,0 +1,10 @@
 | 
				
			|||||||
 | 
					CREATE TABLE `user_password_resetters` (
 | 
				
			||||||
 | 
					  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
 | 
				
			||||||
 | 
					  `user_id` int(10) unsigned NOT NULL,
 | 
				
			||||||
 | 
					  `token` varchar(32) CHARACTER SET ascii NOT NULL,
 | 
				
			||||||
 | 
					  `expires` timestamp NOT NULL,
 | 
				
			||||||
 | 
					  PRIMARY KEY (`id`),
 | 
				
			||||||
 | 
					  KEY `user_id` (`user_id`),
 | 
				
			||||||
 | 
					  KEY `token` (`token`),
 | 
				
			||||||
 | 
					  CONSTRAINT `user_password_resetters_user_id` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`)
 | 
				
			||||||
 | 
					) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;
 | 
				
			||||||
							
								
								
									
										12
									
								
								mail/password-reset.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								mail/password-reset.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,12 @@
 | 
				
			|||||||
 | 
					Hi,
 | 
				
			||||||
 | 
					<br><br>
 | 
				
			||||||
 | 
					You recently requested password reset on {{APP_NAME}} with this email address ({{EMAIL}}).
 | 
				
			||||||
 | 
					To reset the password to your account, please click on the following link:<br>
 | 
				
			||||||
 | 
					<a href="{{RESET_LINK}}" title="Reset password">{{RESET_LINK}}</a><br>
 | 
				
			||||||
 | 
					(This link expires at {{EXPIRES}}.)
 | 
				
			||||||
 | 
					<br><br>
 | 
				
			||||||
 | 
					If you did not requested password reset, no further action is required, your account is not touched.
 | 
				
			||||||
 | 
					<br><br>
 | 
				
			||||||
 | 
					Regards,<br>
 | 
				
			||||||
 | 
					{{APP_NAME}}<br>
 | 
				
			||||||
 | 
					<a href="{{BASE_URL}}" title="{{APP_NAME}}">{{BASE_URL}}</a>
 | 
				
			||||||
@ -1,5 +1,6 @@
 | 
				
			|||||||
<?php namespace MapGuesser\Controller;
 | 
					<?php namespace MapGuesser\Controller;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use DateTime;
 | 
				
			||||||
use MapGuesser\Http\Request;
 | 
					use MapGuesser\Http\Request;
 | 
				
			||||||
use MapGuesser\Interfaces\Request\IRequest;
 | 
					use MapGuesser\Interfaces\Request\IRequest;
 | 
				
			||||||
use MapGuesser\Interfaces\Response\IContent;
 | 
					use MapGuesser\Interfaces\Response\IContent;
 | 
				
			||||||
@ -8,8 +9,10 @@ use MapGuesser\Mailing\Mail;
 | 
				
			|||||||
use MapGuesser\OAuth\GoogleOAuth;
 | 
					use MapGuesser\OAuth\GoogleOAuth;
 | 
				
			||||||
use MapGuesser\PersistentData\Model\User;
 | 
					use MapGuesser\PersistentData\Model\User;
 | 
				
			||||||
use MapGuesser\PersistentData\Model\UserConfirmation;
 | 
					use MapGuesser\PersistentData\Model\UserConfirmation;
 | 
				
			||||||
 | 
					use MapGuesser\PersistentData\Model\UserPasswordResetter;
 | 
				
			||||||
use MapGuesser\PersistentData\PersistentDataManager;
 | 
					use MapGuesser\PersistentData\PersistentDataManager;
 | 
				
			||||||
use MapGuesser\Repository\UserConfirmationRepository;
 | 
					use MapGuesser\Repository\UserConfirmationRepository;
 | 
				
			||||||
 | 
					use MapGuesser\Repository\UserPasswordResetterRepository;
 | 
				
			||||||
use MapGuesser\Repository\UserRepository;
 | 
					use MapGuesser\Repository\UserRepository;
 | 
				
			||||||
use MapGuesser\Response\HtmlContent;
 | 
					use MapGuesser\Response\HtmlContent;
 | 
				
			||||||
use MapGuesser\Response\JsonContent;
 | 
					use MapGuesser\Response\JsonContent;
 | 
				
			||||||
@ -26,12 +29,15 @@ class LoginController
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    private UserConfirmationRepository $userConfirmationRepository;
 | 
					    private UserConfirmationRepository $userConfirmationRepository;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private UserPasswordResetterRepository $userPasswordResetterRepository;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public function __construct(IRequest $request)
 | 
					    public function __construct(IRequest $request)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        $this->request = $request;
 | 
					        $this->request = $request;
 | 
				
			||||||
        $this->pdm = new PersistentDataManager();
 | 
					        $this->pdm = new PersistentDataManager();
 | 
				
			||||||
        $this->userRepository = new UserRepository();
 | 
					        $this->userRepository = new UserRepository();
 | 
				
			||||||
        $this->userConfirmationRepository = new UserConfirmationRepository();
 | 
					        $this->userConfirmationRepository = new UserConfirmationRepository();
 | 
				
			||||||
 | 
					        $this->userPasswordResetterRepository = new UserPasswordResetterRepository();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public function getLoginForm()
 | 
					    public function getLoginForm()
 | 
				
			||||||
@ -97,6 +103,41 @@ class LoginController
 | 
				
			|||||||
        return new HtmlContent('login/google_signup', $data);
 | 
					        return new HtmlContent('login/google_signup', $data);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function getRequestPasswordResetForm()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        if ($this->request->user() !== null) {
 | 
				
			||||||
 | 
					            return new Redirect(\Container::$routeCollection->getRoute('index')->generateLink(), IRedirect::TEMPORARY);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $data = ['email' => $this->request->query('email')];
 | 
				
			||||||
 | 
					        return new HtmlContent('login/password_reset_request', $data);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function getRequestPasswordResetSuccess(): IContent
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return new HtmlContent('login/password_reset_request_success');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function getResetPasswordForm()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        if ($this->request->user() !== null) {
 | 
				
			||||||
 | 
					            return new Redirect(\Container::$routeCollection->getRoute('index')->generateLink(), IRedirect::TEMPORARY);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $token = $this->request->query('token');
 | 
				
			||||||
 | 
					        $resetter = $this->userPasswordResetterRepository->getByToken($token);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if ($resetter === null || $resetter->getExpiresDate() < new DateTime()) {
 | 
				
			||||||
 | 
					            $data = ['success' => false];
 | 
				
			||||||
 | 
					            return new HtmlContent('login/reset_password', $data);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $user = $this->userRepository->getById($resetter->getUserId());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $data = ['success' => true, 'token' => $token, 'email' => $user->getEmail()];
 | 
				
			||||||
 | 
					        return new HtmlContent('login/reset_password', $data);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public function login(): IContent
 | 
					    public function login(): IContent
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        if ($this->request->user() !== null) {
 | 
					        if ($this->request->user() !== null) {
 | 
				
			||||||
@ -127,7 +168,7 @@ class LoginController
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (!$user->checkPassword($this->request->post('password'))) {
 | 
					        if (!$user->checkPassword($this->request->post('password'))) {
 | 
				
			||||||
            $data = ['error' => ['errorText' => 'The given password is wrong.']];
 | 
					            $data = ['error' => ['errorText' => 'The given password is wrong. You can <a href="/password/requestReset?email=' . urlencode($user->getEmail()) . '" title="Request password reset">request password reset</a>!']];
 | 
				
			||||||
            return new JsonContent($data);
 | 
					            return new JsonContent($data);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -196,7 +237,7 @@ class LoginController
 | 
				
			|||||||
        if ($user !== null) {
 | 
					        if ($user !== null) {
 | 
				
			||||||
            if ($user->getActive()) {
 | 
					            if ($user->getActive()) {
 | 
				
			||||||
                if (!$user->checkPassword($this->request->post('password'))) {
 | 
					                if (!$user->checkPassword($this->request->post('password'))) {
 | 
				
			||||||
                    $data = ['error' => ['errorText' => 'There is a user already registered with the given email address, but the given password is wrong.']];
 | 
					                    $data = ['error' => ['errorText' => 'There is a user already registered with the given email address, but the given password is wrong. You can <a href="/password/requestReset?email=' . urlencode($user->getEmail()) . '" title="Request password reset">request password reset</a>!']];
 | 
				
			||||||
                    return new JsonContent($data);
 | 
					                    return new JsonContent($data);
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -370,6 +411,84 @@ class LoginController
 | 
				
			|||||||
        return new HtmlContent('login/cancel', $data);
 | 
					        return new HtmlContent('login/cancel', $data);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function requestPasswordReset(): IContent
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        if ($this->request->user() !== null) {
 | 
				
			||||||
 | 
					            $data = ['redirect' => ['target' => '/' . \Container::$routeCollection->getRoute('home')->generateLink()]];
 | 
				
			||||||
 | 
					            return new JsonContent($data);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $user = $this->userRepository->getByEmail($this->request->post('email'));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if ($user === null) {
 | 
				
			||||||
 | 
					            $data = ['error' => ['errorText' => 'No user found with the given email address. You can <a href="/signup" title="Sign up">sign up</a>!']];
 | 
				
			||||||
 | 
					            return new JsonContent($data);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (!$user->getActive()) {
 | 
				
			||||||
 | 
					            $data = ['error' => ['errorText' => 'User found with the given email address, but the account is not activated. Please check your email and click on the activation link!']];
 | 
				
			||||||
 | 
					            return new JsonContent($data);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $token = bin2hex(random_bytes(16));
 | 
				
			||||||
 | 
					        $expires = new DateTime('+1 hour');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $passwordResetter = new UserPasswordResetter();
 | 
				
			||||||
 | 
					        $passwordResetter->setUser($user);
 | 
				
			||||||
 | 
					        $passwordResetter->setToken($token);
 | 
				
			||||||
 | 
					        $passwordResetter->setExpiresDate($expires);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $this->pdm->saveToDb($passwordResetter);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $this->sendPasswordResetEmail($user->getEmail(), $token, $expires);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $data = ['success' => true];
 | 
				
			||||||
 | 
					        return new JsonContent($data);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function resetPassword(): IContent
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        if ($this->request->user() !== null) {
 | 
				
			||||||
 | 
					            $data = ['redirect' => ['target' => '/' . \Container::$routeCollection->getRoute('home')->generateLink()]];
 | 
				
			||||||
 | 
					            return new JsonContent($data);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $token = $this->request->query('token');
 | 
				
			||||||
 | 
					        $resetter = $this->userPasswordResetterRepository->getByToken($token);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if ($resetter === null || $resetter->getExpiresDate() < new DateTime()) {
 | 
				
			||||||
 | 
					            $data = ['redirect' => ['target' => '/' . \Container::$routeCollection->getRoute('password-reset')->generateLink(['token' => $token])]];
 | 
				
			||||||
 | 
					            return new JsonContent($data);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (strlen($this->request->post('password')) < 6) {
 | 
				
			||||||
 | 
					            $data = ['error' => ['errorText' => 'The given password is too short. Please choose a password that is at least 6 characters long!']];
 | 
				
			||||||
 | 
					            return new JsonContent($data);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if ($this->request->post('password') !== $this->request->post('password_confirm')) {
 | 
				
			||||||
 | 
					            $data = ['error' => ['errorText' => 'The given passwords do not match.']];
 | 
				
			||||||
 | 
					            return new JsonContent($data);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        \Container::$dbConnection->startTransaction();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $this->pdm->deleteFromDb($resetter);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $user = $this->userRepository->getById($resetter->getUserId());
 | 
				
			||||||
 | 
					        $user->setPlainPassword($this->request->post('password'));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $this->pdm->saveToDb($user);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        \Container::$dbConnection->commit();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $this->request->setUser($user);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $data = ['success' => true];
 | 
				
			||||||
 | 
					        return new JsonContent($data);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private function sendConfirmationEmail(string $email, string $token): void
 | 
					    private function sendConfirmationEmail(string $email, string $token): void
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        $mail = new Mail();
 | 
					        $mail = new Mail();
 | 
				
			||||||
@ -377,7 +496,7 @@ class LoginController
 | 
				
			|||||||
        $mail->setSubject('Welcome to ' . $_ENV['APP_NAME'] . ' - Activate your account');
 | 
					        $mail->setSubject('Welcome to ' . $_ENV['APP_NAME'] . ' - Activate your account');
 | 
				
			||||||
        $mail->setBodyFromTemplate('signup', [
 | 
					        $mail->setBodyFromTemplate('signup', [
 | 
				
			||||||
            'EMAIL' => $email,
 | 
					            'EMAIL' => $email,
 | 
				
			||||||
            'ACTIVATE_LINK' => $this->request->getBase() . '/'. \Container::$routeCollection->getRoute('signup.activate')->generateLink(['token' => $token]),
 | 
					            '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]),
 | 
					            'CANCEL_LINK' => $this->request->getBase() . '/' . \Container::$routeCollection->getRoute('signup.cancel')->generateLink(['token' => $token]),
 | 
				
			||||||
        ]);
 | 
					        ]);
 | 
				
			||||||
        $mail->send();
 | 
					        $mail->send();
 | 
				
			||||||
@ -393,4 +512,17 @@ class LoginController
 | 
				
			|||||||
        ]);
 | 
					        ]);
 | 
				
			||||||
        $mail->send();
 | 
					        $mail->send();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private function sendPasswordResetEmail(string $email, string $token, DateTime $expires): void
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $mail = new Mail();
 | 
				
			||||||
 | 
					        $mail->addRecipient($email);
 | 
				
			||||||
 | 
					        $mail->setSubject($_ENV['APP_NAME'] . ' - Password reset');
 | 
				
			||||||
 | 
					        $mail->setBodyFromTemplate('password-reset', [
 | 
				
			||||||
 | 
					            'EMAIL' => $email,
 | 
				
			||||||
 | 
					            'RESET_LINK' => $this->request->getBase() . '/' . \Container::$routeCollection->getRoute('password-reset')->generateLink(['token' => $token]),
 | 
				
			||||||
 | 
					            'EXPIRES' => $expires->format('Y-m-d H:i:s T')
 | 
				
			||||||
 | 
					        ]);
 | 
				
			||||||
 | 
					        $mail->send();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,18 +1,16 @@
 | 
				
			|||||||
<?php namespace MapGuesser\Controller;
 | 
					<?php namespace MapGuesser\Controller;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use DateTime;
 | 
					use DateTime;
 | 
				
			||||||
use MapGuesser\Database\Query\Select;
 | 
					 | 
				
			||||||
use MapGuesser\Http\Request;
 | 
					use MapGuesser\Http\Request;
 | 
				
			||||||
use MapGuesser\Interfaces\Authorization\ISecured;
 | 
					use MapGuesser\Interfaces\Authorization\ISecured;
 | 
				
			||||||
use MapGuesser\Interfaces\Database\IResultSet;
 | 
					 | 
				
			||||||
use MapGuesser\Interfaces\Request\IRequest;
 | 
					use MapGuesser\Interfaces\Request\IRequest;
 | 
				
			||||||
use MapGuesser\Interfaces\Response\IContent;
 | 
					use MapGuesser\Interfaces\Response\IContent;
 | 
				
			||||||
use MapGuesser\Interfaces\Response\IRedirect;
 | 
					use MapGuesser\Interfaces\Response\IRedirect;
 | 
				
			||||||
use MapGuesser\OAuth\GoogleOAuth;
 | 
					use MapGuesser\OAuth\GoogleOAuth;
 | 
				
			||||||
use MapGuesser\PersistentData\PersistentDataManager;
 | 
					use MapGuesser\PersistentData\PersistentDataManager;
 | 
				
			||||||
use MapGuesser\PersistentData\Model\User;
 | 
					use MapGuesser\PersistentData\Model\User;
 | 
				
			||||||
use MapGuesser\PersistentData\Model\UserConfirmation;
 | 
					 | 
				
			||||||
use MapGuesser\Repository\UserConfirmationRepository;
 | 
					use MapGuesser\Repository\UserConfirmationRepository;
 | 
				
			||||||
 | 
					use MapGuesser\Repository\UserPasswordResetterRepository;
 | 
				
			||||||
use MapGuesser\Response\HtmlContent;
 | 
					use MapGuesser\Response\HtmlContent;
 | 
				
			||||||
use MapGuesser\Response\JsonContent;
 | 
					use MapGuesser\Response\JsonContent;
 | 
				
			||||||
use MapGuesser\Response\Redirect;
 | 
					use MapGuesser\Response\Redirect;
 | 
				
			||||||
@ -26,11 +24,14 @@ class UserController implements ISecured
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    private UserConfirmationRepository $userConfirmationRepository;
 | 
					    private UserConfirmationRepository $userConfirmationRepository;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private UserPasswordResetterRepository $userPasswordResetterRepository;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public function __construct(IRequest $request)
 | 
					    public function __construct(IRequest $request)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        $this->request = $request;
 | 
					        $this->request = $request;
 | 
				
			||||||
        $this->pdm = new PersistentDataManager();
 | 
					        $this->pdm = new PersistentDataManager();
 | 
				
			||||||
        $this->userConfirmationRepository = new UserConfirmationRepository();
 | 
					        $this->userConfirmationRepository = new UserConfirmationRepository();
 | 
				
			||||||
 | 
					        $this->userPasswordResetterRepository = new UserPasswordResetterRepository();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public function authorize(): bool
 | 
					    public function authorize(): bool
 | 
				
			||||||
@ -181,6 +182,10 @@ class UserController implements ISecured
 | 
				
			|||||||
            $this->pdm->deleteFromDb($userConfirmation);
 | 
					            $this->pdm->deleteFromDb($userConfirmation);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        foreach ($this->userPasswordResetterRepository->getByUser($user) as $userPasswordResetter) {
 | 
				
			||||||
 | 
					            $this->pdm->deleteFromDb($userPasswordResetter);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        $this->pdm->deleteFromDb($user);
 | 
					        $this->pdm->deleteFromDb($user);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        \Container::$dbConnection->commit();
 | 
					        \Container::$dbConnection->commit();
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										70
									
								
								src/PersistentData/Model/UserPasswordResetter.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								src/PersistentData/Model/UserPasswordResetter.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,70 @@
 | 
				
			|||||||
 | 
					<?php namespace MapGuesser\PersistentData\Model;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use DateTime;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class UserPasswordResetter extends Model
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    protected static string $table = 'user_password_resetters';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected static array $fields = ['user_id', 'token', 'expires'];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected static array $relations = ['user' => User::class];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private ?User $user = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private ?int $userId = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private string $token = '';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private DateTime $expires;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function setUser(User $user): void
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $this->user = $user;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function setUserId(int $userId): void
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $this->userId = $userId;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function setToken(string $token): void
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $this->token = $token;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function setExpiresDate(DateTime $expires): void
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $this->expires = $expires;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function setExpires(string $expires): void
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $this->expires = new DateTime($expires);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function getUser(): ?User
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return $this->user;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function getUserId(): ?int
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return $this->userId;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function getToken(): string
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return $this->token;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function getExpiresDate(): DateTime
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return $this->expires;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function getExpires(): string
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return $this->expires->format('Y-m-d H:i:s');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										38
									
								
								src/Repository/UserPasswordResetterRepository.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								src/Repository/UserPasswordResetterRepository.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,38 @@
 | 
				
			|||||||
 | 
					<?php namespace MapGuesser\Repository;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use Generator;
 | 
				
			||||||
 | 
					use MapGuesser\Database\Query\Select;
 | 
				
			||||||
 | 
					use MapGuesser\PersistentData\Model\User;
 | 
				
			||||||
 | 
					use MapGuesser\PersistentData\Model\UserPasswordResetter;
 | 
				
			||||||
 | 
					use MapGuesser\PersistentData\PersistentDataManager;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class UserPasswordResetterRepository
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    private PersistentDataManager $pdm;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function __construct()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $this->pdm = new PersistentDataManager();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function getById(int $userConfirmationId): ?UserPasswordResetter
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return $this->pdm->selectFromDbById($userConfirmationId, UserPasswordResetter::class);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function getByToken(string $token): ?UserPasswordResetter
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $select = new Select(\Container::$dbConnection);
 | 
				
			||||||
 | 
					        $select->where('token', '=', $token);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return $this->pdm->selectFromDb($select, UserPasswordResetter::class);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function getByUser(User $user): Generator
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $select = new Select(\Container::$dbConnection);
 | 
				
			||||||
 | 
					        $select->where('user_id', '=', $user->getId());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        yield from $this->pdm->selectMultipleFromDb($select, UserPasswordResetter::class);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -3,6 +3,6 @@
 | 
				
			|||||||
@section(main)
 | 
					@section(main)
 | 
				
			||||||
    <h2>Account activation</h2>
 | 
					    <h2>Account activation</h2>
 | 
				
			||||||
    <div class="box">
 | 
					    <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>
 | 
					        <p class="error justify">Activation failed. Please check the link you entered, or retry <a href="/signup" title="Sign up">signing up</a>!</p>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
@endsection
 | 
					@endsection
 | 
				
			||||||
 | 
				
			|||||||
@ -10,6 +10,8 @@
 | 
				
			|||||||
            <div class="right marginTop">
 | 
					            <div class="right marginTop">
 | 
				
			||||||
                <button type="submit">Login</button>
 | 
					                <button type="submit">Login</button>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
 | 
					            <p class="center marginTop"><a href="/password/requestReset" title="Request password reset">Forgot your password?</a></p>
 | 
				
			||||||
 | 
					            <p class="center marginTop"><a href="/signup" title="Sign up">New to <?= $_ENV['APP_NAME'] ?>?</a></p>
 | 
				
			||||||
            <hr>
 | 
					            <hr>
 | 
				
			||||||
            <div class="center">
 | 
					            <div class="center">
 | 
				
			||||||
                <a class="button yellow" href="/login/google" title="Login with Google">Login with Google</a>
 | 
					                <a class="button yellow" href="/login/google" title="Login with Google">Login with Google</a>
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										14
									
								
								views/login/password_reset_request.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								views/login/password_reset_request.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,14 @@
 | 
				
			|||||||
 | 
					@extends(templates/layout_normal)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@section(main)
 | 
				
			||||||
 | 
					    <h2>Request password reset</h2>
 | 
				
			||||||
 | 
					    <div class="box">
 | 
				
			||||||
 | 
					        <form id="passwordResetForm" action="/password/requestReset" method="post" data-redirect-on-success="/password/requestReset/success">
 | 
				
			||||||
 | 
					            <input class="big fullWidth" type="email" name="email" placeholder="Email address" value="<?= isset($email) ? $email : '' ?>" required autofocus>
 | 
				
			||||||
 | 
					            <p id="passwordResetFormError" class="formError justify marginTop"></p>
 | 
				
			||||||
 | 
					            <div class="right marginTop">
 | 
				
			||||||
 | 
					                <button type="submit">Continue</button>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					        </form>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					@endsection
 | 
				
			||||||
							
								
								
									
										8
									
								
								views/login/password_reset_request_success.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								views/login/password_reset_request_success.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,8 @@
 | 
				
			|||||||
 | 
					@extends(templates/layout_normal)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@section(main)
 | 
				
			||||||
 | 
					    <h2>Request password reset</h2>
 | 
				
			||||||
 | 
					    <div class="box">
 | 
				
			||||||
 | 
					        <p class="justify">Password reset was successfully requested. Please check your email and click on the link to reset your password!</p>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					@endsection
 | 
				
			||||||
							
								
								
									
										20
									
								
								views/login/reset_password.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								views/login/reset_password.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,20 @@
 | 
				
			|||||||
 | 
					@extends(templates/layout_normal)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@section(main)
 | 
				
			||||||
 | 
					    <h2>Reset password</h2>
 | 
				
			||||||
 | 
					    <div class="box">
 | 
				
			||||||
 | 
					        <?php if ($success) : ?>
 | 
				
			||||||
 | 
					            <form id="resetPasswordForm" action="/password/reset/<?= $token ?>" method="post" data-redirect-on-success="/">
 | 
				
			||||||
 | 
					                <input class="big fullWidth" type="email" name="email" placeholder="Email address" value="<?= $email ?>" disabled>
 | 
				
			||||||
 | 
					                <input class="big fullWidth marginTop" type="password" name="password" placeholder="Password" required minlength="6" autofocus>
 | 
				
			||||||
 | 
					                <input class="big fullWidth marginTop" type="password" name="password_confirm" placeholder="Password confirmation" required minlength="6">
 | 
				
			||||||
 | 
					                <p id="resetPasswordFormError" class="formError justify marginTop"></p>
 | 
				
			||||||
 | 
					                <div class="right">
 | 
				
			||||||
 | 
					                    <button class="marginTop" type="submit">Reset password</button>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					            </form>
 | 
				
			||||||
 | 
					        <?php else: ?>
 | 
				
			||||||
 | 
					            <p class="error justify">Confirming your identity failed. Please check the link you entered, or retry <a href="/password/requestReset" title="Request password reset">requesting password reset</a>!</p>
 | 
				
			||||||
 | 
					        <?php endif; ?>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					@endsection
 | 
				
			||||||
@ -22,6 +22,7 @@
 | 
				
			|||||||
                 --><button id="resetSignupButton" class="gray marginTop marginLeft" type="button">Reset</button>
 | 
					                 --><button id="resetSignupButton" class="gray marginTop marginLeft" type="button">Reset</button>
 | 
				
			||||||
                <?php endif; ?>
 | 
					                <?php endif; ?>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
 | 
					            <p class="center marginTop"><a href="/login" title="Login">Already have an account?</a></p>
 | 
				
			||||||
            <hr>
 | 
					            <hr>
 | 
				
			||||||
            <div class="center">
 | 
					            <div class="center">
 | 
				
			||||||
                <a class="button yellow" href="/login/google" title="Signup with Google">Signup with Google</a>
 | 
					                <a class="button yellow" href="/login/google" title="Signup with Google">Signup with Google</a>
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										7
									
								
								web.php
									
									
									
									
									
								
							
							
						
						
									
										7
									
								
								web.php
									
									
									
									
									
								
							@ -31,6 +31,13 @@ Container::$routeCollection->group('signup', function (MapGuesser\Routing\RouteC
 | 
				
			|||||||
    $routeCollection->get('signup.activate', 'activate/{token}', [MapGuesser\Controller\LoginController::class, 'activate']);
 | 
					    $routeCollection->get('signup.activate', 'activate/{token}', [MapGuesser\Controller\LoginController::class, 'activate']);
 | 
				
			||||||
    $routeCollection->get('signup.cancel', 'cancel/{token}', [MapGuesser\Controller\LoginController::class, 'cancel']);
 | 
					    $routeCollection->get('signup.cancel', 'cancel/{token}', [MapGuesser\Controller\LoginController::class, 'cancel']);
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					Container::$routeCollection->group('password', function (MapGuesser\Routing\RouteCollection $routeCollection) {
 | 
				
			||||||
 | 
					    $routeCollection->get('password-requestReset', 'requestReset', [MapGuesser\Controller\LoginController::class, 'getRequestPasswordResetForm']);
 | 
				
			||||||
 | 
					    $routeCollection->post('password-requestReset-action', 'requestReset', [MapGuesser\Controller\LoginController::class, 'requestPasswordReset']);
 | 
				
			||||||
 | 
					    $routeCollection->get('password-requestReset.success', 'requestReset/success', [MapGuesser\Controller\LoginController::class, 'getRequestPasswordResetSuccess']);
 | 
				
			||||||
 | 
					    $routeCollection->get('password-reset', 'reset/{token}', [MapGuesser\Controller\LoginController::class, 'getResetPasswordForm']);
 | 
				
			||||||
 | 
					    $routeCollection->post('password-reset.action', 'reset/{token}', [MapGuesser\Controller\LoginController::class, 'resetPassword']);
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
Container::$routeCollection->get('logout', 'logout', [MapGuesser\Controller\LoginController::class, 'logout']);
 | 
					Container::$routeCollection->get('logout', 'logout', [MapGuesser\Controller\LoginController::class, 'logout']);
 | 
				
			||||||
Container::$routeCollection->group('account', function (MapGuesser\Routing\RouteCollection $routeCollection) {
 | 
					Container::$routeCollection->group('account', function (MapGuesser\Routing\RouteCollection $routeCollection) {
 | 
				
			||||||
    $routeCollection->get('account', '', [MapGuesser\Controller\UserController::class, 'getAccount']);
 | 
					    $routeCollection->get('account', '', [MapGuesser\Controller\UserController::class, 'getAccount']);
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user