From 2bb7ea54a23f143277f6afafe3456da98355b197 Mon Sep 17 00:00:00 2001 From: binaryDiv Date: Mon, 26 Jul 2021 23:14:14 +0200 Subject: [PATCH 1/3] Add MariaDB container; add PDO connection; add error middleware --- .env.develop | 20 ++++++++++++++++++-- docker-compose.yml | 15 +++++++++++++++ public/index.php | 2 +- src/Dependencies.php | 22 ++++++++++++++++++++-- src/Middlewares.php | 5 ++++- src/Settings.php | 21 ++++++++++++++++++--- 6 files changed, 76 insertions(+), 9 deletions(-) diff --git a/.env.develop b/.env.develop index e6cfe04..968fd4b 100644 --- a/.env.develop +++ b/.env.develop @@ -1,7 +1,23 @@ # .env.development: Environment variables for local development -# Set composer cache directory +# composer: Set cache directory COMPOSER_CACHE_DIR=./.composer -# Disable Twig cache +# MariaDB container +MYSQL_RANDOM_ROOT_PASSWORD=yes +MYSQL_DATABASE=mailusers +MYSQL_USER=mailaccountadmin +MYSQL_PASSWORD=mailaccountadmin + +# App settings +APP_DEBUG=true + +# - Disable Twig cache TWIG_CACHE_DIR= +TWIG_STRICT=true + +# - Database credentials +DB_HOST=db +DB_DATABASE=mailusers +DB_USER=mailaccountadmin +DB_PASSWORD=mailaccountadmin diff --git a/docker-compose.yml b/docker-compose.yml index f91cc48..f59e2d4 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -12,3 +12,18 @@ services: volumes: - ./:/var/www/ user: ${DOCKER_UID} + + db: + image: mariadb + env_file: + - .env.develop + volumes: + - db_data:/var/lib/mysql + + adminer: + image: adminer + ports: + - 8099:8080 + +volumes: + db_data: diff --git a/public/index.php b/public/index.php index 34eb697..60dd1c2 100644 --- a/public/index.php +++ b/public/index.php @@ -13,7 +13,7 @@ $settings = new Settings(); $container = Dependencies::createContainer($settings); $app = AppFactory::createFromContainer($container); -Middlewares::setMiddlewares($app); +Middlewares::setMiddlewares($app, $settings); Routes::setRoutes($app); $app->run(); diff --git a/src/Dependencies.php b/src/Dependencies.php index d82b70a..f0b2c05 100644 --- a/src/Dependencies.php +++ b/src/Dependencies.php @@ -5,14 +5,16 @@ namespace MailAccountAdmin; use DI\Container; use MailAccountAdmin\Login\LoginController; +use PDO; use Psr\Container\ContainerInterface; use Slim\Views\Twig; class Dependencies { - private const SETTINGS = 'settings'; - private const TWIG = 'view'; + public const SETTINGS = 'settings'; + public const TWIG = 'view'; private const TWIG_TEMPLATE_DIR = __DIR__ . '/../templates'; + public const DATABASE = 'database'; public static function createContainer(Settings $settings): Container { @@ -29,6 +31,22 @@ class Dependencies return Twig::create(self::TWIG_TEMPLATE_DIR, $settings->getTwigSettings()); }); + // Database connection + $container->set(self::DATABASE, function (ContainerInterface $c) { + /** @var Settings $settings */ + $settings = $c->get(self::SETTINGS); + $dbSettings = $settings->getDatabaseSettings(); + + return new PDO( + "mysql:dbname={$dbSettings['dbname']};host={$dbSettings['host']};port={$dbSettings['port']}", + $dbSettings['username'], + $dbSettings['password'], + [ + PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, + ], + ); + }); + // Login, registration, authentication $container->set(LoginController::class, function (ContainerInterface $c) { return new LoginController( diff --git a/src/Middlewares.php b/src/Middlewares.php index cbadfc7..2ed0307 100644 --- a/src/Middlewares.php +++ b/src/Middlewares.php @@ -8,8 +8,11 @@ use Slim\Views\TwigMiddleware; class Middlewares { - public static function setMiddlewares(App $app): void + public static function setMiddlewares(App $app, Settings $settings): void { + $displayErrorDetails = $settings->isDebugMode(); + + $app->addErrorMiddleware($displayErrorDetails, true, true); $app->add(TwigMiddleware::createFromContainer($app)); } } diff --git a/src/Settings.php b/src/Settings.php index b36160f..af0e385 100644 --- a/src/Settings.php +++ b/src/Settings.php @@ -5,12 +5,27 @@ namespace MailAccountAdmin; class Settings { + public function isDebugMode(): bool + { + return getenv('APP_DEBUG') === 'true'; + } + public function getTwigSettings(): array { - $cacheDir = getenv('TWIG_CACHE_DIR'); - return [ - 'cache' => !empty($cacheDir) ? $cacheDir : false, + 'cache' => getenv('TWIG_CACHE_DIR') ?: false, + 'strict_variables' => getenv('TWIG_STRICT') === 'true', + ]; + } + + public function getDatabaseSettings(): array + { + return [ + 'host' => getenv('DB_HOST') ?: 'localhost', + 'port' => getenv('DB_PORT') ?: 3306, + 'dbname' => getenv('DB_DATABASE') ?: '', + 'username' => getenv('DB_USERNAME') ?: '', + 'password' => getenv('DB_PASSWORD') ?: '', ]; } } From 8be7988d9756250d4baa4bafd86dfdfa53575f5c Mon Sep 17 00:00:00 2001 From: binaryDiv Date: Mon, 26 Jul 2021 23:52:59 +0200 Subject: [PATCH 2/3] Implement basic login function; add basic styling --- public/index.php | 2 + public/static/style.css | 50 +++++++++++++++ src/Auth/AuthMiddleware.php | 35 +++++++++++ src/Dependencies.php | 12 +++- .../Dashboard/DashboardController.php} | 9 +-- src/Frontend/Login/LoginController.php | 63 +++++++++++++++++++ src/Middlewares.php | 2 + src/Routes.php | 11 +++- templates/base.html.twig | 23 +++++++ templates/dashboard.html.twig | 11 ++++ templates/login.html | 19 ------ templates/login.html.twig | 42 +++++++++++++ 12 files changed, 252 insertions(+), 27 deletions(-) create mode 100644 public/static/style.css create mode 100644 src/Auth/AuthMiddleware.php rename src/{Login/LoginController.php => Frontend/Dashboard/DashboardController.php} (57%) create mode 100644 src/Frontend/Login/LoginController.php create mode 100644 templates/base.html.twig create mode 100644 templates/dashboard.html.twig delete mode 100644 templates/login.html create mode 100644 templates/login.html.twig diff --git a/public/index.php b/public/index.php index 60dd1c2..37641db 100644 --- a/public/index.php +++ b/public/index.php @@ -9,6 +9,8 @@ use MailAccountAdmin\Routes; use MailAccountAdmin\Settings; use Slim\Factory\AppFactory; +session_start(); + $settings = new Settings(); $container = Dependencies::createContainer($settings); $app = AppFactory::createFromContainer($container); diff --git a/public/static/style.css b/public/static/style.css new file mode 100644 index 0000000..6db3384 --- /dev/null +++ b/public/static/style.css @@ -0,0 +1,50 @@ +html, body { + max-width: 100%; + margin: 0; + padding: 0; +} + +* { + box-sizing: border-box; +} + +body { + font-family: sans-serif; +} + +/* --- Header --- */ +header { + margin: 1em; +} + +header h1 { + margin: 1em; +} + +/* --- Login page --- */ +main.login_page { + margin: 2em; + padding: 1em; + border: 1px gray solid; + width: auto; +} + +main.login_page h2 { + margin: 0 0 0.5em 0; +} + +main.login_page table td { + padding: 0.2em; +} + +/* --- Text and other styling --- */ +.error { + background: #ff4444; + width: 30em; + margin: 1em 0; + padding: 1em; +} + +button { + padding: 0.2em 1em; +} diff --git a/src/Auth/AuthMiddleware.php b/src/Auth/AuthMiddleware.php new file mode 100644 index 0000000..2a8d7eb --- /dev/null +++ b/src/Auth/AuthMiddleware.php @@ -0,0 +1,35 @@ +getUri(); + + // TODO: Lots of stuff. Session middleware, auth handler class, etc... + if ($uri->getPath() !== '/login') { + // Check authorization via session + if (empty($_SESSION['username'])) { + // Not logged in -> Redirect to /login + $response = new Response(); + return $response + ->withHeader('Location', '/login') + ->withStatus(303); + } + } + + return $handler->handle($request); + } +} diff --git a/src/Dependencies.php b/src/Dependencies.php index f0b2c05..cd8d3e3 100644 --- a/src/Dependencies.php +++ b/src/Dependencies.php @@ -4,7 +4,8 @@ declare(strict_types=1); namespace MailAccountAdmin; use DI\Container; -use MailAccountAdmin\Login\LoginController; +use MailAccountAdmin\Frontend\Login\LoginController; +use MailAccountAdmin\Frontend\Dashboard\DashboardController; use PDO; use Psr\Container\ContainerInterface; use Slim\Views\Twig; @@ -47,13 +48,20 @@ class Dependencies ); }); - // Login, registration, authentication + // Login page $container->set(LoginController::class, function (ContainerInterface $c) { return new LoginController( $c->get(self::TWIG) ); }); + // Dashboard + $container->set(DashboardController::class, function (ContainerInterface $c) { + return new DashboardController( + $c->get(self::TWIG) + ); + }); + return $container; } } diff --git a/src/Login/LoginController.php b/src/Frontend/Dashboard/DashboardController.php similarity index 57% rename from src/Login/LoginController.php rename to src/Frontend/Dashboard/DashboardController.php index c0bc80c..bb7218b 100644 --- a/src/Login/LoginController.php +++ b/src/Frontend/Dashboard/DashboardController.php @@ -1,13 +1,13 @@ view = $view; } - public function showLoginPage(Request $request, Response $response): Response + public function showDashboard(Request $request, Response $response): Response { $renderData = [ + 'username' => $_SESSION['username'], ]; - return $this->view->render($response, 'login.html', $renderData); + return $this->view->render($response, 'dashboard.html.twig', $renderData); } } diff --git a/src/Frontend/Login/LoginController.php b/src/Frontend/Login/LoginController.php new file mode 100644 index 0000000..9935f63 --- /dev/null +++ b/src/Frontend/Login/LoginController.php @@ -0,0 +1,63 @@ +view = $view; + } + + public function showLoginPage(Request $request, Response $response): Response + { + if (!empty($_SESSION['username'])) { + // Already logged in, redirect to dashboard + return $response + ->withHeader('Location', '/') + ->withStatus(303); + } + + $renderData = [ + ]; + + return $this->view->render($response, 'login.html.twig', $renderData); + } + + public function authenticateUser(Request $request, Response $response): Response + { + $params = (array)$request->getParsedBody(); + + if (empty($params['username']) || empty($params['password'])) { + throw new HttpBadRequestException($request, 'Missing parameters'); + } + + // TODO: only for testing, obviously + if ($params['username'] === 'lexi' && $params['password'] === 'testpw') { + $_SESSION['username'] = $params['username']; + return $response + ->withHeader('Location', '/') + ->withStatus(303); + } else { + return $this->view->render($response, 'login.html.twig', ['error' => 'Wrong username or password!']); + } + } + + public function logoutUser(Request $request, Response $response): Response + { + session_destroy(); + + return $response + ->withHeader('Location', '/login') + ->withStatus(303); + } +} diff --git a/src/Middlewares.php b/src/Middlewares.php index 2ed0307..375e481 100644 --- a/src/Middlewares.php +++ b/src/Middlewares.php @@ -3,6 +3,7 @@ declare(strict_types=1); namespace MailAccountAdmin; +use MailAccountAdmin\Auth\AuthMiddleware; use Slim\App; use Slim\Views\TwigMiddleware; @@ -13,6 +14,7 @@ class Middlewares $displayErrorDetails = $settings->isDebugMode(); $app->addErrorMiddleware($displayErrorDetails, true, true); + $app->add(new AuthMiddleware()); $app->add(TwigMiddleware::createFromContainer($app)); } } diff --git a/src/Routes.php b/src/Routes.php index 9c7520a..a90372f 100644 --- a/src/Routes.php +++ b/src/Routes.php @@ -3,13 +3,20 @@ declare(strict_types=1); namespace MailAccountAdmin; -use MailAccountAdmin\Login\LoginController; +use MailAccountAdmin\Frontend\Dashboard\DashboardController; +use MailAccountAdmin\Frontend\Login\LoginController; use Slim\App; class Routes { public static function setRoutes(App $app): void { - $app->get('/', LoginController::class . ':showLoginPage'); + // Login + $app->get('/login', LoginController::class . ':showLoginPage'); + $app->post('/login', LoginController::class . ':authenticateUser'); + $app->get('/logout', LoginController::class . ':logoutUser'); + + // Dashboard + $app->get('/', DashboardController::class . ':showDashboard'); } } diff --git a/templates/base.html.twig b/templates/base.html.twig new file mode 100644 index 0000000..916fab1 --- /dev/null +++ b/templates/base.html.twig @@ -0,0 +1,23 @@ + + + + + {% block title %}Untitled page{% endblock %} - MailAccountAdmin + + + + + + +
+

