Files
crewli/api/app/Http/Middleware/BindSentryRouteContext.php
bert.hausmans 9414d09472 refactor: BindSentryContext to AuthScopeContextListener for auth-scope tags
Sentry-context binding split into two responsibilities:

- Route-scope (app, http.method, route_name) stays in middleware on
  the api group as BindSentryRouteContext — works on every request,
  no auth required.
- Auth-scope (user_id, actor_type) moves to AuthScopeContextListener
  on Illuminate\Auth\Events\Authenticated — works on every
  authentication mechanism (Sanctum, portal-tokens, future
  authenticators) without per-route middleware-attachment. Listener
  also augments Log::withContext with user_id (closes OBS-2).

Architecturally fault-preventing rather than fault-detecting: new
authenticated route groups need no separate sentry.context aliasing,
so silent observability gaps are no longer possible (closes OBS-3).

Impersonation tagging is co-located with HandleImpersonation: after
the user-swap, the middleware re-tags Sentry scope with the target
user_id/actor_type and adds impersonation.active /
impersonation.impersonator_user_id / impersonation.session_id. The
Authenticated event fires for the admin (Sanctum's natural flow),
the listener tags the admin, then HandleImpersonation overwrites
post-swap.

Files renamed:
- BindSentryContext -> BindSentryRouteContext (route-scope only)
- BindSentryContextTest -> BindSentryRouteContextTest (4 cases)

Files added:
- AuthScopeContextListener
- AuthScopeContextListenerTest (6 cases)

bootstrap/app.php drops the sentry.context alias and prepends
BindSentryRouteContext to the api group. routes/api.php drops every
sentry.context middleware string from auth:sanctum groups.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 12:53:14 +02:00

42 lines
1.2 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Sentry\State\Scope;
use Symfony\Component\HttpFoundation\Response;
use function Sentry\configureScope;
/**
* Binds route-scope context to Sentry events on every API request.
*
* Auth-scope tags (user_id, actor_type, organisation_id, impersonation.*,
* actor_scope) live in {@see \App\Listeners\Observability\AuthScopeContextListener}
* so they bind on Authenticated event rather than route entry. That keeps
* the auth-scope binding uniform across Sanctum, portal-tokens, and any
* future authenticator without per-route middleware-attachment.
*
* RFC-WS-7 §3.6.
*/
final class BindSentryRouteContext
{
public function handle(Request $request, Closure $next): Response
{
configureScope(static function (Scope $scope) use ($request): void {
$scope->setTag('app', 'api');
$scope->setTag('http.method', $request->method());
$routeName = $request->route()?->getName();
if (is_string($routeName) && $routeName !== '') {
$scope->setTag('route_name', $routeName);
}
});
return $next($request);
}
}