You've already forked Atomcms-edit
Initial commit
This commit is contained in:
+47
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class AdminSecurityMiddleware
|
||||
{
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
$user = Auth::user();
|
||||
|
||||
// Check 1: Must be authenticated
|
||||
if (! $user) {
|
||||
return response()->json(['error' => 'Unauthorized'], 401);
|
||||
}
|
||||
|
||||
// Check 2: Must have admin rank
|
||||
$minRank = (int) setting('min_staff_rank', 7);
|
||||
if ($user->rank < $minRank) {
|
||||
Log::warning('[Security] Unauthorized API access attempt', [
|
||||
'user_id' => $user->id,
|
||||
'username' => $user->username,
|
||||
'ip' => $request->ip(),
|
||||
'path' => $request->path(),
|
||||
]);
|
||||
|
||||
return response()->json(['error' => 'Forbidden'], 403);
|
||||
}
|
||||
|
||||
// Check 3: Audit log every API call
|
||||
Log::info('[Audit] Admin API access', [
|
||||
'user_id' => $user->id,
|
||||
'username' => $user->username,
|
||||
'method' => $request->method(),
|
||||
'path' => $request->path(),
|
||||
'ip' => $request->ip(),
|
||||
'user_agent' => substr($request->userAgent() ?? '', 0, 200),
|
||||
]);
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
Executable
+94
@@ -0,0 +1,94 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class ApiResponseCache
|
||||
{
|
||||
private const int DEFAULT_TTL = 60;
|
||||
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
if (! $this->shouldCache($request)) {
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
$cacheKey = $this->generateCacheKey($request);
|
||||
|
||||
if (Cache::has($cacheKey)) {
|
||||
$cached = Cache::get($cacheKey);
|
||||
|
||||
if (is_array($cached) && isset($cached['data'], $cached['status'])) {
|
||||
return response()->json($cached['data'], $cached['status'], $cached['headers'] ?? []);
|
||||
}
|
||||
}
|
||||
|
||||
$response = $next($request);
|
||||
|
||||
if ($response->isSuccessful()) {
|
||||
$ttl = $this->getTtlFromRoute($request);
|
||||
$data = json_decode((string) $response->getContent(), true);
|
||||
|
||||
if (is_array($data)) {
|
||||
Cache::put($cacheKey, [
|
||||
'data' => $data,
|
||||
'status' => $response->getStatusCode(),
|
||||
], $ttl);
|
||||
}
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
private function shouldCache(Request $request): bool
|
||||
{
|
||||
if (! in_array($request->method(), ['GET', 'HEAD'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($request->bearerToken()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$cacheableRoutes = [
|
||||
'/api/online-users',
|
||||
'/api/online-count',
|
||||
'/api/radio/current-dj',
|
||||
'/api/radio/config',
|
||||
'/api/radio/now-playing',
|
||||
'/api/radio/listeners',
|
||||
'/api/radio/shouts',
|
||||
'/api/settings/radio',
|
||||
'/api/radio/points/leaderboard',
|
||||
'/api/radio/points/stats',
|
||||
];
|
||||
|
||||
return array_any($cacheableRoutes, fn ($route) => str_starts_with($request->path(), ltrim((string) $route, '/')));
|
||||
}
|
||||
|
||||
private function generateCacheKey(Request $request): string
|
||||
{
|
||||
return 'api_cache_' . md5($request->path() . ($request->getQueryString() ?? ''));
|
||||
}
|
||||
|
||||
private function getTtlFromRoute(Request $request): int
|
||||
{
|
||||
$path = $request->path();
|
||||
|
||||
return match (true) {
|
||||
str_contains($path, 'online-count') => 30,
|
||||
str_contains($path, 'now-playing') => 15,
|
||||
str_contains($path, 'current-dj') => 30,
|
||||
str_contains($path, 'listeners') => 30,
|
||||
str_contains($path, 'leaderboard') => 60,
|
||||
str_contains($path, 'shouts') => 30,
|
||||
default => self::DEFAULT_TTL,
|
||||
};
|
||||
}
|
||||
}
|
||||
Executable
+20
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Illuminate\Auth\Middleware\Authenticate as Middleware;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class Authenticate extends Middleware
|
||||
{
|
||||
/**
|
||||
* Get the path the user should be redirected to when they are not authenticated.
|
||||
*/
|
||||
#[\Override]
|
||||
protected function redirectTo(Request $request): ?string
|
||||
{
|
||||
return $request->expectsJson() ? null : route('login');
|
||||
}
|
||||
}
|
||||
Executable
+48
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use App\Models\User\Ban;
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class BannedMiddleware
|
||||
{
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
$authenticated = Auth::check();
|
||||
$ipBan = Ban::where('ip', '=', $request->ip())
|
||||
->where('ban_expire', '>', time())
|
||||
->whereIn('type', ['ip', 'machine'])
|
||||
->orderByDesc('id')
|
||||
->exists();
|
||||
|
||||
if ($request->is('logout')) {
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
if (! $authenticated && ! $ipBan && $request->is('banned')) {
|
||||
return to_route('login');
|
||||
}
|
||||
|
||||
if ($ipBan && ! $request->is('banned')) {
|
||||
return to_route('banned.show');
|
||||
}
|
||||
|
||||
if ($authenticated) {
|
||||
$accountBan = $request->user()?->ban;
|
||||
|
||||
if ($accountBan && ! $request->is('banned')) {
|
||||
return to_route('banned.show');
|
||||
}
|
||||
|
||||
if (! $ipBan && ! $accountBan && $request->is('banned')) {
|
||||
return to_route('me.show');
|
||||
}
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use App\Console\Commands\DDoSDetectionCommand;
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class DDoSTrackingMiddleware
|
||||
{
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
if ((bool) setting('alert_ddos_enabled', false)) {
|
||||
$ip = $request->ip();
|
||||
DDoSDetectionCommand::trackRequest($ip);
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
Executable
+20
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Illuminate\Cookie\Middleware\EncryptCookies as Middleware;
|
||||
|
||||
class EncryptCookies extends Middleware
|
||||
{
|
||||
/**
|
||||
* The names of the cookies that should not be encrypted.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
#[\Override]
|
||||
protected $except = [
|
||||
//
|
||||
];
|
||||
}
|
||||
Executable
+23
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use App\Services\FindRetrosService;
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
/* Credits to Kani for this */
|
||||
class FindRetrosMiddleware
|
||||
{
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
$findRetrosService = new FindRetrosService;
|
||||
|
||||
if (config('habbo.findretros.enabled') && ! $findRetrosService->checkHasVoted()) {
|
||||
return redirect($findRetrosService->getRedirectUri());
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
Executable
+25
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class FixPermissionsPolicy
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param Closure(Request): (Response) $next
|
||||
*/
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
$response = $next($request);
|
||||
|
||||
$response->headers->remove('Permissions-Policy');
|
||||
$response->headers->set('Permissions-Policy', 'fullscreen=*');
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class ForceStaffTwoFactorMiddleware
|
||||
{
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
if (! Auth::check() || ! setting('force_staff_2fa')) {
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
$user = $request->user();
|
||||
$urls = [
|
||||
'user/settings/two-factor',
|
||||
'user/settings/2fa-verify',
|
||||
];
|
||||
|
||||
if (($user->rank >= setting('min_staff_rank') && ! $user->two_factor_confirmed) && ! in_array(request()->path(), $urls)) {
|
||||
return to_route('settings.two-factor');
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
Executable
+31
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use App\Models\Miscellaneous\WebsiteSetting;
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class InjectPwaMeta
|
||||
{
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
$pwaEnabled = WebsiteSetting::where('key', 'pwa_enabled')->first()?->value === '1';
|
||||
|
||||
if ($pwaEnabled) {
|
||||
$config = [
|
||||
'name' => WebsiteSetting::where('key', 'pwa_name')->first()?->value ?? 'Epicnabbo',
|
||||
'short_name' => WebsiteSetting::where('key', 'pwa_short_name')->first()?->value ?? 'Epicnabbo',
|
||||
'theme_color' => WebsiteSetting::where('key', 'pwa_theme_color')->first()?->value ?? '#eeb425',
|
||||
];
|
||||
|
||||
view()->share('pwa_enabled', true);
|
||||
view()->share('pwa_config', $config);
|
||||
} else {
|
||||
view()->share('pwa_enabled', false);
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
+180
@@ -0,0 +1,180 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use App\Models\Miscellaneous\WebsiteInstallation;
|
||||
use App\Services\InstallationService;
|
||||
use Closure;
|
||||
use Exception;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class InstallationMiddleware
|
||||
{
|
||||
public function handle(Request $request, Closure $next)
|
||||
{
|
||||
if (str_starts_with($request->path(), 'api/')) {
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
if (app(InstallationService::class)->isComplete()) {
|
||||
if ($request->is('installation*')) {
|
||||
return to_route('welcome');
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
$this->ensureInstallationTableExists();
|
||||
|
||||
$installation = $this->getInstallation();
|
||||
|
||||
$isInstallationStepHandled = $this->handleInstallationSteps($request, $installation);
|
||||
|
||||
if (! $isInstallationStepHandled) {
|
||||
return $this->redirectIfNotCompleted($installation);
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
private function ensureInstallationTableExists(): void
|
||||
{
|
||||
if (! Schema::hasTable('website_installation')) {
|
||||
Artisan::call('migrate', ['--path' => 'database/migrations/' . findMigration('website_installation')]);
|
||||
|
||||
// Migration executed, proceed
|
||||
}
|
||||
|
||||
if (! Schema::hasTable('sessions')) {
|
||||
Artisan::call('migrate', ['--path' => 'database/migrations/' . findMigration('sessions')]);
|
||||
}
|
||||
}
|
||||
|
||||
private function getInstallation()
|
||||
{
|
||||
try {
|
||||
$cacheKey = 'installation_record';
|
||||
|
||||
$cachedInstallation = Cache::get($cacheKey);
|
||||
|
||||
if ($cachedInstallation) {
|
||||
$installation = new WebsiteInstallation;
|
||||
$installation->fill((array) $cachedInstallation);
|
||||
|
||||
return $installation;
|
||||
}
|
||||
|
||||
$installation = WebsiteInstallation::query()->first();
|
||||
|
||||
if (! $installation) {
|
||||
$installation = WebsiteInstallation::create([
|
||||
'step' => 0,
|
||||
'completed' => false,
|
||||
'installation_key' => Str::uuid(),
|
||||
'user_ip' => request()->ip(),
|
||||
]);
|
||||
}
|
||||
|
||||
if ($installation instanceof WebsiteInstallation && $installation->completed) {
|
||||
Cache::rememberForever($cacheKey, fn () => $installation->toArray());
|
||||
}
|
||||
|
||||
return $installation;
|
||||
} catch (Exception $e) {
|
||||
Log::error('Error fetching or creating WebsiteInstallation: ' . $e->getMessage());
|
||||
abort(500, 'An error occurred while setting up installation.');
|
||||
}
|
||||
}
|
||||
|
||||
private function handleInstallationSteps(Request $request, WebsiteInstallation $installation)
|
||||
{
|
||||
if ($installation->completed) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($this->isWelcomeStep($request, $installation)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($this->isRedirectToWelcome($request, $installation)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->isInvalidAccess($request, $installation)) {
|
||||
abort(403);
|
||||
}
|
||||
|
||||
if ($this->isInvalidStep($request)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ! $this->isMismatchedStep($request, $installation);
|
||||
}
|
||||
|
||||
private function isWelcomeStep(Request $request, WebsiteInstallation $installation)
|
||||
{
|
||||
return $installation->step === 0 && $request->getRequestUri() === '/installation';
|
||||
}
|
||||
|
||||
private function isRedirectToWelcome(Request $request, WebsiteInstallation $installation)
|
||||
{
|
||||
return $installation->step === 0 && $request->getRequestUri() !== '/installation' && $request->method() !== 'POST';
|
||||
}
|
||||
|
||||
private function isInvalidAccess(Request $request, WebsiteInstallation $installation)
|
||||
{
|
||||
// Skip IP check during testing
|
||||
if (app()->environment('testing')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $installation->step > 0 && $request->ip() !== $installation->user_ip;
|
||||
}
|
||||
|
||||
private function isInvalidStep(Request $request)
|
||||
{
|
||||
return ! $this->isValidStep($request) && $this->isNonPostRequest($request);
|
||||
}
|
||||
|
||||
private function isMismatchedStep(Request $request, WebsiteInstallation $installation)
|
||||
{
|
||||
return $this->getCurrentStep($request) !== $installation->step && $this->isNonPostRequest($request);
|
||||
}
|
||||
|
||||
private function isValidStep(Request $request)
|
||||
{
|
||||
$step = $this->getCurrentStep($request);
|
||||
|
||||
return filter_var($step, FILTER_VALIDATE_INT) !== false;
|
||||
}
|
||||
|
||||
private function isNonPostRequest(Request $request)
|
||||
{
|
||||
return $request->method() !== 'POST' || $request->is('restart-installation');
|
||||
}
|
||||
|
||||
private function getCurrentStep(Request $request)
|
||||
{
|
||||
return (int) Str::after($request->path(), 'step/');
|
||||
}
|
||||
|
||||
private function redirectToStep(int $step)
|
||||
{
|
||||
return to_route('installation.show-step', $step);
|
||||
}
|
||||
|
||||
protected function redirectIfNotCompleted(WebsiteInstallation $installation)
|
||||
{
|
||||
|
||||
if ($installation->step === 0) {
|
||||
return to_route('installation.index');
|
||||
}
|
||||
|
||||
return $this->redirectToStep($installation->step ?: 1);
|
||||
}
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use App\Models\Miscellaneous\WebsiteLanguage;
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Support\Facades\Session;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class LocalizationMiddleware
|
||||
{
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
if (Schema::hasTable('website_settings') && Session::has('locale')) {
|
||||
App::setLocale(Session::get('locale'));
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
$countryCode = config('habbo.site.default_language');
|
||||
if (isset($_SERVER['HTTP_CF_IPCOUNTRY'])) {
|
||||
$countryCode = strtolower((string) $_SERVER['HTTP_CF_IPCOUNTRY']);
|
||||
} elseif (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
|
||||
$countryCode = strtolower(substr((string) $_SERVER['HTTP_ACCEPT_LANGUAGE'], 0, 2));
|
||||
}
|
||||
|
||||
// If the language does not exist in the database
|
||||
if (Schema::hasTable('website_languages') && WebsiteLanguage::where('country_code', '=', $countryCode)->doesntExist()) {
|
||||
$defaultCountry = config('habbo.site.default_language');
|
||||
|
||||
App::setLocale($defaultCountry);
|
||||
Session::put('locale', $defaultCountry);
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
App::setLocale($countryCode);
|
||||
Session::put('locale', $countryCode);
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
Executable
+93
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use App\Models\StaffActivity;
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class LogStaffActivity
|
||||
{
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
public function terminate(Request $request, Response $response): void
|
||||
{
|
||||
if (auth()->check() && auth()->user()->rank >= (int) setting('min_staff_rank', 3)) {
|
||||
$this->logRequest($request);
|
||||
}
|
||||
}
|
||||
|
||||
private function logRequest(Request $request): void
|
||||
{
|
||||
$user = auth()->user();
|
||||
$path = $request->path();
|
||||
|
||||
if (str_contains($path, 'housekeeping') || str_starts_with($path, 'hk')) {
|
||||
$action = $this->determineAction($request);
|
||||
$description = $this->generateDescription($request);
|
||||
|
||||
if ($action && $description) {
|
||||
StaffActivity::log(
|
||||
$user->id,
|
||||
$action,
|
||||
$description,
|
||||
$request->route()?->getName(),
|
||||
null,
|
||||
[
|
||||
'method' => $request->method(),
|
||||
'path' => $path,
|
||||
'route' => $request->route()?->getName(),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function determineAction(Request $request): ?string
|
||||
{
|
||||
$method = $request->method();
|
||||
$path = $request->path();
|
||||
|
||||
if ($method === 'POST') {
|
||||
if (str_contains($path, 'ban')) {
|
||||
return 'user_ban';
|
||||
} elseif (str_contains($path, 'delete')) {
|
||||
return 'content_delete';
|
||||
} elseif (str_contains($path, 'create') || str_contains($path, 'store')) {
|
||||
return 'content_create';
|
||||
} elseif (str_contains($path, 'edit') || str_contains($path, 'update')) {
|
||||
return 'content_edit';
|
||||
} elseif (str_contains($path, 'rank')) {
|
||||
return 'rank_change';
|
||||
} elseif (str_contains($path, 'settings')) {
|
||||
return 'settings_update';
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function generateDescription(Request $request): ?string
|
||||
{
|
||||
$action = $this->determineAction($request);
|
||||
|
||||
if (! $action) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return match ($action) {
|
||||
'user_ban' => 'Banned user: ' . ($request->input('user_id') ?? $request->input('username') ?? 'unknown'),
|
||||
'user_unban' => 'Unbanned user',
|
||||
'content_delete' => 'Deleted content: ' . $request->path(),
|
||||
'content_create' => 'Created new content',
|
||||
'content_edit' => 'Updated content: ' . $request->path(),
|
||||
'rank_change' => 'Changed user rank',
|
||||
'settings_update' => 'Updated settings',
|
||||
default => 'Performed action: ' . $action,
|
||||
};
|
||||
}
|
||||
}
|
||||
Executable
+24
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class LogViewerMiddleware
|
||||
{
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
if (! Auth::check()) {
|
||||
return to_route('login');
|
||||
}
|
||||
|
||||
if (! hasPermission('view_server_logs')) {
|
||||
abort(403);
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Throwable;
|
||||
|
||||
class MaintenanceMiddleware
|
||||
{
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
$isPostRequest = $request->method() === 'POST';
|
||||
$isMaintenanceRequest = $request->is('maintenance');
|
||||
$maintenanceEnabled = $this->getMaintenanceEnabled();
|
||||
$isPreview = $request->query('preview') === '1';
|
||||
$minRank = $this->getMinMaintenanceRank();
|
||||
|
||||
$fortify2faRoutes = [
|
||||
'two-factor.login',
|
||||
'two-factor.confirm',
|
||||
];
|
||||
|
||||
if ($maintenanceEnabled && $isPostRequest && ! Auth::check()) {
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
$route = $request->route();
|
||||
$routeName = $route?->getName();
|
||||
$isFortify2faRoute = in_array($routeName, $fortify2faRoutes, true);
|
||||
|
||||
if ($maintenanceEnabled && $isFortify2faRoute) {
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
$isStaff = Auth::check() && Auth::user()->rank >= $minRank;
|
||||
|
||||
if ($isStaff) {
|
||||
if ($isMaintenanceRequest && ! $isPreview) {
|
||||
return to_route('me.show');
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
if ($maintenanceEnabled && ! $isMaintenanceRequest && ! $isPostRequest) {
|
||||
return to_route('maintenance.show');
|
||||
}
|
||||
|
||||
if (! $maintenanceEnabled && $isMaintenanceRequest && ! $isPostRequest && ! $isPreview) {
|
||||
return to_route('welcome');
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
private function getMaintenanceEnabled(): bool
|
||||
{
|
||||
try {
|
||||
return (bool) setting('maintenance_enabled');
|
||||
} catch (Throwable) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private function getMinMaintenanceRank(): int
|
||||
{
|
||||
try {
|
||||
return (int) setting('min_maintenance_login_rank', 1);
|
||||
} catch (Throwable) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Illuminate\Foundation\Http\Middleware\PreventRequestsDuringMaintenance as Middleware;
|
||||
|
||||
class PreventRequestsDuringMaintenance extends Middleware
|
||||
{
|
||||
/**
|
||||
* The URIs that should be reachable while maintenance mode is enabled.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
#[\Override]
|
||||
protected $except = [
|
||||
//
|
||||
];
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class RealClientIpMiddleware
|
||||
{
|
||||
public function handle(Request $request, Closure $next)
|
||||
{
|
||||
$proxyHeaders = [
|
||||
'HTTP_CF_CONNECTING_IP',
|
||||
'HTTP_X_FORWARDED_FOR',
|
||||
'HTTP_X_REAL_IP',
|
||||
'HTTP_CLIENT_IP',
|
||||
'HTTP_TRUE_CLIENT_IP',
|
||||
];
|
||||
|
||||
foreach ($proxyHeaders as $header) {
|
||||
if (! empty($_SERVER[$header])) {
|
||||
$ip = $_SERVER[$header];
|
||||
if (str_contains((string) $ip, ',')) {
|
||||
[$ip] = explode(',', (string) $ip);
|
||||
}
|
||||
$ip = trim((string) $ip);
|
||||
if (filter_var($ip, FILTER_VALIDATE_IP)) {
|
||||
// Set the real IP as REMOTE_ADDR
|
||||
$request->server->set('REMOTE_ADDR', $ip);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Special handling for REMOTE_ADDR with multiple IPs
|
||||
$remoteAddr = $_SERVER['REMOTE_ADDR'] ?? '';
|
||||
if (! empty($remoteAddr) && str_contains((string) $remoteAddr, ',')) {
|
||||
[$ip] = explode(',', (string) $remoteAddr);
|
||||
$ip = trim($ip);
|
||||
if (filter_var($ip, FILTER_VALIDATE_IP)) {
|
||||
$request->server->set('REMOTE_ADDR', $ip);
|
||||
}
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use App\Providers\RouteServiceProvider;
|
||||
use Closure;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class RedirectIfAuthenticated
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param Closure(Request):((\Illuminate\Http\Response|RedirectResponse)) $next
|
||||
*/
|
||||
public function handle(Request $request, Closure $next, string ...$guards): Response
|
||||
{
|
||||
$guards = $guards === [] ? [null] : $guards;
|
||||
|
||||
foreach ($guards as $guard) {
|
||||
if (Auth::guard($guard)->check()) {
|
||||
if ($request->expectsJson()) {
|
||||
return response()->json();
|
||||
}
|
||||
|
||||
return redirect(RouteServiceProvider::HOME);
|
||||
}
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
Executable
+24
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Qirolab\Theme\Theme;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class SetThemeMiddleware
|
||||
{
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
$theme = setting('theme');
|
||||
|
||||
if (empty($theme) || $theme === '1') {
|
||||
Theme::set('atom');
|
||||
} else {
|
||||
Theme::set($theme);
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
Executable
+54
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
|
||||
class SystemCheck
|
||||
{
|
||||
public function handle(Request $request, Closure $next)
|
||||
{
|
||||
$checks = [];
|
||||
|
||||
// 1. Check .env exists
|
||||
if (! file_exists(base_path('.env'))) {
|
||||
if (file_exists(base_path('.env.example'))) {
|
||||
copy(base_path('.env.example'), base_path('.env'));
|
||||
Artisan::call('key:generate', ['--no-interaction' => true]);
|
||||
$checks[] = 'Created .env from example';
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Check APP_KEY
|
||||
if (empty(Config::get('app.key'))) {
|
||||
Artisan::call('key:generate', ['--no-interaction' => true]);
|
||||
$checks[] = 'Generated APP_KEY';
|
||||
}
|
||||
|
||||
// 3. Check storage symlink
|
||||
if (! file_exists(public_path('storage'))) {
|
||||
Artisan::call('storage:link');
|
||||
$checks[] = 'Created storage symlink';
|
||||
}
|
||||
|
||||
// 4. Check required PHP extensions
|
||||
$required = ['pdo_mysql', 'curl', 'mbstring', 'openssl', 'tokenizer'];
|
||||
$missing = array_filter($required, fn ($ext) => ! extension_loaded($ext));
|
||||
if (! empty($missing)) {
|
||||
return response()->json([
|
||||
'error' => 'Missing PHP extensions: ' . implode(', ', $missing),
|
||||
'solution' => 'Install: apt install php-' . implode(' php-', $missing),
|
||||
], 500);
|
||||
}
|
||||
|
||||
// 5. Log any auto-fixes
|
||||
if (! empty($checks)) {
|
||||
\Log::info('SystemCheck auto-fixed: ' . implode(', ', $checks));
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
Executable
+22
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Illuminate\Foundation\Http\Middleware\TrimStrings as Middleware;
|
||||
|
||||
class TrimStrings extends Middleware
|
||||
{
|
||||
/**
|
||||
* The names of the attributes that should not be trimmed.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
#[\Override]
|
||||
protected $except = [
|
||||
'current_password',
|
||||
'password',
|
||||
'password_confirmation',
|
||||
];
|
||||
}
|
||||
Executable
+32
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Illuminate\Http\Middleware\TrustProxies as Middleware;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class TrustProxies extends Middleware
|
||||
{
|
||||
/**
|
||||
* The trusted proxies for this application.
|
||||
*
|
||||
* @var array<int, string>|string|null
|
||||
*/
|
||||
#[\Override]
|
||||
protected $proxies;
|
||||
|
||||
/**
|
||||
* The headers that should be used to detect proxies.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
#[\Override]
|
||||
protected $headers =
|
||||
Request::HEADER_X_FORWARDED_FOR |
|
||||
Request::HEADER_X_FORWARDED_HOST |
|
||||
Request::HEADER_X_FORWARDED_PORT |
|
||||
Request::HEADER_X_FORWARDED_PROTO |
|
||||
Request::HEADER_X_FORWARDED_AWS_ELB;
|
||||
}
|
||||
Executable
+124
@@ -0,0 +1,124 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use App\Models\Miscellaneous\WebsiteBlockedCountry;
|
||||
use App\Models\Miscellaneous\WebsiteIpBlacklist;
|
||||
use App\Models\Miscellaneous\WebsiteIpWhitelist;
|
||||
use App\Services\IpLookupService;
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class VPNCheckerMiddleware
|
||||
{
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
$isEnabled = setting('vpn_block_enabled') === '1';
|
||||
|
||||
if (! $isEnabled) {
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
if (hasPermission('bypass_vpn')) {
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
$userIp = $request->ip();
|
||||
|
||||
if (WebsiteIpWhitelist::where('ip_address', $userIp)->exists()) {
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
if (WebsiteIpBlacklist::where('ip_address', $userIp)->exists()) {
|
||||
return $this->denyAccess($request);
|
||||
}
|
||||
|
||||
$ipService = new IpLookupService('');
|
||||
|
||||
$countryInfo = $ipService->getCountryInfo($userIp);
|
||||
|
||||
if (! empty($countryInfo['country_code'])) {
|
||||
$countryCode = $countryInfo['country_code'];
|
||||
|
||||
if (WebsiteBlockedCountry::where('country_code', $countryCode)->exists()) {
|
||||
return $this->denyAccess($request);
|
||||
}
|
||||
|
||||
$countryWhitelisted = WebsiteIpWhitelist::where('country_code', $countryCode)
|
||||
->where('whitelist_country', true)
|
||||
->exists();
|
||||
|
||||
if (! $countryWhitelisted) {
|
||||
$countryBlacklisted = WebsiteIpBlacklist::where('country_code', $countryCode)
|
||||
->where('blacklist_country', true)
|
||||
->exists();
|
||||
|
||||
if ($countryBlacklisted) {
|
||||
return $this->denyAccess($request);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$threatInfo = $ipService->checkVpnProxyTor($userIp);
|
||||
|
||||
if (! empty($threatInfo['asn'])) {
|
||||
$asnWhitelisted = WebsiteIpWhitelist::where('asn', $threatInfo['asn'])
|
||||
->where('whitelist_asn', true)
|
||||
->exists();
|
||||
|
||||
if (! $asnWhitelisted) {
|
||||
$asnBlacklisted = WebsiteIpBlacklist::where('asn', $threatInfo['asn'])
|
||||
->where('blacklist_asn', true)
|
||||
->exists();
|
||||
|
||||
if ($asnBlacklisted) {
|
||||
return $this->denyAccess($request);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$blockVpn = setting('block_vpn') === '1';
|
||||
$blockTor = setting('block_tor') === '1';
|
||||
$blockMalicious = setting('block_malicious') === '1';
|
||||
|
||||
$shouldBlock = false;
|
||||
|
||||
if ($blockVpn && ($threatInfo['is_vpn'] ?? false)) {
|
||||
$shouldBlock = true;
|
||||
}
|
||||
|
||||
if ($blockTor && ($threatInfo['is_tor'] ?? false)) {
|
||||
$shouldBlock = true;
|
||||
}
|
||||
|
||||
if ($blockMalicious && ($threatInfo['is_malicious'] ?? false)) {
|
||||
$shouldBlock = true;
|
||||
}
|
||||
|
||||
if ($shouldBlock) {
|
||||
WebsiteIpBlacklist::create([
|
||||
'ip_address' => $userIp,
|
||||
'asn' => $threatInfo['asn'] ?? null,
|
||||
'country_code' => $countryInfo['country_code'] ?? null,
|
||||
]);
|
||||
|
||||
return $this->denyAccess($request);
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
private function denyAccess(Request $request): Response
|
||||
{
|
||||
if ($request->expectsJson()) {
|
||||
return response()->json([
|
||||
'message' => __('Your IP has been restricted - If you think this is a mistake, you can contact us on our Discord.'),
|
||||
], 403);
|
||||
}
|
||||
|
||||
return to_route('me.show')->withErrors([
|
||||
'message' => __('Your IP has been restricted - If you think this is a mistake, you can contact us on our Discord.'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
Executable
+24
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Illuminate\Routing\Middleware\ValidateSignature as Middleware;
|
||||
|
||||
class ValidateSignature extends Middleware
|
||||
{
|
||||
/**
|
||||
* The names of the query string parameters that should be ignored.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $except = [
|
||||
// 'fbclid',
|
||||
// 'utm_campaign',
|
||||
// 'utm_content',
|
||||
// 'utm_medium',
|
||||
// 'utm_source',
|
||||
// 'utm_term',
|
||||
];
|
||||
}
|
||||
Executable
+18
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
class VerifyCsrfToken extends \Illuminate\Foundation\Http\Middleware\VerifyCsrfToken
|
||||
{
|
||||
/**
|
||||
* The URIs that should be excluded from CSRF verification.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
#[\Override]
|
||||
protected $except = [
|
||||
//
|
||||
];
|
||||
}
|
||||
Reference in New Issue
Block a user