From 3d7eb7bbb10eb1a0012ed531dc19f760addf7af0 Mon Sep 17 00:00:00 2001 From: Override-6 Date: Tue, 21 Nov 2023 19:40:30 +0100 Subject: [PATCH] add session handle --- Documentation/models.puml | 8 +-- public/index.php | 5 +- sql/setup-tables.sql | 24 +++++---- src/Controller/FrontController.php | 52 +++++++++---------- src/Controller/Sub/AuthController.php | 24 ++++----- src/Controller/UserController.php | 6 +-- src/Data/Account.php | 73 ++++----------------------- src/Gateway/AccountGateway.php | 56 ++++++++++++++++++++ src/Gateway/AuthGateway.php | 47 ----------------- src/Model/AuthModel.php | 66 ++++++++++++------------ src/Session/MutableSessionHandle.php | 9 ++++ src/Session/PhpSessionHandle.php | 24 +++++++++ src/Session/SessionHandle.php | 11 ++++ 13 files changed, 206 insertions(+), 199 deletions(-) create mode 100644 src/Gateway/AccountGateway.php delete mode 100644 src/Gateway/AuthGateway.php create mode 100644 src/Session/MutableSessionHandle.php create mode 100644 src/Session/PhpSessionHandle.php create mode 100644 src/Session/SessionHandle.php diff --git a/Documentation/models.puml b/Documentation/models.puml index d95343c..82037bc 100755 --- a/Documentation/models.puml +++ b/Documentation/models.puml @@ -81,7 +81,7 @@ AuthController --> "- model" AuthModel class AuthModel{ + register(username : string, password : string, confirmPassword : string, email : string): array - + getUserFields(email : string):array + + getAccount(email : string):array + login(email : string, password : string) } AuthModel --> "- gateway" AuthGateway @@ -89,9 +89,9 @@ AuthModel --> "- gateway" AuthGateway class AuthGateway{ -con : Connection - + mailExist(email : string) : bool + + mailExists(email : string) : bool + insertAccount(username : string, hash : string, email : string) - + getUserHash(email : string):string - + getUserFields (email : string): array + + getHash(email : string):string + + getAccount (email : string): array } @enduml \ No newline at end of file diff --git a/public/index.php b/public/index.php index f898d7f..ce1df33 100644 --- a/public/index.php +++ b/public/index.php @@ -1,5 +1,6 @@ run(); + +$frontController->run(PhpSessionHandle::init()); diff --git a/sql/setup-tables.sql b/sql/setup-tables.sql index 108b62a..a289336 100644 --- a/sql/setup-tables.sql +++ b/sql/setup-tables.sql @@ -1,18 +1,24 @@ - -- drop tables here DROP TABLE IF EXISTS FormEntries; -DROP TABLE IF EXISTS AccountUser; +DROP TABLE IF EXISTS Account; DROP TABLE IF EXISTS TacticInfo; -CREATE TABLE FormEntries(name varchar, description varchar); -CREATE TABLE AccountUser( +CREATE TABLE FormEntries +( + name varchar, + description varchar +); +CREATE TABLE Account +( username varchar, - hash varchar, - email varchar unique + hash varchar, + email varchar unique, + token varchar(256) NOT NULL UNIQUE ); -CREATE TABLE TacticInfo( - id integer PRIMARY KEY AUTOINCREMENT, - name varchar, +CREATE TABLE TacticInfo +( + id integer PRIMARY KEY AUTOINCREMENT, + name varchar, creation_date timestamp DEFAULT CURRENT_TIMESTAMP ); \ No newline at end of file diff --git a/src/Controller/FrontController.php b/src/Controller/FrontController.php index 58e5d9d..d5c6417 100644 --- a/src/Controller/FrontController.php +++ b/src/Controller/FrontController.php @@ -7,6 +7,7 @@ use App\Http\HttpCodes; use App\Http\HttpResponse; use App\Http\JsonHttpResponse; use App\Http\ViewHttpResponse; +use App\Session\MutableSessionHandle; use Exception; use Twig\Environment; use Twig\Error\LoaderError; @@ -14,12 +15,10 @@ use Twig\Error\RuntimeError; use Twig\Error\SyntaxError; use Twig\Loader\FilesystemLoader; -class FrontController -{ +class FrontController { private AltoRouter $router; - public function __construct(string $basePath) - { + public function __construct(string $basePath) { $this->router = $this->createRouter($basePath); $this->initializeRouterMap(); } @@ -29,11 +28,10 @@ class FrontController * * @return void */ - public function run(): void - { + public function run(MutableSessionHandle $session): void { $match = $this->router->match(); if ($match != null) { - $this->handleMatch($match); + $this->handleMatch($match, $session); } else { $this->displayViewByKind(ViewHttpResponse::twig("error.html.twig", [], HttpCodes::NOT_FOUND)); } @@ -45,8 +43,7 @@ class FrontController * @param string $basePath * @return AltoRouter */ - public function createRouter(string $basePath): AltoRouter - { + public function createRouter(string $basePath): AltoRouter { $router = new AltoRouter(); $router->setBasePath($basePath); return $router; @@ -57,8 +54,7 @@ class FrontController * * @return void */ - private function initializeRouterMap(): void - { + private function initializeRouterMap(): void { $this->router->map("GET", "/", "UserController"); $this->router->map("GET|POST", "/[a:action]?", "UserController"); $this->router->map("GET|POST", "/tactic/[a:action]/[i:idTactic]?", "UserController"); @@ -68,28 +64,34 @@ class FrontController * @param array $match * @return void */ - private function handleMatch(array $match): void - { + private function handleMatch(array $match, MutableSessionHandle $session): void { $tag = $match['target']; $action = $this->getAction($match); $params = $match["params"]; unset($params['action']); - $this->handleResponseByType($this->tryToCall($tag, $action, array_values($params))); + $this->handleResponseByType($this->tryToCall($tag, $action, array_values($params), $session)); } /** * @param string $controller * @param string $action * @param array $params + * @param MutableSessionHandle $session * @return HttpResponse */ - private function tryToCall(string $controller, string $action, array $params): HttpResponse - { + + private function tryToCall(string $controller, string $action, array $params, MutableSessionHandle $session): HttpResponse { $controller = $this->getController($controller); - if (is_callable([$controller, $action])) { - return call_user_func_array([$controller, $action], $params); - } else { + try { + if (is_callable([$controller, $action])) { + // append the session as the last parameter of a controller function + $params[] = $session; + return call_user_func_array([$controller, $action], $params); + } else { + return ViewHttpResponse::twig("error.html.twig", [], HttpCodes::NOT_FOUND); + } + } catch (Exception $e) { return ViewHttpResponse::twig("error.html.twig", [], HttpCodes::NOT_FOUND); } } @@ -100,8 +102,7 @@ class FrontController * @param array $match * @return string */ - private function getAction(array $match): string - { + private function getAction(array $match): string { if (isset($match["params"]["action"])) { return $match["params"]["action"]; } @@ -114,8 +115,7 @@ class FrontController * @param string $controller * @return mixed */ - private function getController(string $controller) - { + private function getController(string $controller) { $namespace = "\\App\\Controller\\"; $controller = $namespace . $controller; return new $controller(); @@ -127,8 +127,7 @@ class FrontController * @param HttpResponse $response * @return void */ - private function handleResponseByType(HttpResponse $response): void - { + private function handleResponseByType(HttpResponse $response): void { http_response_code($response->getCode()); if ($response instanceof ViewHttpResponse) { $this->displayViewByKind($response); @@ -144,8 +143,7 @@ class FrontController * @param ViewHttpResponse $response * @return void */ - private function displayViewByKind(ViewHttpResponse $response): void - { + private function displayViewByKind(ViewHttpResponse $response): void { $file = $response->getFile(); $args = $response->getArguments(); diff --git a/src/Controller/Sub/AuthController.php b/src/Controller/Sub/AuthController.php index a78b02e..7a537f2 100644 --- a/src/Controller/Sub/AuthController.php +++ b/src/Controller/Sub/AuthController.php @@ -2,6 +2,7 @@ namespace App\Controller\Sub; +use App\Gateway\AccountGateway; use App\Http\HttpRequest; use App\Http\HttpResponse; use App\Http\ViewHttpResponse; @@ -49,17 +50,16 @@ class AuthController { "username" => [Validators::name(), Validators::lenBetween(2, 32)], "password" => [Validators::lenBetween(6, 256)], "confirmpassword" => [Validators::lenBetween(6, 256)], - "email" => [Validators::regex("/^\\S+@\\S+\\.\\S+$/"),Validators::lenBetween(5, 256)], + "email" => [Validators::regex("/^\\S+@\\S+\\.\\S+$/"), Validators::lenBetween(5, 256)], ]); if (!empty($fails)) { return $this->displayBadFields("display_register.html.twig", $fails); } - $fails = $this->model->register($request['username'], $request["password"], $request['confirmpassword'], $request['email']); - if (empty($fails)) { - $results = $this->model->getUserFields($request['email']); - return ViewHttpResponse::twig("display_auth_confirm.html.twig", ['username' => $results['username'], 'email' => $results['email']]); + $account = $this->model->register($request['username'], $request["password"], $request['confirmpassword'], $request['email'], $fails); + if (!empty($fails)) { + return $this->displayBadFields("display_register.html.twig", $fails); } - return $this->displayBadFields("display_register.html.twig", $fails); + return ViewHttpResponse::twig("display_auth_confirm.html.twig", ['username' => $account->getName(), 'email' => $account->getEmail()]); } @@ -75,18 +75,18 @@ class AuthController { $fails = []; $request = HttpRequest::from($request, $fails, [ "password" => [Validators::lenBetween(6, 256)], - "email" => [Validators::regex("/^\\S+@\\S+\\.\\S+$/"),Validators::lenBetween(5, 256)], + "email" => [Validators::regex("/^\\S+@\\S+\\.\\S+$/"), Validators::lenBetween(5, 256)], ]); if (!empty($fails)) { return $this->displayBadFields("display_login.html.twig", $fails); } - $fails = $this->model->login($request['email'], $request['password']); - if (empty($fails)) { - $results = $this->model->getUserFields($request['email']); - return ViewHttpResponse::twig("display_auth_confirm.html.twig", ['username' => $results['username'], 'email' => $results['email']]); + $account = $this->model->login($request['email'], $request['password'], $fails); + if (!empty($fails)) { + return $this->displayBadFields("display_login.html.twig", $fails); } - return $this->displayBadFields("display_login.html.twig", $fails); + + return ViewHttpResponse::twig("display_auth_confirm.html.twig", ['username' => $account->getName(), 'email' => $account->getEmail()]); } } diff --git a/src/Controller/UserController.php b/src/Controller/UserController.php index 2a95bcc..360c9a3 100644 --- a/src/Controller/UserController.php +++ b/src/Controller/UserController.php @@ -3,7 +3,7 @@ namespace App\Controller; use App\Connexion; -use App\Gateway\AuthGateway; +use App\Gateway\AccountGateway; use App\Gateway\TacticInfoGateway; use App\Http\HttpResponse; use App\Http\ViewHttpResponse; @@ -16,7 +16,7 @@ class UserController { } public function register(): HttpResponse { - $model = new AuthModel(new AuthGateway(new Connexion(get_database()))); + $model = new AuthModel(new AccountGateway(new Connexion(get_database()))); if ($_SERVER['REQUEST_METHOD'] === 'GET') { return (new Sub\AuthController($model))->displayRegister(); } @@ -24,7 +24,7 @@ class UserController { } public function login(): HttpResponse { - $model = new AuthModel(new AuthGateway(new Connexion(get_database()))); + $model = new AuthModel(new AccountGateway(new Connexion(get_database()))); if ($_SERVER['REQUEST_METHOD'] === 'GET') { return (new Sub\AuthController($model))->displayLogin(); } diff --git a/src/Data/Account.php b/src/Data/Account.php index 2a21bf1..8b58e1c 100755 --- a/src/Data/Account.php +++ b/src/Data/Account.php @@ -4,8 +4,6 @@ namespace App\Data; use http\Exception\InvalidArgumentException; -const PHONE_NUMBER_REGEXP = "/^\\+[0-9]+$/"; - /** * Base class of a user account. * Contains the private information that we don't want @@ -16,27 +14,16 @@ class Account { * @var string $email account's mail address */ private string $email; - /** - * @var string account's phone number. - * its format is specified by the {@link PHONE_NUMBER_REGEXP} constant - * - */ - private string $phoneNumber; /** - * @var AccountUser account's public and shared information + * @var string string token */ - private AccountUser $user; + private string $token; /** - * @var Team[] account's teams + * @var string the account's username */ - private array $teams; - - /** - * @var int account's unique identifier - */ - private int $id; + private string $name; /** @@ -46,60 +33,22 @@ class Account { * @param Team[] $teams * @param int $id */ - public function __construct(string $email, string $phoneNumber, AccountUser $user, array $teams, int $id) { + public function __construct(string $email, string $name, string $token) { $this->email = $email; - $this->phoneNumber = $phoneNumber; - $this->user = $user; - $this->teams = $teams; - $this->id = $id; + $this->name = $name; + $this->token = $token; } - /** - * @return string - */ public function getEmail(): string { return $this->email; } - /** - * @param string $email - */ - public function setEmail(string $email): void { - if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { - throw new InvalidArgumentException("Invalid mail address"); - } - $this->email = $email; - } - - /** - * @return string - */ - public function getPhoneNumber(): string { - return $this->phoneNumber; + public function getToken(): string { + return $this->token; } - /** - * @param string $phoneNumber - */ - public function setPhoneNumber(string $phoneNumber): void { - if (!preg_match(PHONE_NUMBER_REGEXP, $phoneNumber)) { - throw new InvalidArgumentException("Invalid phone number"); - } - $this->phoneNumber = $phoneNumber; + public function getName(): string { + return $this->name; } - public function getId(): int { - return $this->id; - } - - /** - * @return Team[] - */ - public function getTeams(): array { - return $this->teams; - } - - public function getUser(): AccountUser { - return $this->user; - } } diff --git a/src/Gateway/AccountGateway.php b/src/Gateway/AccountGateway.php new file mode 100644 index 0000000..a4686d6 --- /dev/null +++ b/src/Gateway/AccountGateway.php @@ -0,0 +1,56 @@ +con = $con; + } + + + public function exists(string $email): bool { + return $this->getAccount($email) != null; + } + + + public function insertAccount(Account $account, string $hash): void { + $this->con->exec("INSERT INTO Account VALUES (:username,:hash,:email)", [ + ':username' => [$account->getName(), PDO::PARAM_STR], + ':hash' => [$hash, PDO::PARAM_STR], + ':email' => [$account->getEmail(), PDO::PARAM_STR], + ':token' => [$account->getToken(), PDO::PARAM_STR] + ]); + } + + public function getHash(string $email): string { + $results = $this->con->fetch("SELECT hash FROM Account WHERE email = :email", [ + ':email' => [$email, PDO::PARAM_STR] + ]); + return $results[0]['hash']; + } + + + /** + * @param string $email + * @return Account|null + */ + public function getAccount(string $email): ?Account { + $results = $this->con->fetch("SELECT username,email,token FROM Account WHERE email = :email", [':email' => [$email, PDO::PARAM_STR]]); + if (empty($results)) + return null; + + $acc = $results[0]; + return new Account($acc["email"], $acc["name"], $acc["token"]); + } + + +} diff --git a/src/Gateway/AuthGateway.php b/src/Gateway/AuthGateway.php deleted file mode 100644 index 5acc01c..0000000 --- a/src/Gateway/AuthGateway.php +++ /dev/null @@ -1,47 +0,0 @@ -con = $con; - } - - - public function mailExist(string $email): bool { - return $this->getUserFields($email) != null; - } - - - public function insertAccount(string $username, string $hash, string $email): void { - $this->con->exec("INSERT INTO AccountUser VALUES (:username,:hash,:email)", [':username' => [$username, PDO::PARAM_STR],':hash' => [$hash, PDO::PARAM_STR],':email' => [$email, PDO::PARAM_STR]]); - } - - public function getUserHash(string $email): string { - $results = $this->con->fetch("SELECT hash FROM AccountUser WHERE email = :email", [':email' => [$email, PDO::PARAM_STR]]); - return $results[0]['hash']; - } - - - /** - * @param string $email - * @return array|null - */ - public function getUserFields(string $email): ?array { - $results = $this->con->fetch("SELECT username,email FROM AccountUser WHERE email = :email", [':email' => [$email, PDO::PARAM_STR]]); - $firstRow = $results[0] ?? null; - return $firstRow; - } - - - - -} diff --git a/src/Model/AuthModel.php b/src/Model/AuthModel.php index 312cca8..b786c2e 100644 --- a/src/Model/AuthModel.php +++ b/src/Model/AuthModel.php @@ -2,16 +2,19 @@ namespace App\Model; -use App\Gateway\AuthGateway; +use App\Controller\AuthController; +use App\Data\Account; +use App\Gateway\AccountGateway; use App\Validation\FieldValidationFail; use App\Validation\ValidationFail; class AuthModel { - private AuthGateway $gateway; + private AccountGateway $gateway; + /** - * @param AuthGateway $gateway + * @param AccountGateway $gateway */ - public function __construct(AuthGateway $gateway) { + public function __construct(AccountGateway $gateway) { $this->gateway = $gateway; } @@ -21,59 +24,54 @@ class AuthModel { * @param string $password * @param string $confirmPassword * @param string $email - * @return ValidationFail[] + * @param ValidationFail[] $failures + * @return Account|null the registered account or null if failures occurred */ - public function register(string $username, string $password, string $confirmPassword, string $email): array { - $errors = []; + public function register(string $username, string $password, string $confirmPassword, string $email, array &$failures): ?Account { if ($password != $confirmPassword) { - $errors[] = new FieldValidationFail("confirmpassword", "password and password confirmation are not equals"); + $failures[] = new FieldValidationFail("confirmpassword", "password and password confirmation are not equals"); } - if ($this->gateway->mailExist($email)) { - $errors[] = new FieldValidationFail("email", "email already exist"); + if ($this->gateway->exists($email)) { + $failures[] = new FieldValidationFail("email", "email already exist"); } - if(empty($errors)) { - $hash = password_hash($password, PASSWORD_DEFAULT); - $this->gateway->insertAccount($username, $hash, $email); + if (!empty($errors)) { + return null; } - return $errors; - } + $hash = password_hash($password, PASSWORD_DEFAULT); - /** - * @param string $email - * @return array|null - */ - public function getUserFields(string $email): ?array { - return $this->gateway->getUserFields($email); + $account = new Account($email, $username, $this->generateToken()); + $this->gateway->insertAccount($account, $hash); + return $account; } + private function generateToken(): string { + return base64_encode(random_bytes(64)); + } /** * @param string $email * @param string $password - * @return ValidationFail[] $errors + * @param ValidationFail[] $failures + * @return Account|null the authenticated account or null if failures occurred */ - public function login(string $email, string $password): array { - $errors = []; - - if (!$this->gateway->mailExist($email)) { - $errors[] = new FieldValidationFail("email", "email doesnt exists"); - return $errors; + public function login(string $email, string $password, array &$failures): ?Account { + if (!$this->gateway->exists($email)) { + $failures = new FieldValidationFail("email", "email doesnt exists"); + return null; } - $hash = $this->gateway->getUserHash($email); + $hash = $this->gateway->getHash($email); if (!password_verify($password, $hash)) { - $errors[] = new FieldValidationFail("password", "invalid password"); + $failures = new FieldValidationFail("password", "invalid password"); + return null; } - return $errors; + return $this->gateway->getAccount($email); } - - - } diff --git a/src/Session/MutableSessionHandle.php b/src/Session/MutableSessionHandle.php new file mode 100644 index 0000000..a7822a3 --- /dev/null +++ b/src/Session/MutableSessionHandle.php @@ -0,0 +1,9 @@ +