lthn.io/app/Core/Bouncer/Gate/ActionGateMiddleware.php
Claude 41a90cbff8
feat: lthn.io API serving live chain data
Fixed: basePath self→static binding, namespace detection, event wiring,
SQLite cache, file cache driver. All Mod Boot classes converted to
$listens pattern for lifecycle event discovery.

Working endpoints:
- /v1/explorer/info — live chain height, difficulty, aliases
- /v1/explorer/stats — formatted chain statistics
- /v1/names/directory — alias directory grouped by type
- /v1/names/available/{name} — name availability check
- /v1/names/lookup/{name} — name details

Co-Authored-By: Charon <charon@lethean.io>
2026-04-03 17:17:42 +01:00

155 lines
4.4 KiB
PHP

<?php
/*
* Core PHP Framework
*
* Licensed under the European Union Public Licence (EUPL) v1.2.
* See LICENSE file for details.
*/
declare(strict_types=1);
namespace Core\Bouncer\Gate;
use Closure;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
/**
* Action Gate Middleware - enforces action whitelisting.
*
* Intercepts requests and checks if the target action is permitted.
*
* ## Integration
*
* ```
* Request -> BouncerGate (action whitelisting) -> Laravel Gate/Policy -> Controller
* ```
*
* ## Behavior by Mode
*
* **Production (training_mode = false):**
* - Allowed actions proceed normally
* - Unknown/denied actions return 403 Forbidden
*
* **Training Mode (training_mode = true):**
* - Allowed actions proceed normally
* - Unknown actions return a training response:
* - API requests: JSON with action details and approval prompt
* - Web requests: Redirect back with flash message
*/
class ActionGateMiddleware
{
public function __construct(
protected ActionGateService $gateService,
) {}
public function handle(Request $request, Closure $next): Response
{
// Skip for routes that explicitly bypass the gate
if ($request->route()?->getAction('bypass_gate')) {
return $next($request);
}
$result = $this->gateService->check($request);
return match ($result['result']) {
ActionGateService::RESULT_ALLOWED => $next($request),
ActionGateService::RESULT_TRAINING => $this->trainingResponse($request, $result),
default => $this->deniedResponse($request, $result),
};
}
/**
* Generate response for training mode.
*/
protected function trainingResponse(Request $request, array $result): Response
{
$action = $result['action'];
$scope = $result['scope'];
if ($this->wantsJson($request)) {
return $this->trainingJsonResponse($request, $action, $scope);
}
return $this->trainingWebResponse($request, $action, $scope);
}
/**
* JSON response for training mode (API requests).
*/
protected function trainingJsonResponse(Request $request, string $action, ?string $scope): JsonResponse
{
return response()->json([
'error' => 'action_not_trained',
'message' => "Action '{$action}' is not trained. Approve this action to continue.",
'action' => $action,
'scope' => $scope,
'route' => $request->path(),
'method' => $request->method(),
'training_mode' => true,
'approval_url' => $this->approvalUrl($action, $scope, $request),
], 403);
}
/**
* Web response for training mode (browser requests).
*/
protected function trainingWebResponse(Request $request, string $action, ?string $scope): RedirectResponse
{
$message = "Action '{$action}' requires training approval.";
return redirect()
->back()
->with('bouncer_training', [
'action' => $action,
'scope' => $scope,
'route' => $request->path(),
'method' => $request->method(),
'message' => $message,
])
->withInput();
}
/**
* Generate response for denied action.
*/
protected function deniedResponse(Request $request, array $result): Response
{
$action = $result['action'];
if ($this->wantsJson($request)) {
return response()->json([
'error' => 'action_denied',
'message' => "Action '{$action}' is not permitted.",
'action' => $action,
], 403);
}
abort(403, "Action '{$action}' is not permitted.");
}
/**
* Check if request expects JSON response.
*/
protected function wantsJson(Request $request): bool
{
return $request->expectsJson()
|| $request->is('api/*')
|| $request->header('Accept') === 'application/json';
}
/**
* Generate URL for approving an action.
*/
protected function approvalUrl(string $action, ?string $scope, Request $request): string
{
return route('bouncer.gate.approve', [
'action' => $action,
'scope' => $scope,
'redirect' => $request->fullUrl(),
]);
}
}