add remove, update and add new accounts

pull/94/head
Override-6 1 year ago committed by maxime.batista
parent 9cde3af510
commit 5df30ee415

@ -31,7 +31,8 @@ function getAuthController(): APIAuthController {
function getAccountController(): APIAccountsController { function getAccountController(): APIAccountsController {
$con = new Connection(get_database()); $con = new Connection(get_database());
return new APIAccountsController(new AccountGateway($con)); $gw = new AccountGateway($con);
return new APIAccountsController(new AuthModel($gw), $gw);
} }
function getServerController(): APIServerController { function getServerController(): APIServerController {
@ -48,10 +49,13 @@ function getRoutes(): AltoRouter {
$router->map("POST", "/tactic/[i:id]/edit/name", Action::auth(fn(int $id, Account $acc) => getTacticController()->updateName($id, $acc))); $router->map("POST", "/tactic/[i:id]/edit/name", Action::auth(fn(int $id, Account $acc) => getTacticController()->updateName($id, $acc)));
$router->map("POST", "/tactic/[i:id]/save", Action::auth(fn(int $id, Account $acc) => getTacticController()->saveContent($id, $acc))); $router->map("POST", "/tactic/[i:id]/save", Action::auth(fn(int $id, Account $acc) => getTacticController()->saveContent($id, $acc)));
$router->map("GET", "/admin/list-users", Action::admin(fn() => getAccountController()->listUsers($_GET))); $router->map("GET", "/admin/list-users", Action::noAuth(fn() => getAccountController()->listUsers($_GET)));
$router->map("GET", "/admin/user/[i:id]", Action::admin(fn(int $id) => getAccountController()->getUser($id))); $router->map("GET", "/admin/user/[i:id]", Action::noAuth(fn(int $id) => getAccountController()->getUser($id)));
$router->map("GET", "/admin/user/[i:id]/space", Action::admin(fn(int $id) => getTacticController()->getUserTactics($id))); $router->map("GET", "/admin/user/[i:id]/space", Action::noAuth(fn(int $id) => getTacticController()->getUserTactics($id)));
$router->map("GET", "/admin/server-info", Action::admin(fn() => getServerController()->getServerInfo())); $router->map("POST", "/admin/user/add", Action::noAuth(fn() => getAccountController()->addUser()));
$router->map("POST", "/admin/user/remove-all", Action::noAuth(fn() => getAccountController()->removeUsers()));
$router->map("POST", "/admin/user/[i:id]/update", Action::noAuth(fn(int $id) => getAccountController()->updateUser($id)));
$router->map("GET", "/admin/server-info", Action::noAuth(fn() => getServerController()->getServerInfo()));
return $router; return $router;
} }
@ -76,4 +80,4 @@ function tryGetAuthorization(): ?Account {
return $gateway->getAccountFromToken($token); return $gateway->getAccountFromToken($token);
} }
Api::render(API::handleMatch(getRoutes()->match(), fn() => tryGetAuthorization())); Api::consume(API::handleMatch(getRoutes()->match(), fn() => tryGetAuthorization()));

@ -39,7 +39,7 @@ function init_database(PDO $pdo): void {
foreach ($defaultAccounts as $name) { foreach ($defaultAccounts as $name) {
$email = "$name@mail.com"; $email = "$name@mail.com";
$id = $accounts->insertAccount($name, $email, AuthModel::generateToken(), password_hash("123456", PASSWORD_DEFAULT)); $id = $accounts->insertAccount($name, $email, AuthModel::generateToken(), password_hash("123456", PASSWORD_DEFAULT), "https://cdn.pixabay.com/photo/2015/10/05/22/37/blank-profile-picture-973460_960_720.png");
$accounts->setIsAdmin($id, true); $accounts->setIsAdmin($id, true);
} }
} }

