From d81e450a0e420e4dcda3b41e08539ed211edb57c Mon Sep 17 00:00:00 2001 From: maurine Date: Wed, 18 Mar 2026 17:34:21 +0100 Subject: [PATCH] gestion de la connexion de l'utilisateur --- .env | 2 +- .gitignore | 5 + composer.json | 4 +- composer.lock | 228 ++++++++++++++++++++- config/bundles.php | 1 + config/packages/asset_mapper.yaml | 11 + config/packages/security.yaml | 14 +- config/packages/symfonycasts_tailwind.yaml | 6 + config/reference.php | 15 +- importmap.php | 28 +++ src/Controller/SecurityController.php | 47 ++++- src/Controller/UserController.php | 50 ++++- src/DataFixtures/AppFixtures.php | 32 ++- src/Entity/User.php | 20 +- symfony.lock | 27 +++ tailwind.config.js | 16 +- templates/base.html.twig | 18 +- templates/home/hello.html.twig | 28 +++ templates/security/login.html.twig | 55 +++-- 19 files changed, 534 insertions(+), 73 deletions(-) create mode 100644 config/packages/asset_mapper.yaml create mode 100644 config/packages/symfonycasts_tailwind.yaml create mode 100644 importmap.php diff --git a/.env b/.env index 33625ce..09a703b 100644 --- a/.env +++ b/.env @@ -1,5 +1,5 @@ APP_ENV=dev -APP_SECRET= +APP_SECRET=je_te_remplis_parce_que_tu_me_mets_des_messages_d_erreur APP_SHARE_DIR=var/share APP_VERSION=0.0.1 DATABASE_URL="postgresql://app:!ChangeMe!@127.0.0.1:5432/app?serverVersion=16&charset=utf8" diff --git a/.gitignore b/.gitignore index bc06d0f..1753b28 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,8 @@ Thumbs.db npm-debug.log yarn-error.log ###< symfony/webpack-encore-bundle ### + +###> symfony/asset-mapper ### +/public/assets/ +/assets/vendor/ +###< symfony/asset-mapper ### diff --git a/composer.json b/composer.json index 056e799..c7011b9 100644 --- a/composer.json +++ b/composer.json @@ -43,6 +43,7 @@ "symfony/web-link": "8.0.*", "symfony/webpack-encore-bundle": "^2.4", "symfony/yaml": "8.0.*", + "symfonycasts/tailwind-bundle": "^0.12.0", "twig/extra-bundle": "^2.12|^3.0", "twig/twig": "^2.12|^3.0" }, @@ -80,7 +81,8 @@ "scripts": { "auto-scripts": { "cache:clear": "symfony-cmd", - "assets:install %PUBLIC_DIR%": "symfony-cmd" + "assets:install %PUBLIC_DIR%": "symfony-cmd", + "importmap:install": "symfony-cmd" }, "post-install-cmd": [ "@auto-scripts" diff --git a/composer.lock b/composer.lock index 163d60f..1f66794 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,85 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "4597facec97a6ff342cba0371179ad02", + "content-hash": "b2f5377481dc83317c4b7c49f2dd183e", "packages": [ + { + "name": "composer/semver", + "version": "3.4.4", + "source": { + "type": "git", + "url": "https://github.com/composer/semver.git", + "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/semver/zipball/198166618906cb2de69b95d7d47e5fa8aa1b2b95", + "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.11", + "symfony/phpunit-bridge": "^3 || ^7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Semver\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + }, + { + "name": "Rob Bast", + "email": "rob.bast@gmail.com", + "homepage": "http://robbast.nl" + } + ], + "description": "Semver library that offers utilities, version constraint parsing and validation.", + "keywords": [ + "semantic", + "semver", + "validation", + "versioning" + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/semver/issues", + "source": "https://github.com/composer/semver/tree/3.4.4" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + } + ], + "time": "2025-08-20T19:15:30+00:00" + }, { "name": "doctrine/collections", "version": "2.6.0", @@ -1950,6 +2027,87 @@ ], "time": "2026-01-13T13:06:50+00:00" }, + { + "name": "symfony/asset-mapper", + "version": "v8.0.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/asset-mapper.git", + "reference": "80635c3722b9bb5481e0282497ae23796dcd3712" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/asset-mapper/zipball/80635c3722b9bb5481e0282497ae23796dcd3712", + "reference": "80635c3722b9bb5481e0282497ae23796dcd3712", + "shasum": "" + }, + "require": { + "composer/semver": "^3.0", + "php": ">=8.4", + "symfony/filesystem": "^7.4|^8.0", + "symfony/http-client": "^7.4|^8.0" + }, + "require-dev": { + "symfony/asset": "^7.4|^8.0", + "symfony/browser-kit": "^7.4|^8.0", + "symfony/console": "^7.4|^8.0", + "symfony/event-dispatcher-contracts": "^3.0", + "symfony/finder": "^7.4|^8.0", + "symfony/framework-bundle": "^7.4|^8.0", + "symfony/http-foundation": "^7.4|^8.0", + "symfony/http-kernel": "^7.4|^8.0", + "symfony/process": "^7.4|^8.0", + "symfony/runtime": "^7.4|^8.0", + "symfony/web-link": "^7.4|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\AssetMapper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Maps directories of assets & makes them available in a public directory with versioned filenames.", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/asset-mapper/tree/v8.0.6" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-02-17T13:07:04+00:00" + }, { "name": "symfony/cache", "version": "v8.0.5", @@ -3293,16 +3451,16 @@ }, { "name": "symfony/form", - "version": "v8.0.4", + "version": "v8.0.7", "source": { "type": "git", "url": "https://github.com/symfony/form.git", - "reference": "c34ec2c2648e2dfedab3ce7e3c6c86f8d89c3092" + "reference": "954e17b053dad9fb227ebd90260752e3a46bb06a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/form/zipball/c34ec2c2648e2dfedab3ce7e3c6c86f8d89c3092", - "reference": "c34ec2c2648e2dfedab3ce7e3c6c86f8d89c3092", + "url": "https://api.github.com/repos/symfony/form/zipball/954e17b053dad9fb227ebd90260752e3a46bb06a", + "reference": "954e17b053dad9fb227ebd90260752e3a46bb06a", "shasum": "" }, "require": { @@ -3364,7 +3522,7 @@ "description": "Allows to easily create, process and reuse HTML forms", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/form/tree/v8.0.4" + "source": "https://github.com/symfony/form/tree/v8.0.7" }, "funding": [ { @@ -3384,7 +3542,7 @@ "type": "tidelift" } ], - "time": "2026-01-23T11:07:10+00:00" + "time": "2026-03-06T13:17:40+00:00" }, { "name": "symfony/framework-bundle", @@ -7473,6 +7631,62 @@ ], "time": "2025-12-04T18:17:06+00:00" }, + { + "name": "symfonycasts/tailwind-bundle", + "version": "v0.12.0", + "source": { + "type": "git", + "url": "https://github.com/SymfonyCasts/tailwind-bundle.git", + "reference": "17c85e25d3ceb54b8599e8ca4c5b67c485f2a48a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/SymfonyCasts/tailwind-bundle/zipball/17c85e25d3ceb54b8599e8ca4c5b67c485f2a48a", + "reference": "17c85e25d3ceb54b8599e8ca4c5b67c485f2a48a", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/asset-mapper": "^6.3|^7.0|^8.0", + "symfony/cache": "^6.3|^7.0|^8.0", + "symfony/console": "^5.4|^6.3|^7.0|^8.0", + "symfony/deprecation-contracts": "^2.2|^3.0", + "symfony/http-client": "^5.4|^6.3|^7.0|^8.0", + "symfony/process": "^5.4|^6.3|^7.0|^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.6", + "symfony/filesystem": "^6.3|^7.0|^8.0", + "symfony/framework-bundle": "^6.3|^7.0|^8.0", + "symfony/phpunit-bridge": "^6.3.9|^7.0|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfonycasts\\TailwindBundle\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ryan Weaver", + "homepage": "https://symfonycasts.com" + } + ], + "description": "Delightful Tailwind Support for Symfony + AssetMapper", + "keywords": [ + "asset-mapper", + "tailwind" + ], + "support": { + "issues": "https://github.com/SymfonyCasts/tailwind-bundle/issues", + "source": "https://github.com/SymfonyCasts/tailwind-bundle/tree/v0.12.0" + }, + "time": "2025-11-24T10:14:04+00:00" + }, { "name": "twig/extra-bundle", "version": "v3.23.0", diff --git a/config/bundles.php b/config/bundles.php index ce7f544..083a65f 100644 --- a/config/bundles.php +++ b/config/bundles.php @@ -15,4 +15,5 @@ return [ Symfony\UX\StimulusBundle\StimulusBundle::class => ['all' => true], Symfony\UX\Turbo\TurboBundle::class => ['all' => true], Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle::class => ['dev' => true, 'test' => true], + Symfonycasts\TailwindBundle\SymfonycastsTailwindBundle::class => ['all' => true], ]; diff --git a/config/packages/asset_mapper.yaml b/config/packages/asset_mapper.yaml new file mode 100644 index 0000000..f7653e9 --- /dev/null +++ b/config/packages/asset_mapper.yaml @@ -0,0 +1,11 @@ +framework: + asset_mapper: + # The paths to make available to the asset mapper. + paths: + - assets/ + missing_import_mode: strict + +when@prod: + framework: + asset_mapper: + missing_import_mode: warn diff --git a/config/packages/security.yaml b/config/packages/security.yaml index b6a1a56..4173719 100644 --- a/config/packages/security.yaml +++ b/config/packages/security.yaml @@ -32,7 +32,17 @@ security: check_path: app_login enable_csrf: true logout: + # où rediriger après la déconnexion path: app_logout +# custom_authenticator: App\Security\AppCustomAuthenticator + + remember_me: + secret: '%kernel.secret%' + lifetime: 604800 + path: / + # by default, the feature is enabled by checking a checkbox in the + # login form, uncomment the following line to always enable it. + #always_remember_me: true # where to redirect after logout # target: app_any_route @@ -45,8 +55,8 @@ security: # Note: Only the *first* matching rule is applied # autorisations access_control: - # - { path: ^/admin, roles: ROLE_ADMIN } - # - { path: ^/profile, roles: ROLE_USER } + - { path: ^/admin, roles: ROLE_ADMIN } + - { path: ^/user, roles: ROLE_USER } when@test: security: diff --git a/config/packages/symfonycasts_tailwind.yaml b/config/packages/symfonycasts_tailwind.yaml new file mode 100644 index 0000000..3921e12 --- /dev/null +++ b/config/packages/symfonycasts_tailwind.yaml @@ -0,0 +1,6 @@ +symfonycasts_tailwind: + # Specify the EXACT version of Tailwind CSS you want to use + binary_version: 'v4.1.11' + + # Alternatively, you can specify the path to the binary that you manage yourself + #binary: 'node_modules/.bin/tailwindcss' diff --git a/config/reference.php b/config/reference.php index 0cbae3e..6bd5b62 100644 --- a/config/reference.php +++ b/config/reference.php @@ -280,7 +280,7 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param; * }>, * }, * asset_mapper?: bool|array{ // Asset Mapper configuration - * enabled?: bool|Param, // Default: false + * enabled?: bool|Param, // Default: true * paths?: array, * excluded_patterns?: list, * exclude_dotfiles?: bool|Param, // If true, any files starting with "." will be excluded from the asset mapper. // Default: true @@ -1465,6 +1465,15 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param; * }, * default_transport?: scalar|Param|null, // Default: "default" * } + * @psalm-type SymfonycastsTailwindConfig = array{ + * input_css?: list, + * config_file?: scalar|Param|null, // Path to the tailwind.config.js file // Default: "%kernel.project_dir%/tailwind.config.js" + * binary?: scalar|Param|null, // The tailwind binary to use instead of downloading a new one // Default: null + * binary_version?: scalar|Param|null, // Tailwind CLI version to download - null means the latest version // Default: null + * binary_platform?: "auto"|"linux-arm64"|"linux-arm64-musl"|"linux-x64"|"linux-x64-musl"|"macos-arm64"|"macos-x64"|"windows-x64"|Param, // Tailwind CLI platform to download - "auto" will try to detect the platform automatically // Default: "auto" + * postcss_config_file?: scalar|Param|null, // Path to PostCSS config file which is passed to the Tailwind CLI // Default: null + * strict_mode?: bool|Param|null, // When enabled, an exception will be thrown if there are no built assets (default: false in `test` env, true otherwise) // Default: null + * } * @psalm-type ConfigType = array{ * imports?: ImportsConfig, * parameters?: ParametersConfig, @@ -1479,6 +1488,7 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param; * webpack_encore?: WebpackEncoreConfig, * stimulus?: StimulusConfig, * turbo?: TurboConfig, + * symfonycasts_tailwind?: SymfonycastsTailwindConfig, * "when@dev"?: array{ * imports?: ImportsConfig, * parameters?: ParametersConfig, @@ -1496,6 +1506,7 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param; * webpack_encore?: WebpackEncoreConfig, * stimulus?: StimulusConfig, * turbo?: TurboConfig, + * symfonycasts_tailwind?: SymfonycastsTailwindConfig, * }, * "when@prod"?: array{ * imports?: ImportsConfig, @@ -1511,6 +1522,7 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param; * webpack_encore?: WebpackEncoreConfig, * stimulus?: StimulusConfig, * turbo?: TurboConfig, + * symfonycasts_tailwind?: SymfonycastsTailwindConfig, * }, * "when@test"?: array{ * imports?: ImportsConfig, @@ -1527,6 +1539,7 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param; * webpack_encore?: WebpackEncoreConfig, * stimulus?: StimulusConfig, * turbo?: TurboConfig, + * symfonycasts_tailwind?: SymfonycastsTailwindConfig, * }, * ... [ + 'path' => './assets/app.js', + 'entrypoint' => true, + ], + '@hotwired/stimulus' => [ + 'version' => '3.2.2', + ], + '@symfony/stimulus-bundle' => [ + 'path' => './vendor/symfony/stimulus-bundle/assets/dist/loader.js', + ], + '@hotwired/turbo' => [ + 'version' => '7.3.0', + ], +]; diff --git a/src/Controller/SecurityController.php b/src/Controller/SecurityController.php index e54abe2..375204d 100644 --- a/src/Controller/SecurityController.php +++ b/src/Controller/SecurityController.php @@ -2,38 +2,63 @@ namespace App\Controller; -use LogicException; +use App\Repository\UserRepository; // 👈 AJOUTE CETTE LIGNE ICI ! +use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface; // 👈 ET CELLE-CI AUSSI ! use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Attribute\Route; use Symfony\Component\Security\Http\Authentication\AuthenticationUtils; class SecurityController extends AbstractController { - #[Route(path: '/login', name: 'app_login', methods: ['GET','POST'])] - public function login(AuthenticationUtils $authenticationUtils): Response + #[Route(path: '/test-password', name: 'test_password')] + public function testPassword(UserRepository $userRepo, UserPasswordHasherInterface $hasher): Response { + // 1. On va chercher ton admin directement en base + $admin = $userRepo->findOneBy(['email' => 'admin@kaz.fr']); + + if (!$admin) { + dd("L'utilisateur n'existe pas dans la base lue par ce contrôleur !"); + } + + // 2. On vérifie si "password" est bien le bon mot de passe + $isValid = $hasher->isPasswordValid($admin, 'password'); + + // 3. On affiche le résultat brut + dd([ + 'email' => $admin->getEmail(), + 'mot_de_passe_hache' => $admin->getPassword(), + 'est_ce_que_password_est_valide' => $isValid + ]); + } + #[Route(path: '/login', name: 'app_login')] + public function login(AuthenticationUtils $authenticationUtils, Request $request): Response // 👈 2. ON AJOUTE $request ICI + { + // 🚨 3. NOTRE CODE DE DÉTECTIVE POUR VOIR CE QUI EST ENVOYÉ : + if ($request->isMethod('POST')) { + dd($request->request->all()); + } + // 🚨 FIN DU CODE DE DÉTECTIVE - // si on a un utilisateur déjà connecté, alors on le redirige sur la page d'accueil if ($this->getUser()) { return $this->redirectToRoute('app_home'); } - // get the login error if there is one + // Récupération de l'erreur de connexion (s'il y en a une) $error = $authenticationUtils->getLastAuthenticationError(); - - // last username entered by the user + // Récupération du dernier nom d'utilisateur saisi par l'adhérent $lastUsername = $authenticationUtils->getLastUsername(); return $this->render('security/login.html.twig', [ 'last_username' => $lastUsername, - 'error' => $error, + 'error' => $error ]); } - #[Route(path: '/logout', name: 'app_logout', methods: ['POST'])] + #[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.'); + throw new \LogicException('This method can be blank - it will be intercepted by the logout key on your firewall.'); } -} +} \ No newline at end of file diff --git a/src/Controller/UserController.php b/src/Controller/UserController.php index 7c045b4..79082ae 100644 --- a/src/Controller/UserController.php +++ b/src/Controller/UserController.php @@ -2,9 +2,14 @@ namespace App\Controller; +use App\Form\ChangePasswordType; use App\Service\KazApiService; +use Doctrine\ORM\EntityManagerInterface; +use Symfony\Component\HttpFoundation\Request; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Component\Form\FormError; use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface; use Symfony\Component\Routing\Attribute\Route; use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface; use Symfony\Contracts\HttpClient\Exception\DecodingExceptionInterface; @@ -14,9 +19,6 @@ use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; class UserController extends AbstractController { - - // TODO : UserPasswordHasherInterface - // voir : https://symfony.com/doc/current/security/passwords.html#hashing-the-password /** * Permet de vérifier si un utilisateur existe dans le ldap. * @@ -30,6 +32,7 @@ class UserController extends AbstractController * @throws ServerExceptionInterface * @throws TransportExceptionInterface */ + #[Route('/user/{email}', name: 'app_user', methods: ['GET'])] public function index(string $email, KazApiService $apiClient): Response { @@ -39,4 +42,45 @@ class UserController extends AbstractController 'exist' => $exist, ]); } + + #[Route('/user/mot-de-passe', name: 'app_user_edit_password', methods: ['GET', 'POST'])] + public function editPassword(Request $request, UserPasswordHasherInterface $hasher, EntityManagerInterface $entityManager): Response + { + # Récupération de l'adhérent actuellement connecté + $user = $this->getUser(); + + # Création du formulaire + $form = $this->createForm(ChangePasswordType::class); + + # Liaison du formulaire à la requête HTTP + $form->handleRequest($request); + + # Vérification du formulaire, s'il est bien soumis et valide + if ($form->isSubmitted() && $form->isValid()) { + # Récupération des données du formulaire + $user = $this->getUser(); + $plainOldPassword = $form->get('oldPassword')->getData(); + $newPassword = $form->get('newPassword')->getData(); + + # Vérification de l'ancien mot de passe + if (!$hasher->isPasswordValid($user, $plainOldPassword)) { + $form->get('oldPassword')->addError(new FormError('L\'ancien mot de passe est incorrect.')); + } else { + # Si tout est OK : Hachage du mot de passe + $hashedPassword = $hasher->hashPassword($user, $newPassword); + $user->setPassword($hashedPassword); + + # Sauvegarde en BDD + $entityManager->flush(); + + # Message de succès pour l'adhérent + $this->addFlash('success', 'Votre mot de passe a bien été mis à jour !'); + + return $this->redirectToRoute('app_user_edit_password'); + } + } + return $this->render('user/edit_password.html.twig', [ + 'form' => $form->createView(), + ]); + } } \ No newline at end of file diff --git a/src/DataFixtures/AppFixtures.php b/src/DataFixtures/AppFixtures.php index 2d293a6..23f759e 100644 --- a/src/DataFixtures/AppFixtures.php +++ b/src/DataFixtures/AppFixtures.php @@ -29,20 +29,15 @@ class AppFixtures extends Fixture for ($i = 0; $i < 10; $i++) { # Instanciation d'un nouvel utilisateur (Adhérent) $user = new User(); - # Attribution d'un email aléatoire et unique $user->setEmail($faker->unique()->safeEmail()); - # Définition des droits d'accès de l'utilisateur $user->setRoles(['ROLE_USER']); - # Hachage sécurisé du mot de passe "password" $user->setPassword($this->hasher->hashPassword($user, 'password')); - # Définition d'un NOM et Prénom $user->setFirstname($faker->firstName()); $user->setLastname($faker->lastName()); - # autres fixtures à modifier plus tard $user->setNextcloudQuota($faker->numberBetween(1, 20) . 'G'); $user->setQuota($faker->numberBetween(1, 10) . 'G'); @@ -52,11 +47,32 @@ class AppFixtures extends Fixture $user->setHasMobilizon($faker->boolean(50)); $user->setHasNextcloudAccess($faker->boolean(90)); $user->setIdentifiantKaz($faker->uuid()); + } + + // Création d'un compte de test fixe + $admin = new User(); + $admin->setEmail('admin@kaz.bzh'); + $admin->setRoles(['ROLE_USER', 'ROLE_ADMIN']); + $admin->setPassword($this->hasher->hashPassword($admin, 'password')); + $admin->setFirstName('Admin'); + $admin->setLastName('KAZ'); + + // Remplissage des champs obligatoires restants pour éviter les erreurs SQL + $admin->setEmailDeSecours('secours@kaz.bzh'); + $admin->setIdentifiantKaz('ADMIN-KAZ-001'); + $admin->setQuota('5G'); + $admin->setEmailQuota('1G'); + $admin->setNextcloudQuota('10G'); + $admin->setHasNextcloudAccess(true); + $admin->setHasMobilizon(true); + $admin->setHasAgoraAccess(true); + + $manager->persist($admin); # Préparation de l'enregistrement de l'objet en base de données $manager->persist($user); + + # Exécution réelle des requêtes SQL (envoi vers la base), une fois la bouche finie + $manager->flush(); } - # Exécution réelle des requêtes SQL (envoi vers la base), une fois la bouche finie - $manager->flush(); - } } diff --git a/src/Entity/User.php b/src/Entity/User.php index 59874d8..8e8f827 100644 --- a/src/Entity/User.php +++ b/src/Entity/User.php @@ -4,6 +4,7 @@ namespace App\Entity; use App\Repository\UserRepository; use Doctrine\ORM\Mapping as ORM; +use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface; use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Uid\Uuid; @@ -11,6 +12,7 @@ use Symfony\Component\Uid\Uuid; #[ORM\Entity(repositoryClass: UserRepository::class)] #[ORM\Table(name: '`user`')] #[ORM\UniqueConstraint(name: 'UNIQ_IDENTIFIER_EMAIL', fields: ['email'])] +#[UniqueEntity(fields: ['email'], message: 'There is already an account with this email')] class User implements UserInterface, PasswordAuthenticatedUserInterface { #[ORM\Id] @@ -35,7 +37,7 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface private ?string $password = null; #[ORM\Column(length: 255)] - private ?string $emailQuota = null; + private ?string $emailQuota = '1G'; #[ORM\Column(length: 255)] private ?string $emailDeSecours = null; @@ -50,7 +52,7 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface private ?bool $hasNextcloudAccess = null; #[ORM\Column(length: 255)] - private ?string $nextcloudQuota = '1G'; + private ?string $nextcloudQuota = null; #[ORM\Column] private ?bool $hasMobilizon = null; @@ -104,9 +106,7 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface public function getRoles(): array { $roles = $this->roles; - // guarantee every user at least has ROLE_USER - $roles[] = 'ROLE_USER'; - + $roles[] = 'ROLE_USER'; // garantit qu'il a au moins ce rôle return array_unique($roles); } @@ -120,6 +120,16 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface return $this; } + /** + * @see UserInterface + * Ajout de cette fonction, car obligatoire pour faire fonctionner UserInterface correctement + */ + public function eraseCredentials(): void + { + // Si vous stockez des données temporaires sensibles sur l'utilisateur, nettoyez-les ici + // $this->plainPassword = null; + } + /** * @see PasswordAuthenticatedUserInterface */ diff --git a/symfony.lock b/symfony.lock index af9f8ca..ba0e69f 100644 --- a/symfony.lock +++ b/symfony.lock @@ -62,6 +62,21 @@ "bin/phpunit" ] }, + "symfony/asset-mapper": { + "version": "8.0", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "6.4", + "ref": "5ad1308aa756d58f999ffbe1540d1189f5d7d14a" + }, + "files": [ + "assets/app.js", + "assets/styles/app.css", + "config/packages/asset_mapper.yaml", + "importmap.php" + ] + }, "symfony/console": { "version": "8.0", "recipe": { @@ -341,6 +356,18 @@ "webpack.config.js" ] }, + "symfonycasts/tailwind-bundle": { + "version": "0.12", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "0.8", + "ref": "d0bd0276f74de90adfaa4c6cd74cc0caacd77e0a" + }, + "files": [ + "config/packages/symfonycasts_tailwind.yaml" + ] + }, "twig/extra-bundle": { "version": "v3.23.0" } diff --git a/tailwind.config.js b/tailwind.config.js index ac6f7e5..4064a67 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -1,11 +1,23 @@ /** @type {import('tailwindcss').Config} */ module.exports = { content: [ - "./templates/**/*.html.twig", "./assets/**/*.js", + "./templates/**/*.html.twig", ], theme: { - extend: {}, + extend: { + colors: { + 'brand-teal': '#4DD5C8', // Boutons + 'brand-gold': '#E6A638', // Accent + 'brand-dark': '#000000', // Police + 'bg-primary': '#F9FCF7', // Fond principal + 'bg-secondary': '#23978B', // Fond secondaire + }, + fontFamily: { + 'sans': ['Sora', 'system-ui', 'sans-serif'], + 'title': ['Caveat', 'cursive'], + }, + }, }, plugins: [], } \ No newline at end of file diff --git a/templates/base.html.twig b/templates/base.html.twig index 0d7cd48..03ef230 100644 --- a/templates/base.html.twig +++ b/templates/base.html.twig @@ -2,16 +2,30 @@ - {% block title %}Welcome!{% endblock %} + {% block title %}Association KAZ{% endblock %} {% block stylesheets %} - + {% endblock %} {% block javascripts %} + {% block importmap %}{{ importmap('app') }}{% endblock %} {% endblock %} + +
+ {# Section des notifications #} + {% for label, messages in app.flashes %} + {% for message in messages %} +
+ {{ message }} +
+ {% endfor %} + {% endfor %} + {% block body %}{% endblock %} +
+ diff --git a/templates/home/hello.html.twig b/templates/home/hello.html.twig index e69de29..110edd8 100644 --- a/templates/home/hello.html.twig +++ b/templates/home/hello.html.twig @@ -0,0 +1,28 @@ +{% extends 'base.html.twig' %} + +{% block title %}Accueil | Tableau de bord KAZ{% endblock %} + +{% block body %} +
+

Bienvenue sur ton tableau de bord KAZ

+ + {# Vérification si un utilisateur est connecté #} + {% if app.user %} +
+

Succès ! Tu es connecté avec l'adresse :

+

{{ app.user.userIdentifier }}

+
+ +

+ + Se déconnecter + +

+ {% else %} +

Tu n'es pas encore connecté à ton espace.

+ + Aller à la page de connexion + + {% endif %} +
+{% endblock %} \ No newline at end of file diff --git a/templates/security/login.html.twig b/templates/security/login.html.twig index 6efc644..8af8452 100644 --- a/templates/security/login.html.twig +++ b/templates/security/login.html.twig @@ -1,39 +1,34 @@ {% extends 'base.html.twig' %} -{% block title %}Log in!{% endblock %} +{% block title %}Se connecter | {{ parent() }}{% endblock %} {% block body %} -
- {% if error %} -
- Email ou mot de passe invalide. -
- {% endif %} + + {% if error %} +
{{ error.messageKey|trans(error.messageData, 'security') }}
+ {% endif %} - {% if app.user %} -
- You are logged in as {{ app.user.userIdentifier }}, Se déconnecter -
- {% endif %} + {% if app.user %} +
+ You are logged in as {{ app.user.userIdentifier }}, Logout +
+ {% endif %} -

Se connecter à mon compte KAZ

- - - - - +

Se connecter à mon tableau de bord KAZ

+ + + + + +
+ +
- {# TODO : vérifier qu'il y a bien une option "Se souvenir de moi" dans mon firewall pour activer la fonctionnalité. #} - {# pour en savoir plus : https://symfony.com/doc/current/security/remember_me.html #} - -
- - -
- - -
+ + {% endblock %}