✨ Ajout réinitialisation du mot de passe
This commit is contained in:
parent
6e140f84f7
commit
d6e1679aba
@ -5,3 +5,4 @@ vendor/
|
||||
|
||||
uploads/*
|
||||
!uploads/.gitkeep
|
||||
node_modules/
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -42,3 +42,4 @@
|
||||
###> phpstan/phpstan ###
|
||||
phpstan.neon
|
||||
###< phpstan/phpstan ###
|
||||
node_modules/
|
@ -6,5 +6,4 @@ import './bootstrap.js';
|
||||
* 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 'flowbite';
|
||||
|
@ -50,6 +50,7 @@
|
||||
"symfony/validator": "7.2.*",
|
||||
"symfony/web-link": "7.2.*",
|
||||
"symfony/yaml": "7.2.*",
|
||||
"symfonycasts/reset-password-bundle": "^1.23",
|
||||
"symfonycasts/tailwind-bundle": "^0.6.1",
|
||||
"tales-from-a-dev/flowbite-bundle": "^0.7.1",
|
||||
"twig/extra-bundle": "^2.12|^3.18",
|
||||
|
50
composer.lock
generated
50
composer.lock
generated
@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "ad6bf07457863ff03fcd219a3f64fd2e",
|
||||
"content-hash": "475090da932db4e99759d3f0f296636f",
|
||||
"packages": [
|
||||
{
|
||||
"name": "composer/semver",
|
||||
@ -8167,6 +8167,54 @@
|
||||
],
|
||||
"time": "2024-10-23T06:56:12+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfonycasts/reset-password-bundle",
|
||||
"version": "v1.23.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/SymfonyCasts/reset-password-bundle.git",
|
||||
"reference": "bde42fe5956e0cd523931da886ee41ab660c45b2"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/SymfonyCasts/reset-password-bundle/zipball/bde42fe5956e0cd523931da886ee41ab660c45b2",
|
||||
"reference": "bde42fe5956e0cd523931da886ee41ab660c45b2",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-json": "*",
|
||||
"php": ">=8.1.10",
|
||||
"symfony/config": "^5.4 | ^6.0 | ^7.0",
|
||||
"symfony/dependency-injection": "^5.4 | ^6.0 | ^7.0",
|
||||
"symfony/deprecation-contracts": "^2.2 | ^3.0",
|
||||
"symfony/http-kernel": "^5.4 | ^6.0 | ^7.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"doctrine/annotations": "^1.0",
|
||||
"doctrine/doctrine-bundle": "^2.8",
|
||||
"doctrine/orm": "^2.13",
|
||||
"symfony/framework-bundle": "^5.4 | ^6.0 | ^7.0",
|
||||
"symfony/phpunit-bridge": "^5.4 | ^6.0 | ^7.0",
|
||||
"symfony/process": "^6.4 | ^7.0 | ^7.1",
|
||||
"symfonycasts/internal-test-helpers": "dev-main"
|
||||
},
|
||||
"type": "symfony-bundle",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"SymfonyCasts\\Bundle\\ResetPassword\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"description": "Symfony bundle that adds password reset functionality.",
|
||||
"support": {
|
||||
"issues": "https://github.com/SymfonyCasts/reset-password-bundle/issues",
|
||||
"source": "https://github.com/SymfonyCasts/reset-password-bundle/tree/v1.23.1"
|
||||
},
|
||||
"time": "2024-12-09T19:04:36+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfonycasts/tailwind-bundle",
|
||||
"version": "v0.6.1",
|
||||
|
@ -22,4 +22,5 @@ return [
|
||||
TalesFromADev\Twig\Extra\Tailwind\Bridge\Symfony\Bundle\TalesFromADevTwigExtraTailwindBundle::class => ['all' => true],
|
||||
TalesFromADev\FlowbiteBundle\TalesFromADevFlowbiteBundle::class => ['all' => true],
|
||||
Symfony\UX\Dropzone\DropzoneBundle::class => ['all' => true],
|
||||
SymfonyCasts\Bundle\ResetPassword\SymfonyCastsResetPasswordBundle::class => ['all' => true],
|
||||
];
|
||||
|
@ -13,7 +13,7 @@ framework:
|
||||
max_retries: 3
|
||||
multiplier: 2
|
||||
failed: 'doctrine://default?queue_name=failed'
|
||||
# sync: 'sync://'
|
||||
sync: 'sync://'
|
||||
|
||||
default_bus: messenger.bus.default
|
||||
|
||||
@ -21,9 +21,9 @@ framework:
|
||||
messenger.bus.default: []
|
||||
|
||||
routing:
|
||||
Symfony\Component\Mailer\Messenger\SendEmailMessage: async
|
||||
Symfony\Component\Notifier\Message\ChatMessage: async
|
||||
Symfony\Component\Notifier\Message\SmsMessage: async
|
||||
Symfony\Component\Mailer\Messenger\SendEmailMessage: sync
|
||||
Symfony\Component\Notifier\Message\ChatMessage: sync
|
||||
Symfony\Component\Notifier\Message\SmsMessage: sync
|
||||
|
||||
# Route your messages to the transports
|
||||
# 'App\Message\YourMessage': async
|
||||
|
2
config/packages/reset_password.yaml
Normal file
2
config/packages/reset_password.yaml
Normal file
@ -0,0 +1,2 @@
|
||||
symfonycasts_reset_password:
|
||||
request_password_repository: App\Repository\ResetPasswordRequestRepository
|
@ -27,4 +27,17 @@ return [
|
||||
'@hotwired/turbo' => [
|
||||
'version' => '8.0.12',
|
||||
],
|
||||
'flowbite' => [
|
||||
'version' => '2.5.2',
|
||||
],
|
||||
'@popperjs/core' => [
|
||||
'version' => '2.11.8',
|
||||
],
|
||||
'flowbite-datepicker' => [
|
||||
'version' => '1.3.0',
|
||||
],
|
||||
'flowbite/dist/flowbite.min.css' => [
|
||||
'version' => '2.5.2',
|
||||
'type' => 'css',
|
||||
],
|
||||
];
|
||||
|
64
migrations/Version20250116210156.php
Normal file
64
migrations/Version20250116210156.php
Normal file
@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20250116210156 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql('CREATE TABLE reset_password_request (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, selector VARCHAR(20) NOT NULL, hashed_token VARCHAR(100) NOT NULL, requested_at DATETIME NOT NULL, expires_at DATETIME NOT NULL, user_id BLOB NOT NULL, CONSTRAINT FK_7CE748AA76ED395 FOREIGN KEY (user_id) REFERENCES "user" (id) NOT DEFERRABLE INITIALLY IMMEDIATE)');
|
||||
$this->addSql('CREATE INDEX IDX_7CE748AA76ED395 ON reset_password_request (user_id)');
|
||||
$this->addSql('CREATE TEMPORARY TABLE __temp__user AS SELECT id, email, roles, password FROM user');
|
||||
$this->addSql('DROP TABLE user');
|
||||
$this->addSql('CREATE TABLE user (id BLOB NOT NULL, email VARCHAR(255) NOT NULL, roles CLOB NOT NULL, password VARCHAR(255) NOT NULL, PRIMARY KEY(id))');
|
||||
$this->addSql('INSERT INTO user (id, email, roles, password) SELECT id, email, roles, password FROM __temp__user');
|
||||
$this->addSql('DROP TABLE __temp__user');
|
||||
$this->addSql('CREATE UNIQUE INDEX UNIQ_IDENTIFIER_EMAIL ON user (email)');
|
||||
$this->addSql('CREATE TEMPORARY TABLE __temp__messenger_messages AS SELECT id, body, headers, queue_name, created_at, available_at, delivered_at FROM messenger_messages');
|
||||
$this->addSql('DROP TABLE messenger_messages');
|
||||
$this->addSql('CREATE TABLE messenger_messages (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, body CLOB NOT NULL, headers CLOB NOT NULL, queue_name VARCHAR(190) NOT NULL, created_at DATETIME NOT NULL, available_at DATETIME NOT NULL, delivered_at DATETIME DEFAULT NULL)');
|
||||
$this->addSql('INSERT INTO messenger_messages (id, body, headers, queue_name, created_at, available_at, delivered_at) SELECT id, body, headers, queue_name, created_at, available_at, delivered_at FROM __temp__messenger_messages');
|
||||
$this->addSql('DROP TABLE __temp__messenger_messages');
|
||||
$this->addSql('CREATE INDEX IDX_75EA56E016BA31DB ON messenger_messages (delivered_at)');
|
||||
$this->addSql('CREATE INDEX IDX_75EA56E0E3BD61CE ON messenger_messages (available_at)');
|
||||
$this->addSql('CREATE INDEX IDX_75EA56E0FB7336F0 ON messenger_messages (queue_name)');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('DROP TABLE reset_password_request');
|
||||
$this->addSql('CREATE TEMPORARY TABLE __temp__messenger_messages AS SELECT id, body, headers, queue_name, created_at, available_at, delivered_at FROM messenger_messages');
|
||||
$this->addSql('DROP TABLE messenger_messages');
|
||||
$this->addSql('CREATE TABLE messenger_messages (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, body CLOB NOT NULL, headers CLOB NOT NULL, queue_name VARCHAR(190) NOT NULL, created_at DATETIME NOT NULL --(DC2Type:datetime_immutable)
|
||||
, available_at DATETIME NOT NULL --(DC2Type:datetime_immutable)
|
||||
, delivered_at DATETIME DEFAULT NULL --(DC2Type:datetime_immutable)
|
||||
)');
|
||||
$this->addSql('INSERT INTO messenger_messages (id, body, headers, queue_name, created_at, available_at, delivered_at) SELECT id, body, headers, queue_name, created_at, available_at, delivered_at FROM __temp__messenger_messages');
|
||||
$this->addSql('DROP TABLE __temp__messenger_messages');
|
||||
$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('CREATE TEMPORARY TABLE __temp__user AS SELECT id, email, roles, password FROM "user"');
|
||||
$this->addSql('DROP TABLE "user"');
|
||||
$this->addSql('CREATE TABLE "user" (id BLOB NOT NULL --(DC2Type:uuid)
|
||||
, email VARCHAR(255) NOT NULL, roles CLOB NOT NULL --(DC2Type:json)
|
||||
, password VARCHAR(255) NOT NULL, PRIMARY KEY(id))');
|
||||
$this->addSql('INSERT INTO "user" (id, email, roles, password) SELECT id, email, roles, password FROM __temp__user');
|
||||
$this->addSql('DROP TABLE __temp__user');
|
||||
$this->addSql('CREATE UNIQUE INDEX UNIQ_IDENTIFIER_EMAIL ON "user" (email)');
|
||||
}
|
||||
}
|
217
package-lock.json
generated
Normal file
217
package-lock.json
generated
Normal file
@ -0,0 +1,217 @@
|
||||
{
|
||||
"name": "Kumora",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"dependencies": {
|
||||
"flowbite": "^2.5.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@popperjs/core": {
|
||||
"version": "2.11.8",
|
||||
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
|
||||
"integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/popperjs"
|
||||
}
|
||||
},
|
||||
"node_modules/@rollup/plugin-node-resolve": {
|
||||
"version": "15.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.3.1.tgz",
|
||||
"integrity": "sha512-tgg6b91pAybXHJQMAAwW9VuWBO6Thi+q7BCNARLwSqlmsHz0XYURtGvh/AuwSADXSI4h/2uHbs7s4FzlZDGSGA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@rollup/pluginutils": "^5.0.1",
|
||||
"@types/resolve": "1.20.2",
|
||||
"deepmerge": "^4.2.2",
|
||||
"is-module": "^1.0.0",
|
||||
"resolve": "^1.22.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"rollup": "^2.78.0||^3.0.0||^4.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"rollup": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@rollup/pluginutils": {
|
||||
"version": "5.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.4.tgz",
|
||||
"integrity": "sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/estree": "^1.0.0",
|
||||
"estree-walker": "^2.0.2",
|
||||
"picomatch": "^4.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"rollup": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@types/estree": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
|
||||
"integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/resolve": {
|
||||
"version": "1.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz",
|
||||
"integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/deepmerge": {
|
||||
"version": "4.3.1",
|
||||
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
|
||||
"integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/estree-walker": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
|
||||
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/flowbite": {
|
||||
"version": "2.5.2",
|
||||
"resolved": "https://registry.npmjs.org/flowbite/-/flowbite-2.5.2.tgz",
|
||||
"integrity": "sha512-kwFD3n8/YW4EG8GlY3Od9IoKND97kitO+/ejISHSqpn3vw2i5K/+ZI8Jm2V+KC4fGdnfi0XZ+TzYqQb4Q1LshA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@popperjs/core": "^2.9.3",
|
||||
"flowbite-datepicker": "^1.3.0",
|
||||
"mini-svg-data-uri": "^1.4.3"
|
||||
}
|
||||
},
|
||||
"node_modules/flowbite-datepicker": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/flowbite-datepicker/-/flowbite-datepicker-1.3.2.tgz",
|
||||
"integrity": "sha512-6Nfm0MCVX3mpaR7YSCjmEO2GO8CDt6CX8ZpQnGdeu03WUCWtEPQ/uy0PUiNtIJjJZWnX0Cm3H55MOhbD1g+E/g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||
"flowbite": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/function-bind": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/hasown": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
||||
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"function-bind": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/is-core-module": {
|
||||
"version": "2.16.1",
|
||||
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
|
||||
"integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"hasown": "^2.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/is-module": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz",
|
||||
"integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/mini-svg-data-uri": {
|
||||
"version": "1.4.4",
|
||||
"resolved": "https://registry.npmjs.org/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz",
|
||||
"integrity": "sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"mini-svg-data-uri": "cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/path-parse": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
|
||||
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/picomatch": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
|
||||
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/resolve": {
|
||||
"version": "1.22.10",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
|
||||
"integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"is-core-module": "^2.16.0",
|
||||
"path-parse": "^1.0.7",
|
||||
"supports-preserve-symlinks-flag": "^1.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"resolve": "bin/resolve"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/supports-preserve-symlinks-flag": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
|
||||
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
5
package.json
Normal file
5
package.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"flowbite": "^2.5.2"
|
||||
}
|
||||
}
|
7
phpstan-baseline.neon
Normal file
7
phpstan-baseline.neon
Normal file
@ -0,0 +1,7 @@
|
||||
parameters:
|
||||
ignoreErrors:
|
||||
-
|
||||
message: '#^Property App\\Entity\\ResetPasswordRequest\:\:\$id \(int\|null\) is never assigned int so it can be removed from the property type\.$#'
|
||||
identifier: property.unusedType
|
||||
count: 1
|
||||
path: src/Entity/ResetPasswordRequest.php
|
@ -1,3 +1,6 @@
|
||||
includes:
|
||||
- phpstan-baseline.neon
|
||||
|
||||
parameters:
|
||||
level: 5
|
||||
paths:
|
||||
@ -9,6 +12,7 @@ parameters:
|
||||
strictRules:
|
||||
noVariableVariables: false
|
||||
|
||||
|
||||
reportUnmatchedIgnoredErrors: false
|
||||
treatPhpDocTypesAsCertain: false
|
||||
|
||||
|
@ -39,7 +39,7 @@ class CreateUserCommand extends Command
|
||||
|
||||
try {
|
||||
$user = $this->userRepository->findOneBy(['email' => $email]);
|
||||
if ($user !== null) {
|
||||
if (null !== $user) {
|
||||
$io->error('Un utilisateur existe déjà avec cet email');
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
11
src/Controller/ProfileController.php
Normal file
11
src/Controller/ProfileController.php
Normal file
@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
|
||||
class ProfileController extends AbstractController
|
||||
{
|
||||
}
|
181
src/Controller/ResetPasswordController.php
Normal file
181
src/Controller/ResetPasswordController.php
Normal file
@ -0,0 +1,181 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Entity\User;
|
||||
use App\Form\ChangePasswordFormType;
|
||||
use App\Form\ResetPasswordRequestFormType;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Bridge\Twig\Mime\TemplatedEmail;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Mailer\MailerInterface;
|
||||
use Symfony\Component\Mime\Address;
|
||||
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
use SymfonyCasts\Bundle\ResetPassword\Controller\ResetPasswordControllerTrait;
|
||||
use SymfonyCasts\Bundle\ResetPassword\Exception\ResetPasswordExceptionInterface;
|
||||
use SymfonyCasts\Bundle\ResetPassword\ResetPasswordHelperInterface;
|
||||
|
||||
#[Route('/reset-password')]
|
||||
class ResetPasswordController extends AbstractController
|
||||
{
|
||||
use ResetPasswordControllerTrait;
|
||||
|
||||
public function __construct(
|
||||
private ResetPasswordHelperInterface $resetPasswordHelper,
|
||||
private EntityManagerInterface $entityManager
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Display & process form to request a password reset.
|
||||
*/
|
||||
#[Route('', name: 'app_forgot_password_request')]
|
||||
public function request(Request $request, MailerInterface $mailer, TranslatorInterface $translator): Response
|
||||
{
|
||||
$form = $this->createForm(ResetPasswordRequestFormType::class);
|
||||
$form->handleRequest($request);
|
||||
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
/** @var string $email */
|
||||
$email = $form->get('email')->getData();
|
||||
|
||||
return $this->processSendingPasswordResetEmail(
|
||||
$email,
|
||||
$mailer
|
||||
);
|
||||
}
|
||||
|
||||
return $this->render('reset_password/request.html.twig', [
|
||||
'requestForm' => $form,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirmation page after a user has requested a password reset.
|
||||
*/
|
||||
#[Route('/check-email', name: 'app_check_email')]
|
||||
public function checkEmail(): Response
|
||||
{
|
||||
// Generate a fake token if the user does not exist or someone hit this page directly.
|
||||
// This prevents exposing whether or not a user was found with the given email address or not
|
||||
if (!$resetToken = $this->getTokenObjectFromSession() instanceof \SymfonyCasts\Bundle\ResetPassword\Model\ResetPasswordToken) {
|
||||
$resetToken = $this->resetPasswordHelper->generateFakeResetToken();
|
||||
}
|
||||
|
||||
return $this->render('reset_password/check_email.html.twig', [
|
||||
'resetToken' => $resetToken,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates and process the reset URL that the user clicked in their email.
|
||||
*/
|
||||
#[Route('/reset/{token}', name: 'app_reset_password')]
|
||||
public function reset(Request $request, UserPasswordHasherInterface $passwordHasher, TranslatorInterface $translator, string $token = null): Response
|
||||
{
|
||||
if (null !== $token) {
|
||||
// We store the token in session and remove it from the URL, to avoid the URL being
|
||||
// loaded in a browser and potentially leaking the token to 3rd party JavaScript.
|
||||
$this->storeTokenInSession($token);
|
||||
|
||||
return $this->redirectToRoute('app_reset_password');
|
||||
}
|
||||
|
||||
$token = $this->getTokenFromSession();
|
||||
if (null === $token) {
|
||||
throw $this->createNotFoundException('No reset password token found in the URL or in the session.');
|
||||
}
|
||||
|
||||
try {
|
||||
/** @var User $user */
|
||||
$user = $this->resetPasswordHelper->validateTokenAndFetchUser($token);
|
||||
} catch (ResetPasswordExceptionInterface $e) {
|
||||
$this->addFlash('reset_password_error', sprintf(
|
||||
'%s - %s',
|
||||
$translator->trans(ResetPasswordExceptionInterface::MESSAGE_PROBLEM_VALIDATE, [], 'ResetPasswordBundle'),
|
||||
$translator->trans($e->getReason(), [], 'ResetPasswordBundle')
|
||||
));
|
||||
|
||||
return $this->redirectToRoute('app_forgot_password_request');
|
||||
}
|
||||
|
||||
// The token is valid; allow the user to change their password.
|
||||
$form = $this->createForm(ChangePasswordFormType::class);
|
||||
$form->handleRequest($request);
|
||||
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
// A password reset token should be used only once, remove it.
|
||||
$this->resetPasswordHelper->removeResetRequest($token);
|
||||
|
||||
/** @var string $plainPassword */
|
||||
$plainPassword = $form->get('plainPassword')->getData();
|
||||
|
||||
// Encode(hash) the plain password, and set it.
|
||||
$user->setPassword($passwordHasher->hashPassword($user, $plainPassword));
|
||||
$this->entityManager->flush();
|
||||
|
||||
// The session is cleaned up after the password has been changed.
|
||||
$this->cleanSessionAfterReset();
|
||||
|
||||
$this->addFlash('reset_password_success', 'Votre mot de passe a été réinitialisé avec succès.');
|
||||
|
||||
return $this->redirectToRoute('app_home');
|
||||
}
|
||||
|
||||
return $this->render('reset_password/reset.html.twig', [
|
||||
'resetForm' => $form,
|
||||
]);
|
||||
}
|
||||
|
||||
private function processSendingPasswordResetEmail(string $emailFormData, MailerInterface $mailer): RedirectResponse
|
||||
{
|
||||
$user = $this->entityManager->getRepository(User::class)->findOneBy([
|
||||
'email' => $emailFormData,
|
||||
]);
|
||||
|
||||
// Do not reveal whether a user account was found or not.
|
||||
if (null === $user) {
|
||||
return $this->redirectToRoute('app_check_email');
|
||||
}
|
||||
|
||||
try {
|
||||
$resetToken = $this->resetPasswordHelper->generateResetToken($user);
|
||||
} catch (ResetPasswordExceptionInterface) {
|
||||
// If you want to tell the user why a reset email was not sent, uncomment
|
||||
// the lines below and change the redirect to 'app_forgot_password_request'.
|
||||
// Caution: This may reveal if a user is registered or not.
|
||||
//
|
||||
// $this->addFlash('reset_password_error', sprintf(
|
||||
// '%s - %s',
|
||||
// $translator->trans(ResetPasswordExceptionInterface::MESSAGE_PROBLEM_HANDLE, [], 'ResetPasswordBundle'),
|
||||
// $translator->trans($e->getReason(), [], 'ResetPasswordBundle')
|
||||
// ));
|
||||
|
||||
return $this->redirectToRoute('app_check_email');
|
||||
}
|
||||
|
||||
$email = (new TemplatedEmail())
|
||||
->from(new Address('contact@camelia-studio.org', 'Camélia Studio'))
|
||||
->to((string) $user->getEmail())
|
||||
->subject('Kumora - Votre demande de réinitialisation de mot de passe')
|
||||
->htmlTemplate('reset_password/email.html.twig')
|
||||
->context([
|
||||
'resetToken' => $resetToken,
|
||||
])
|
||||
;
|
||||
|
||||
$mailer->send($email);
|
||||
|
||||
// Store the token object in session for retrieval in check-email route.
|
||||
$this->setTokenObjectInSession($resetToken);
|
||||
|
||||
return $this->redirectToRoute('app_check_email');
|
||||
}
|
||||
}
|
38
src/Entity/ResetPasswordRequest.php
Normal file
38
src/Entity/ResetPasswordRequest.php
Normal file
@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use App\Repository\ResetPasswordRequestRepository;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use SymfonyCasts\Bundle\ResetPassword\Model\ResetPasswordRequestInterface;
|
||||
use SymfonyCasts\Bundle\ResetPassword\Model\ResetPasswordRequestTrait;
|
||||
|
||||
#[ORM\Entity(repositoryClass: ResetPasswordRequestRepository::class)]
|
||||
class ResetPasswordRequest implements ResetPasswordRequestInterface
|
||||
{
|
||||
use ResetPasswordRequestTrait;
|
||||
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
#[ORM\Column]
|
||||
private ?int $id = null;
|
||||
|
||||
public function __construct(#[ORM\ManyToOne]
|
||||
#[ORM\JoinColumn(nullable: false)]
|
||||
private ?User $user, \DateTimeInterface $expiresAt, string $selector, string $hashedToken)
|
||||
{
|
||||
$this->initialize($expiresAt, $selector, $hashedToken);
|
||||
}
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getUser(): User
|
||||
{
|
||||
return $this->user;
|
||||
}
|
||||
}
|
67
src/Form/ChangePasswordFormType.php
Normal file
67
src/Form/ChangePasswordFormType.php
Normal file
@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Form;
|
||||
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\RepeatedType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use Symfony\Component\Validator\Constraints\Length;
|
||||
use Symfony\Component\Validator\Constraints\NotBlank;
|
||||
use Symfony\Component\Validator\Constraints\NotCompromisedPassword;
|
||||
use Symfony\Component\Validator\Constraints\PasswordStrength;
|
||||
|
||||
class ChangePasswordFormType extends AbstractType
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$builder
|
||||
->add('plainPassword', RepeatedType::class, [
|
||||
'type' => PasswordType::class,
|
||||
'options' => [
|
||||
'attr' => [
|
||||
'autocomplete' => 'new-password',
|
||||
],
|
||||
],
|
||||
'first_options' => [
|
||||
'constraints' => [
|
||||
new NotBlank([
|
||||
'message' => 'Merci de renseigner un mot de passe',
|
||||
]),
|
||||
new Length([
|
||||
'min' => 12,
|
||||
'minMessage' => 'Votre mot de passe doit contenir au moins {{ limit }} caractères',
|
||||
// max length allowed by Symfony for security reasons
|
||||
'max' => 4096,
|
||||
]),
|
||||
new NotCompromisedPassword([
|
||||
'message' => 'Ce mot de passe a été exposé lors d\'une fuite de données, il ne peut pas être utilisé.',
|
||||
]),
|
||||
],
|
||||
'label' => 'Nouveau mot de passe',
|
||||
],
|
||||
'second_options' => [
|
||||
'label' => 'Répéter le mot de passe',
|
||||
],
|
||||
'invalid_message' => 'Les mots de passe ne correspondent pas.',
|
||||
// Instead of being set onto the object directly,
|
||||
// this is read and encoded in the controller
|
||||
'mapped' => false,
|
||||
])
|
||||
->add('submit', SubmitType::class, [
|
||||
'label' => 'Mettre à jour',
|
||||
])
|
||||
;
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'csrf_protection' => false,
|
||||
]);
|
||||
}
|
||||
}
|
39
src/Form/ResetPasswordRequestFormType.php
Normal file
39
src/Form/ResetPasswordRequestFormType.php
Normal file
@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Form;
|
||||
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\EmailType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use Symfony\Component\Validator\Constraints\NotBlank;
|
||||
|
||||
class ResetPasswordRequestFormType extends AbstractType
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$builder
|
||||
->add('email', EmailType::class, [
|
||||
'attr' => ['autocomplete' => 'email'],
|
||||
'constraints' => [
|
||||
new NotBlank([
|
||||
'message' => 'Merci de renseigner votre adresse email',
|
||||
]),
|
||||
],
|
||||
])
|
||||
->add('submit', SubmitType::class, [
|
||||
'label' => 'Envoyer',
|
||||
])
|
||||
;
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'csrf_protection' => false,
|
||||
]);
|
||||
}
|
||||
}
|
34
src/Repository/ResetPasswordRequestRepository.php
Normal file
34
src/Repository/ResetPasswordRequestRepository.php
Normal file
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Repository;
|
||||
|
||||
use App\Entity\ResetPasswordRequest;
|
||||
use App\Entity\User;
|
||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
use SymfonyCasts\Bundle\ResetPassword\Model\ResetPasswordRequestInterface;
|
||||
use SymfonyCasts\Bundle\ResetPassword\Persistence\Repository\ResetPasswordRequestRepositoryTrait;
|
||||
use SymfonyCasts\Bundle\ResetPassword\Persistence\ResetPasswordRequestRepositoryInterface;
|
||||
|
||||
/**
|
||||
* @extends ServiceEntityRepository<ResetPasswordRequest>
|
||||
*/
|
||||
class ResetPasswordRequestRepository extends ServiceEntityRepository implements ResetPasswordRequestRepositoryInterface
|
||||
{
|
||||
use ResetPasswordRequestRepositoryTrait;
|
||||
|
||||
public function __construct(ManagerRegistry $registry)
|
||||
{
|
||||
parent::__construct($registry, ResetPasswordRequest::class);
|
||||
}
|
||||
|
||||
public function createResetPasswordRequest(object $user, \DateTimeInterface $expiresAt, string $selector, string $hashedToken): ResetPasswordRequestInterface
|
||||
{
|
||||
if (!$user instanceof User) {
|
||||
throw new \InvalidArgumentException('User must be an instance of ' . User::class);
|
||||
}
|
||||
return new ResetPasswordRequest($user, $expiresAt, $selector, $hashedToken);
|
||||
}
|
||||
}
|
12
symfony.lock
12
symfony.lock
@ -380,6 +380,18 @@
|
||||
"config/routes/web_profiler.yaml"
|
||||
]
|
||||
},
|
||||
"symfonycasts/reset-password-bundle": {
|
||||
"version": "1.23",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "1.0",
|
||||
"ref": "97c1627c0384534997ae1047b93be517ca16de43"
|
||||
},
|
||||
"files": [
|
||||
"config/packages/reset_password.yaml"
|
||||
]
|
||||
},
|
||||
"symfonycasts/tailwind-bundle": {
|
||||
"version": "v0.6.1"
|
||||
},
|
||||
|
@ -8,5 +8,8 @@ module.exports = {
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
plugins: [],
|
||||
plugins: [
|
||||
require('flowbite/plugin')
|
||||
],
|
||||
darkMode: 'media',
|
||||
}
|
||||
|
@ -13,6 +13,7 @@
|
||||
</head>
|
||||
<body data-turbo="false">
|
||||
{% include "partials/navbar.html.twig" %}
|
||||
{% include "partials/alerts.html.twig" %}
|
||||
{% block body %}{% endblock %}
|
||||
</body>
|
||||
</html>
|
||||
|
@ -1,21 +1,23 @@
|
||||
{% for label, messages in app.flashes %}
|
||||
{% for message in messages %}
|
||||
{% if label == 'success' %}
|
||||
<div class="border border-green-300 dark:border-green-800 p-4 mb-4 text-sm text-green-800 rounded-lg bg-green-50 dark:bg-gray-800 dark:text-green-400" role="alert">
|
||||
{{ message }}
|
||||
</div>
|
||||
{% elseif label == 'info' %}
|
||||
<div class="border border-blue-300 dark:border-blue-800 p-4 mb-4 text-sm text-blue-800 rounded-lg bg-blue-50 dark:bg-gray-800 dark:text-blue-400" role="alert">
|
||||
{{ message }}
|
||||
</div>
|
||||
{% elseif label == 'error' %}
|
||||
<div class="border border-red-300 dark:border-red-800 p-4 mb-4 text-sm text-red-800 rounded-lg bg-red-50 dark:bg-gray-800 dark:text-red-400" role="alert">
|
||||
{{ message }}
|
||||
</div>
|
||||
{% elseif label == 'warning' %}
|
||||
<div class="border border-yellow-300 dark:border-yellow-800 p-4 mb-4 text-sm text-yellow-800 rounded-lg bg-yellow-50 dark:bg-gray-800 dark:text-yellow-300" role="alert">
|
||||
{{ message }}
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="container mx-auto px-16 mt-4">
|
||||
{% for label, messages in app.flashes %}
|
||||
{% for message in messages %}
|
||||
{% if label == 'success' %}
|
||||
<div class="border border-green-300 dark:border-green-800 p-4 mb-4 text-sm text-green-800 rounded-lg bg-green-50 dark:bg-gray-800 dark:text-green-400" role="alert">
|
||||
{{ message }}
|
||||
</div>
|
||||
{% elseif label == 'info' %}
|
||||
<div class="border border-blue-300 dark:border-blue-800 p-4 mb-4 text-sm text-blue-800 rounded-lg bg-blue-50 dark:bg-gray-800 dark:text-blue-400" role="alert">
|
||||
{{ message }}
|
||||
</div>
|
||||
{% elseif label == 'error' %}
|
||||
<div class="border border-red-300 dark:border-red-800 p-4 mb-4 text-sm text-red-800 rounded-lg bg-red-50 dark:bg-gray-800 dark:text-red-400" role="alert">
|
||||
{{ message }}
|
||||
</div>
|
||||
{% elseif label == 'warning' %}
|
||||
<div class="border border-yellow-300 dark:border-yellow-800 p-4 mb-4 text-sm text-yellow-800 rounded-lg bg-yellow-50 dark:bg-gray-800 dark:text-yellow-300" role="alert">
|
||||
{{ message }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
</div>
|
15
templates/reset_password/check_email.html.twig
Normal file
15
templates/reset_password/check_email.html.twig
Normal file
@ -0,0 +1,15 @@
|
||||
{% extends 'base.html.twig' %}
|
||||
|
||||
{% block title %}Password Reset Email Sent{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
|
||||
<div class="container mx-auto px-16 mt-4">
|
||||
<div class="p-6 bg-white border border-gray-200 rounded-lg shadow dark:bg-gray-800 dark:border-gray-700">
|
||||
<p> Si un compte correspondant à votre adresse e-mail existe, un e-mail contenant un lien que vous pouvez utiliser pour réinitialiser votre mot de passe vient d'être envoyé. Ce lien expirera dans 1 heure.
|
||||
</p>
|
||||
<p>Si vous ne recevez pas d'e-mail, veuillez vérifier votre dossier spam ou <a class="font-medium text-blue-600 dark:text-blue-500 hover:underline" href="{{ path('app_forgot_password_request') }}">réessayer</a>.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
10
templates/reset_password/email.html.twig
Normal file
10
templates/reset_password/email.html.twig
Normal file
@ -0,0 +1,10 @@
|
||||
<h1>Bonjour !</h1>
|
||||
|
||||
<p>Pour réinitialiser ton mot de passe, merci d'utiliser ce lien :</p>
|
||||
|
||||
<a href="{{ url('app_reset_password', {token: resetToken.token}) }}">{{ url('app_reset_password', {token: resetToken.token}) }}</a>
|
||||
|
||||
<p>Ce lien expire dans {{ resetToken.expirationMessageKey|trans(resetToken.expirationMessageData, 'ResetPasswordBundle') }}.</p>
|
||||
|
||||
<p>A bientôt</p>
|
||||
<p>Camélia Studio - Kumora</p>
|
27
templates/reset_password/request.html.twig
Normal file
27
templates/reset_password/request.html.twig
Normal file
@ -0,0 +1,27 @@
|
||||
{% extends 'base.html.twig' %}
|
||||
|
||||
{% block title %}Reset your password{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div class="container mx-auto px-16 mt-4">
|
||||
<div class="p-6 bg-white border border-gray-200 rounded-lg shadow dark:bg-gray-800 dark:border-gray-700">
|
||||
<h5 class="text-2xl font-bold tracking-tight text-gray-900 dark:text-white mb-4">Réinitialiser votre mot de passe</h5>
|
||||
{% for flash_error in app.flashes('reset_password_error') %}
|
||||
<div class="flex items-center p-4 mb-4 text-sm text-red-800 border border-red-300 rounded-lg bg-red-50 dark:bg-gray-800 dark:text-red-400 dark:border-red-800" role="alert">
|
||||
<div>
|
||||
{{ flash_error }}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{{ form_start(requestForm) }}
|
||||
{{ form_row(requestForm.email) }}
|
||||
<div class="-mt-3 mb-4">
|
||||
<small>
|
||||
Entrez votre adresse email, et nous vous enverrons un lien pour réinitialiser votre mot de passe.
|
||||
</small>
|
||||
</div>
|
||||
{{ form_row(requestForm.submit) }}
|
||||
{{ form_end(requestForm) }}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
16
templates/reset_password/reset.html.twig
Normal file
16
templates/reset_password/reset.html.twig
Normal file
@ -0,0 +1,16 @@
|
||||
{% extends 'base.html.twig' %}
|
||||
|
||||
{% block title %}Reset your password{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
|
||||
<div class="container mx-auto px-16 mt-4">
|
||||
<div class="p-6 bg-white border border-gray-200 rounded-lg shadow dark:bg-gray-800 dark:border-gray-700">
|
||||
<h5 class="text-2xl font-bold tracking-tight text-gray-900 dark:text-white mb-4">Réinitialiser votre mot de passe</h5>
|
||||
{{ form_start(resetForm) }}
|
||||
{{ form_row(resetForm.plainPassword) }}
|
||||
{{ form_row(resetForm.submit) }}
|
||||
{{ form_end(resetForm) }}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
@ -29,7 +29,11 @@
|
||||
<input type="hidden" name="_csrf_token" data-controller="csrf-protection"
|
||||
value="{{ csrf_token('authenticate') }}"
|
||||
>
|
||||
<button type="submit" class="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 dark:bg-blue-600 dark:hover:bg-blue-700 focus:outline-none dark:focus:ring-blue-800">Se connecter</button>
|
||||
<div class="flex gap-3 items-center">
|
||||
<button type="submit" class="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 dark:bg-blue-600 dark:hover:bg-blue-700 focus:outline-none dark:focus:ring-blue-800">Se connecter</button>
|
||||
<a href="{{ path('app_forgot_password_request') }}" class="font-medium text-blue-600 dark:text-blue-500 hover:underline">Mot de passe oublié ?</a>
|
||||
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
Loading…
x
Reference in New Issue
Block a user