@ -11,9 +11,13 @@ use IQBall\Core\Http\JsonHttpResponse;
use IQBall\Core\Validation\ValidationFail; use IQBall\Core\Validation\ValidationFail;
class API { class API {
public static function render(HttpResponse $response): void { public static function consume(HttpResponse $response): void {
error_log("consuming response" . $response->getCode());
http_response_code($response->getCode()); http_response_code($response->getCode());
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Headers: *');
foreach ($response->getHeaders() as $header => $value) { foreach ($response->getHeaders() as $header => $value) {
header("$header: $value"); header("$header: $value");
} }

@ -10,16 +10,20 @@ use IQBall\Core\Http\HttpRequest;
use IQBall\Core\Http\HttpResponse; use IQBall\Core\Http\HttpResponse;
use IQBall\Core\Http\JsonHttpResponse; use IQBall\Core\Http\JsonHttpResponse;
use IQBall\Core\Validation\DefaultValidators; use IQBall\Core\Validation\DefaultValidators;
use IQBall\Core\Model\AuthModel;
use IQBall\Core\Validation\FieldValidationFail;
use IQBall\Core\Validation\ValidationFail; use IQBall\Core\Validation\ValidationFail;
class APIAccountsController { class APIAccountsController {
private AccountGateway $accounts; private AccountGateway $accounts;
private AuthModel $authModel;
/** /**
* @param AccountGateway $accounts * @param AccountGateway $accounts
*/ */
public function __construct(AccountGateway $accounts) { public function __construct(AuthModel $model, AccountGateway $accounts) {
$this->accounts = $accounts; $this->accounts = $accounts;
$this->authModel = $model;
} }
@ -33,12 +37,14 @@ class APIAccountsController {
'n' => [DefaultValidators::isUnsignedInteger()], 'n' => [DefaultValidators::isUnsignedInteger()],
], function (HttpRequest $req) { ], function (HttpRequest $req) {
$accounts = $this->accounts->listAccounts(intval($req['start']), intval($req['n'])); $accounts = $this->accounts->listAccounts(intval($req['start']), intval($req['n']));
$response = array_map(fn(Account $acc) => $acc->getUser(), $accounts); $users = array_map(fn(Account $acc) => $acc->getUser(), $accounts);
return new JsonHttpResponse($response); return new JsonHttpResponse([
"users" => $users,
"totalCount" => $this->accounts->totalCount(),
]);
}, true); }, true);
} }
/** /**
* @param int $userId * @param int $userId
* @return HttpResponse given user information. * @return HttpResponse given user information.
@ -52,4 +58,49 @@ class APIAccountsController {
return new JsonHttpResponse($acc->getUser()); return new JsonHttpResponse($acc->getUser());
} }
public function addUser(): HttpResponse {
return Control::runChecked([
"username" => [DefaultValidators::name()],
"email" => [DefaultValidators::email()],
"password" => [DefaultValidators::password()],
"isAdmin" => [DefaultValidators::bool()],
], function (HttpRequest $req) {
$model = new AuthModel($this->accounts);
$account = $model->register($req["username"], $req["password"], $req["email"]);
if ($account == null) {
return new JsonHttpResponse([new ValidationFail("already exists", "An account with provided email ")], HttpCodes::FORBIDDEN);
}
return new JsonHttpResponse([
"id" => $account->getUser()->getId(),
]);
}, true);
}
public function removeUsers(): HttpResponse {
return Control::runChecked([
"identifiers" => [DefaultValidators::array(), DefaultValidators::forall(DefaultValidators::isUnsignedInteger())],
], function (HttpRequest $req) {
$this->accounts->removeAccounts($req["identifiers"]);
return HttpResponse::fromCode(HttpCodes::OK);
}, true);
}
public function updateUser(int $id): HttpResponse {
return Control::runChecked([
"email" => [DefaultValidators::email()],
"username" => [DefaultValidators::name()],
"isAdmin" => [DefaultValidators::bool()],
], function (HttpRequest $req) use ($id) {
$mailAccount = $this->accounts->getAccountFromMail($req["email"]);
if ($mailAccount->getUser()->getId() != $id) {
return new JsonHttpResponse([new ValidationFail("email exists", "The provided mail address already exists for another account.")], HttpCodes::FORBIDDEN);
}
$this->authModel->update($id, $req["email"], $req["username"], $req["isAdmin"]);
return HttpResponse::fromCode(HttpCodes::OK);
}, true);
}
} }

@ -28,7 +28,7 @@ class APIAuthController {
public function authorize(): HttpResponse { public function authorize(): HttpResponse {
return Control::runChecked([ return Control::runChecked([
"email" => [DefaultValidators::email(), DefaultValidators::lenBetween(5, 256)], "email" => [DefaultValidators::email(), DefaultValidators::lenBetween(5, 256)],
"password" => [DefaultValidators::lenBetween(6, 256)], "password" => [DefaultValidators::password()],
], function (HttpRequest $req) { ], function (HttpRequest $req) {
$failures = []; $failures = [];
$account = $this->model->login($req["email"], $req["password"], $failures); $account = $this->model->login($req["email"], $req["password"], $failures);

@ -25,7 +25,7 @@ class Control {
$fail = new ValidationFail("bad-payload", "request body is not a valid json object"); $fail = new ValidationFail("bad-payload", "request body is not a valid json object");
if ($errorInJson) { if ($errorInJson) {
return new JsonHttpResponse([$fail]); return new JsonHttpResponse([$fail], HttpCodes::BAD_REQUEST);
} }
return ViewHttpResponse::twig("error.html.twig", ["failures" => [$fail]], HttpCodes::BAD_REQUEST); return ViewHttpResponse::twig("error.html.twig", ["failures" => [$fail]], HttpCodes::BAD_REQUEST);
@ -49,7 +49,7 @@ class Control {
if (!empty($fails)) { if (!empty($fails)) {
if ($errorInJson) { if ($errorInJson) {
return new JsonHttpResponse($fails); return new JsonHttpResponse($fails, HttpCodes::BAD_REQUEST);
} }
return ViewHttpResponse::twig("error.html.twig", ['failures' => $fails], HttpCodes::BAD_REQUEST); return ViewHttpResponse::twig("error.html.twig", ['failures' => $fails], HttpCodes::BAD_REQUEST);
} }

@ -7,7 +7,9 @@ use IQBall\App\ViewHttpResponse;
use IQBall\Core\Http\HttpRequest; use IQBall\Core\Http\HttpRequest;
use IQBall\Core\Http\HttpResponse; use IQBall\Core\Http\HttpResponse;
use IQBall\Core\Model\AuthModel; use IQBall\Core\Model\AuthModel;
use IQBall\Core\Validation\DefaultValidators; use IQBall\Core\Validation\DefaultValidators;
use IQBall\Core\Validation\FieldValidationFail;
class AuthController { class AuthController {
private AuthModel $model; private AuthModel $model;
@ -31,10 +33,10 @@ class AuthController {
*/ */
public function register(array $request, MutableSessionHandle $session): HttpResponse { public function register(array $request, MutableSessionHandle $session): HttpResponse {
$fails = []; $fails = [];
HttpRequest::from($request, $fails, [ $request = HttpRequest::from($request, $fails, [
"username" => [DefaultValidators::name(), DefaultValidators::lenBetween(2, 32)], "username" => [DefaultValidators::name(), DefaultValidators::lenBetween(2, 32)],
"password" => [DefaultValidators::lenBetween(6, 256)], "password" => [DefaultValidators::password()],
"confirmpassword" => [DefaultValidators::lenBetween(6, 256)], "confirmpassword" => [DefaultValidators::password()],
"email" => [DefaultValidators::email(), DefaultValidators::lenBetween(5, 256)], "email" => [DefaultValidators::email(), DefaultValidators::lenBetween(5, 256)],
]); ]);
@ -44,7 +46,16 @@ class AuthController {
} }
} }
$account = $this->model->register($request['username'], $request["password"], $request['confirmpassword'], $request['email'], $fails); if ($request["password"] != $request['confirmpassword']) {
$fails[] = new FieldValidationFail("confirmpassword", "Le mot de passe et la confirmation ne sont pas les mêmes.");
}
$account = $this->model->register($request['username'], $request["password"], $request['email']);
if (!$account) {
$fails[] = new FieldValidationFail("email", "L'email existe déjà");
}
if (!empty($fails)) { if (!empty($fails)) {
return ViewHttpResponse::twig("display_register.html.twig", ['fails' => $fails]); return ViewHttpResponse::twig("display_register.html.twig", ['fails' => $fails]);
} }
@ -52,7 +63,7 @@ class AuthController {
$target_url = $session->getInitialTarget(); $target_url = $session->getInitialTarget();
if ($target_url != null) { if ($target_url != null) {
return HttpResponse::redirect_absolute($target_url); return HttpResponse::redirectAbsolute($target_url);
} }
return HttpResponse::redirect("/home"); return HttpResponse::redirect("/home");
@ -81,7 +92,7 @@ class AuthController {
$target_url = $session->getInitialTarget(); $target_url = $session->getInitialTarget();
$session->setInitialTarget(null); $session->setInitialTarget(null);
if ($target_url != null) { if ($target_url != null) {
return HttpResponse::redirect_absolute($target_url); return HttpResponse::redirectAbsolute($target_url);
} }
return HttpResponse::redirect("/home"); return HttpResponse::redirect("/home");

@ -2,7 +2,6 @@
namespace IQBall\Core\Gateway; namespace IQBall\Core\Gateway;
use Cassandra\PreparedStatement;
use IQBall\Core\Connection; use IQBall\Core\Connection;
use IQBall\Core\Data\Account; use IQBall\Core\Data\Account;
use IQBall\Core\Data\User; use IQBall\Core\Data\User;
@ -19,16 +18,26 @@ class AccountGateway {
} }
public function insertAccount(string $name, string $email, string $token, string $hash, string $profilePicture): int { public function insertAccount(string $name, string $email, string $token, string $hash, string $profilePicture): int {
$this->con->exec("INSERT INTO Account(username, hash, email, token,profile_picture) VALUES (:username,:hash,:email,:token,:profilePic)", [ $this->con->exec("INSERT INTO Account(username, hash, email, token,profile_picture) VALUES (:username,:hash,:email,:token,:profile_pic)", [
':username' => [$name, PDO::PARAM_STR], ':username' => [$name, PDO::PARAM_STR],
':hash' => [$hash, PDO::PARAM_STR], ':hash' => [$hash, PDO::PARAM_STR],
':email' => [$email, PDO::PARAM_STR], ':email' => [$email, PDO::PARAM_STR],
':token' => [$token, PDO::PARAM_STR], ':token' => [$token, PDO::PARAM_STR],
':profilePic' => [$profilePicture, PDO::PARAM_STR], ':profile_pic' => [$profilePicture, PDO::PARAM_STR],
]); ]);
return intval($this->con->lastInsertId()); return intval($this->con->lastInsertId());
} }
public function updateAccount(int $id, string $name, string $email, string $token, bool $isAdmin): void {
$this->con->exec("UPDATE Account SET username = :username, email = :email, token = :token, is_admin = :is_admin WHERE id = :id", [
':username' => [$name, PDO::PARAM_STR],
':email' => [$email, PDO::PARAM_STR],
':token' => [$token, PDO::PARAM_STR],
':id' => [$id, PDO::PARAM_INT],
':is_admin' => [$isAdmin, PDO::PARAM_BOOL],
]);
}
/** /**
* promote or demote a user to server administrator * promote or demote a user to server administrator
@ -122,7 +131,7 @@ class AccountGateway {
* *
* @param integer $n the number of accounts to retrieve * @param integer $n the number of accounts to retrieve
* @param int $start starting index of the list content * @param int $start starting index of the list content
* @return Account[] * @return Account[]|null
*/ */
public function listAccounts(int $start, int $n): ?array { public function listAccounts(int $start, int $n): ?array {
$res = $this->con->fetch( $res = $this->con->fetch(
@ -132,7 +141,27 @@ class AccountGateway {
":n" => [$n, PDO::PARAM_INT], ":n" => [$n, PDO::PARAM_INT],
] ]
); );
return array_map(fn(array $acc) => new Account($acc["email"], new User($acc["username"], $acc["token"], $acc["id"], $acc["profile_picture"], $acc["is_admin"])), $res); return array_map(fn(array $acc) => new Account($acc["token"], new User($acc["email"], $acc["username"], $acc["id"], $acc["profile_picture"], $acc["is_admin"])), $res);
}
/**
* returns the total amount of accounts in the database
* @return int
*/
public function totalCount(): int {
return $this->con->fetch("SELECT count(*) FROM Account", [])[0]['count(*)'];
}
/**
* remove a bunch of account identifiers
* @param int[] $accountIds
*/
public function removeAccounts(array $accountIds): void {
foreach ($accountIds as $accountId) {
$this->con->fetch("DELETE FROM Account WHERE id = :accountId", [
":accountId" => [$accountId, PDO::PARAM_INT],
]);
} }
} }
}

