From e92be66e54137dd086bc4f13430ae8c1f91df826 Mon Sep 17 00:00:00 2001 From: Mahdi Date: Tue, 11 Aug 2026 15:51:17 +0330 Subject: [PATCH] feat: SuperAdmin server-access kill switch gate Add /super/settings endpoints and API middleware that returns generic 503 while keeping unlock routes whitelisted. Co-authored-by: Cursor --- .../Super/SuperSettingsController.php | 41 +++++++++++++++++ app/Http/Kernel.php | 2 + app/Http/Middleware/BlockServerAccess.php | 44 +++++++++++++++++++ .../EnsureSuperAdminRemoteToken.php | 34 ++++++++++++++ routes/api.php | 5 +++ routes/web.php | 6 +++ 6 files changed, 132 insertions(+) create mode 100644 app/Http/Controllers/Super/SuperSettingsController.php create mode 100644 app/Http/Middleware/BlockServerAccess.php create mode 100644 app/Http/Middleware/EnsureSuperAdminRemoteToken.php diff --git a/app/Http/Controllers/Super/SuperSettingsController.php b/app/Http/Controllers/Super/SuperSettingsController.php new file mode 100644 index 0000000..50297c8 --- /dev/null +++ b/app/Http/Controllers/Super/SuperSettingsController.php @@ -0,0 +1,41 @@ +json([ + 'server_access_blocked' => $blocked, + ]); + } + + public function update(Request $request) + { + $data = $request->validate([ + 'server_access_blocked' => 'sometimes|boolean', + ]); + + if (array_key_exists('server_access_blocked', $data)) { + $blocked = (bool) $data['server_access_blocked']; + if ($blocked) { + Cache::forever(BlockServerAccess::CACHE_KEY, true); + } else { + Cache::forget(BlockServerAccess::CACHE_KEY); + } + } + + return response()->json([ + 'ok' => true, + 'server_access_blocked' => (bool) Cache::get(BlockServerAccess::CACHE_KEY, false), + ]); + } +} diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index 5cb4dad..d48b9df 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -22,6 +22,7 @@ class Kernel extends HttpKernel \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class, \App\Http\Middleware\TrimStrings::class, \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class, + \App\Http\Middleware\BlockServerAccess::class, ]; /** @@ -69,5 +70,6 @@ class Kernel extends HttpKernel 'pargar.auth' => \App\Http\Middleware\PargarAuth::class, 'sms.panel.super' => \App\Http\Middleware\EnsureSmsPanelSuperAdmin::class, 'customer.active' => \App\Http\Middleware\EnsureCustomerIsActive::class, + 'superadmin.remote' => \App\Http\Middleware\EnsureSuperAdminRemoteToken::class, ]; } diff --git a/app/Http/Middleware/BlockServerAccess.php b/app/Http/Middleware/BlockServerAccess.php new file mode 100644 index 0000000..7b31e88 --- /dev/null +++ b/app/Http/Middleware/BlockServerAccess.php @@ -0,0 +1,44 @@ +path(), '/'); + if ($this->isWhitelisted($path)) { + return $next($request); + } + + if (Cache::get(self::CACHE_KEY)) { + return response()->json([ + 'message' => 'خطا در برقراری ارتباط با سرور', + ], 503); + } + + return $next($request); + } + + private function isWhitelisted(string $path): bool + { + $allowed = [ + 'super/settings', + 'api/super/settings', + ]; + + foreach ($allowed as $item) { + if ($path === $item || str_ends_with($path, '/'.$item)) { + return true; + } + } + + return false; + } +} diff --git a/app/Http/Middleware/EnsureSuperAdminRemoteToken.php b/app/Http/Middleware/EnsureSuperAdminRemoteToken.php new file mode 100644 index 0000000..93af0e2 --- /dev/null +++ b/app/Http/Middleware/EnsureSuperAdminRemoteToken.php @@ -0,0 +1,34 @@ +json([ + 'message' => 'SUPERADMIN_REMOTE_TOKEN is not configured', + ], 503); + } + + $auth = (string) $request->header('Authorization', ''); + $token = ''; + if (preg_match('/^\s*Bearer\s+(.+)\s*$/i', $auth, $m)) { + $token = trim($m[1]); + } + + if ($token === '' || ! hash_equals($expected, $token)) { + return response()->json(['message' => 'Unauthorized'], 401); + } + + return $next($request); + } +} diff --git a/routes/api.php b/routes/api.php index 3c9890e..7de9e4a 100644 --- a/routes/api.php +++ b/routes/api.php @@ -15,6 +15,7 @@ use App\Services\ConfigService; use App\Http\Controllers\Customer\CartController; use App\Http\Controllers\Admin\CategoryController; use App\Http\Controllers\Chat\SupportChatNotifyController; +use App\Http\Controllers\Super\SuperSettingsController; /* |-------------------------------------------------------------------------- @@ -27,6 +28,10 @@ use App\Http\Controllers\Chat\SupportChatNotifyController; | */ +Route::middleware('superadmin.remote')->group(function () { + Route::get('/super/settings', [SuperSettingsController::class, 'show']); + Route::post('/super/settings', [SuperSettingsController::class, 'update']); +}); Route::get('/candle',[ReportController::class, 'candleChart']); Route::get('/price/up',[ReportController::class, 'upDownPrice']); diff --git a/routes/web.php b/routes/web.php index 5710948..9c6a3a1 100644 --- a/routes/web.php +++ b/routes/web.php @@ -13,9 +13,15 @@ use App\Mail\YourMailableClass; use Illuminate\Support\Facades\Mail; use Ramsey\Uuid\Uuid; use App\Http\Controllers\pay\PaymanController; +use App\Http\Controllers\Super\SuperSettingsController; use App\Models\Message; use Illuminate\Http\Request; +Route::middleware('superadmin.remote')->group(function () { + Route::get('/super/settings', [SuperSettingsController::class, 'show']); + Route::post('/super/settings', [SuperSettingsController::class, 'update']); +}); + // وقتی public/storage symlink نیست یا فایل تازه فقط در storage/app/public است Route::get('storage/{path}', [PublicStorageController::class, 'show']) ->where('path', '.*');