From e8df5bf01962c943a805c52d0d6f6c37264043f1 Mon Sep 17 00:00:00 2001 From: Skylar Sadlier Date: Sat, 9 Sep 2023 03:29:51 -0600 Subject: [PATCH] Initial commit --- .env | 10 +- .gitignore | 3 + LICENSE | 20 +- composer.json | 3 + composer.lock | 403 ++++++++++++++++++++++++- config/packages/lock.yaml | 2 + config/packages/security.yaml | 44 ++- docker-compose.yml | 2 +- migrations/Version20230909083131.php | 37 +++ src/Controller/DashboardController.php | 20 ++ src/Controller/SecurityController.php | 34 +++ src/Entity/Domain.php | 129 ++++++++ src/Entity/Email.php | 111 +++++++ src/Entity/User.php | 100 ++++++ src/Repository/DomainRepository.php | 48 +++ src/Repository/EmailRepository.php | 48 +++ src/Repository/UserRepository.php | 67 ++++ symfony.lock | 21 ++ templates/dashboard/index.html.twig | 20 ++ templates/login/index.html.twig | 20 ++ 20 files changed, 1132 insertions(+), 10 deletions(-) create mode 100644 config/packages/lock.yaml create mode 100644 migrations/Version20230909083131.php create mode 100644 src/Controller/DashboardController.php create mode 100644 src/Controller/SecurityController.php create mode 100644 src/Entity/Domain.php create mode 100644 src/Entity/Email.php create mode 100644 src/Entity/User.php create mode 100644 src/Repository/DomainRepository.php create mode 100644 src/Repository/EmailRepository.php create mode 100644 src/Repository/UserRepository.php create mode 100644 templates/dashboard/index.html.twig create mode 100644 templates/login/index.html.twig diff --git a/.env b/.env index 3f10f85..e65fe83 100644 --- a/.env +++ b/.env @@ -25,8 +25,8 @@ APP_SECRET=0ac2ef94aa7fbf17c0fa7caca5d59ba8 # # DATABASE_URL="sqlite:///%kernel.project_dir%/var/data.db" # DATABASE_URL="mysql://app:!ChangeMe!@127.0.0.1:3306/app?serverVersion=8.0.32&charset=utf8mb4" -# DATABASE_URL="mysql://app:!ChangeMe!@127.0.0.1:3306/app?serverVersion=10.11.2-MariaDB&charset=utf8mb4" -DATABASE_URL="postgresql://app:!ChangeMe!@127.0.0.1:5432/app?serverVersion=15&charset=utf8" +# DATABASE_URL="mysql://MyComments:Plentiful-Easiness-Gracious6-Boggle@127.0.0.1:3306/MyComments?serverVersion=11.1.2-MariaDB&charset=utf8mb4" +DATABASE_URL="postgresql://app:Liqueur-Battalion7-Woof-Spokesman@127.0.0.1:5432/app?serverVersion=15&charset=utf8" ###< doctrine/doctrine-bundle ### ###> symfony/messenger ### @@ -39,3 +39,9 @@ MESSENGER_TRANSPORT_DSN=doctrine://default?auto_setup=0 ###> symfony/mailer ### # MAILER_DSN=null://null ###< symfony/mailer ### + +###> symfony/lock ### +# Choose one of the stores below +# postgresql+advisory://db_user:db_password@localhost/db_name +LOCK_DSN=flock +###< symfony/lock ### diff --git a/.gitignore b/.gitignore index de562d7..0e4d75d 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,6 @@ .phpunit.result.cache /phpunit.xml ###< symfony/phpunit-bridge ### + +# IDE folders +.idea \ No newline at end of file diff --git a/LICENSE b/LICENSE index 036929f..9cd6b96 100644 --- a/LICENSE +++ b/LICENSE @@ -1,9 +1,21 @@ MIT License -Copyright (c) 2024 skylord123 +Copyright (c) 2023 MyComments -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/composer.json b/composer.json index 672b705..bf7b9fb 100644 --- a/composer.json +++ b/composer.json @@ -10,6 +10,7 @@ "doctrine/doctrine-bundle": "^2.10", "doctrine/doctrine-migrations-bundle": "^3.2", "doctrine/orm": "^2.16", + "gedmo/doctrine-extensions": "^3.13", "phpdocumentor/reflection-docblock": "^5.3", "phpstan/phpdoc-parser": "^1.24", "symfony/asset": "6.3.*", @@ -22,6 +23,7 @@ "symfony/framework-bundle": "6.3.*", "symfony/http-client": "6.3.*", "symfony/intl": "6.3.*", + "symfony/lock": "6.3.*", "symfony/mailer": "6.3.*", "symfony/mime": "6.3.*", "symfony/monolog-bundle": "^3.0", @@ -29,6 +31,7 @@ "symfony/process": "6.3.*", "symfony/property-access": "6.3.*", "symfony/property-info": "6.3.*", + "symfony/rate-limiter": "6.3.*", "symfony/runtime": "6.3.*", "symfony/security-bundle": "6.3.*", "symfony/serializer": "6.3.*", diff --git a/composer.lock b/composer.lock index 940c784..c491893 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,133 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "e077926b0d83c217ece524f4a3d61fa8", + "content-hash": "0e74667a94096c60a3ca270a79c1331d", "packages": [ + { + "name": "behat/transliterator", + "version": "v1.5.0", + "source": { + "type": "git", + "url": "https://github.com/Behat/Transliterator.git", + "reference": "baac5873bac3749887d28ab68e2f74db3a4408af" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Behat/Transliterator/zipball/baac5873bac3749887d28ab68e2f74db3a4408af", + "reference": "baac5873bac3749887d28ab68e2f74db3a4408af", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "require-dev": { + "chuyskywalker/rolling-curl": "^3.1", + "php-yaoi/php-yaoi": "^1.0", + "phpunit/phpunit": "^8.5.25 || ^9.5.19" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Behat\\Transliterator\\": "src/Behat/Transliterator" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Artistic-1.0" + ], + "description": "String transliterator", + "keywords": [ + "i18n", + "slug", + "transliterator" + ], + "support": { + "issues": "https://github.com/Behat/Transliterator/issues", + "source": "https://github.com/Behat/Transliterator/tree/v1.5.0" + }, + "time": "2022-03-30T09:27:43+00:00" + }, + { + "name": "doctrine/annotations", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/annotations.git", + "reference": "e157ef3f3124bbf6fe7ce0ffd109e8a8ef284e7f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/annotations/zipball/e157ef3f3124bbf6fe7ce0ffd109e8a8ef284e7f", + "reference": "e157ef3f3124bbf6fe7ce0ffd109e8a8ef284e7f", + "shasum": "" + }, + "require": { + "doctrine/lexer": "^2 || ^3", + "ext-tokenizer": "*", + "php": "^7.2 || ^8.0", + "psr/cache": "^1 || ^2 || ^3" + }, + "require-dev": { + "doctrine/cache": "^2.0", + "doctrine/coding-standard": "^10", + "phpstan/phpstan": "^1.8.0", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "symfony/cache": "^5.4 || ^6", + "vimeo/psalm": "^4.10" + }, + "suggest": { + "php": "PHP 8.0 or higher comes with attributes, a native replacement for annotations" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Common\\Annotations\\": "lib/Doctrine/Common/Annotations" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Docblock Annotations Parser", + "homepage": "https://www.doctrine-project.org/projects/annotations.html", + "keywords": [ + "annotations", + "docblock", + "parser" + ], + "support": { + "issues": "https://github.com/doctrine/annotations/issues", + "source": "https://github.com/doctrine/annotations/tree/2.0.1" + }, + "time": "2023-02-02T22:02:53+00:00" + }, { "name": "doctrine/cache", "version": "2.2.0", @@ -1387,6 +1512,133 @@ ], "time": "2023-01-14T14:17:03+00:00" }, + { + "name": "gedmo/doctrine-extensions", + "version": "v3.13.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine-extensions/DoctrineExtensions.git", + "reference": "291d0c527d2dc9ee07b888c9a4e2a179893f08ab" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine-extensions/DoctrineExtensions/zipball/291d0c527d2dc9ee07b888c9a4e2a179893f08ab", + "reference": "291d0c527d2dc9ee07b888c9a4e2a179893f08ab", + "shasum": "" + }, + "require": { + "behat/transliterator": "^1.2", + "doctrine/annotations": "^1.13 || ^2.0", + "doctrine/collections": "^1.2 || ^2.0", + "doctrine/common": "^2.13 || ^3.0", + "doctrine/event-manager": "^1.2 || ^2.0", + "doctrine/persistence": "^2.2 || ^3.0", + "php": "^7.2 || ^8.0", + "psr/cache": "^1 || ^2 || ^3", + "symfony/cache": "^4.4 || ^5.3 || ^6.0", + "symfony/deprecation-contracts": "^2.1 || ^3.0" + }, + "conflict": { + "doctrine/dbal": "<2.13.1 || ^3.0 <3.2", + "doctrine/mongodb-odm": "<2.3", + "doctrine/orm": "<2.10.2 || 2.16.0 || 2.16.1", + "sebastian/comparator": "<2.0" + }, + "require-dev": { + "doctrine/cache": "^1.11 || ^2.0", + "doctrine/dbal": "^2.13.1 || ^3.2", + "doctrine/doctrine-bundle": "^2.3", + "doctrine/mongodb-odm": "^2.3", + "doctrine/orm": "^2.10.2", + "friendsofphp/php-cs-fixer": "^3.4.0 <3.10", + "nesbot/carbon": "^2.55", + "phpstan/phpstan": "^1.10.2", + "phpstan/phpstan-doctrine": "^1.0", + "phpstan/phpstan-phpunit": "^1.0", + "phpunit/phpunit": "^8.5 || ^9.5", + "rector/rector": "^0.15.20", + "symfony/console": "^4.4 || ^5.3 || ^6.0", + "symfony/phpunit-bridge": "^6.0", + "symfony/yaml": "^4.4 || ^5.3 || ^6.0" + }, + "suggest": { + "doctrine/mongodb-odm": "to use the extensions with the MongoDB ODM", + "doctrine/orm": "to use the extensions with the ORM" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.13-dev" + } + }, + "autoload": { + "psr-4": { + "Gedmo\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gediminas Morkevicius", + "email": "gediminas.morkevicius@gmail.com" + }, + { + "name": "Gustavo Falco", + "email": "comfortablynumb84@gmail.com" + }, + { + "name": "David Buchmann", + "email": "david@liip.ch" + } + ], + "description": "Doctrine behavioral extensions", + "homepage": "http://gediminasm.org/", + "keywords": [ + "Blameable", + "behaviors", + "doctrine", + "extensions", + "gedmo", + "loggable", + "nestedset", + "odm", + "orm", + "sluggable", + "sortable", + "timestampable", + "translatable", + "tree", + "uploadable" + ], + "support": { + "email": "gediminas.morkevicius@gmail.com", + "issues": "https://github.com/doctrine-extensions/DoctrineExtensions/issues", + "source": "https://github.com/doctrine-extensions/DoctrineExtensions/tree/v3.13.0", + "wiki": "https://github.com/Atlantic18/DoctrineExtensions/tree/main/doc" + }, + "funding": [ + { + "url": "https://github.com/l3pp4rd", + "type": "github" + }, + { + "url": "https://github.com/mbabker", + "type": "github" + }, + { + "url": "https://github.com/phansys", + "type": "github" + }, + { + "url": "https://github.com/stof", + "type": "github" + } + ], + "time": "2023-09-06T13:16:12+00:00" + }, { "name": "monolog/monolog", "version": "3.4.0", @@ -4061,6 +4313,85 @@ ], "time": "2023-07-20T07:43:09+00:00" }, + { + "name": "symfony/lock", + "version": "v6.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/lock.git", + "reference": "cde6dbd72d217024b1049d42a4519e713e2f18af" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/lock/zipball/cde6dbd72d217024b1049d42a4519e713e2f18af", + "reference": "cde6dbd72d217024b1049d42a4519e713e2f18af", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/log": "^1|^2|^3", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "conflict": { + "doctrine/dbal": "<2.13", + "symfony/cache": "<6.2" + }, + "require-dev": { + "doctrine/dbal": "^2.13|^3.0", + "predis/predis": "^1.1|^2.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Lock\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jérémy Derussé", + "email": "jeremy@derusse.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Creates and manages locks, a mechanism to provide exclusive access to a shared resource", + "homepage": "https://symfony.com", + "keywords": [ + "cas", + "flock", + "locking", + "mutex", + "redlock", + "semaphore" + ], + "support": { + "source": "https://github.com/symfony/lock/tree/v6.3.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-06-28T15:25:15+00:00" + }, { "name": "symfony/mailer", "version": "v6.3.0", @@ -5410,6 +5741,76 @@ ], "time": "2023-05-19T08:06:44+00:00" }, + { + "name": "symfony/rate-limiter", + "version": "v6.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/rate-limiter.git", + "reference": "5223387e4017f26c8c61c46d8e39c11fe4d504b7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/rate-limiter/zipball/5223387e4017f26c8c61c46d8e39c11fe4d504b7", + "reference": "5223387e4017f26c8c61c46d8e39c11fe4d504b7", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/options-resolver": "^5.4|^6.0" + }, + "require-dev": { + "psr/cache": "^1.0|^2.0|^3.0", + "symfony/lock": "^5.4|^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\RateLimiter\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Wouter de Jong", + "email": "wouter@wouterj.nl" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides a Token Bucket implementation to rate limit input and output in your application", + "homepage": "https://symfony.com", + "keywords": [ + "limiter", + "rate-limiter" + ], + "support": { + "source": "https://github.com/symfony/rate-limiter/tree/v6.3.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-07-13T14:29:38+00:00" + }, { "name": "symfony/routing", "version": "v6.3.3", diff --git a/config/packages/lock.yaml b/config/packages/lock.yaml new file mode 100644 index 0000000..574879f --- /dev/null +++ b/config/packages/lock.yaml @@ -0,0 +1,2 @@ +framework: + lock: '%env(LOCK_DSN)%' diff --git a/config/packages/security.yaml b/config/packages/security.yaml index 367af25..54e375e 100644 --- a/config/packages/security.yaml +++ b/config/packages/security.yaml @@ -1,17 +1,57 @@ +framework: + rate_limiter: + # define 2 rate limiters (one for username+IP, the other for IP) + username_ip_login: + policy: token_bucket + limit: 5 + rate: { interval: '5 minutes' } + + ip_login: + policy: sliding_window + limit: 40 + interval: '30 minutes' + +services: + # our custom login rate limiter + app.login_rate_limiter: + class: Symfony\Component\Security\Http\RateLimiter\DefaultLoginRateLimiter + arguments: + # globalFactory is the limiter for IP + $globalFactory: '@limiter.ip_login' + # localFactory is the limiter for username+IP + $localFactory: '@limiter.username_ip_login' + security: # https://symfony.com/doc/current/security.html#registering-the-user-hashing-passwords password_hashers: 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\User + 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 + + #logout: + # path: app_logout + # where to redirect after logout + # target: app_any_route + + # configure the maximum login attempts + login_throttling: + limiter: app.login_rate_limiter # activate different ways to authenticate # https://symfony.com/doc/current/security.html#the-firewall diff --git a/docker-compose.yml b/docker-compose.yml index 1067b9c..c2871ca 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,7 +7,7 @@ services: environment: POSTGRES_DB: ${POSTGRES_DB:-app} # You should definitely change the password in production - POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-!ChangeMe!} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-Liqueur-Battalion7-Woof-Spokesman} POSTGRES_USER: ${POSTGRES_USER:-app} volumes: - database_data:/var/lib/postgresql/data:rw diff --git a/migrations/Version20230909083131.php b/migrations/Version20230909083131.php new file mode 100644 index 0000000..02970d3 --- /dev/null +++ b/migrations/Version20230909083131.php @@ -0,0 +1,37 @@ +addSql('CREATE TABLE domain (id INT AUTO_INCREMENT NOT NULL, domain VARCHAR(255) NOT NULL, owner_token VARCHAR(64) NOT NULL, name VARCHAR(255) NOT NULL, enabled TINYINT(1) NOT NULL, default_sort_policy VARCHAR(32) NOT NULL, updated_at DATETIME DEFAULT NULL, created_at DATETIME NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); + $this->addSql('CREATE TABLE email (id INT AUTO_INCREMENT NOT NULL, email VARCHAR(255) NOT NULL, unsubscribe_token VARCHAR(255) NOT NULL, last_notification_sent_at DATETIME DEFAULT NULL, pending_emails INT NOT NULL, send_reply_notifications TINYINT(1) NOT NULL, send_moderator_notifications TINYINT(1) NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); + $this->addSql('CREATE TABLE `user` (id INT AUTO_INCREMENT NOT NULL, email VARCHAR(180) NOT NULL, roles LONGTEXT NOT NULL COMMENT \'(DC2Type:json)\', password VARCHAR(255) NOT NULL, UNIQUE INDEX UNIQ_8D93D649E7927C74 (email), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); + $this->addSql('CREATE TABLE messenger_messages (id BIGINT AUTO_INCREMENT NOT NULL, body LONGTEXT NOT NULL, headers LONGTEXT NOT NULL, queue_name VARCHAR(190) NOT NULL, created_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', available_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', delivered_at DATETIME DEFAULT NULL COMMENT \'(DC2Type:datetime_immutable)\', INDEX IDX_75EA56E0FB7336F0 (queue_name), INDEX IDX_75EA56E0E3BD61CE (available_at), INDEX IDX_75EA56E016BA31DB (delivered_at), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('DROP TABLE domain'); + $this->addSql('DROP TABLE email'); + $this->addSql('DROP TABLE `user`'); + $this->addSql('DROP TABLE messenger_messages'); + } +} diff --git a/src/Controller/DashboardController.php b/src/Controller/DashboardController.php new file mode 100644 index 0000000..9e0c6a4 --- /dev/null +++ b/src/Controller/DashboardController.php @@ -0,0 +1,20 @@ +render('dashboard/index.html.twig', [ + 'controller_name' => 'DashboardController', + ]); + } +} diff --git a/src/Controller/SecurityController.php b/src/Controller/SecurityController.php new file mode 100644 index 0000000..aefbfaa --- /dev/null +++ b/src/Controller/SecurityController.php @@ -0,0 +1,34 @@ +getLastAuthenticationError(); + + // last username entered by the user + $lastUsername = $authenticationUtils->getLastUsername(); + + return $this->render('login/index.html.twig', [ + 'controller_name' => 'LoginController', + 'last_username' => $lastUsername, + 'error' => $error, + ]); + } + + #[Route('/logout', name: 'app_logout', methods: ['GET'])] + public function logout(Security $security): Response + { + return $security->logout(false); + } +} diff --git a/src/Entity/Domain.php b/src/Entity/Domain.php new file mode 100644 index 0000000..1138005 --- /dev/null +++ b/src/Entity/Domain.php @@ -0,0 +1,129 @@ +id; + } + + public function getDomain(): ?string + { + return $this->domain; + } + + public function setDomain(string $domain): static + { + $this->domain = $domain; + + return $this; + } + + public function getOwnerToken(): ?string + { + return $this->ownerToken; + } + + public function setOwnerToken(string $ownerToken): static + { + $this->ownerToken = $ownerToken; + + return $this; + } + + public function getName(): ?string + { + return $this->name; + } + + public function setName(string $name): static + { + $this->name = $name; + + return $this; + } + + public function isEnabled(): ?bool + { + return $this->enabled; + } + + public function setEnabled(bool $enabled): static + { + $this->enabled = $enabled; + + return $this; + } + + public function getDefaultSortPolicy(): ?string + { + return $this->defaultSortPolicy; + } + + public function setDefaultSortPolicy(string $defaultSortPolicy): static + { + $this->defaultSortPolicy = $defaultSortPolicy; + + return $this; + } + + public function getUpdatedAt(): ?\DateTimeInterface + { + return $this->updatedAt; + } + + public function setUpdatedAt(?\DateTimeInterface $updatedAt): static + { + $this->updatedAt = $updatedAt; + + return $this; + } + + public function getCreatedAt(): ?\DateTimeInterface + { + return $this->createdAt; + } + + public function setCreatedAt(\DateTimeInterface $createdAt): static + { + $this->createdAt = $createdAt; + + return $this; + } +} diff --git a/src/Entity/Email.php b/src/Entity/Email.php new file mode 100644 index 0000000..16bb9e3 --- /dev/null +++ b/src/Entity/Email.php @@ -0,0 +1,111 @@ +id; + } + + public function getEmail(): ?string + { + return $this->email; + } + + public function setEmail(string $email): static + { + $this->email = $email; + + return $this; + } + + public function getUnsubscribeToken(): ?string + { + return $this->unsubscribeToken; + } + + public function setUnsubscribeToken(string $unsubscribeToken): static + { + $this->unsubscribeToken = $unsubscribeToken; + + return $this; + } + + public function getLastNotificationSentAt(): ?\DateTimeInterface + { + return $this->lastNotificationSentAt; + } + + public function setLastNotificationSentAt(?\DateTimeInterface $lastNotificationSentAt): static + { + $this->lastNotificationSentAt = $lastNotificationSentAt; + + return $this; + } + + public function getPendingEmails(): ?int + { + return $this->pendingEmails; + } + + public function setPendingEmails(int $pendingEmails): static + { + $this->pendingEmails = $pendingEmails; + + return $this; + } + + public function isSendReplyNotifications(): ?bool + { + return $this->sendReplyNotifications; + } + + public function setSendReplyNotifications(bool $sendReplyNotifications): static + { + $this->sendReplyNotifications = $sendReplyNotifications; + + return $this; + } + + public function isSendModeratorNotifications(): ?bool + { + return $this->sendModeratorNotifications; + } + + public function setSendModeratorNotifications(bool $sendModeratorNotifications): static + { + $this->sendModeratorNotifications = $sendModeratorNotifications; + + return $this; + } +} diff --git a/src/Entity/User.php b/src/Entity/User.php new file mode 100644 index 0000000..940ddc6 --- /dev/null +++ b/src/Entity/User.php @@ -0,0 +1,100 @@ +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 + */ + public function getRoles(): array + { + $roles = $this->roles; + // guarantee every user at least has ROLE_USER + $roles[] = 'ROLE_USER'; + + return array_unique($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; + } +} diff --git a/src/Repository/DomainRepository.php b/src/Repository/DomainRepository.php new file mode 100644 index 0000000..157c74b --- /dev/null +++ b/src/Repository/DomainRepository.php @@ -0,0 +1,48 @@ + + * + * @method Domain|null find($id, $lockMode = null, $lockVersion = null) + * @method Domain|null findOneBy(array $criteria, array $orderBy = null) + * @method Domain[] findAll() + * @method Domain[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) + */ +class DomainRepository extends ServiceEntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, Domain::class); + } + +// /** +// * @return Domain[] Returns an array of Domain objects +// */ +// public function findByExampleField($value): array +// { +// return $this->createQueryBuilder('d') +// ->andWhere('d.exampleField = :val') +// ->setParameter('val', $value) +// ->orderBy('d.id', 'ASC') +// ->setMaxResults(10) +// ->getQuery() +// ->getResult() +// ; +// } + +// public function findOneBySomeField($value): ?Domain +// { +// return $this->createQueryBuilder('d') +// ->andWhere('d.exampleField = :val') +// ->setParameter('val', $value) +// ->getQuery() +// ->getOneOrNullResult() +// ; +// } +} diff --git a/src/Repository/EmailRepository.php b/src/Repository/EmailRepository.php new file mode 100644 index 0000000..f22b0ee --- /dev/null +++ b/src/Repository/EmailRepository.php @@ -0,0 +1,48 @@ + + * + * @method Email|null find($id, $lockMode = null, $lockVersion = null) + * @method Email|null findOneBy(array $criteria, array $orderBy = null) + * @method Email[] findAll() + * @method Email[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) + */ +class EmailRepository extends ServiceEntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, Email::class); + } + +// /** +// * @return Email[] Returns an array of Email objects +// */ +// public function findByExampleField($value): array +// { +// return $this->createQueryBuilder('e') +// ->andWhere('e.exampleField = :val') +// ->setParameter('val', $value) +// ->orderBy('e.id', 'ASC') +// ->setMaxResults(10) +// ->getQuery() +// ->getResult() +// ; +// } + +// public function findOneBySomeField($value): ?Email +// { +// return $this->createQueryBuilder('e') +// ->andWhere('e.exampleField = :val') +// ->setParameter('val', $value) +// ->getQuery() +// ->getOneOrNullResult() +// ; +// } +} diff --git a/src/Repository/UserRepository.php b/src/Repository/UserRepository.php new file mode 100644 index 0000000..c788f46 --- /dev/null +++ b/src/Repository/UserRepository.php @@ -0,0 +1,67 @@ + + * + * @implements PasswordUpgraderInterface + * + * @method User|null find($id, $lockMode = null, $lockVersion = null) + * @method User|null findOneBy(array $criteria, array $orderBy = null) + * @method User[] findAll() + * @method User[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) + */ +class UserRepository extends ServiceEntityRepository implements PasswordUpgraderInterface +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, User::class); + } + + /** + * Used to upgrade (rehash) the user's password automatically over time. + */ + public function upgradePassword(PasswordAuthenticatedUserInterface $user, string $newHashedPassword): void + { + if (!$user instanceof User) { + throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', $user::class)); + } + + $user->setPassword($newHashedPassword); + $this->getEntityManager()->persist($user); + $this->getEntityManager()->flush(); + } + +// /** +// * @return User[] Returns an array of User objects +// */ +// public function findByExampleField($value): array +// { +// return $this->createQueryBuilder('u') +// ->andWhere('u.exampleField = :val') +// ->setParameter('val', $value) +// ->orderBy('u.id', 'ASC') +// ->setMaxResults(10) +// ->getQuery() +// ->getResult() +// ; +// } + +// public function findOneBySomeField($value): ?User +// { +// return $this->createQueryBuilder('u') +// ->andWhere('u.exampleField = :val') +// ->setParameter('val', $value) +// ->getQuery() +// ->getOneOrNullResult() +// ; +// } +} diff --git a/symfony.lock b/symfony.lock index 11fe58d..adbf9e1 100644 --- a/symfony.lock +++ b/symfony.lock @@ -1,4 +1,13 @@ { + "doctrine/annotations": { + "version": "2.0", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "1.10", + "ref": "64d8583af5ea57b7afa4aba4b159907f3a148b05" + } + }, "doctrine/doctrine-bundle": { "version": "2.10", "recipe": { @@ -95,6 +104,18 @@ "src/Kernel.php" ] }, + "symfony/lock": { + "version": "6.3", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "5.2", + "ref": "8e937ff2b4735d110af1770f242c1107fdab4c8e" + }, + "files": [ + "config/packages/lock.yaml" + ] + }, "symfony/mailer": { "version": "6.3", "recipe": { diff --git a/templates/dashboard/index.html.twig b/templates/dashboard/index.html.twig new file mode 100644 index 0000000..6070b01 --- /dev/null +++ b/templates/dashboard/index.html.twig @@ -0,0 +1,20 @@ +{% extends 'base.html.twig' %} + +{% block title %}Hello DashboardController!{% endblock %} + +{% block body %} + + +
+

Hello {{ controller_name }}! ✅

+ + This friendly message is coming from: + +
+{% endblock %} diff --git a/templates/login/index.html.twig b/templates/login/index.html.twig new file mode 100644 index 0000000..8ede310 --- /dev/null +++ b/templates/login/index.html.twig @@ -0,0 +1,20 @@ +{% extends 'base.html.twig' %} + +{% block body %} + {% if error %} +
{{ error.messageKey|trans(error.messageData, 'security') }}
+ {% endif %} + +
+ + + + + + + {# If you want to control the URL the user is redirected to on success + #} + + +
+{% endblock %} \ No newline at end of file