Add incredibly simple authentication to the app.
TODO: Manage feeds
This commit is contained in:
parent
49ac6f34bc
commit
2e07b29941
@ -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(
|
||||
|
@ -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();
|
||||
|
37
src/Controllers/LoginController.php
Normal file
37
src/Controllers/LoginController.php
Normal file
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace Lewisdale\App\Controllers;
|
||||
|
||||
use Lewisdale\App\Models\Repositories\UserRepository;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Slim\Views\Twig;
|
||||
|
||||
class LoginController {
|
||||
function __construct(
|
||||
private readonly Twig $view,
|
||||
private readonly UserRepository $users,
|
||||
)
|
||||
{
|
||||
}
|
||||
|
||||
public function index(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface {
|
||||
return $this->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');
|
||||
}
|
||||
}
|
@ -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')]
|
||||
|
41
src/Models/Repositories/UserRepository.php
Normal file
41
src/Models/Repositories/UserRepository.php
Normal file
@ -0,0 +1,41 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace Lewisdale\App\Models\Repositories;
|
||||
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
use Lewisdale\App\Models\Data\User;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use function DI\string;
|
||||
|
||||
/** @extends EntityRepository<User> */
|
||||
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();
|
||||
}
|
||||
}
|
37
src/Session/LoginMiddleware.php
Normal file
37
src/Session/LoginMiddleware.php
Normal file
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace Lewisdale\App\Session;
|
||||
|
||||
use Lewisdale\App\Models\Repositories\UserRepository;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\MiddlewareInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
use Slim\Psr7\Response;
|
||||
|
||||
class LoginMiddleware implements MiddlewareInterface {
|
||||
|
||||
function __construct(
|
||||
private readonly UserRepository $users,
|
||||
) {}
|
||||
|
||||
private function redirectToLogin() {
|
||||
$response = new Response(302);
|
||||
return $response->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));
|
||||
}
|
||||
}
|
48
src/Tools/Console/CreateUser.php
Normal file
48
src/Tools/Console/CreateUser.php
Normal file
@ -0,0 +1,48 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace Lewisdale\App\Tools\Console;
|
||||
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use Doctrine\ORM\Tools\Console\Command\SchemaTool\AbstractCommand;
|
||||
use Lewisdale\App\Models\Data\User;
|
||||
use Lewisdale\App\Models\Repositories\FeedRepository;
|
||||
use Lewisdale\App\Models\Repositories\UserRepository;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
#[AsCommand(name: 'user:create', description: 'Create a user')]
|
||||
class CreateUser extends Command {
|
||||
public function __construct(private readonly EntityManager $em,
|
||||
private readonly UserRepository $userRepository)
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function configure(): void {
|
||||
$this->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('<error>User with email ' . $email . ' already exists.</error>');
|
||||
return 1;
|
||||
}
|
||||
|
||||
$user = new User('', $email);
|
||||
$user->updatePassword($password);
|
||||
|
||||
$this->userRepository->save($user);
|
||||
$output->writeln("<info>User $email created!</info>");
|
||||
return 0;
|
||||
}
|
||||
}
|
11
src/app.php
11
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();
|
16
views/login/index.twig.html
Normal file
16
views/login/index.twig.html
Normal file
@ -0,0 +1,16 @@
|
||||
<h1>Login</h1>
|
||||
|
||||
<form method="post">
|
||||
{{ csrf() | raw }}
|
||||
|
||||
{% if error %}
|
||||
<div class="alert alert-danger">{{ error }}</div>
|
||||
{% endif %}
|
||||
<label for="email">Email address</label>
|
||||
<input id="email" type="email" name="email" placeholder="Email" required />
|
||||
|
||||
<label for="password">Password</label>
|
||||
<input id="password" type="password" name="password" placeholder="Password" required minlength="8" />
|
||||
|
||||
<button type="submit">Login</button>
|
||||
</form>
|
Loading…
Reference in New Issue
Block a user