-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Bartlomiej Chmura
committed
Sep 18, 2023
0 parents
commit 89419a9
Showing
19 changed files
with
836 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
on: | ||
push: | ||
branches: | ||
- master | ||
pull_request: | ||
branches: | ||
- master | ||
|
||
jobs: | ||
test: | ||
runs-on: ubuntu-latest | ||
|
||
steps: | ||
- uses: actions/checkout@v2 | ||
|
||
- name: Setup PHP | ||
uses: shivammathur/setup-php@v2 | ||
with: | ||
php-version: 8.2 | ||
|
||
- name: Install dependencies | ||
run: composer install | ||
|
||
- name: Run PHPUnit | ||
run: vendor/bin/phpunit |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
/.idea/ | ||
/vendor/ | ||
|
||
/.phpunit.cache | ||
/composer.lock |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
# Nuvola Cloudflare Turnstile Authenticator Bundle | ||
[![.github/workflows/main.yaml](https://github.com/nuvolapl/cf-turnstile-authenticator-bundle/actions/workflows/main.yaml/badge.svg)](https://github.com/nuvolapl/cf-turnstile-authenticator-bundle/actions/workflows/main.yaml) | ||
|
||
This bundle provides authentication based on the response from [Cloudflare Turnstile](https://www.cloudflare.com/products/turnstile/). | ||
|
||
## Configuration | ||
|
||
### To install the bundle, follow these steps: | ||
|
||
- The following parameters are required for bundle configuration in the `./config/packages/cf_turnstile_authenticator.yaml` file: | ||
|
||
```yaml | ||
cf_turnstile_authenticator: | ||
secret_key: '%env(string:CF_TURNSTILE_AUTHENTICATOR_SECRET_KEY)%' | ||
``` | ||
- add the `CF_TURNSTILE_AUTHENTICATOR_SECRET_KEY` environment variable to the `.env` file with a [dummy secret key](https://developers.cloudflare.com/turnstile/reference/testing/#dummy-sitekeys-and-secret-keys/) | ||
- add the `CF_TURNSTILE_AUTHENTICATOR_SECRET_KEY` environment variable to the `.env.local` file with the secret key from [Cloudflare Turnstile](https://www.cloudflare.com/products/turnstile/) | ||
|
||
## Installation | ||
|
||
### To install the bundle, follow these steps: | ||
|
||
- Run the following command to install the bundle: | ||
|
||
```shell | ||
composer require nuvola/cloudflare-turnstile-authenticator-bundle | ||
``` | ||
- add the bundle to the `./config/bundles.php` file: | ||
|
||
```php | ||
<?php | ||
// ... | ||
Nuvola\CloudflareTurnstileAuthenticatorBundle\CloudflareTurnstileAuthenticatorBundle::class => ['all' => true], | ||
// ... | ||
``` | ||
|
||
- to use the bundle, add the following code to the `./config/packages/security.yaml` file: | ||
|
||
```yaml | ||
security: | ||
# ... | ||
firewalls: | ||
# ... | ||
# adjust the name and pattern to your application! | ||
public: | ||
pattern: ^/api/public/ | ||
stateless: true | ||
custom_authenticators: | ||
- Nuvola\CloudflareTurnstileAuthenticatorBundle\Security\CloudflareTurnstileAuthenticator | ||
# ... | ||
access_control: | ||
- { path: ^/api/public/, roles: IS_AUTHENTICATED_FULLY } | ||
# ... | ||
``` | ||
|
||
After adding this configuration, only authenticated by [response token](https://developers.cloudflare.com/turnstile/get-started/client-side-rendering/) from the [Cloudflare Turnstile](https://developers.cloudflare.com/turnstile/get-started/server-side-validation/) will be passed. | ||
|
||
## Usage | ||
```shell | ||
curl -H "x-cf-turnstile-response: $RESPONSE" https://api.nuvola.pl/api/public/users/7ff847d9-a2e0-4f93-9c00-b59ecd51a766 | ||
``` | ||
- $RESPONSE is a variable that stores [the token retrieved](https://developers.cloudflare.com/turnstile/get-started/client-side-rendering/) in the web browser |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
{ | ||
"name": "nuvolapl/cf-turnstile-authenticator-bundle", | ||
"type": "library", | ||
"license": "proprietary", | ||
"minimum-stability": "dev", | ||
"prefer-stable": true, | ||
"require": { | ||
"php": "^8.2", | ||
"ext-json": "*", | ||
"symfony/config": "^6.3", | ||
"symfony/dependency-injection": "^6.3", | ||
"symfony/http-client": "^6.3", | ||
"symfony/http-foundation": "^6.3", | ||
"symfony/http-kernel": "^6.3", | ||
"symfony/security-core": "^6.3", | ||
"symfony/security-http": "^6.3", | ||
"symfony/uid": "^6.3", | ||
"symfony/yaml": "^6.3" | ||
}, | ||
"require-dev": { | ||
"phpunit/phpunit": "^10.3" | ||
}, | ||
"config": { | ||
"optimize-autoloader": true, | ||
"preferred-install": { | ||
"*": "dist" | ||
}, | ||
"sort-packages": true | ||
}, | ||
"autoload": { | ||
"psr-4": { | ||
"Nuvola\\CloudflareTurnstileAuthenticatorBundle\\": "src/" | ||
} | ||
}, | ||
"autoload-dev": { | ||
"psr-4": { | ||
"Nuvola\\CloudflareTurnstileAuthenticatorBundle\\Tests\\": "tests/" | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.3/phpunit.xsd" | ||
bootstrap="vendor/autoload.php" | ||
cacheDirectory=".phpunit.cache" | ||
executionOrder="depends,defects" | ||
requireCoverageMetadata="false" | ||
beStrictAboutCoverageMetadata="true" | ||
beStrictAboutOutputDuringTests="true" | ||
failOnRisky="true" | ||
failOnWarning="true"> | ||
<testsuites> | ||
<testsuite name="default"> | ||
<directory>tests</directory> | ||
</testsuite> | ||
</testsuites> | ||
|
||
<source restrictDeprecations="true" restrictNotices="true" restrictWarnings="true"> | ||
<include> | ||
<directory>src</directory> | ||
</include> | ||
</source> | ||
</phpunit> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Nuvola\CloudflareTurnstileAuthenticatorBundle; | ||
|
||
use Nuvola\CloudflareTurnstileAuthenticatorBundle\DependencyInjection\CloudflareTurnstileAuthenticatorExtension; | ||
use Symfony\Component\DependencyInjection\Extension\ExtensionInterface; | ||
use Symfony\Component\HttpKernel\Bundle\Bundle; | ||
|
||
final class CloudflareTurnstileAuthenticatorBundle extends Bundle | ||
{ | ||
public function getContainerExtension(): ?ExtensionInterface | ||
{ | ||
return new CloudflareTurnstileAuthenticatorExtension(); | ||
} | ||
} |
33 changes: 33 additions & 0 deletions
33
src/DependencyInjection/CloudflareTurnstileAuthenticatorExtension.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Nuvola\CloudflareTurnstileAuthenticatorBundle\DependencyInjection; | ||
|
||
use Nuvola\CloudflareTurnstileAuthenticatorBundle\Service\SiteService; | ||
use Symfony\Component\Config\FileLocator; | ||
use Symfony\Component\DependencyInjection\ContainerBuilder; | ||
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader; | ||
use Symfony\Component\HttpKernel\DependencyInjection\ConfigurableExtension; | ||
|
||
final class CloudflareTurnstileAuthenticatorExtension extends ConfigurableExtension | ||
{ | ||
public function loadInternal(array $mergedConfig, ContainerBuilder $container): void | ||
{ | ||
$loader = new YamlFileLoader( | ||
$container, | ||
new FileLocator(__DIR__.'/../Resources/config') | ||
); | ||
|
||
$loader->load('services.yaml'); | ||
|
||
$definition = $container->getDefinition(SiteService::class); | ||
$definition->replaceArgument(1, $mergedConfig['endpoint']); | ||
$definition->replaceArgument(2, $mergedConfig['secret_key']); | ||
} | ||
|
||
public function getAlias(): string | ||
{ | ||
return 'cf_turnstile_authenticator'; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Nuvola\CloudflareTurnstileAuthenticatorBundle\DependencyInjection; | ||
|
||
use Symfony\Component\Config\Definition\Builder\TreeBuilder; | ||
use Symfony\Component\Config\Definition\ConfigurationInterface; | ||
|
||
final class Configuration implements ConfigurationInterface | ||
{ | ||
public const DEFAULT_CF_TRUNSTILE_ENDPOINT = 'https://challenges.cloudflare.com/turnstile/v0/siteverify'; | ||
|
||
public function getConfigTreeBuilder() | ||
{ | ||
$treeBuilder = new TreeBuilder('cf_turnstile_authenticator'); | ||
$treeBuilder->getRootNode() | ||
->children() | ||
->scalarNode('endpoint')->cannotBeEmpty()->defaultValue(self::DEFAULT_CF_TRUNSTILE_ENDPOINT)->end() | ||
->scalarNode('secret_key')->isRequired()->cannotBeEmpty()->end() | ||
->end() | ||
->end() | ||
; | ||
|
||
return $treeBuilder; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Nuvola\CloudflareTurnstileAuthenticatorBundle\EventDispatcher\Event; | ||
|
||
use Symfony\Component\Security\Core\User\UserInterface; | ||
|
||
final class ResponseVerifiedEvent | ||
{ | ||
private ?UserInterface $user = null; | ||
|
||
public function getUser(): ?UserInterface | ||
{ | ||
return $this->user; | ||
} | ||
|
||
public function isUserSet(): bool | ||
{ | ||
return $this->user instanceof UserInterface; | ||
} | ||
|
||
public function setUser(UserInterface $user): void | ||
{ | ||
$this->user = $user; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
services: | ||
_defaults: | ||
autowire: false | ||
autoconfigure: false | ||
|
||
nuvola.http_client: | ||
class: Symfony\Contracts\HttpClient\HttpClientInterface | ||
factory: [ 'Symfony\Component\HttpClient\HttpClient', 'create' ] | ||
|
||
Nuvola\CloudflareTurnstileAuthenticatorBundle\Service\SiteService: | ||
- '@nuvola.http_client' | ||
- ~ # compiled | ||
- ~ # compiled | ||
|
||
Nuvola\CloudflareTurnstileAuthenticatorBundle\Service\SiteServiceInterface: '@Nuvola\CloudflareTurnstileAuthenticatorBundle\Service\SiteService' | ||
|
||
Nuvola\CloudflareTurnstileAuthenticatorBundle\Security\CloudflareTurnstileAuthenticator: | ||
- '@Nuvola\CloudflareTurnstileAuthenticatorBundle\Service\SiteServiceInterface' | ||
- '@Symfony\Contracts\EventDispatcher\EventDispatcherInterface' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Nuvola\CloudflareTurnstileAuthenticatorBundle\Security; | ||
|
||
use Nuvola\CloudflareTurnstileAuthenticatorBundle\EventDispatcher\Event\ResponseVerifiedEvent; | ||
use Nuvola\CloudflareTurnstileAuthenticatorBundle\Security\User\NullUser; | ||
use Nuvola\CloudflareTurnstileAuthenticatorBundle\Service\SiteServiceInterface; | ||
use Symfony\Component\EventDispatcher\EventDispatcherInterface; | ||
use Symfony\Component\HttpFoundation\Request; | ||
use Symfony\Component\HttpFoundation\Response; | ||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; | ||
use Symfony\Component\Security\Core\Exception\AuthenticationException; | ||
use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException; | ||
use Symfony\Component\Security\Core\Exception\TokenNotFoundException; | ||
use Symfony\Component\Security\Http\Authenticator\AbstractAuthenticator; | ||
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge; | ||
use Symfony\Component\Security\Http\Authenticator\Passport\Passport; | ||
use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport; | ||
use Symfony\Component\Uid\Uuid; | ||
|
||
final class CloudflareTurnstileAuthenticator extends AbstractAuthenticator | ||
{ | ||
private const HEADER_NAME = 'x-cf-turnstile-response'; | ||
|
||
public function __construct( | ||
private readonly SiteServiceInterface $siteService, | ||
private readonly ?EventDispatcherInterface $eventDispatcher = null, | ||
) {} | ||
|
||
public function supports(Request $request): ?bool | ||
{ | ||
return $request->headers->has(self::HEADER_NAME); | ||
} | ||
|
||
public function authenticate(Request $request): Passport | ||
{ | ||
$response = $request->headers->get(self::HEADER_NAME); | ||
|
||
if (null === $response) { | ||
throw new CustomUserMessageAuthenticationException( | ||
sprintf('Header "%s" cannot be empty.', self::HEADER_NAME) | ||
); | ||
} | ||
|
||
$idempotencyKey = Uuid::v5(Uuid::fromString(Uuid::NAMESPACE_DNS), hash('ripemd160', $response))->toRfc4122(); | ||
|
||
try { | ||
$this->siteService->verify($response, $idempotencyKey, $request->getClientIp()); | ||
} catch (\RuntimeException $e) { | ||
throw new TokenNotFoundException('Invalid token.', 0, $e); | ||
} | ||
|
||
$event = $this->eventDispatcher?->dispatch(new ResponseVerifiedEvent()); | ||
|
||
return new SelfValidatingPassport( | ||
new UserBadge( | ||
$idempotencyKey, | ||
function (string $identifier) use ($event) { | ||
if ($event && $event->isUserSet()) { | ||
return $event->getUser(); | ||
} | ||
|
||
return new NullUser(); | ||
} | ||
), | ||
); | ||
} | ||
|
||
// f57edf2a-9f12-52ec-8b95-e9b8dac04ac1 | ||
// f57edf2a-9f12-52ec-8b95-e9b8dac04ac1 | ||
// f57edf2a-9f12-52ec-8b95-e9b8dac04ac1 | ||
public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response | ||
{ | ||
return null; | ||
} | ||
|
||
public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response | ||
{ | ||
return null; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Nuvola\CloudflareTurnstileAuthenticatorBundle\Security\User; | ||
|
||
use Symfony\Component\Security\Core\User\UserInterface; | ||
|
||
final class NullUser implements UserInterface | ||
{ | ||
public function getRoles(): array | ||
{ | ||
return []; | ||
} | ||
|
||
public function eraseCredentials(): void {} | ||
|
||
public function getUserIdentifier(): string | ||
{ | ||
return ''; | ||
} | ||
} |
Oops, something went wrong.