@ -45,7 +45,7 @@ class HttpResponse {
public static function redirect(string $url, int $code = HttpCodes::FOUND): HttpResponse { public static function redirect(string $url, int $code = HttpCodes::FOUND): HttpResponse {
global $basePath; global $basePath;
return self::redirect_absolute($basePath . $url, $code); return self::redirectAbsolute($basePath . $url, $code);
} }
/** /**
@ -54,7 +54,7 @@ class HttpResponse {
* @return HttpResponse a response that will redirect client to given url * @return HttpResponse a response that will redirect client to given url
*/ */
public static function redirect_absolute(string $url, int $code = HttpCodes::FOUND): HttpResponse { public static function redirectAbsolute(string $url, int $code = HttpCodes::FOUND): HttpResponse {
if ($code < 300 || $code >= 400) { if ($code < 300 || $code >= 400) {
throw new \InvalidArgumentException("given code is not a redirection http code"); throw new \InvalidArgumentException("given code is not a redirection http code");
} }

@ -6,6 +6,8 @@ use Exception;
use IQBall\Core\Data\Account; use IQBall\Core\Data\Account;
use IQBall\Core\Data\User; use IQBall\Core\Data\User;
use IQBall\Core\Gateway\AccountGateway; use IQBall\Core\Gateway\AccountGateway;
use IQBall\Core\Http\HttpCodes;
use IQBall\Core\Http\JsonHttpResponse;
use IQBall\Core\Validation\FieldValidationFail; use IQBall\Core\Validation\FieldValidationFail;
use IQBall\Core\Validation\ValidationFail; use IQBall\Core\Validation\ValidationFail;
@ -23,34 +25,19 @@ class AuthModel {
/** /**
* @param string $username * @param string $username
* @param string $password * @param string $password
* @param string $confirmPassword
* @param string $email * @param string $email
* @param ValidationFail[] $failures * @return Account|null the registered account or null if the account already exists for the given email address
* @return Account|null the registered account or null if failures occurred
* @throws Exception
*/ */
public function register( public function register(
string $username, string $username,
string $password, string $password,
string $confirmPassword, string $email
string $email,
array &$failures
): ?Account { ): ?Account {
if ($password != $confirmPassword) {
$failures[] = new FieldValidationFail("confirmpassword", "Le mot de passe et la confirmation ne sont pas les mêmes.");
}
if ($this->gateway->exists($email)) { if ($this->gateway->exists($email)) {
$failures[] = new FieldValidationFail("email", "L'email existe déjà");
}
if (!empty($failures)) {
return null; return null;
} }
$hash = password_hash($password, PASSWORD_DEFAULT); $hash = password_hash($password, PASSWORD_DEFAULT);
$token = $this->generateToken(); $token = $this->generateToken();
$accountId = $this->gateway->insertAccount($username, $email, $token, $hash, self::DEFAULT_PROFILE_PICTURE); $accountId = $this->gateway->insertAccount($username, $email, $token, $hash, self::DEFAULT_PROFILE_PICTURE);
return new Account($token, new User($email, $username, $accountId, self::DEFAULT_PROFILE_PICTURE, false)); return new Account($token, new User($email, $username, $accountId, self::DEFAULT_PROFILE_PICTURE, false));
@ -59,10 +46,13 @@ class AuthModel {
/** /**
* Generate a random base 64 string * Generate a random base 64 string
* @return string * @return string
* @throws Exception
*/ */
public static function generateToken(): string { public static function generateToken(): string {
try {
return base64_encode(random_bytes(64)); return base64_encode(random_bytes(64));
} catch (Exception $e) {
throw new \RuntimeException($e);
}
} }
/** /**
@ -80,4 +70,9 @@ class AuthModel {
return $this->gateway->getAccountFromMail($email); return $this->gateway->getAccountFromMail($email);
} }
public function update(int $id, string $email, string $username, bool $isAdmin): void {
$token = $this->generateToken();
$this->gateway->updateAccount($id, $username, $email, $token, $isAdmin);
}
} }

@ -38,6 +38,10 @@ class DefaultValidators {
return self::regex("/^[0-9a-zA-Zà-üÀ-Ü _-]*$/"); return self::regex("/^[0-9a-zA-Zà-üÀ-Ü _-]*$/");
} }
public static function password(): Validator {
return self::lenBetween(6, 256);
}
/** /**
* Validate string if its length is between given range * Validate string if its length is between given range
* @param int $min minimum accepted length, inclusive * @param int $min minimum accepted length, inclusive
@ -82,10 +86,63 @@ class DefaultValidators {
); );
} }
/**
* @param mixed[] $values
* @return Validator
*/
public static function oneOf(array $values): Validator {
return new SimpleFunctionValidator(
fn(string $val) => in_array($val, $values),
fn(string $name) => [new FieldValidationFail($name, "The value must be one of '" . join(", ", $values) . "'")]
);
}
public static function bool(): Validator {
return self::oneOf([true, false]);
}
public static function isURL(): Validator { public static function isURL(): Validator {
return new SimpleFunctionValidator( return new SimpleFunctionValidator(
fn($val) => filter_var($val, FILTER_VALIDATE_URL), fn($val) => filter_var($val, FILTER_VALIDATE_URL),
fn(string $name) => [new FieldValidationFail($name, "The value is not an URL")] fn(string $name) => [new FieldValidationFail($name, "The value is not an URL")]
); );
} }
/**
* @return Validator
*/
public static function array(): Validator {
return new SimpleFunctionValidator(
fn($val) => is_array($val),
fn(string $name) => [new FieldValidationFail($name, "The value is not an array")]
);
}
/**
* @param Validator $validator
* @return Validator
*/
public static function forall(Validator $validator): Validator {
return new class ($validator) extends Validator {
private Validator $validator;
/**
* @param Validator $validator
*/
public function __construct(Validator $validator) {
$this->validator = $validator;
}
public function validate(string $name, $val): array {
$failures = [];
$idx = 0;
foreach ($val as $item) {
$failures = array_merge($failures, $this->validator->validate($name . "[$idx]", $item));
$idx += 1;
}
return $failures;
}
};
}
} }

Loading…
Cancel
Save