Compare commits

..

13 Commits

Author SHA1 Message Date
tracer 5e8c945170 added password hashing 2022-10-24 20:07:59 +02:00
tracer 54ecb6b15e added more classes 2022-10-24 20:05:08 +02:00
tracer 7160fd0804 renamed a label 2022-10-24 20:04:36 +02:00
tracer 2b34a89068 renamed a label 2022-10-24 20:04:24 +02:00
tracer ab7ac074ba renamed password to new_password 2022-10-24 20:03:43 +02:00
tracer 6461046c31 renamed password to new_password 2022-10-24 20:03:27 +02:00
tracer d2cdcdded4 simplified regex for route creation 2022-10-24 18:56:30 +02:00
tracer 7e19efd420 added path method 2022-10-24 18:54:43 +02:00
tracer ffe4d43600 added router->path 2022-10-24 18:44:42 +02:00
tracer c05aee49d5 added router->path 2022-10-24 18:44:18 +02:00
tracer 10d56751e4 renamed a variable 2022-10-24 18:43:43 +02:00
tracer 71f8e1623e renamed a variable 2022-10-24 18:43:28 +02:00
tracer 2836e0d878 renamed template 2022-10-24 18:43:00 +02:00
8 changed files with 120 additions and 45 deletions

View File

@ -14,16 +14,31 @@ use App\Enums\UserAuth;
class User class User
{ {
public function __construct( public function __construct(
private string $nick = '', private string $nick = '',
private string $password = '', private string $password = '',
private string $first = '', private readonly string $newPassword = '',
private string $last = '', private string $first = '',
private int $id = 0, private string $last = '',
private bool $isAdmin = false, private int $id = 0,
private UserAuth $userAuth = UserAuth::AUTH_ANONYMOUS private bool $isAdmin = false,
private UserAuth $userAuth = UserAuth::AUTH_ANONYMOUS
) )
{ {
// empty body if (!empty($this->newPassword)) {
echo "password";
$this->password = password_hash(password: $this->newPassword, algo: PASSWORD_ARGON2I);
}
if (session_status() === PHP_SESSION_ACTIVE) {
// ANONYMOUS has id 0
if ($this->id != 0) {
if ($this->isAdmin) {
$this->userAuth = UserAuth::AUTH_ADMIN;
} else {
$this->userAuth = UserAuth::AUTH_USER;
}
}
}
} }
public function getNick(): string public function getNick(): string
@ -86,12 +101,12 @@ class User
$this->isAdmin = $isAdmin; $this->isAdmin = $isAdmin;
} }
public function getAuth() public function getAuth(): UserAuth
{ {
return UserAuth::AUTH_ANONYMOUS; return $this->userAuth;
} }
public function setAuth(UserAuth $userAuth) public function setAuth(UserAuth $userAuth): void
{ {
$this->userAuth = $userAuth; $this->userAuth = $userAuth;
} }

View File

