Files
SGF/app/Http/Middleware/AuthenticateSgcInvoice.php
T
2026-11-11 09:00:17 +02:00

71 lines
3.7 KiB
PHP

<?php
namespace App\Http\Middleware;
use App\Models\User;
use App\Shared\Context\CompanyContext;
use App\Shared\Context\RequireModuleContext;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Session\ArraySessionHandler;
use Illuminate\Session\Store;
use Illuminate\Support\Facades\Auth;
final class AuthenticateSgcInvoice
{
public function handle(Request $request, Closure $next)
{
abort_unless(config('sgc_invoice.enabled') === true, 404);
// The installed native adapter is QA-only. This transport cannot remove that boundary.
abort_unless(app()->environment('budget-testing'), 403);
abort_unless($request->isSecure() || in_array($request->ip(), ['127.0.0.1', '::1'], true), 403);
abort_if(strlen($request->getContent()) > 131072, 413);
if ($request->isMethod('GET')) abort_if($request->query->count() || strlen($request->getContent()), 422);
else abort_unless($request->isJson(), 415);
$client = (string)$request->header('X-SGC-Client');
$principals = config('sgc_invoice.principals', []);
$principal = preg_match('/^[a-zA-Z0-9_-]{1,64}$/D', $client) ? ($principals[$client] ?? null) : null;
$token = $request->bearerToken();
abort_unless(is_array($principal) && ($principal['enabled'] ?? false) === true
&& is_string($token) && strlen($token) >= 32 && strlen($token) <= 512
&& preg_match('/^[a-f0-9]{64}$/D', $principal['token_sha256'] ?? '')
&& hash_equals($principal['token_sha256'], hash('sha256', $token)), 401);
foreach (['user_id', 'auth_version', 'tenant_id', 'company_id', 'unit_id'] as $key) {
abort_unless(is_int($principal[$key] ?? null) && $principal[$key] > 0, 403);
}
$version = $principal['context_version'] ?? null;
abort_unless(is_string($version) && preg_match('/^[a-f0-9]{64}$/D', $version)
&& hash_equals($version, (string)$request->header('X-SGC-Context')), 409);
$user = User::find($principal['user_id']);
abort_unless($user && $user->auth_version === $principal['auth_version'], 401);
$context = app(CompanyContext::class);
$guard = Auth::getDefaultDriver();
$oldUser = Auth::guard($guard)->user();
$oldResolver = $request->getUserResolver();
$oldSession = $request->hasSession() ? $request->session() : null;
try {
// Context and person come exclusively from the server-side principal, never JSON/cookies.
$context->selectFor($user->id, $principal['tenant_id'], $principal['company_id'], $principal['unit_id']);
Auth::guard($guard)->setUser($user);
$request->setUserResolver(fn () => $user);
$session = new Store('sgc_invoice_ephemeral', new ArraySessionHandler(10));
$session->start();
$session->put('company_context', ['tenant_id'=>$principal['tenant_id'], 'company_id'=>$principal['company_id'],
'unit_id'=>$principal['unit_id'], 'module_id'=>'purchasing', 'token'=>$version]);
$request->setLaravelSession($session);
$request->headers->set('X-SGF-Context', $version);
app(RequireModuleContext::class)->check($request, $context);
$response = $next($request);
$response->headers->set('Cache-Control', 'no-store, private');
return $response;
} finally {
$context->clear();
if (isset($session)) $session->flush();
Auth::forgetGuards(); Auth::shouldUse($guard);
if ($oldUser) Auth::guard($guard)->setUser($oldUser);
$request->setUserResolver($oldResolver);
if ($oldSession) $request->setLaravelSession($oldSession);
}
}
}