From 2e07b2994146c39601f36c7bbc7dfad02da8709a Mon Sep 17 00:00:00 2001 From: Lewis Dale Date: Tue, 31 Dec 2024 16:17:13 +0000 Subject: [PATCH] Add incredibly simple authentication to the app. TODO: Manage feeds --- bin/doctrine | 3 +- src/Controllers/FeedController.php | 5 +++ src/Controllers/LoginController.php | 37 +++++++++++++++++ src/Models/Data/User.php | 2 +- src/Models/Repositories/UserRepository.php | 41 ++++++++++++++++++ src/Session/LoginMiddleware.php | 37 +++++++++++++++++ src/Tools/Console/CreateUser.php | 48 ++++++++++++++++++++++ src/app.php | 11 ++++- views/login/index.twig.html | 16 ++++++++ 9 files changed, 197 insertions(+), 3 deletions(-) create mode 100644 src/Controllers/LoginController.php create mode 100644 src/Models/Repositories/UserRepository.php create mode 100644 src/Session/LoginMiddleware.php create mode 100644 src/Tools/Console/CreateUser.php create mode 100644 views/login/index.twig.html diff --git a/bin/doctrine b/bin/doctrine index b9ea0d1..653b679 100644 --- a/bin/doctrine +++ b/bin/doctrine @@ -15,7 +15,8 @@ require_once __DIR__ . '/../src/dependencies.php'; global $container; $commands = [ - $container->get(TestFeed::class), + $container->get(TestFeed::class), + $container->get(\Lewisdale\App\Tools\Console\CreateUser::class), ]; ConsoleRunner::run( diff --git a/src/Controllers/FeedController.php b/src/Controllers/FeedController.php index 553816a..94b5867 100644 --- a/src/Controllers/FeedController.php +++ b/src/Controllers/FeedController.php @@ -30,6 +30,11 @@ class FeedController { $this->logger->info("FeedController::get_feed() called"); $feed = $this->feedRepository->find($request->getAttribute('id')); + + if (empty($feed)) { + return $response->withStatus(404); + } + $filtered = $feed->get_filtered_feed(); $body = $response->getBody(); diff --git a/src/Controllers/LoginController.php b/src/Controllers/LoginController.php new file mode 100644 index 0000000..bc71f4a --- /dev/null +++ b/src/Controllers/LoginController.php @@ -0,0 +1,37 @@ +view->render($response, 'login/index.twig.html', []); + } + + public function login(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface { + $body = $request->getParsedBody(); + $email = $body['email']; + $password = $body['password']; + + $user = $this->users->validateCredentials($email, $password); + + if (!$user) { + return $this->view->render($response, 'login/index.twig.html', ['error' => 'Invalid email or password']); + } + + $_SESSION['user'] = $user->id; + + return $response->withStatus(302)->withHeader('Location', '/feed'); + } +} \ No newline at end of file diff --git a/src/Models/Data/User.php b/src/Models/Data/User.php index c47748c..c4d8ae2 100644 --- a/src/Models/Data/User.php +++ b/src/Models/Data/User.php @@ -18,7 +18,7 @@ class User #[ORM\Id] #[ORM\Column(type: 'integer')] #[ORM\GeneratedValue] - private ?int $id; + public ?int $id; #[ORM\Column(type: 'string')] private string $password; #[ORM\Column(type: 'string')] diff --git a/src/Models/Repositories/UserRepository.php b/src/Models/Repositories/UserRepository.php new file mode 100644 index 0000000..70dd522 --- /dev/null +++ b/src/Models/Repositories/UserRepository.php @@ -0,0 +1,41 @@ + */ +class UserRepository extends EntityRepository { + private readonly LoggerInterface $logger; + + public function __construct(EntityManager $em, LoggerInterface $logger) + { + parent::__construct($em, $em->getClassMetadata(User::class)); + $this->logger = $logger; + } + + public function findByEmail(string $email): ?User { + return $this->findOneBy(['email' => $email]); + } + + public function validateCredentials(string $email, string $password): ?User { + $user = $this->findByEmail($email); + if (!$user || !$user->validate($password)) { + $this->logger->warning('Invalid credentials.', ['email' => $email, 'password' => $password, 'user' => $user, 'valid' => $user?->validate($password)]); + return null; + } + + return $user; + } + + public function save(User $user): void { + $this->_em->persist($user); + $this->_em->flush(); + } +} \ No newline at end of file diff --git a/src/Session/LoginMiddleware.php b/src/Session/LoginMiddleware.php new file mode 100644 index 0000000..d46b060 --- /dev/null +++ b/src/Session/LoginMiddleware.php @@ -0,0 +1,37 @@ +withHeader('Location', '/account/login'); + } + + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + if (!isset($_SESSION['user'])) { + return $this->redirectToLogin(); + } + + $user = $this->users->find($_SESSION['user']); + + if (!$user) { + return $this->redirectToLogin(); + } + + return $handler->handle($request->withAttribute('user', $user)); + } +} \ No newline at end of file diff --git a/src/Tools/Console/CreateUser.php b/src/Tools/Console/CreateUser.php new file mode 100644 index 0000000..a3d6279 --- /dev/null +++ b/src/Tools/Console/CreateUser.php @@ -0,0 +1,48 @@ +addArgument('email', InputArgument::REQUIRED, "The user's email address") + ->addArgument('password', InputArgument::REQUIRED, "The user's password"); + } + + protected function execute(InputInterface $input, OutputInterface $output): int { + $email = $input->getArgument('email'); + $password = $input->getArgument('password'); + + $user = $this->userRepository->findOneBy(['email' => $email]); + if ($user !== null) { + $output->writeln('User with email ' . $email . ' already exists.'); + return 1; + } + + $user = new User('', $email); + $user->updatePassword($password); + + $this->userRepository->save($user); + $output->writeln("User $email created!"); + return 0; + } +} \ No newline at end of file diff --git a/src/app.php b/src/app.php index f8366a8..6f2541c 100644 --- a/src/app.php +++ b/src/app.php @@ -2,9 +2,12 @@ use Lewisdale\App\Controllers\FeedController; use Lewisdale\App\Controllers\HomeController; +use Lewisdale\App\Controllers\LoginController; use Slim\Views\TwigMiddleware; ini_set('user_agent', 'Baleen/1.0 (https://baleen.lewisdale.dev)'); +ini_set('session.name', 'sessid'); +ini_set('session.cookie_samesite', 'Lax'); require_once __DIR__ . "/dependencies.php"; @@ -21,10 +24,16 @@ $app->add('csrf'); $app->get("/", [HomeController::class, 'get']); -$app->get('/feed', [FeedController::class, 'get']); +$app->get('/feed', [FeedController::class, 'get']) + ->add($container->get(\Lewisdale\App\Session\LoginMiddleware::class)); $app->get('/feed/{id}', [FeedController::class, 'get_feed']); +$app->group('/account', function (\Slim\Routing\RouteCollectorProxy $group) use ($app, $container) { + $group->get('/login', [LoginController::class, 'index']); + $group->post('/login', [LoginController::class, 'login']); +}); + $app->addErrorMiddleware(true, true, true); $app->run(); \ No newline at end of file diff --git a/views/login/index.twig.html b/views/login/index.twig.html new file mode 100644 index 0000000..ebae021 --- /dev/null +++ b/views/login/index.twig.html @@ -0,0 +1,16 @@ +

Login

+ +
+ {{ csrf() | raw }} + + {% if error %} +
{{ error }}
+ {% endif %} + + + + + + + +
\ No newline at end of file