Priority 70 sits after RejectListener (77) and before LoginListener (66), and both bounds are load-bearing: - After 77 so a rate-limited IP never reaches a ceremony. Passkeys cannot be used to sidestep a lockout (D3), which is the point of the reviewer's third clarification. - Before 66 because LoginListener treats any POST to the auth subdomain as a login attempt. A ceremony finish body has no username/totp, so Payload::load() returns null and the request would be scored as a failed login, burning a rate-limit token for every legitimate passkey login. Verified in the live container rather than assumed: debug:event-dispatcher confirms 77 -> 70 -> 66. Other properties asserted by tests: every header-bearing request gets a JSON response so fetch() callers never receive HTML; registration identity comes from the live session, never the request body; a failed ceremony is indistinguishable from a wrong TOTP code and spends the same shared budget; and begin is bounded by a separate resource guard that deliberately does not consume failure budget. The listener also marks its responses so SecurityHeadersListener can apply no-store: these are the only browser-facing 2xx this application produces, since the auth subdomain has no forward_auth in front of it. CSP gains publickey-credentials-get/-create only when passkeys are available, so the unavailable case stays byte-identical to before.
43 lines
1.5 KiB
PHP
43 lines
1.5 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Tests;
|
|
|
|
use App\Kernel as AppKernel;
|
|
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
|
|
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
|
|
|
/**
|
|
* Kernel used by the functional test suite.
|
|
*
|
|
* In production the nonce cache is backed by APCu, which naturally persists
|
|
* across PHP requests. In the test environment the nonce cache is an
|
|
* in-memory ArrayAdapter; Symfony's ServicesResetter clears it between
|
|
* requests (even with KernelBrowser::disableReboot()), which would discard
|
|
* the nonce issued on the login-page request before the login-submission
|
|
* request can verify it.
|
|
*
|
|
* This kernel removes the kernel.reset tag from the nonceCache (and
|
|
* rateLimitCache) pools so their in-memory state survives across requests
|
|
* within a single test, mirroring the persistence behaviour of APCu.
|
|
*/
|
|
class TestKernel extends AppKernel
|
|
{
|
|
protected function build(ContainerBuilder $container): void
|
|
{
|
|
parent::build($container);
|
|
|
|
$container->addCompilerPass(new class implements CompilerPassInterface {
|
|
public function process(ContainerBuilder $container): void
|
|
{
|
|
foreach (['nonceCache', 'rateLimitCache', 'sessionCache', 'sessionStorage', 'publicRateLimitCache', 'passkeyRateLimitCache'] as $poolId) {
|
|
if ($container->hasDefinition($poolId)) {
|
|
$container->getDefinition($poolId)->clearTag('kernel.reset');
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|