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{
+ 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

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

@ -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
);

@ -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<string, mixed> $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<int, mixed> $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<string, mixed> $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();

@ -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()]);
}
}

@ -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();
}

@ -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;
}
}

@ -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;
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<string,string>|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);
}
}

@ -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