mirror of
https://github.com/chylex/Lightning-Tracker.git
synced 2025-04-10 02:15:43 +02:00
Add user registration and login pages
This commit is contained in:
parent
53a25994f2
commit
04f86b8b06
src
30
src/Pages/Controllers/Handlers/RequireLoginState.php
Normal file
30
src/Pages/Controllers/Handlers/RequireLoginState.php
Normal file
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace Pages\Controllers\Handlers;
|
||||
|
||||
use Pages\Controllers\IControlHandler;
|
||||
use Pages\IAction;
|
||||
use Routing\Request;
|
||||
use Session\Session;
|
||||
use function Pages\Actions\redirect;
|
||||
|
||||
class RequireLoginState implements IControlHandler{
|
||||
private bool $should_be_logged_in;
|
||||
|
||||
public function __construct(bool $should_be_logged_in){
|
||||
$this->should_be_logged_in = $should_be_logged_in;
|
||||
}
|
||||
|
||||
public function run(Request $req, Session $sess): ?IAction{
|
||||
if ($this->should_be_logged_in !== $sess->isLoggedOn()){
|
||||
return redirect([BASE_URL_ENC,
|
||||
$req->getBasePath()->encoded(),
|
||||
$this->should_be_logged_in ? 'login?return='.rawurlencode(ltrim($_SERVER['REQUEST_URI'], '/')) : '']);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
36
src/Pages/Controllers/Mixed/LoginController.php
Normal file
36
src/Pages/Controllers/Mixed/LoginController.php
Normal file
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace Pages\Controllers\Mixed;
|
||||
|
||||
use Generator;
|
||||
use Pages\Controllers\AbstractHandlerController;
|
||||
use Pages\Controllers\Handlers\RequireLoginState;
|
||||
use Pages\IAction;
|
||||
use Pages\Models\Mixed\LoginModel;
|
||||
use Pages\Views\Mixed\LoginPage;
|
||||
use Routing\Request;
|
||||
use Session\Session;
|
||||
use function Pages\Actions\redirect;
|
||||
use function Pages\Actions\view;
|
||||
|
||||
class LoginController extends AbstractHandlerController{
|
||||
protected function prerequisites(): Generator{
|
||||
yield new RequireLoginState(false);
|
||||
}
|
||||
|
||||
protected function finally(Request $req, Session $sess): IAction{
|
||||
$model = new LoginModel($req);
|
||||
$data = $req->getData();
|
||||
|
||||
if (!empty($data) && $model->loginUser($data, $sess)){
|
||||
return redirect([BASE_URL_ENC,
|
||||
$req->getBasePath()->encoded(),
|
||||
isset($_GET['return']) ? ltrim($_GET['return'], '/') : '']);
|
||||
}
|
||||
|
||||
return view(new LoginPage($model->load()));
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
53
src/Pages/Controllers/Mixed/RegisterController.php
Normal file
53
src/Pages/Controllers/Mixed/RegisterController.php
Normal file
@ -0,0 +1,53 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace Pages\Controllers\Mixed;
|
||||
|
||||
use Database\Objects\TrackerInfo;
|
||||
use Generator;
|
||||
use Pages\Controllers\AbstractHandlerController;
|
||||
use Pages\IAction;
|
||||
use Pages\Models\BasicMixedPageModel;
|
||||
use Pages\Models\ErrorModel;
|
||||
use Pages\Models\Mixed\RegisterModel;
|
||||
use Pages\Views\ErrorPage;
|
||||
use Pages\Views\Mixed\RegisterPage;
|
||||
use Routing\Request;
|
||||
use Session\Session;
|
||||
use function Pages\Actions\redirect;
|
||||
use function Pages\Actions\view;
|
||||
|
||||
class RegisterController extends AbstractHandlerController{
|
||||
protected function prerequisites(): Generator{
|
||||
yield from [];
|
||||
}
|
||||
|
||||
protected function finally(Request $req, Session $sess): IAction{
|
||||
if (isset($_GET['success'])){
|
||||
$model = new RegisterModel($req, true);
|
||||
return view(new RegisterPage($model->load()));
|
||||
}
|
||||
|
||||
if (!SYS_ENABLE_REGISTRATION){
|
||||
$page_model = new BasicMixedPageModel($req);
|
||||
$error_model = new ErrorModel($page_model, 'Registration Error', 'User registrations are disabled by the administrator.');
|
||||
|
||||
return view(new ErrorPage($error_model->load()));
|
||||
}
|
||||
|
||||
if ($sess->isLoggedOn()){
|
||||
return redirect([BASE_URL_ENC, $req->getBasePath()->encoded()]);
|
||||
}
|
||||
|
||||
$model = new RegisterModel($req);
|
||||
$data = $req->getData();
|
||||
|
||||
if (!empty($data) && $model->registerUser($data, $sess)){
|
||||
return redirect([BASE_URL_ENC, $req->getBasePath()->encoded(), 'register?success']);
|
||||
}
|
||||
|
||||
return view(new RegisterPage($model->load()));
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
@ -31,6 +31,16 @@ abstract class AbstractPageModel implements IModel{
|
||||
$this->nav = $this->createNavigation();
|
||||
$this->setupNavigation($this->nav, $logon_user);
|
||||
|
||||
if ($logon_user !== null){
|
||||
}
|
||||
else{
|
||||
$this->nav->addRight(Text::withIcon('Login', 'enter'), '/login');
|
||||
|
||||
if (SYS_ENABLE_REGISTRATION){
|
||||
$this->nav->addRight(Text::withIcon('Register', 'user'), '/register');
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
14
src/Pages/Models/BasicMixedPageModel.php
Normal file
14
src/Pages/Models/BasicMixedPageModel.php
Normal file
@ -0,0 +1,14 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace Pages\Models;
|
||||
|
||||
use Routing\Request;
|
||||
|
||||
class BasicMixedPageModel extends AbstractWrapperModel{
|
||||
public function __construct(Request $req){
|
||||
parent::__construct(new BasicRootPageModel($req)); // TODO
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
62
src/Pages/Models/Mixed/LoginModel.php
Normal file
62
src/Pages/Models/Mixed/LoginModel.php
Normal file
@ -0,0 +1,62 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace Pages\Models\Mixed;
|
||||
|
||||
use Database\DB;
|
||||
use Database\Tables\UserTable;
|
||||
use Exception;
|
||||
use Pages\Components\Forms\FormComponent;
|
||||
use Pages\Components\Text;
|
||||
use Pages\Models\BasicMixedPageModel;
|
||||
use Routing\Request;
|
||||
use Session\Session;
|
||||
|
||||
class LoginModel extends BasicMixedPageModel{
|
||||
private FormComponent $form;
|
||||
|
||||
public function __construct(Request $req){
|
||||
parent::__construct($req);
|
||||
|
||||
$this->form = new FormComponent();
|
||||
|
||||
$this->form->addTextField('Name')
|
||||
->label('Username')
|
||||
->type('text')
|
||||
->autocomplete('username');
|
||||
|
||||
$this->form->addTextField('Password')
|
||||
->type('password')
|
||||
->autocomplete('current-password');
|
||||
|
||||
$this->form->addButton('submit', 'Login')
|
||||
->icon('enter');
|
||||
}
|
||||
|
||||
public function getForm(): FormComponent{
|
||||
return $this->form;
|
||||
}
|
||||
|
||||
public function loginUser(array $data, Session $sess): bool{
|
||||
if (!$this->form->accept($data)){
|
||||
return false;
|
||||
}
|
||||
|
||||
try{
|
||||
$users = new UserTable(DB::get());
|
||||
$login_info = $users->getLoginInfo($data['Name']);
|
||||
|
||||
if ($login_info === null || !$login_info->checkPassword($data['Password'])){
|
||||
$this->form->addMessage(FormComponent::MESSAGE_ERROR, Text::warning('Invalid username or password.'));
|
||||
return false;
|
||||
}
|
||||
|
||||
return $sess->tryLoginWithId($login_info->getId());
|
||||
}catch(Exception $e){
|
||||
$this->form->onGeneralError($e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
117
src/Pages/Models/Mixed/RegisterModel.php
Normal file
117
src/Pages/Models/Mixed/RegisterModel.php
Normal file
@ -0,0 +1,117 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace Pages\Models\Mixed;
|
||||
|
||||
use Database\DB;
|
||||
use Database\SQL;
|
||||
use Database\Tables\UserTable;
|
||||
use Exception;
|
||||
use LogicException;
|
||||
use Pages\Components\Forms\FormComponent;
|
||||
use Pages\Models\BasicMixedPageModel;
|
||||
use PDOException;
|
||||
use Routing\Request;
|
||||
use Session\Session;
|
||||
use Validation\ValidationException;
|
||||
use Validation\Validator;
|
||||
|
||||
class RegisterModel extends BasicMixedPageModel{
|
||||
private FormComponent $form;
|
||||
private bool $successful_login;
|
||||
|
||||
public function __construct(Request $req, bool $successful_login = false){
|
||||
parent::__construct($req);
|
||||
|
||||
$this->form = new FormComponent();
|
||||
|
||||
$this->form->addTextField('Name')
|
||||
->label('Username')
|
||||
->type('text')
|
||||
->autocomplete('username');
|
||||
|
||||
$this->form->addTextField('Password')
|
||||
->type('password')
|
||||
->autocomplete('new-password');
|
||||
|
||||
$this->form->addTextField('PasswordRepeated')
|
||||
->label('Confirm Password')
|
||||
->type('password')
|
||||
->autocomplete('new-password');
|
||||
|
||||
$this->form->addTextField('Email')
|
||||
->type('email')
|
||||
->autocomplete('email');
|
||||
|
||||
$this->form->addButton('submit', 'Register')
|
||||
->icon('pencil');
|
||||
|
||||
$this->successful_login = $successful_login;
|
||||
}
|
||||
|
||||
public function getForm(): FormComponent{
|
||||
return $this->form;
|
||||
}
|
||||
|
||||
public function isSuccessfulLogin(): bool{
|
||||
return $this->successful_login;
|
||||
}
|
||||
|
||||
public function registerUser(array $data, Session $sess): bool{
|
||||
if (!$this->form->accept($data)){
|
||||
return false;
|
||||
}
|
||||
|
||||
$name = $data['Name'];
|
||||
$email = $data['Email'];
|
||||
$password = $data['Password'];
|
||||
$password_repeated = $data['PasswordRepeated'];
|
||||
|
||||
$validator = new Validator(); // TODO deduplicate validation of same fields in other places
|
||||
$validator->str('Name', $name)->notEmpty();
|
||||
$validator->str('Email', $email)->notEmpty()->contains('@', 'Email is not valid.');
|
||||
$validator->str('Password', $password)->minLength(7)->maxLength(72);
|
||||
$validator->str('PasswordRepeated', $password_repeated)->isTrue(fn($v): bool => $v === $password, 'Passwords do not match.');
|
||||
|
||||
try{
|
||||
$validator->validate();
|
||||
$users = new UserTable(DB::get());
|
||||
$users->addUser($name, $email, $password);
|
||||
|
||||
if ($sess->tryLoginWithName($name)){
|
||||
return true;
|
||||
}
|
||||
else{
|
||||
throw new LogicException('Could not login a newly registered user.');
|
||||
}
|
||||
}catch(ValidationException $e){
|
||||
$this->form->invalidateFields($e->getFields());
|
||||
}catch(PDOException $e){
|
||||
if ($e->getCode() === SQL::CONSTRAINT_VIOLATION){
|
||||
try{
|
||||
$users = new UserTable(DB::get());
|
||||
|
||||
if ($users->checkEmailExists($email)){
|
||||
$this->form->invalidateField('Email', 'User with this email already exists.');
|
||||
return false;
|
||||
}
|
||||
elseif ($users->checkNameExists($name)){
|
||||
$this->form->invalidateField('Name', 'User with this name already exists.');
|
||||
return false;
|
||||
}
|
||||
}catch(Exception $e){
|
||||
$this->form->onGeneralError($e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
$this->form->onGeneralError($e);
|
||||
}catch(Exception $e){
|
||||
$this->form->onGeneralError($e);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
35
src/Pages/Views/Mixed/LoginPage.php
Normal file
35
src/Pages/Views/Mixed/LoginPage.php
Normal file
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace Pages\Views\Mixed;
|
||||
|
||||
use Pages\Components\Forms\FormComponent;
|
||||
use Pages\Models\Mixed\LoginModel;
|
||||
use Pages\Views\AbstractPage;
|
||||
|
||||
class LoginPage extends AbstractPage{
|
||||
private LoginModel $model;
|
||||
|
||||
public function __construct(LoginModel $model){
|
||||
parent::__construct($model);
|
||||
$this->model = $model;
|
||||
}
|
||||
|
||||
protected function getHeading(): string{
|
||||
return 'Login';
|
||||
}
|
||||
|
||||
protected function getLayout(): string{
|
||||
return self::LAYOUT_COMPACT;
|
||||
}
|
||||
|
||||
protected function echoPageHead(){
|
||||
FormComponent::echoHead();
|
||||
}
|
||||
|
||||
protected function echoPageBody(): void{
|
||||
$this->model->getForm()->echoBody();
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
42
src/Pages/Views/Mixed/RegisterPage.php
Normal file
42
src/Pages/Views/Mixed/RegisterPage.php
Normal file
@ -0,0 +1,42 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace Pages\Views\Mixed;
|
||||
|
||||
use Pages\Components\Forms\FormComponent;
|
||||
use Pages\Models\Mixed\RegisterModel;
|
||||
use Pages\Views\AbstractPage;
|
||||
|
||||
class RegisterPage extends AbstractPage{
|
||||
private RegisterModel $model;
|
||||
|
||||
public function __construct(RegisterModel $model){
|
||||
parent::__construct($model);
|
||||
$this->model = $model;
|
||||
}
|
||||
|
||||
protected function getHeading(): string{
|
||||
return 'Register';
|
||||
}
|
||||
|
||||
protected function getLayout(): string{
|
||||
return self::LAYOUT_COMPACT;
|
||||
}
|
||||
|
||||
protected function echoPageHead(){
|
||||
FormComponent::echoHead();
|
||||
}
|
||||
|
||||
protected function echoPageBody(): void{
|
||||
if ($this->model->isSuccessfulLogin()){
|
||||
echo <<<HTML
|
||||
<p>Registration successful, you are now logged in!</p>
|
||||
HTML;
|
||||
}
|
||||
else{
|
||||
$this->model->getForm()->echoBody();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
@ -1,6 +1,8 @@
|
||||
<?php
|
||||
define('BASE_URL', 'http://localhost');
|
||||
|
||||
define('SYS_ENABLE_REGISTRATION', true);
|
||||
|
||||
define('DB_DRIVER', 'mysql');
|
||||
define('DB_NAME', 'tracker');
|
||||
define('DB_HOST', 'localhost');
|
||||
|
@ -53,6 +53,9 @@ $router = new Router();
|
||||
|
||||
$router->add('&/about', 'Root/AboutController');
|
||||
|
||||
$router->add('&/login', 'Mixed/LoginController');
|
||||
$router->add('&/register', 'Mixed/RegisterController');
|
||||
|
||||
function handle_error(int $code, string $title, string $message): void{
|
||||
http_response_code($code);
|
||||
$page_model = new BasicRootPageModel(new Request('', '', []));
|
||||
|
Loading…
Reference in New Issue
Block a user