feat: accept form submissions from the websites over an API endpoint
This commit is contained in:
1 parent
f48e53fdd0
commit
30ad5e8488
17 files changed
+1350
-4
No files matched your search
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace App\Security\AccessToken;
|
||||
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\Security\Http\AccessToken\AccessTokenExtractorInterface;
|
||||
|
||||
/**
|
||||
* Reads the API key of the stateless "api" firewall from its own header. The built-in header
|
||||
* extractor expects "Authorization: Bearer", which would claim a shared secret is an OAuth
|
||||
* access token - this application is the client of a real OAuth provider (MYEP_OAUTH2_*), and
|
||||
* the two must not look alike in a request.
|
||||
*/
|
||||
class ApiKeyExtractor implements AccessTokenExtractorInterface
|
||||
{
|
||||
public const HEADER = 'X-Api-Key';
|
||||
|
||||
public function extractAccessToken(Request $request): ?string
|
||||
{
|
||||
$key = trim((string) $request->headers->get(self::HEADER, ''));
|
||||
|
||||
return '' === $key ? null : $key;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace App\Security\AccessToken;
|
||||
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
|
||||
use Symfony\Component\Security\Http\AccessToken\AccessTokenHandlerInterface;
|
||||
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
|
||||
|
||||
/**
|
||||
* Validates the shared key a sibling website posts with. There is no person to log in and no
|
||||
* account to look up: the key is the identity, and the user it resolves to is the in-memory
|
||||
* "api" user of the api_user_provider - nothing about it may reach the user table, where a row
|
||||
* would be a login to this application's UI.
|
||||
*/
|
||||
class ApiKeyTokenHandler implements AccessTokenHandlerInterface
|
||||
{
|
||||
public const USER_IDENTIFIER = 'api';
|
||||
|
||||
public function __construct(
|
||||
private readonly string $apiKey,
|
||||
private readonly LoggerInterface $logger,
|
||||
) {
|
||||
}
|
||||
|
||||
public function getUserBadgeFrom(string $accessToken): UserBadge
|
||||
{
|
||||
// An unconfigured key locks the endpoint instead of opening it: hash_equals('', '')
|
||||
// is true, so without this check a deployment that forgot the secret would accept
|
||||
// an empty key from anyone.
|
||||
if ('' === $this->apiKey) {
|
||||
$this->logger->error('API key authentication attempted while no key is configured');
|
||||
|
||||
throw new BadCredentialsException('No API key configured.');
|
||||
}
|
||||
|
||||
if (false === hash_equals($this->apiKey, $accessToken)) {
|
||||
$this->logger->warning('API key authentication failed');
|
||||
|
||||
throw new BadCredentialsException('Invalid API key.');
|
||||
}
|
||||
|
||||
return new UserBadge(self::USER_IDENTIFIER);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user