2026-08-11 15:51:17 +03:30
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
namespace App\Http\Middleware;
|
|
|
|
|
|
|
|
|
|
use Closure;
|
|
|
|
|
use Illuminate\Http\Request;
|
|
|
|
|
|
|
|
|
|
/**
|
2026-08-11 19:33:30 +03:30
|
|
|
* Auth for SuperAdmin → product (kill-switch / super settings):
|
|
|
|
|
* 1) Preferred: shared HMAC (same secret as SMS)
|
|
|
|
|
* X-Abshode-Domain, X-Abshode-Timestamp, X-Abshode-Signature
|
|
|
|
|
* payload = `{ts}.{METHOD}./api/super/settings.{domain}`
|
|
|
|
|
* 2) Fallback: Authorization Bearer {SUPERADMIN_REMOTE_TOKEN}
|
2026-08-11 15:51:17 +03:30
|
|
|
*/
|
|
|
|
|
class EnsureSuperAdminRemoteToken
|
|
|
|
|
{
|
2026-08-11 19:33:30 +03:30
|
|
|
private const MAX_SKEW_SECONDS = 300;
|
|
|
|
|
|
|
|
|
|
private const CANONICAL_PATH = '/api/super/settings';
|
|
|
|
|
|
2026-08-11 15:51:17 +03:30
|
|
|
public function handle(Request $request, Closure $next)
|
|
|
|
|
{
|
2026-08-11 19:33:30 +03:30
|
|
|
if ($this->authByHmac($request)) {
|
|
|
|
|
return $next($request);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($this->authByBearer($request)) {
|
|
|
|
|
return $next($request);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$hasSecret = $this->sharedSecret() !== '';
|
|
|
|
|
$hasToken = $this->bearerToken() !== '';
|
|
|
|
|
if (! $hasSecret && ! $hasToken) {
|
2026-08-11 15:51:17 +03:30
|
|
|
return response()->json([
|
2026-08-11 19:33:30 +03:30
|
|
|
'message' => 'SUPERADMIN_SMS_SHARED_SECRET or SUPERADMIN_REMOTE_TOKEN is not configured',
|
2026-08-11 15:51:17 +03:30
|
|
|
], 503);
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-11 19:33:30 +03:30
|
|
|
return response()->json(['message' => 'Unauthorized'], 401);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private function authByHmac(Request $request): bool
|
|
|
|
|
{
|
|
|
|
|
$secret = $this->sharedSecret();
|
|
|
|
|
if ($secret === '') {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$domainHeader = trim((string) $request->header('X-Abshode-Domain', ''));
|
|
|
|
|
$tsRaw = trim((string) $request->header('X-Abshode-Timestamp', ''));
|
|
|
|
|
$sigRaw = trim((string) $request->header('X-Abshode-Signature', ''));
|
|
|
|
|
if ($domainHeader === '' || $tsRaw === '' || $sigRaw === '') {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (! ctype_digit($tsRaw)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
$timestamp = (int) $tsRaw;
|
|
|
|
|
if (abs(time() - $timestamp) > self::MAX_SKEW_SECONDS) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$domain = $this->normalizeHost($domainHeader);
|
|
|
|
|
$expectedDomain = $this->configuredDomain();
|
|
|
|
|
if ($domain === '' || $expectedDomain === '' || ! hash_equals($expectedDomain, $domain)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$method = strtoupper($request->getMethod());
|
|
|
|
|
$payload = $timestamp.'.'.$method.'.'.self::CANONICAL_PATH.'.'.$domain;
|
|
|
|
|
$expected = hash_hmac('sha256', $payload, $secret);
|
|
|
|
|
|
|
|
|
|
return hash_equals(strtolower($expected), strtolower($sigRaw));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private function authByBearer(Request $request): bool
|
|
|
|
|
{
|
|
|
|
|
$expected = $this->bearerToken();
|
|
|
|
|
if ($expected === '') {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-11 15:51:17 +03:30
|
|
|
$auth = (string) $request->header('Authorization', '');
|
2026-08-11 19:33:30 +03:30
|
|
|
if (! preg_match('/^\s*Bearer\s+(.+)\s*$/i', $auth, $m)) {
|
|
|
|
|
return false;
|
2026-08-11 15:51:17 +03:30
|
|
|
}
|
|
|
|
|
|
2026-08-11 19:33:30 +03:30
|
|
|
$token = trim($m[1]);
|
|
|
|
|
|
|
|
|
|
return $token !== '' && hash_equals($expected, $token);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private function sharedSecret(): string
|
|
|
|
|
{
|
|
|
|
|
return trim((string) config('services.superadmin.sms_shared_secret', ''));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private function bearerToken(): string
|
|
|
|
|
{
|
|
|
|
|
return trim((string) config('services.superadmin.remote_token', ''));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private function configuredDomain(): string
|
|
|
|
|
{
|
|
|
|
|
$override = trim((string) config('services.superadmin.sms_domain', ''));
|
|
|
|
|
if ($override !== '') {
|
|
|
|
|
return $this->normalizeHost($override);
|
2026-08-11 15:51:17 +03:30
|
|
|
}
|
|
|
|
|
|
2026-08-11 19:33:30 +03:30
|
|
|
$fromApp = parse_url((string) config('app.url'), PHP_URL_HOST);
|
|
|
|
|
|
|
|
|
|
return $this->normalizeHost(is_string($fromApp) ? $fromApp : '');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private function normalizeHost(string $value): string
|
|
|
|
|
{
|
|
|
|
|
$host = strtolower(trim($value));
|
|
|
|
|
$host = preg_replace('#^https?://#', '', $host) ?? $host;
|
|
|
|
|
$host = explode('/', $host)[0] ?? $host;
|
|
|
|
|
$host = explode(':', $host)[0] ?? $host;
|
|
|
|
|
|
|
|
|
|
return trim($host);
|
2026-08-11 15:51:17 +03:30
|
|
|
}
|
|
|
|
|
}
|