feat(security): emit authentication failures for firewall handling

Signed-off-by: Sebastian Krupinski <krupinski01@gmail.com>
This commit is contained in:
2026-08-05 23:26:01 -04:00
parent a363a1a4bc
commit a8e29d0305
13 changed files with 377 additions and 95 deletions
+45 -1
View File
@@ -20,6 +20,7 @@ use KTXC\Http\Middleware\RequestHandlerInterface;
use KTXC\Http\Middleware\RouterMiddleware;
use KTXC\Http\Middleware\TenantMiddleware;
use KTXC\Http\Request\Request;
use KTXC\Http\Request\RequestContext;
use KTXC\Http\Response\Response;
use KTXC\Http\Response\StreamedResponse;
use KTXC\KernelInterface;
@@ -78,6 +79,31 @@ final class HttpRuntimeTest extends TestCase
self::assertTrue($kernel->outcome?->successful);
}
#[Test]
#[TestDox('HTTP requests are available only for the active runtime execution')]
public function scopesRequestContext(): void
{
$requestContext = new RequestContext();
$observer = new RequestContextMiddleware($requestContext);
$kernel = new RuntimeKernel(
new RuntimeContainer([
RequestContext::class => $requestContext,
TenantMiddleware::class => new PassMiddleware(),
FirewallMiddleware::class => new PassMiddleware(),
AuthenticationMiddleware::class => new PassMiddleware(),
RouterMiddleware::class => $observer,
]),
new TenantContext($this->createStub(TenantService::class)),
new IdentityContext(),
);
$request = Request::create('/auth/verify', 'POST');
(new HttpRuntime($kernel, false))->run($request, send: false);
self::assertSame($request, $observer->observed);
self::assertNull($requestContext->current());
}
#[Test]
#[TestDox('HTTP exceptions render a response and still terminate')]
public function fails(): void
@@ -198,9 +224,12 @@ final class RuntimeKernel implements KernelInterface
final class RuntimeContainer implements ContainerInterface
{
private readonly array $services;
public function __construct(
private readonly array $services,
array $services,
) {
$this->services = [RequestContext::class => new RequestContext(), ...$services];
}
public function get(string $id): mixed
@@ -250,6 +279,21 @@ final class ResponseMiddleware extends PassMiddleware
}
}
final class RequestContextMiddleware extends PassMiddleware
{
public ?Request $observed = null;
public function __construct(
private readonly RequestContext $requestContext,
) {}
public function process(Request $request, RequestHandlerInterface $handler): Response
{
$this->observed = $this->requestContext->current();
return new Response('ok');
}
}
final class ThrowingMiddleware extends PassMiddleware
{
public function process(Request $request, RequestHandlerInterface $handler): Response