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 <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,41 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Super;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Http\Middleware\BlockServerAccess;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Cache;
|
||||||
|
|
||||||
|
class SuperSettingsController extends Controller
|
||||||
|
{
|
||||||
|
public function show()
|
||||||
|
{
|
||||||
|
$blocked = (bool) Cache::get(BlockServerAccess::CACHE_KEY, false);
|
||||||
|
|
||||||
|
return response()->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),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,6 +22,7 @@ class Kernel extends HttpKernel
|
|||||||
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
|
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
|
||||||
\App\Http\Middleware\TrimStrings::class,
|
\App\Http\Middleware\TrimStrings::class,
|
||||||
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::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,
|
'pargar.auth' => \App\Http\Middleware\PargarAuth::class,
|
||||||
'sms.panel.super' => \App\Http\Middleware\EnsureSmsPanelSuperAdmin::class,
|
'sms.panel.super' => \App\Http\Middleware\EnsureSmsPanelSuperAdmin::class,
|
||||||
'customer.active' => \App\Http\Middleware\EnsureCustomerIsActive::class,
|
'customer.active' => \App\Http\Middleware\EnsureCustomerIsActive::class,
|
||||||
|
'superadmin.remote' => \App\Http\Middleware\EnsureSuperAdminRemoteToken::class,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Cache;
|
||||||
|
|
||||||
|
class BlockServerAccess
|
||||||
|
{
|
||||||
|
public const CACHE_KEY = 'sa_server_access_blocked';
|
||||||
|
|
||||||
|
public function handle(Request $request, Closure $next)
|
||||||
|
{
|
||||||
|
$path = trim($request->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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bearer token must match SUPERADMIN_REMOTE_TOKEN (same as SuperAdmin Tenant.remoteApiToken).
|
||||||
|
*/
|
||||||
|
class EnsureSuperAdminRemoteToken
|
||||||
|
{
|
||||||
|
public function handle(Request $request, Closure $next)
|
||||||
|
{
|
||||||
|
$expected = trim((string) config('services.superadmin.remote_token', ''));
|
||||||
|
if ($expected === '') {
|
||||||
|
return response()->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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,6 +15,7 @@ use App\Services\ConfigService;
|
|||||||
use App\Http\Controllers\Customer\CartController;
|
use App\Http\Controllers\Customer\CartController;
|
||||||
use App\Http\Controllers\Admin\CategoryController;
|
use App\Http\Controllers\Admin\CategoryController;
|
||||||
use App\Http\Controllers\Chat\SupportChatNotifyController;
|
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('/candle',[ReportController::class, 'candleChart']);
|
||||||
Route::get('/price/up',[ReportController::class, 'upDownPrice']);
|
Route::get('/price/up',[ReportController::class, 'upDownPrice']);
|
||||||
|
|||||||
@@ -13,9 +13,15 @@ use App\Mail\YourMailableClass;
|
|||||||
use Illuminate\Support\Facades\Mail;
|
use Illuminate\Support\Facades\Mail;
|
||||||
use Ramsey\Uuid\Uuid;
|
use Ramsey\Uuid\Uuid;
|
||||||
use App\Http\Controllers\pay\PaymanController;
|
use App\Http\Controllers\pay\PaymanController;
|
||||||
|
use App\Http\Controllers\Super\SuperSettingsController;
|
||||||
use App\Models\Message;
|
use App\Models\Message;
|
||||||
use Illuminate\Http\Request;
|
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 است
|
// وقتی public/storage symlink نیست یا فایل تازه فقط در storage/app/public است
|
||||||
Route::get('storage/{path}', [PublicStorageController::class, 'show'])
|
Route::get('storage/{path}', [PublicStorageController::class, 'show'])
|
||||||
->where('path', '.*');
|
->where('path', '.*');
|
||||||
|
|||||||
Reference in New Issue
Block a user