From ea89d6b7ec1616d3cd6e899f8d566e804c73ad74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Melaine=20G=C3=A9rard?= Date: Mon, 18 Nov 2024 13:54:39 +0100 Subject: [PATCH] :sparkles: Add auth --- .gitignore | 5 + assets/app.js | 9 +- assets/controllers.json | 2 +- assets/controllers/.gitkeep | 0 assets/controllers/hello_controller.js | 16 ---- assets/styles/app.css | 3 - config/packages/security.yaml | 16 +++- migrations/Version20241118124508.php | 48 ++++++++++ src/Command/CreateUserCommand.php | 49 ++++++++++ src/Controller/HomeController.php | 18 ++++ src/Controller/SecurityController.php | 32 +++++++ src/Entity/AppUser.php | 123 +++++++++++++++++++++++++ src/Repository/AppUserRepository.php | 60 ++++++++++++ templates/home/index.html.twig | 20 ++++ templates/security/login.html.twig | 41 +++++++++ 15 files changed, 412 insertions(+), 30 deletions(-) create mode 100644 assets/controllers/.gitkeep delete mode 100644 assets/controllers/hello_controller.js create mode 100644 migrations/Version20241118124508.php create mode 100644 src/Command/CreateUserCommand.php create mode 100644 src/Controller/HomeController.php create mode 100644 src/Controller/SecurityController.php create mode 100644 src/Entity/AppUser.php create mode 100644 src/Repository/AppUserRepository.php create mode 100644 templates/home/index.html.twig create mode 100644 templates/security/login.html.twig diff --git a/.gitignore b/.gitignore index 4daae38..77b0b30 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,8 @@ /public/assets/ /assets/vendor/ ###< symfony/asset-mapper ### + +.idea/ +.vscode/ +.fleet/ +*.iml \ No newline at end of file diff --git a/assets/app.js b/assets/app.js index 8725cc5..ab8041c 100644 --- a/assets/app.js +++ b/assets/app.js @@ -1,10 +1,3 @@ import './bootstrap.js'; -/* - * Welcome to your app's main JavaScript file! - * - * This file will be included onto the page via the importmap() Twig function, - * which should already be in your base.html.twig. - */ -import './styles/app.css'; -console.log('This log comes from assets/app.js - welcome to AssetMapper! 🎉'); +import './styles/app.css'; diff --git a/assets/controllers.json b/assets/controllers.json index 29ea244..2230c71 100644 --- a/assets/controllers.json +++ b/assets/controllers.json @@ -2,7 +2,7 @@ "controllers": { "@symfony/ux-turbo": { "turbo-core": { - "enabled": true, + "enabled": false, "fetch": "eager" }, "mercure-turbo-stream": { diff --git a/assets/controllers/.gitkeep b/assets/controllers/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/assets/controllers/hello_controller.js b/assets/controllers/hello_controller.js deleted file mode 100644 index e847027..0000000 --- a/assets/controllers/hello_controller.js +++ /dev/null @@ -1,16 +0,0 @@ -import { Controller } from '@hotwired/stimulus'; - -/* - * This is an example Stimulus controller! - * - * Any element with a data-controller="hello" attribute will cause - * this controller to be executed. The name "hello" comes from the filename: - * hello_controller.js -> "hello" - * - * Delete this file or adapt it for your use! - */ -export default class extends Controller { - connect() { - this.element.textContent = 'Hello Stimulus! Edit me in assets/controllers/hello_controller.js'; - } -} diff --git a/assets/styles/app.css b/assets/styles/app.css index dd6181a..e69de29 100644 --- a/assets/styles/app.css +++ b/assets/styles/app.css @@ -1,3 +0,0 @@ -body { - background-color: skyblue; -} diff --git a/config/packages/security.yaml b/config/packages/security.yaml index 367af25..1722f26 100644 --- a/config/packages/security.yaml +++ b/config/packages/security.yaml @@ -4,14 +4,26 @@ security: Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto' # https://symfony.com/doc/current/security.html#loading-the-user-the-user-provider providers: - users_in_memory: { memory: null } + # used to reload user from session & other features (e.g. switch_user) + app_user_provider: + entity: + class: App\Entity\AppUser + property: email firewalls: dev: pattern: ^/(_(profiler|wdt)|css|images|js)/ security: false main: lazy: true - provider: users_in_memory + provider: app_user_provider + form_login: + login_path: app_login + check_path: app_login + enable_csrf: true + logout: + path: app_logout + # where to redirect after logout + # target: app_any_route # activate different ways to authenticate # https://symfony.com/doc/current/security.html#the-firewall diff --git a/migrations/Version20241118124508.php b/migrations/Version20241118124508.php new file mode 100644 index 0000000..1918c2d --- /dev/null +++ b/migrations/Version20241118124508.php @@ -0,0 +1,48 @@ +addSql('CREATE TABLE app_user (id SERIAL NOT NULL, email VARCHAR(180) NOT NULL, roles JSON NOT NULL, password VARCHAR(255) NOT NULL, username VARCHAR(255) NOT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_IDENTIFIER_EMAIL ON app_user (email)'); + $this->addSql('CREATE TABLE messenger_messages (id BIGSERIAL NOT NULL, body TEXT NOT NULL, headers TEXT NOT NULL, queue_name VARCHAR(190) NOT NULL, created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, available_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, delivered_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_75EA56E0FB7336F0 ON messenger_messages (queue_name)'); + $this->addSql('CREATE INDEX IDX_75EA56E0E3BD61CE ON messenger_messages (available_at)'); + $this->addSql('CREATE INDEX IDX_75EA56E016BA31DB ON messenger_messages (delivered_at)'); + $this->addSql('COMMENT ON COLUMN messenger_messages.created_at IS \'(DC2Type:datetime_immutable)\''); + $this->addSql('COMMENT ON COLUMN messenger_messages.available_at IS \'(DC2Type:datetime_immutable)\''); + $this->addSql('COMMENT ON COLUMN messenger_messages.delivered_at IS \'(DC2Type:datetime_immutable)\''); + $this->addSql('CREATE OR REPLACE FUNCTION notify_messenger_messages() RETURNS TRIGGER AS $$ + BEGIN + PERFORM pg_notify(\'messenger_messages\', NEW.queue_name::text); + RETURN NEW; + END; + $$ LANGUAGE plpgsql;'); + $this->addSql('DROP TRIGGER IF EXISTS notify_trigger ON messenger_messages;'); + $this->addSql('CREATE TRIGGER notify_trigger AFTER INSERT OR UPDATE ON messenger_messages FOR EACH ROW EXECUTE PROCEDURE notify_messenger_messages();'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('CREATE SCHEMA public'); + $this->addSql('DROP TABLE app_user'); + $this->addSql('DROP TABLE messenger_messages'); + } +} diff --git a/src/Command/CreateUserCommand.php b/src/Command/CreateUserCommand.php new file mode 100644 index 0000000..858ebfe --- /dev/null +++ b/src/Command/CreateUserCommand.php @@ -0,0 +1,49 @@ +ask("Username"); + $email = $io->ask("Email"); + $password = $io->askHidden("Password"); + + $user = new AppUser(); + $user->setUsername($username); + $user->setEmail($email); + $user->setPassword($this->passwordHasher->hashPassword($user, $password)); + + $user->setRoles(["ROLE_ADMIN"]); + + $this->entityManager->persist($user); + $this->entityManager->flush(); + + $io->success("User created successfully"); + + return Command::SUCCESS; + } +} diff --git a/src/Controller/HomeController.php b/src/Controller/HomeController.php new file mode 100644 index 0000000..a6eeda2 --- /dev/null +++ b/src/Controller/HomeController.php @@ -0,0 +1,18 @@ +render('home/index.html.twig', [ + 'controller_name' => 'HomeController', + ]); + } +} diff --git a/src/Controller/SecurityController.php b/src/Controller/SecurityController.php new file mode 100644 index 0000000..76bf5c4 --- /dev/null +++ b/src/Controller/SecurityController.php @@ -0,0 +1,32 @@ +getLastAuthenticationError(); + + // last username entered by the user + $lastUsername = $authenticationUtils->getLastUsername(); + + return $this->render('security/login.html.twig', [ + 'last_username' => $lastUsername, + 'error' => $error, + ]); + } + + #[Route(path: '/logout', name: 'app_logout')] + public function logout(): void + { + throw new \LogicException('This method can be blank - it will be intercepted by the logout key on your firewall.'); + } +} diff --git a/src/Entity/AppUser.php b/src/Entity/AppUser.php new file mode 100644 index 0000000..89ae5f2 --- /dev/null +++ b/src/Entity/AppUser.php @@ -0,0 +1,123 @@ + The user roles + */ + #[ORM\Column] + private array $roles = []; + + /** + * @var string The hashed password + */ + #[ORM\Column] + private ?string $password = null; + + #[ORM\Column(length: 255)] + private ?string $username = null; + + public function getId(): ?int + { + return $this->id; + } + + public function getEmail(): ?string + { + return $this->email; + } + + public function setEmail(string $email): static + { + $this->email = $email; + + return $this; + } + + /** + * A visual identifier that represents this user. + * + * @see UserInterface + */ + public function getUserIdentifier(): string + { + return (string) $this->email; + } + + /** + * @see UserInterface + * + * @return list + */ + public function getRoles(): array + { + $roles = $this->roles; + // guarantee every user at least has ROLE_USER + $roles[] = 'ROLE_USER'; + + return array_unique($roles); + } + + /** + * @param list $roles + */ + public function setRoles(array $roles): static + { + $this->roles = $roles; + + return $this; + } + + /** + * @see PasswordAuthenticatedUserInterface + */ + public function getPassword(): ?string + { + return $this->password; + } + + public function setPassword(string $password): static + { + $this->password = $password; + + return $this; + } + + /** + * @see UserInterface + */ + public function eraseCredentials(): void + { + // If you store any temporary, sensitive data on the user, clear it here + // $this->plainPassword = null; + } + + public function getUsername(): ?string + { + return $this->username; + } + + public function setUsername(string $username): static + { + $this->username = $username; + + return $this; + } +} diff --git a/src/Repository/AppUserRepository.php b/src/Repository/AppUserRepository.php new file mode 100644 index 0000000..190e80a --- /dev/null +++ b/src/Repository/AppUserRepository.php @@ -0,0 +1,60 @@ + + */ +class AppUserRepository extends ServiceEntityRepository implements PasswordUpgraderInterface +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, AppUser::class); + } + + /** + * Used to upgrade (rehash) the user's password automatically over time. + */ + public function upgradePassword(PasswordAuthenticatedUserInterface $user, string $newHashedPassword): void + { + if (!$user instanceof AppUser) { + throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', $user::class)); + } + + $user->setPassword($newHashedPassword); + $this->getEntityManager()->persist($user); + $this->getEntityManager()->flush(); + } + + // /** + // * @return AppUser[] Returns an array of AppUser objects + // */ + // public function findByExampleField($value): array + // { + // return $this->createQueryBuilder('a') + // ->andWhere('a.exampleField = :val') + // ->setParameter('val', $value) + // ->orderBy('a.id', 'ASC') + // ->setMaxResults(10) + // ->getQuery() + // ->getResult() + // ; + // } + + // public function findOneBySomeField($value): ?AppUser + // { + // return $this->createQueryBuilder('a') + // ->andWhere('a.exampleField = :val') + // ->setParameter('val', $value) + // ->getQuery() + // ->getOneOrNullResult() + // ; + // } +} diff --git a/templates/home/index.html.twig b/templates/home/index.html.twig new file mode 100644 index 0000000..4ceac06 --- /dev/null +++ b/templates/home/index.html.twig @@ -0,0 +1,20 @@ +{% extends 'base.html.twig' %} + +{% block title %}Hello HomeController!{% endblock %} + +{% block body %} + + +
+

Hello {{ controller_name }}! ✅

+ + This friendly message is coming from: +
    +
  • Your controller at /home/melaine.gerard/dev/perso/gachamelia-admin/src/Controller/HomeController.php
  • +
  • Your template at /home/melaine.gerard/dev/perso/gachamelia-admin/templates/home/index.html.twig
  • +
+
+{% endblock %} diff --git a/templates/security/login.html.twig b/templates/security/login.html.twig new file mode 100644 index 0000000..07b5cd4 --- /dev/null +++ b/templates/security/login.html.twig @@ -0,0 +1,41 @@ +{% extends 'base.html.twig' %} + +{% block title %}Log in!{% endblock %} + +{% block body %} +
+ {% if error %} +
{{ error.messageKey|trans(error.messageData, 'security') }}
+ {% endif %} + + {% if app.user %} +
+ You are logged in as {{ app.user.userIdentifier }}, Logout +
+ {% endif %} + +

Please sign in

+ + + + + + + + {# + Uncomment this section and add a remember_me option below your firewall to activate remember me functionality. + See https://symfony.com/doc/current/security/remember_me.html + +
+ + +
+ #} + + +
+{% endblock %}