header('X-Impersonate-User'); if (! $targetUserId) { return $next($request); } /** @var User|null $admin */ $admin = $request->user(); if (! $admin) { return response()->json(['message' => 'Authentication required.'], 401); } // Block sensitive routes during impersonation if ($this->isSensitiveRoute($request)) { return response()->json([ 'message' => 'This action is not allowed during impersonation.', ], 403); } // Validate impersonation session via Redis $session = $this->impersonationService->validateRequest( $admin->id, $targetUserId, $request->ip(), ); if (! $session) { return response()->json([ 'message' => 'Impersonation session is invalid or has expired.', 'impersonation_ended' => true, ], 403); } // Load the target user $targetUser = User::find($targetUserId); if (! $targetUser) { return response()->json(['message' => 'Target user not found.'], 404); } // Store impersonation context in request attributes $request->attributes->set('impersonator', $admin); $request->attributes->set('impersonation_session', $session); // Swap auth context — the rest of the request sees the target user app('auth')->setUser($targetUser); // Tag all log entries with impersonation context Log::shareContext([ 'impersonated_by' => $admin->id, 'impersonation_session_id' => $session->id, ]); // Increment actions count $this->impersonationService->incrementActionsCount($session); return $next($request); } private function isSensitiveRoute(Request $request): bool { // Get path relative to API prefix (strip api/v1/) $path = $request->path(); $path = preg_replace('#^api/v1/#', '', $path); // Block profile mutations but allow GET (viewing) if (str_starts_with($path, 'me/profile') && $request->method() !== 'GET') { return true; } foreach (self::BLOCKED_ROUTE_PREFIXES as $prefix) { if (str_starts_with($path, $prefix)) { return true; } } return false; } }