diff --git a/.env b/.env index 3cfd70d..d6b13e3 100644 --- a/.env +++ b/.env @@ -50,3 +50,9 @@ JWT_SECRET_KEY=%kernel.project_dir%/config/jwt/private.pem JWT_PUBLIC_KEY=%kernel.project_dir%/config/jwt/public.pem JWT_PASSPHRASE=827c9f8cce8bb82e75b2aec4a14a61f572ac28c7a8531f08dcdf1652573a7049 ###< lexik/jwt-authentication-bundle ### + +###> 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/assets/controllers.json b/assets/controllers.json index c00bb6d..29ea244 100644 --- a/assets/controllers.json +++ b/assets/controllers.json @@ -1,15 +1,15 @@ { - "controllers": { - "@symfony/ux-turbo": { - "turbo-core": { - "enabled": true, - "fetch": "eager" - }, - "mercure-turbo-stream": { - "enabled": false, - "fetch": "eager" - } - } - }, - "entrypoints": [] -} \ No newline at end of file + "controllers": { + "@symfony/ux-turbo": { + "turbo-core": { + "enabled": true, + "fetch": "eager" + }, + "mercure-turbo-stream": { + "enabled": false, + "fetch": "eager" + } + } + }, + "entrypoints": [] +} diff --git a/composer.json b/composer.json index 537c68c..5ecdd7a 100644 --- a/composer.json +++ b/composer.json @@ -46,6 +46,7 @@ "symfony/framework-bundle": "7.1.*", "symfony/http-client": "7.1.*", "symfony/intl": "7.1.*", + "symfony/lock": "7.1.*", "symfony/mailer": "7.1.*", "symfony/mime": "7.1.*", "symfony/monolog-bundle": "^3.0", @@ -53,6 +54,7 @@ "symfony/process": "7.1.*", "symfony/property-access": "7.1.*", "symfony/property-info": "7.1.*", + "symfony/rate-limiter": "7.1.*", "symfony/runtime": "7.1.*", "symfony/scheduler": "7.1.*", "symfony/security-bundle": "7.1.*", diff --git a/composer.lock b/composer.lock index 9548ff9..ba02405 100644 --- a/composer.lock +++ b/composer.lock @@ -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": "2e69cb333159db20699449d20ab40c4a", + "content-hash": "a8eb6290221266a2746c56c3cfa4c5d7", "packages": [ { "name": "api-platform/core", @@ -5056,6 +5056,84 @@ ], "time": "2024-05-31T14:57:53+00:00" }, + { + "name": "symfony/lock", + "version": "v7.1.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/lock.git", + "reference": "1f8c941f1270dee046e09a826bcdd3b2ebada45e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/lock/zipball/1f8c941f1270dee046e09a826bcdd3b2ebada45e", + "reference": "1f8c941f1270dee046e09a826bcdd3b2ebada45e", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "psr/log": "^1|^2|^3" + }, + "conflict": { + "doctrine/dbal": "<3.6", + "symfony/cache": "<6.4" + }, + "require-dev": { + "doctrine/dbal": "^3.6|^4", + "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/v7.1.1" + }, + "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": "2024-05-31T14:57:53+00:00" + }, { "name": "symfony/mailer", "version": "v7.1.2", @@ -6465,6 +6543,76 @@ ], "time": "2024-06-26T07:21:35+00:00" }, + { + "name": "symfony/rate-limiter", + "version": "v7.1.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/rate-limiter.git", + "reference": "f1fbc60e7fed63f1c77bbf8601170cc80fddd95a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/rate-limiter/zipball/f1fbc60e7fed63f1c77bbf8601170cc80fddd95a", + "reference": "f1fbc60e7fed63f1c77bbf8601170cc80fddd95a", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/options-resolver": "^6.4|^7.0" + }, + "require-dev": { + "psr/cache": "^1.0|^2.0|^3.0", + "symfony/lock": "^6.4|^7.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/v7.1.1" + }, + "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": "2024-05-31T14:57:53+00:00" + }, { "name": "symfony/routing", "version": "v7.1.1", diff --git a/config/packages/framework.yaml b/config/packages/framework.yaml index 877eb25..ab36b44 100644 --- a/config/packages/framework.yaml +++ b/config/packages/framework.yaml @@ -8,6 +8,11 @@ framework: #esi: true #fragments: true + rate_limiter: + authenticated_api: + policy: 'sliding_window' + limit: 25 + interval: '1 day' when@test: framework: 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/src/Controller/DomainRefreshController.php b/src/Controller/DomainRefreshController.php index a93a29f..51fefe9 100644 --- a/src/Controller/DomainRefreshController.php +++ b/src/Controller/DomainRefreshController.php @@ -8,12 +8,15 @@ use App\Service\RDAPService; use DateTimeImmutable; use Exception; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Component\HttpKernel\Exception\TooManyRequestsHttpException; +use Symfony\Component\RateLimiter\RateLimiterFactory; class DomainRefreshController extends AbstractController { - public function __construct(private readonly DomainRepository $domainRepository, - private readonly RDAPService $RDAPService) + public function __construct(private readonly DomainRepository $domainRepository, + private readonly RDAPService $RDAPService, + private readonly RateLimiterFactory $authenticatedApiLimiter) { } @@ -27,7 +30,11 @@ class DomainRefreshController extends AbstractController if ($domain === null || $domain->getUpdatedAt()->diff(new DateTimeImmutable('now'))->days >= 7) { - //TODO : Domain search rate limit here, before the RDAP request + $limiter = $this->authenticatedApiLimiter->create($this->getUser()->getUserIdentifier()); + if (false === $limiter->consume()->isAccepted()) { + throw new TooManyRequestsHttpException(); + } + $domain = $this->RDAPService->registerDomain($ldhName); } return $domain; diff --git a/symfony.lock b/symfony.lock index 424f873..5499387 100644 --- a/symfony.lock +++ b/symfony.lock @@ -148,6 +148,18 @@ "src/Kernel.php" ] }, + "symfony/lock": { + "version": "7.1", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "5.2", + "ref": "8e937ff2b4735d110af1770f242c1107fdab4c8e" + }, + "files": [ + "config/packages/lock.yaml" + ] + }, "symfony/mailer": { "version": "7.1", "recipe": {