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>
This commit is contained in:
2026-05-06 12:53:14 +02:00
parent 42994522eb
commit 9414d09472
11 changed files with 401 additions and 532 deletions

View File

@@ -204,6 +204,15 @@ class AppServiceProvider extends ServiceProvider
\App\Listeners\Observability\TagJobAttemptOnSentry::class,
);
// RFC-WS-7 §3.6 — auth-scope Sentry tags + Log::withContext on
// every successful authentication. Decoupled from middleware so
// future authenticators (e.g. portal-token) only need to fire
// the Authenticated event for tagging to work.
\Illuminate\Support\Facades\Event::listen(
\Illuminate\Auth\Events\Authenticated::class,
\App\Listeners\Observability\AuthScopeContextListener::class,
);
ResetPassword::createUrlUsing(function ($user, string $token) {
return config('crewli.portal_url').'/wachtwoord-resetten?token='.$token.'&email='.urlencode($user->email);
});