add session handle

pull/19/head
Override-6 1 year ago committed by maxime.batista
parent 14612226cd
commit 3d7eb7bbb1

@ -81,7 +81,7 @@ AuthController --> "- model" AuthModel
class AuthModel{ class AuthModel{
+ register(username : string, password : string, confirmPassword : string, email : string): array + register(username : string, password : string, confirmPassword : string, email : string): array
+ getUserFields(email : string):array + getAccount(email : string):array
+ login(email : string, password : string) + login(email : string, password : string)
} }
AuthModel --> "- gateway" AuthGateway AuthModel --> "- gateway" AuthGateway
@ -89,9 +89,9 @@ AuthModel --> "- gateway" AuthGateway
class AuthGateway{ class AuthGateway{
-con : Connection -con : Connection
+ mailExist(email : string) : bool + mailExists(email : string) : bool
+ insertAccount(username : string, hash : string, email : string) + insertAccount(username : string, hash : string, email : string)
+ getUserHash(email : string):string + getHash(email : string):string
+ getUserFields (email : string): array + getAccount (email : string): array
} }
@enduml @enduml

@ -1,5 +1,6 @@
<?php <?php
require "../vendor/autoload.php"; require "../vendor/autoload.php";
require "../config.php"; require "../config.php";
require "../sql/database.php"; require "../sql/database.php";
@ -7,7 +8,9 @@ require "utils.php";
require "../src/react-display.php"; require "../src/react-display.php";
use App\Controller\FrontController; use App\Controller\FrontController;
use App\Session\PhpSessionHandle;
$basePath = get_public_path(); $basePath = get_public_path();
$frontController = new FrontController($basePath); $frontController = new FrontController($basePath);
$frontController->run();
$frontController->run(PhpSessionHandle::init());

