Files
Investidor-V2/app/Http/Middleware/TerritorialRequest.php

70 lines
3.9 KiB
PHP

<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
class TerritorialRequest
{
/** @param Closure(Request): Response $next */
public function handle(Request $request, Closure $next): Response
{
$correlation = (string) Str::uuid();
try {
abort_unless(config('territorial.enabled'), 404);
if ($request->isMethodSafe()) {
$response = $next($request);
} else {
abort_unless($request->user()->can('manage-portal') && $request->user()->two_factor_confirmed_at && $request->user()->two_factor_secret, 403);
$role = $request->is('*/transitions') ? match ($request->input('action')) {
'approved', 'rejected' => 'reviewers', 'published', 'archived', 'revoked' => 'publishers', default => 'editors',
} : 'editors';
abort_unless(in_array(strtolower($request->user()->email), config('territorial.'.$role, []), true), 403, 'Função editorial não atribuída.');
$key = $request->header('Idempotency-Key', '');
abort_unless(Str::isUuid($key), 422, 'Idempotency-Key UUID obrigatório.');
$scope = $request->method().':'.$request->path();
$actor = $request->user()->id;
$hash = hash('sha256', json_encode($request->all(), JSON_THROW_ON_ERROR));
$response = DB::transaction(function () use ($request, $next, $key, $scope, $actor, $hash) {
DB::select('SELECT pg_advisory_xact_lock(280926, hashtext(?))', [$actor.':'.$key.':'.$scope]);
$query = DB::table('geo_write_receipts')->where('actor_id', $actor)->where('idempotency_key', $key)->where('scope', $scope);
$existing = $query->first();
if ($existing) {
abort_unless(hash_equals($existing->request_hash, $hash), 409, 'Chave reutilizada com conteúdo diferente.');
return response(Crypt::decryptString($existing->response), $existing->http_status, ['Content-Type' => 'application/json']);
}
$result = $next($request);
if ($result->isSuccessful()) {
$body = $result->getContent();
if (! is_string($body)) {
throw new \RuntimeException('Expected JSON response body.');
}
DB::table('geo_write_receipts')->insert(['id' => (string) Str::uuid(), 'actor_id' => $actor, 'idempotency_key' => $key,
'scope' => $scope, 'request_hash' => $hash, 'response' => Crypt::encryptString($body), 'http_status' => $result->getStatusCode()]);
}
return $result;
});
}
} catch (ValidationException $exception) {
$response = response()->json(['type' => 'about:blank', 'title' => 'Dados inválidos', 'status' => 422, 'errors' => $exception->errors(), 'correlation_id' => $correlation], 422, ['Content-Type' => 'application/problem+json']);
} catch (HttpExceptionInterface $exception) {
$status = $exception->getStatusCode();
$response = response()->json(['type' => 'about:blank', 'title' => Response::$statusTexts[$status] ?? 'Request failed', 'status' => $status,
'detail' => $exception->getMessage(), 'correlation_id' => $correlation], $status, ['Content-Type' => 'application/problem+json']);
}
$response->headers->set('X-Correlation-ID', $correlation);
$response->headers->set('Cache-Control', 'private, no-store');
return $response;
}
}