MailAccountAdmin

+
+ +
+ {% block content %} + Nothing to see here... + {% endblock %} +
+ + + diff --git a/templates/dashboard.html.twig b/templates/dashboard.html.twig new file mode 100644 index 0000000..92cad0f --- /dev/null +++ b/templates/dashboard.html.twig @@ -0,0 +1,11 @@ +{% extends "base.html.twig" %} + +{% block title %}Dashboard{% endblock %} + +{% block content %} +

Dashboard

+ +

Hello, {{ username }}!

+ + Logout. +{% endblock %} diff --git a/templates/login.html b/templates/login.html deleted file mode 100644 index e46301d..0000000 --- a/templates/login.html +++ /dev/null @@ -1,19 +0,0 @@ - - - - - MailAccountAdmin - - - - - - -

MailAccountAdmin - Login

- -Hello. - - - - - diff --git a/templates/login.html.twig b/templates/login.html.twig new file mode 100644 index 0000000..b22a5b8 --- /dev/null +++ b/templates/login.html.twig @@ -0,0 +1,42 @@ + + + + + Login - MailAccountAdmin + + + + + + +
+

MailAccountAdmin

+
+ +
+

Login

+ +
+ {% if error is defined %} +
{{ error }}
+ {% endif %} + + + + + + + + + + + + + + +
+
+
+ + + From b24fe56c8ae6fc3e3f7a9b153fc4e5a6f3c7cb38 Mon Sep 17 00:00:00 2001 From: binaryDiv Date: Tue, 27 Jul 2021 01:25:03 +0200 Subject: [PATCH 3/3] Implement basic database access and login --- docker-compose.yml | 3 + sql/init_tables.sql | 50 +++++++++++++ src/Auth/AuthMiddleware.php | 1 + src/Common/UserHelper.php | 33 ++++++++ src/Dependencies.php | 23 +++++- src/Exceptions/AdminUserNotFoundException.php | 8 ++ src/Exceptions/AppException.php | 10 +++ src/Frontend/BaseController.php | 21 ++++++ .../Dashboard/DashboardController.php | 17 ++--- src/Frontend/Login/LoginController.php | 51 ++++++++----- src/Models/AdminUser.php | 75 +++++++++++++++++++ src/Repositories/AdminUserRepository.php | 35 +++++++++ src/Settings.php | 2 +- templates/dashboard.html.twig | 9 +++ 14 files changed, 307 insertions(+), 31 deletions(-) create mode 100644 sql/init_tables.sql create mode 100644 src/Common/UserHelper.php create mode 100644 src/Exceptions/AdminUserNotFoundException.php create mode 100644 src/Exceptions/AppException.php create mode 100644 src/Frontend/BaseController.php create mode 100644 src/Models/AdminUser.php create mode 100644 src/Repositories/AdminUserRepository.php diff --git a/docker-compose.yml b/docker-compose.yml index f59e2d4..f742930 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -17,8 +17,11 @@ services: image: mariadb env_file: - .env.develop + ports: + - 13306:3306 volumes: - db_data:/var/lib/mysql + - ./sql/init_tables.sql:/docker-entrypoint-initdb.d/init_tables.sql:ro adminer: image: adminer diff --git a/sql/init_tables.sql b/sql/init_tables.sql new file mode 100644 index 0000000..f45577d --- /dev/null +++ b/sql/init_tables.sql @@ -0,0 +1,50 @@ +-- Create tables + +SET NAMES utf8mb4; + +-- TODO create on prod +CREATE TABLE `admin_users` +( + `admin_id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `username` varchar(255) NOT NULL, + `password` varchar(255) NOT NULL, + `is_active` tinyint(3) unsigned NOT NULL DEFAULT 1, + `created_at` datetime NOT NULL DEFAULT NOW(), + `modified_at` datetime NOT NULL DEFAULT NOW(), + PRIMARY KEY (`admin_id`), + UNIQUE KEY `username` (`username`) +) DEFAULT CHARSET = utf8mb4; + +-- Create initial admin user for development (password is 'admin') +INSERT INTO `admin_users` + (`username`, `password`, `is_active`, `created_at`, `modified_at`) +VALUES ('admin', '$2y$10$zaNOBUk4PBlhDZD40h35CeyUxiqixi9LTrxlAxnrckXd95hcCctl6', '1', NOW(), NOW()); + +-- TODO rename on prod `users` -> `mail_users` +CREATE TABLE `mail_users` +( + `user_id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `username` varchar(255) NOT NULL, + `password` varchar(255) NOT NULL, + `is_active` tinyint(3) unsigned NOT NULL DEFAULT 1, + `home_dir` varchar(255) NOT NULL, + `memo` text DEFAULT NULL, + `created_at` datetime NOT NULL DEFAULT NOW(), + `modified_at` datetime NOT NULL DEFAULT NOW(), + PRIMARY KEY (`user_id`), + UNIQUE KEY `username` (`username`) +) DEFAULT CHARSET = utf8mb4; + +CREATE TABLE `mail_aliases` +( + `alias_id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `user_id` int(10) unsigned NOT NULL, + `mail_address` varchar(255) NOT NULL, + `created_at` datetime NOT NULL DEFAULT NOW(), + `modified_at` datetime NOT NULL DEFAULT NOW(), + PRIMARY KEY (`alias_id`), + UNIQUE KEY `mail_address` (`mail_address`), + KEY `user_id` (`user_id`), + -- TODO rename on prod `users` -> `mail_users` + CONSTRAINT `mail_aliases_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `mail_users` (`user_id`) +) DEFAULT CHARSET = utf8mb4; diff --git a/src/Auth/AuthMiddleware.php b/src/Auth/AuthMiddleware.php index 2a8d7eb..6738f6e 100644 --- a/src/Auth/AuthMiddleware.php +++ b/src/Auth/AuthMiddleware.php @@ -21,6 +21,7 @@ class AuthMiddleware implements MiddlewareInterface // TODO: Lots of stuff. Session middleware, auth handler class, etc... if ($uri->getPath() !== '/login') { // Check authorization via session + // TODO username or user ID? if (empty($_SESSION['username'])) { // Not logged in -> Redirect to /login $response = new Response(); diff --git a/src/Common/UserHelper.php b/src/Common/UserHelper.php new file mode 100644 index 0000000..e67f56b --- /dev/null +++ b/src/Common/UserHelper.php @@ -0,0 +1,33 @@ +adminUserRepository = $adminUserRepository; + } + + public function isLoggedIn(): bool + { + return !empty($_SESSION['username']); + } + + public function getCurrentUser(): AdminUser + { + $username = $_SESSION['username'] ?? null; + if (empty($username)) { + throw new RuntimeException('Not logged in!'); + } + return $this->adminUserRepository->getUserByName($username); + } +} diff --git a/src/Dependencies.php b/src/Dependencies.php index cd8d3e3..9f2e901 100644 --- a/src/Dependencies.php +++ b/src/Dependencies.php @@ -4,8 +4,10 @@ declare(strict_types=1); namespace MailAccountAdmin; use DI\Container; +use MailAccountAdmin\Common\UserHelper; use MailAccountAdmin\Frontend\Login\LoginController; use MailAccountAdmin\Frontend\Dashboard\DashboardController; +use MailAccountAdmin\Repositories\AdminUserRepository; use PDO; use Psr\Container\ContainerInterface; use Slim\Views\Twig; @@ -48,17 +50,34 @@ class Dependencies ); }); + // Repositories + $container->set(AdminUserRepository::class, function (ContainerInterface $c) { + return new AdminUserRepository( + $c->get(self::DATABASE), + ); + }); + + // Helper classes + $container->set(UserHelper::class, function (ContainerInterface $c) { + return new UserHelper( + $c->get(AdminUserRepository::class), + ); + }); + // Login page $container->set(LoginController::class, function (ContainerInterface $c) { return new LoginController( - $c->get(self::TWIG) + $c->get(self::TWIG), + $c->get(UserHelper::class), + $c->get(AdminUserRepository::class), ); }); // Dashboard $container->set(DashboardController::class, function (ContainerInterface $c) { return new DashboardController( - $c->get(self::TWIG) + $c->get(self::TWIG), + $c->get(UserHelper::class), ); }); diff --git a/src/Exceptions/AdminUserNotFoundException.php b/src/Exceptions/AdminUserNotFoundException.php new file mode 100644 index 0000000..413b5ff --- /dev/null +++ b/src/Exceptions/AdminUserNotFoundException.php @@ -0,0 +1,8 @@ +view = $view; + $this->userHelper = $userHelper; + } +} diff --git a/src/Frontend/Dashboard/DashboardController.php b/src/Frontend/Dashboard/DashboardController.php index bb7218b..7538686 100644 --- a/src/Frontend/Dashboard/DashboardController.php +++ b/src/Frontend/Dashboard/DashboardController.php @@ -3,24 +3,19 @@ declare(strict_types=1); namespace MailAccountAdmin\Frontend\Dashboard; +use MailAccountAdmin\Frontend\BaseController; use Psr\Http\Message\ResponseInterface as Response; use Psr\Http\Message\ServerRequestInterface as Request; -use Slim\Views\Twig; -class DashboardController +class DashboardController extends BaseController { - /** @var Twig */ - private $view; - - public function __construct(Twig $view) - { - $this->view = $view; - } - public function showDashboard(Request $request, Response $response): Response { + $currentUser = $this->userHelper->getCurrentUser(); + $renderData = [ - 'username' => $_SESSION['username'], + 'username' => $currentUser->getUsername(), + 'user' => $currentUser, ]; return $this->view->render($response, 'dashboard.html.twig', $renderData); diff --git a/src/Frontend/Login/LoginController.php b/src/Frontend/Login/LoginController.php index 9935f63..f486c44 100644 --- a/src/Frontend/Login/LoginController.php +++ b/src/Frontend/Login/LoginController.php @@ -3,52 +3,69 @@ declare(strict_types=1); namespace MailAccountAdmin\Frontend\Login; +use MailAccountAdmin\Common\UserHelper; +use MailAccountAdmin\Exceptions\AdminUserNotFoundException; +use MailAccountAdmin\Frontend\BaseController; +use MailAccountAdmin\Repositories\AdminUserRepository; use Psr\Http\Message\ResponseInterface as Response; use Psr\Http\Message\ServerRequestInterface as Request; -use Slim\Exception\HttpBadRequestException; use Slim\Views\Twig; -class LoginController +class LoginController extends BaseController { - /** @var Twig */ - private $view; + /** @var AdminUserRepository */ + private $adminUserRepository; - public function __construct(Twig $view) + public function __construct(Twig $view, UserHelper $userHelper, AdminUserRepository $adminUserRepository) { - $this->view = $view; + parent::__construct($view, $userHelper); + $this->adminUserRepository = $adminUserRepository; + } + + private function renderLoginPage(Response $response, array $renderData = []): Response + { + return $this->view->render($response, 'login.html.twig', $renderData); } public function showLoginPage(Request $request, Response $response): Response { - if (!empty($_SESSION['username'])) { + if ($this->userHelper->isLoggedIn()) { // Already logged in, redirect to dashboard return $response ->withHeader('Location', '/') ->withStatus(303); } - $renderData = [ - ]; - - return $this->view->render($response, 'login.html.twig', $renderData); + return $this->renderLoginPage($response); } public function authenticateUser(Request $request, Response $response): Response { $params = (array)$request->getParsedBody(); - if (empty($params['username']) || empty($params['password'])) { - throw new HttpBadRequestException($request, 'Missing parameters'); + if (empty($params['username'])) { + return $this->renderLoginPage($response, ['error' => 'Missing username!']); + } elseif (empty($params['password'])) { + return $this->renderLoginPage($response, ['error' => 'Missing password!']); } - // TODO: only for testing, obviously - if ($params['username'] === 'lexi' && $params['password'] === 'testpw') { - $_SESSION['username'] = $params['username']; + $loginUsername = $params['username']; + $loginPassword = $params['password']; + + try { + $user = $this->adminUserRepository->getUserByName($loginUsername); + } + catch (AdminUserNotFoundException $e) { + $user = null; + } + + if ($user !== null && password_verify($loginPassword, $user->getPasswordHash())) { + $_SESSION['username'] = $user->getUsername(); return $response ->withHeader('Location', '/') ->withStatus(303); } else { - return $this->view->render($response, 'login.html.twig', ['error' => 'Wrong username or password!']); + return $this->renderLoginPage($response, ['error' => 'Wrong username or password!']); } } diff --git a/src/Models/AdminUser.php b/src/Models/AdminUser.php new file mode 100644 index 0000000..cef9c7b --- /dev/null +++ b/src/Models/AdminUser.php @@ -0,0 +1,75 @@ +id = $id; + $this->username = $username; + $this->passwordHash = $passwordHash; + $this->active = $isActive; + $this->createdAt = $createdAt; + $this->modifiedAt = $modifiedAt; + } + + public static function createFromArray(array $data): self + { + return new self( + (int)$data['admin_id'], + $data['username'], + $data['password'], + $data['is_active'] === '1', + new DateTimeImmutable($data['created_at']), + new DateTimeImmutable($data['modified_at']), + ); + } + + public function getId(): int + { + return $this->id; + } + + public function getUsername(): string + { + return $this->username; + } + + public function getPasswordHash(): string + { + return $this->passwordHash; + } + + public function isActive(): bool + { + return $this->active; + } + + public function getCreatedAt(): DateTimeImmutable + { + return $this->createdAt; + } + + public function getModifiedAt(): DateTimeImmutable + { + return $this->modifiedAt; + } +} diff --git a/src/Repositories/AdminUserRepository.php b/src/Repositories/AdminUserRepository.php new file mode 100644 index 0000000..31e2a41 --- /dev/null +++ b/src/Repositories/AdminUserRepository.php @@ -0,0 +1,35 @@ +pdo = $pdo; + } + + /** + * @throws AdminUserNotFoundException + */ + public function getUserByName(string $username): AdminUser + { + $statement = $this->pdo->prepare('SELECT * FROM admin_users WHERE username = :username LIMIT 1'); + $statement->execute(['username' => $username]); + + if ($statement->rowCount() < 1) { + throw new AdminUserNotFoundException("Admin with username '$username' was not found."); + } + + $row = $statement->fetch(PDO::FETCH_ASSOC); + return AdminUser::createFromArray($row); + } +} diff --git a/src/Settings.php b/src/Settings.php index af0e385..ca111a0 100644 --- a/src/Settings.php +++ b/src/Settings.php @@ -24,7 +24,7 @@ class Settings 'host' => getenv('DB_HOST') ?: 'localhost', 'port' => getenv('DB_PORT') ?: 3306, 'dbname' => getenv('DB_DATABASE') ?: '', - 'username' => getenv('DB_USERNAME') ?: '', + 'username' => getenv('DB_USER') ?: '', 'password' => getenv('DB_PASSWORD') ?: '', ]; } diff --git a/templates/dashboard.html.twig b/templates/dashboard.html.twig index 92cad0f..cf4cb89 100644 --- a/templates/dashboard.html.twig +++ b/templates/dashboard.html.twig @@ -7,5 +7,14 @@

Hello, {{ username }}!

+
+        ID: {{ user.getId() }}
+        username: {{ user.getUsername() }}
+        password: {{ user.getPasswordHash() }}
+        is_active: {{ user.isActive() }}
+        created_at: {{ user.getCreatedAt() | date() }}
+        modified_at: {{ user.getModifiedAt() | date() }}
+    
+ Logout. {% endblock %}