@ -1,29 +1,62 @@
<?php <?php
/*
* Copyright (c) 2022. Micha Espey <tracer@24unix.net>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
*/
namespace App\Service; namespace App\Service;
use App\Controller\AddressBook; use App\Controller\AddressBookAdminController;
use App\Controller\AddressBookController;
use App\Controller\SecurityController;
use App\Entity\User;
use App\Repository\UserRepository;
/*
* A quick and dirty class container for DI.
* Caveat: Classes are always instantiated
* No autowiring (yet, maybe later, but it might fit for a demo)
*/
class Container class Container
{ {
// caveat: Classes are always instantiated
// No autowiring (yet, maybe later, but it might fit for a demo)
private Template $template; private AddressBookController $addressBook;
private AddressBook $addressBook; private AddressBookAdminController $addressBookAdmin;
private Router $router; private Config $config;
private DatabaseConnection $databaseConnection;
private Router $router;
private SecurityController $securityController;
private Template $template;
private User $user;
private UserRepository $userRepository;
public function __construct() public function __construct()
{ {
$this->template = new Template(templateDir: dirname(path: __DIR__, levels: 2) . '/templates/'); $this->config = new Config();
$this->addressBook = new AddressBook(template: $this->template); $this->databaseConnection = new DatabaseConnection(config: $this->config);
$this->router = new Router(); $this->template = new Template(templateDir: dirname(path: __DIR__, levels: 2) . '/templates/');
$this->router = new Router(template: $this->template);
$this->userRepository = new UserRepository(databaseConnection: $this->databaseConnection);
$this->securityController = new SecurityController(template: $this->template, userRepository: $this->userRepository, router: $this->router);
if (empty($_SESSION['user_id'])) {
$this->user = new User(); // ANONYMOUS
} else {
$this->user = $this->userRepository->findByID(id: $_SESSION['user_id']);
}
$this->addressBook = new AddressBookController(template: $this->template, user: $this->user, userRepository: $this->userRepository, router: $this->router);
$this->addressBookAdmin = new AddressBookAdminController(template: $this->template, user: $this->user, userRepository: $this->userRepository, router: $this->router);
} }
public function get(string $className): object public function get(string $className): object
{ {
return match ($className) { return match ($className) {
'App\Controller\AddressBook' => $this->addressBook, 'App\Controller\AddressBookController' => $this->addressBook,
'App\Controller\AddressBookAdminController' => $this->addressBookAdmin,
'App\Controller\SecurityController' => $this->securityController,
'App\Service\Router' => $this->router, 'App\Service\Router' => $this->router,
//default => throw new Exception(message: "Missing class definition: $class") //default => throw new Exception(message: "Missing class definition: $class")
default => die("Missing class definition: $className") default => die("Missing class definition: $className")

View File

@ -16,17 +16,17 @@ use Closure;
/* /*
* A small router implementation for the address book demo. * A small router implementation for the address book demo.
* Currently it doesn't handle GET requests, as not needed. * Currently it doesn't handle GET requests, as not needed.
* But if I reuse the code in my bindApi I'll support GET as well. * But if I reuse the code in my bindApi I'll maybe support GET as well.
*/ */
class Router class Router
{ {
/* /*
* The easiest wy to differentiate between static and dynamic routes is using * The easiest way to differentiate between static and dynamic routes is using
* two arrays, no need to pollute the class Route with that information * two arrays, no need to pollute the class Route with that information
*/ */
private array $staticRoutes; private array $staticRoutes = [];
private array $dynamicRoutes; private array $dynamicRoutes = [];
public function __construct(private readonly Template $template) public function __construct(private readonly Template $template)
{ {
@ -44,13 +44,9 @@ class Router
$parameters = $matches[0]; $parameters = $matches[0];
// create regex for route: // create regex for route:
$regex = preg_replace(pattern: '/(?<={).+?(?=})/', replacement: '(.*?)', subject: $route); $regex = preg_replace(pattern: '/{.+?}/', replacement: '([a-zA-Z0-9]*)', subject: $route);
// code below is ugly, better match including the braces
$regex = str_replace(search: '{', replace: '', subject: $regex);
$regex = str_replace(search: '}', replace: '', subject: $regex);
$regex = '/^' . str_replace(search: "/", replace: '\\/', subject: $regex) . '$/i'; $regex = '/^' . str_replace(search: "/", replace: '\\/', subject: $regex) . '$/i';
$route = new Route(name: $name, route: $route, regEx: $regex, parameters: $parameters, callback: $callback); $route = new Route(name: $name, route: $route, regEx: $regex, parameters: $parameters, callback: $callback);
if ($parameters) { if ($parameters) {
@ -61,7 +57,7 @@ class Router
} }
/* /*
* Check if there is a known route and executes the callback. * Checks if there is a known route and executes the callback.
*/ */
public function handleRouting(): void public function handleRouting(): void
{ {
@ -71,28 +67,56 @@ class Router
* Static routes have preference over dynamic ones, so * Static routes have preference over dynamic ones, so
* /admin/user/add to add and * /admin/user/add to add and
* /admin/user/{name} to edit is possible. * /admin/user/{name} to edit is possible.
* A user named "add" of course not :)
*
* But who wants to call their users "add" or "delete"?
* That's as weird as Little Bobby Tables (https://xkcd.com/327/)
*/ */
foreach ($this->staticRoutes as $route) { foreach ($this->staticRoutes as $route) {
if (preg_match(pattern: $route->getRegex(), subject: $requestUri, matches: $matches)) { if (preg_match(pattern: $route->getRegex(), subject: $requestUri, matches: $matches)) {
call_user_func(callback: $route->getCallback()); call_user_func(callback: $route->getCallback());
// We've found our route, bail out.
return; return;
} }
} }
foreach ($this->dynamicRoutes as $route) { foreach ($this->dynamicRoutes as $route) {
$parameters = [];
if (preg_match(pattern: $route->getRegex(), subject: $requestUri, matches: $matches)) { if (preg_match(pattern: $route->getRegex(), subject: $requestUri, matches: $matches)) {
$parameters = [];
foreach ($route->getParameters() as $id => $parameter) { foreach ($route->getParameters() as $id => $parameter) {
$parameters[$parameter] = $matches[$id + 1]; $parameters[$parameter] = $matches[$id + 1];
} }
// PHP is mad about named parameters in call_user_func // PHP is mad about named parameters in call_user_func when adding parameters.
// Uncaught Error: Unknown named parameter $args in // Uncaught Error: Unknown named parameter $args in <sourceFile>
// But PHPStorm seems happy without them. So what? // But PHPStorm seems happy without them. So what?
call_user_func($route->getCallback(), $parameters); call_user_func($route->getCallback(), $parameters);
return; return;
} }
} }
// if no route is matched, throw a 404
$this->template->render(templateName: 'status/404.html.php'); $this->template->render(templateName: 'status/404.html.php');
} }
public function path(string $routeName, array $vars = [])
{
foreach (array_merge($this->dynamicRoutes, $this->staticRoutes) as $route) {
if ($route->getName() == $routeName) {
if ($vars) {
// build route
$route = $route->getRoute();
// replace placeholder with current values
foreach ($vars as $key => $value) {
$route = str_replace(search: '{' . $key . '}', replace: $value, subject: $route);
}
return $route;
} else {
return $route->getRoute();
}
}
}
// no 404, this is reached only if the code is wrong
die("Missing Route: $routeName");
}
} }

View File

@ -1,6 +1,6 @@
<?php include dirname(path: __DIR__) . '/_header.html.php'; ?> <?php include dirname(path: __DIR__) . '/_header.html.php'; ?>
<h1>Address Book - Admin</h1> <h1>Address Book - Admin</h1>
<a href="/admin/users">👱Users</a> <a href="<?= $router->path('app_admin_users') ?>">👱Users</a>
<?php include dirname(path: __DIR__) . '/_footer.html.php' ?> <?php include dirname(path: __DIR__) . '/_footer.html.php' ?>

View File

@ -7,7 +7,7 @@
<th>First</th> <th>First</th>
<th>Last</th> <th>Last</th>
<th>Is Admin</th> <th>Is Admin</th>
<th>&nbsp;</th> <th colspan="2">&nbsp;</th>
</tr> </tr>
<?php foreach ($users as $userLine): ?> <?php foreach ($users as $userLine): ?>
<tr> <tr>
@ -22,11 +22,14 @@
<?php endif; ?> <?php endif; ?>
</td> </td>
<td> <td>
<a href="/admin/users/<?= $userLine->getNick(); ?>">edit</a> <a href="<?= $router->path('app_admin_users_edit', vars: ['nick' => $userLine->getNick()]); ?>">edit</a>
</td>
<td>
<a href="<?= $router->path('app_admin_users_delete', vars: ['nick' => $userLine->getNick()]); ?>">delete</a>
</td> </td>
</tr> </tr>
<?php endforeach; ?> <?php endforeach; ?>
</table> </table>
<a href="/admin/users/add">Add User</a> <a href="<?= $router->path('app_admin_users_add'); ?>">Add User</a>
<?php include dirname(path: __DIR__) . '/_footer.html.php' ?> <?php include dirname(path: __DIR__) . '/_footer.html.php' ?>

View File

@ -5,8 +5,8 @@
<input type="text" name="nick" id="nick" required> <input type="text" name="nick" id="nick" required>
<br> <br>
<label for="password">Password</label> <label for="new_password">Password</label>
<input type="password" name="password" id="password" required> <input type="password" name="new_password" id="new_password" required>
<br> <br>
<label for="first">First</label> <label for="first">First</label>
@ -17,7 +17,7 @@
<input type="text" name="last" id="last" required> <input type="text" name="last" id="last" required>
<br> <br>
<label for="is-admin">Is Admin</label> <label for="is_admin">Is Admin</label>
<input type="checkbox" name="is_admin" id="is_admin"> <input type="checkbox" name="is_admin" id="is_admin">
<br> <br>

View File

@ -7,8 +7,8 @@
<input type="text" name="nick" id="nick" value="<?= $editUser->getNick() ?>" required> <input type="text" name="nick" id="nick" value="<?= $editUser->getNick() ?>" required>
<br> <br>
<label for="password">Password (leave empty to keep the current one)</label> <label for="new_password">Password (leave empty to keep the current one)</label>
<input type="password" name="password" id="password"> <input type="password" name="new_password" id="new_password">
<br> <br>
<label for="first">First</label> <label for="first">First</label>
@ -19,7 +19,7 @@
<input type="text" name="last" id="last" value="<?= $editUser->getLast() ?>" required> <input type="text" name="last" id="last" value="<?= $editUser->getLast() ?>" required>
<br> <br>
<label for="is-admin">Is Admin</label> <label for="is_admin">Is Admin</label>
<input type="checkbox" name="is_admin" id="is_admin" <?= $editUser->isAdmin()?'checked':'' ?>> <input type="checkbox" name="is_admin" id="is_admin" <?= $editUser->isAdmin()?'checked':'' ?>>
<br> <br>