@ -1,17 +1,23 @@
-- drop tables here -- drop tables here
DROP TABLE IF EXISTS FormEntries; DROP TABLE IF EXISTS FormEntries;
DROP TABLE IF EXISTS AccountUser; DROP TABLE IF EXISTS Account;
DROP TABLE IF EXISTS TacticInfo; DROP TABLE IF EXISTS TacticInfo;
CREATE TABLE FormEntries(name varchar, description varchar); CREATE TABLE FormEntries
CREATE TABLE AccountUser( (
name varchar,
description varchar
);
CREATE TABLE Account
(
username varchar, username varchar,
hash varchar, hash varchar,
email varchar unique email varchar unique,
token varchar(256) NOT NULL UNIQUE
); );
CREATE TABLE TacticInfo( CREATE TABLE TacticInfo
(
id integer PRIMARY KEY AUTOINCREMENT, id integer PRIMARY KEY AUTOINCREMENT,
name varchar, name varchar,
creation_date timestamp DEFAULT CURRENT_TIMESTAMP creation_date timestamp DEFAULT CURRENT_TIMESTAMP

@ -7,6 +7,7 @@ use App\Http\HttpCodes;
use App\Http\HttpResponse; use App\Http\HttpResponse;
use App\Http\JsonHttpResponse; use App\Http\JsonHttpResponse;
use App\Http\ViewHttpResponse; use App\Http\ViewHttpResponse;
use App\Session\MutableSessionHandle;
use Exception; use Exception;
use Twig\Environment; use Twig\Environment;
use Twig\Error\LoaderError; use Twig\Error\LoaderError;
@ -14,12 +15,10 @@ use Twig\Error\RuntimeError;
use Twig\Error\SyntaxError; use Twig\Error\SyntaxError;
use Twig\Loader\FilesystemLoader; use Twig\Loader\FilesystemLoader;
class FrontController class FrontController {
{
private AltoRouter $router; private AltoRouter $router;
public function __construct(string $basePath) public function __construct(string $basePath) {
{
$this->router = $this->createRouter($basePath); $this->router = $this->createRouter($basePath);
$this->initializeRouterMap(); $this->initializeRouterMap();
} }
@ -29,11 +28,10 @@ class FrontController
* *
* @return void * @return void
*/ */
public function run(): void public function run(MutableSessionHandle $session): void {
{
$match = $this->router->match(); $match = $this->router->match();
if ($match != null) { if ($match != null) {
$this->handleMatch($match); $this->handleMatch($match, $session);
} else { } else {
$this->displayViewByKind(ViewHttpResponse::twig("error.html.twig", [], HttpCodes::NOT_FOUND)); $this->displayViewByKind(ViewHttpResponse::twig("error.html.twig", [], HttpCodes::NOT_FOUND));
} }
@ -45,8 +43,7 @@ class FrontController
* @param string $basePath * @param string $basePath
* @return AltoRouter * @return AltoRouter
*/ */
public function createRouter(string $basePath): AltoRouter public function createRouter(string $basePath): AltoRouter {
{
$router = new AltoRouter(); $router = new AltoRouter();
$router->setBasePath($basePath); $router->setBasePath($basePath);
return $router; return $router;
@ -57,8 +54,7 @@ class FrontController
* *
* @return void * @return void
*/ */
private function initializeRouterMap(): void private function initializeRouterMap(): void {
{
$this->router->map("GET", "/", "UserController"); $this->router->map("GET", "/", "UserController");
$this->router->map("GET|POST", "/[a:action]?", "UserController"); $this->router->map("GET|POST", "/[a:action]?", "UserController");
$this->router->map("GET|POST", "/tactic/[a:action]/[i:idTactic]?", "UserController"); $this->router->map("GET|POST", "/tactic/[a:action]/[i:idTactic]?", "UserController");
@ -68,30 +64,36 @@ class FrontController
* @param array<string, mixed> $match * @param array<string, mixed> $match
* @return void * @return void
*/ */
private function handleMatch(array $match): void private function handleMatch(array $match, MutableSessionHandle $session): void {
{
$tag = $match['target']; $tag = $match['target'];
$action = $this->getAction($match); $action = $this->getAction($match);
$params = $match["params"]; $params = $match["params"];
unset($params['action']); 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 $controller
* @param string $action * @param string $action
* @param array<int, mixed> $params * @param array<int, mixed> $params
* @param MutableSessionHandle $session
* @return HttpResponse * @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); $controller = $this->getController($controller);
try {
if (is_callable([$controller, $action])) { 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); return call_user_func_array([$controller, $action], $params);
} else { } else {
return ViewHttpResponse::twig("error.html.twig", [], HttpCodes::NOT_FOUND); 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<string, mixed> $match * @param array<string, mixed> $match
* @return string * @return string
*/ */
private function getAction(array $match): string private function getAction(array $match): string {
{
if (isset($match["params"]["action"])) { if (isset($match["params"]["action"])) {
return $match["params"]["action"]; return $match["params"]["action"];
} }
@ -114,8 +115,7 @@ class FrontController
* @param string $controller * @param string $controller
* @return mixed * @return mixed
*/ */
private function getController(string $controller) private function getController(string $controller) {
{
$namespace = "\\App\\Controller\\"; $namespace = "\\App\\Controller\\";
$controller = $namespace . $controller; $controller = $namespace . $controller;
return new $controller(); return new $controller();
@ -127,8 +127,7 @@ class FrontController
* @param HttpResponse $response * @param HttpResponse $response
* @return void * @return void
*/ */
private function handleResponseByType(HttpResponse $response): void private function handleResponseByType(HttpResponse $response): void {
{
http_response_code($response->getCode()); http_response_code($response->getCode());
if ($response instanceof ViewHttpResponse) { if ($response instanceof ViewHttpResponse) {
$this->displayViewByKind($response); $this->displayViewByKind($response);
@ -144,8 +143,7 @@ class FrontController
* @param ViewHttpResponse $response * @param ViewHttpResponse $response
* @return void * @return void
*/ */
private function displayViewByKind(ViewHttpResponse $response): void private function displayViewByKind(ViewHttpResponse $response): void {
{
$file = $response->getFile(); $file = $response->getFile();
$args = $response->getArguments(); $args = $response->getArguments();

@ -2,6 +2,7 @@
namespace App\Controller\Sub; namespace App\Controller\Sub;
use App\Gateway\AccountGateway;
use App\Http\HttpRequest; use App\Http\HttpRequest;
use App\Http\HttpResponse; use App\Http\HttpResponse;
use App\Http\ViewHttpResponse; use App\Http\ViewHttpResponse;
@ -54,13 +55,12 @@ class AuthController {
if (!empty($fails)) { if (!empty($fails)) {
return $this->displayBadFields("display_register.html.twig", $fails); return $this->displayBadFields("display_register.html.twig", $fails);
} }
$fails = $this->model->register($request['username'], $request["password"], $request['confirmpassword'], $request['email']); $account = $this->model->register($request['username'], $request["password"], $request['confirmpassword'], $request['email'], $fails);
if (empty($fails)) { if (!empty($fails)) {
$results = $this->model->getUserFields($request['email']);
return ViewHttpResponse::twig("display_auth_confirm.html.twig", ['username' => $results['username'], 'email' => $results['email']]);
}
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()]);
}
public function displayLogin(): HttpResponse { public function displayLogin(): HttpResponse {
@ -81,12 +81,12 @@ class AuthController {
return $this->displayBadFields("display_login.html.twig", $fails); return $this->displayBadFields("display_login.html.twig", $fails);
} }
$fails = $this->model->login($request['email'], $request['password']); $account = $this->model->login($request['email'], $request['password'], $fails);
if (empty($fails)) { if (!empty($fails)) {
$results = $this->model->getUserFields($request['email']);
return ViewHttpResponse::twig("display_auth_confirm.html.twig", ['username' => $results['username'], 'email' => $results['email']]);
}
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()]);
}
} }

@ -3,7 +3,7 @@
namespace App\Controller; namespace App\Controller;
use App\Connexion; use App\Connexion;
use App\Gateway\AuthGateway; use App\Gateway\AccountGateway;
use App\Gateway\TacticInfoGateway; use App\Gateway\TacticInfoGateway;
use App\Http\HttpResponse; use App\Http\HttpResponse;
use App\Http\ViewHttpResponse; use App\Http\ViewHttpResponse;
@ -16,7 +16,7 @@ class UserController {
} }
public function register(): HttpResponse { 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') { if ($_SERVER['REQUEST_METHOD'] === 'GET') {
return (new Sub\AuthController($model))->displayRegister(); return (new Sub\AuthController($model))->displayRegister();
} }
@ -24,7 +24,7 @@ class UserController {
} }
public function login(): HttpResponse { 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') { if ($_SERVER['REQUEST_METHOD'] === 'GET') {
return (new Sub\AuthController($model))->displayLogin(); return (new Sub\AuthController($model))->displayLogin();
} }

@ -4,8 +4,6 @@ namespace App\Data;
use http\Exception\InvalidArgumentException; use http\Exception\InvalidArgumentException;
const PHONE_NUMBER_REGEXP = "/^\\+[0-9]+$/";
/** /**
* Base class of a user account. * Base class of a user account.
* Contains the private information that we don't want * Contains the private information that we don't want
@ -16,27 +14,16 @@ class Account {
* @var string $email account's mail address * @var string $email account's mail address
*/ */
private string $email; 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; private string $name;
/**
* @var int account's unique identifier
*/
private int $id;
/** /**
@ -46,60 +33,22 @@ class Account {
* @param Team[] $teams * @param Team[] $teams
* @param int $id * @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->email = $email;
$this->phoneNumber = $phoneNumber; $this->name = $name;
$this->user = $user; $this->token = $token;
$this->teams = $teams;
$this->id = $id;
} }
/**
* @return string
*/
public function getEmail(): string { public function getEmail(): string {
return $this->email; return $this->email;
} }
/** public function getToken(): string {
* @param string $email return $this->token;
*/
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;
}
/**
* @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 getId(): int {
return $this->id;
} }
/** public function getName(): string {
* @return Team[] return $this->name;
*/
public function getTeams(): array {
return $this->teams;
} }
public function getUser(): AccountUser {
return $this->user;
}
} }

@ -0,0 +1,56 @@
<?php
namespace App\Gateway;
use App\Connexion;
use App\Data\Account;
use PDO;
class AccountGateway {
private Connexion $con;
/**
* @param Connexion $con
*/
public function __construct(Connexion $con) {
$this->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"]);
}
}

@ -1,47 +0,0 @@
<?php
namespace App\Gateway;
use App\Connexion;
use PDO;
class AuthGateway {
private Connexion $con;
/**
* @param Connexion $con
*/
public function __construct(Connexion $con) {
$this->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<string,string>|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;
}
}

@ -2,16 +2,19 @@
namespace App\Model; 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\FieldValidationFail;
use App\Validation\ValidationFail; use App\Validation\ValidationFail;
class AuthModel { 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; $this->gateway = $gateway;
} }
@ -21,59 +24,54 @@ class AuthModel {
* @param string $password * @param string $password
* @param string $confirmPassword * @param string $confirmPassword
* @param string $email * @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 { public function register(string $username, string $password, string $confirmPassword, string $email, array &$failures): ?Account {
$errors = [];
if ($password != $confirmPassword) { 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)) { if ($this->gateway->exists($email)) {
$errors[] = new FieldValidationFail("email", "email already exist"); $failures[] = new FieldValidationFail("email", "email already exist");
} }
if(empty($errors)) { if (!empty($errors)) {
$hash = password_hash($password, PASSWORD_DEFAULT); return null;
$this->gateway->insertAccount($username, $hash, $email);
} }
return $errors; $hash = password_hash($password, PASSWORD_DEFAULT);
}
/** $account = new Account($email, $username, $this->generateToken());
* @param string $email $this->gateway->insertAccount($account, $hash);
* @return array<string,string>|null return $account;
*/
public function getUserFields(string $email): ?array {
return $this->gateway->getUserFields($email);
} }
private function generateToken(): string {
return base64_encode(random_bytes(64));
}
/** /**
* @param string $email * @param string $email
* @param string $password * @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 { public function login(string $email, string $password, array &$failures): ?Account {
$errors = []; if (!$this->gateway->exists($email)) {
$failures = new FieldValidationFail("email", "email doesnt exists");
if (!$this->gateway->mailExist($email)) { return null;
$errors[] = new FieldValidationFail("email", "email doesnt exists");
return $errors;
} }
$hash = $this->gateway->getUserHash($email); $hash = $this->gateway->getHash($email);
if (!password_verify($password, $hash)) { 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);
} }
} }

@ -0,0 +1,9 @@
<?php
namespace App\Session;
use App\Data\Account;
interface MutableSessionHandle extends SessionHandle {
public function setAccount(Account $account): void;
}

@ -0,0 +1,24 @@
<?php
namespace App\Session;
use App\Data\Account;
class PhpSessionHandle implements MutableSessionHandle {
public static function init(): PhpSessionHandle {
if (session_status() !== PHP_SESSION_NONE) {
throw new \Exception("A php session is already started !");
}
session_start();
return new PhpSessionHandle();
}
public function getAccount(): ?Account {
return $_SESSION["account"] ?? null;
}
public function setAccount(Account $account): void {
$_SESSION["account"] = $account;
}
}

@ -0,0 +1,11 @@
<?php
namespace App\Session;
use App\Data\Account;
interface SessionHandle {
public function getAccount(): ?Account;
}
Loading…
Cancel
Save