Fix bin/publish: copy docs.dist from project root

Fix bin/publish: use correct .env path for rspade_system
Fix bin/publish script: prevent grep exit code 1 from terminating script

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
root
2025-10-21 02:08:33 +00:00
commit f6fac6c4bc
79758 changed files with 10547827 additions and 0 deletions

View File

@@ -0,0 +1,17 @@
<?php
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.
*/
protected function redirectTo(Request $request): ?string
{
return $request->expectsJson() ? null : route('login');
}
}

View File

@@ -0,0 +1,46 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpKernel\Exception\ServiceUnavailableHttpException;
class CheckMigrationMode
{
protected $flag_file = '/var/www/html/.migrating';
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle(Request $request, Closure $next)
{
// Only check migration mode in development environment
if (!app()->environment('production') && file_exists($this->flag_file)) {
$session_info = json_decode(file_get_contents($this->flag_file), true);
$started_at = $session_info['started_at'] ?? 'unknown';
// Create a detailed error message
$message = "🚧 Database Migration in Progress\n\n";
$message .= "A database migration session is currently active.\n";
$message .= "Started at: {$started_at}\n\n";
$message .= "The application is temporarily unavailable to ensure data integrity.\n\n";
$message .= "To complete the migration session, run one of these commands:\n";
$message .= " • php artisan migrate:commit - Keep the changes\n";
$message .= " • php artisan migrate:rollback - Revert to snapshot\n\n";
$message .= "For status: php artisan migrate:status";
// Throw service unavailable exception
throw new ServiceUnavailableHttpException(
null,
$message
);
}
return $next($request);
}
}

View File

@@ -0,0 +1,17 @@
<?php
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>
*/
protected $except = [
//
];
}

View File

@@ -0,0 +1,176 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class Gatekeeper
{
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next): Response
{
// Check if gatekeeper is enabled
if (!config('rsx.gatekeeper.enabled')) {
return $next($request);
}
// Always allow CLI requests
if (php_sapi_name() === 'cli') {
return $next($request);
}
// Always allow IDE helper endpoints for VS Code extension integration
if (str_starts_with($request->path(), '_idehelper')) {
return $next($request);
}
// Check if request is whitelisted (localhost without reverse proxy headers)
if ($this->is_whitelisted($request)) {
return $next($request);
}
// Check if user has valid authentication cookie
$cookie_name = config('rsx.gatekeeper.cookie_name', 'gatekeeper_auth');
$password = config('rsx.gatekeeper.password');
if (!$password) {
throw new \Exception('Gatekeeper enabled but no password configured. Set GATEKEEPER_PASSWORD in .env');
}
$cookie_value = $request->cookie($cookie_name);
$expected_hash = hash('sha256', $password);
// If authenticated, renew cookie and continue
if ($cookie_value === $expected_hash) {
$lifetime_hours = config('rsx.gatekeeper.cookie_lifetime_hours', 12);
$cookie = cookie($cookie_name, $expected_hash, 60 * $lifetime_hours);
$response = $next($request);
// Only add cookie to regular responses, not binary file responses
if (method_exists($response, 'withCookie')) {
return $response->withCookie($cookie);
}
return $response;
}
// Handle login POST request
if ($request->isMethod('POST') && $request->path() === '_gatekeeper/login') {
return $this->handle_login($request);
}
// Show login page
return $this->show_login_page($request);
}
/**
* Check if the request is whitelisted (localhost without reverse proxy headers)
*/
private function is_whitelisted(Request $request): bool
{
// Get the client IP
$ip = $request->ip();
// List of localhost IPs
$localhost_ips = [
'127.0.0.1',
'localhost',
'::1',
'0.0.0.0',
];
// Check if IP matches localhost patterns
$is_localhost = in_array($ip, $localhost_ips) ||
str_starts_with($ip, '127.') ||
$ip === '::1';
if (!$is_localhost) {
return false;
}
// Check for reverse proxy headers - if present, this is NOT a true localhost request
$proxy_headers = [
'HTTP_X_FORWARDED_FOR',
'HTTP_X_FORWARDED_HOST',
'HTTP_X_FORWARDED_PORT',
'HTTP_X_FORWARDED_PROTO',
'HTTP_X_FORWARDED_SERVER',
'HTTP_X_REAL_IP',
'HTTP_X_ORIGINAL_URL',
'HTTP_FORWARDED',
'HTTP_CLIENT_IP',
'HTTP_CF_CONNECTING_IP', // Cloudflare
'HTTP_TRUE_CLIENT_IP', // Cloudflare Enterprise
'HTTP_X_CLUSTER_CLIENT_IP',
'HTTP_X_FORWARDED',
'HTTP_FORWARDED_FOR',
'HTTP_VIA',
];
foreach ($proxy_headers as $header) {
if (!empty($_SERVER[$header])) {
// Reverse proxy header detected - force authentication
return false;
}
}
// Also check Laravel's request headers
if ($request->headers->has('X-Forwarded-For') ||
$request->headers->has('X-Forwarded-Host') ||
$request->headers->has('X-Forwarded-Proto') ||
$request->headers->has('X-Real-IP') ||
$request->headers->has('Forwarded')) {
return false;
}
// True localhost request without proxy headers
return true;
}
/**
* Handle login POST request
*/
private function handle_login(Request $request): Response
{
$password = config('rsx.gatekeeper.password');
$submitted = $request->input('password');
if ($submitted === $password) {
// Authentication successful
$cookie_name = config('rsx.gatekeeper.cookie_name', 'gatekeeper_auth');
$lifetime_hours = config('rsx.gatekeeper.cookie_lifetime_hours', 12);
$cookie_value = hash('sha256', $password);
$cookie = cookie($cookie_name, $cookie_value, 60 * $lifetime_hours);
// Redirect to originally requested URL or home
$redirect = $request->input('redirect', '/');
return redirect($redirect)->withCookie($cookie);
}
// Authentication failed - show login page with error
return $this->show_login_page($request, 'Invalid password. Please try again.');
}
/**
* Show the gatekeeper login page
*/
private function show_login_page(Request $request, string $error = null): Response
{
$data = [
'title' => config('rsx.gatekeeper.title', 'Development Preview'),
'subtitle' => config('rsx.gatekeeper.subtitle', 'This is a restricted development preview site. Please enter the access password to continue.'),
'logo' => config('rsx.gatekeeper.logo'),
'error' => $error,
'redirect' => $request->fullUrl(),
];
return response()->view('gatekeeper.login', $data, 403);
}
}

View File

@@ -0,0 +1,59 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use App\RSpade\Core\Debug\Debugger;
class PlaywrightTestMode
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle(Request $request, Closure $next)
{
// Only process Playwright headers in non-production environments
// and only from loopback addresses (no proxy headers)
if (app()->environment('production') || !is_loopback_ip()) {
return $next($request);
}
// If this is a Playwright test request, configure console debug from headers
if ($request->hasHeader('X-Playwright-Test')) {
// Configure console debug settings from headers
Debugger::configure_from_headers();
$response = $next($request);
// If this is a redirect response, modify the Location header to be relative
if ($response->isRedirect()) {
$location = $response->headers->get('Location');
// Parse the URL and extract just the path and query
$parsed = parse_url($location);
if (isset($parsed['path'])) {
$relative_url = $parsed['path'];
if (isset($parsed['query'])) {
$relative_url .= '?' . $parsed['query'];
}
if (isset($parsed['fragment'])) {
$relative_url .= '#' . $parsed['fragment'];
}
// Only modify if it's a local redirect (not external)
if (!isset($parsed['host']) || $parsed['host'] === $request->getHost() || $parsed['host'] === '127.0.0.1' || $parsed['host'] === 'localhost') {
$response->headers->set('Location', $relative_url);
}
}
}
return $response;
}
return $next($request);
}
}

View File

@@ -0,0 +1,17 @@
<?php
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>
*/
protected $except = [
//
];
}

View File

@@ -0,0 +1,30 @@
<?php
namespace App\Http\Middleware;
use App\Providers\RouteServiceProvider;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Symfony\Component\HttpFoundation\Response;
class RedirectIfAuthenticated
{
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next, string ...$guards): Response
{
$guards = empty($guards) ? [null] : $guards;
foreach ($guards as $guard) {
if (Auth::guard($guard)->check()) {
return redirect(RouteServiceProvider::HOME);
}
}
return $next($request);
}
}

View File

@@ -0,0 +1,195 @@
<?php
/**
* DISABLED: 2025-09-14
* This middleware was disabled as it appears to be a leftover from an earlier failed routing attempt.
* RSX routing is now handled through the 404 exception handler in app/Exceptions/Handler.php.
*
* This file is kept for observation in case any key functionality needs to be restored:
* - Automatic cache clearing in development mode
* - Path exclusion for certain prefixes (_debugbar, horizon, telescope, etc.)
* - Session initialization for RSX requests
* - RSX debugging headers
*
* To re-enable this middleware:
* 1. Rename file back to RsxMiddleware.php (remove .disabled extension)
* 2. Re-add to app/Http/Kernel.php:
* protected $routeMiddleware = [
* 'rsx' => \App\Http\Middleware\RsxMiddleware::class,
* ];
*
* CODING CONVENTION:
* This file follows the coding convention where variable_names and function_names
* use snake_case (underscore_wherever_possible).
*/
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
/**
* RsxMiddleware processes requests before RSX dispatch
*
* This middleware:
* - Checks if request should be handled by RSX
* - Maintains session and auth state
* - Adds RSX-specific headers
*/
class RsxMiddleware
{
/**
* Handle an incoming request
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle(Request $request, Closure $next)
{
// In development mode, automatically disable all Laravel caches
if (!app()->environment('production')) {
$this->ensure_caches_disabled();
}
// Note: Asset requests are handled by the RSX AssetHandler in the Dispatcher
// No need to block them here
// Check if request path should be excluded from RSX
if ($this->is_excluded_path($request)) {
abort(404);
}
// Add RSX indicator to request
$request->attributes->set('rsx_request', true);
// Ensure session is started for RSX requests
if ($request->hasSession()) {
$request->session()->start();
}
// Process the request
$response = $next($request);
// Add RSX response headers if configured
if (config('rsx.development.show_route_details', false)) {
$response->headers->set('X-RSX-Dispatch', 'true');
// Add dispatch time if available
if ($request->attributes->has('rsx_dispatch_time')) {
$response->headers->set(
'X-RSX-Dispatch-Time',
$request->attributes->get('rsx_dispatch_time') . 'ms'
);
}
}
return $response;
}
/**
* Check if path should be excluded from RSX routing
*
* @param Request $request
* @return bool
*/
protected function is_excluded_path(Request $request)
{
$path = $request->path();
// Never handle Laravel's default routes
$excluded_prefixes = [
'_debugbar',
'horizon',
'telescope',
'nova',
'livewire',
'sanctum',
'broadcasting'
];
foreach ($excluded_prefixes as $prefix) {
if (str_starts_with($path, $prefix . '/') || $path === $prefix) {
return true;
}
}
// Check custom excluded paths from config
$custom_excluded = config('rsx.routing.excluded_paths', []);
foreach ($custom_excluded as $excluded) {
if (str_starts_with($path, $excluded)) {
return true;
}
}
return false;
}
/**
* Ensure all Laravel caches are disabled in development mode
*
* @return void
*/
protected function ensure_caches_disabled()
{
static $caches_checked = false;
// Only check once per request lifecycle
if ($caches_checked) {
return;
}
$caches_checked = true;
// Check if any caches exist that shouldn't in development
$cache_files = [
'config' => app()->getCachedConfigPath(),
'routes' => app()->getCachedRoutesPath(),
'events' => app()->getCachedEventsPath(),
];
$files = app('files');
foreach ($cache_files as $type => $path) {
if ($files->exists($path)) {
// Clear the cache silently using output buffering
ob_start();
try {
switch ($type) {
case 'config':
\Artisan::call('config:clear');
break;
case 'routes':
\Artisan::call('route:clear');
break;
case 'events':
\Artisan::call('event:clear');
break;
}
} catch (\Exception $e) {
// Silently ignore any errors - we're just trying to clear caches
} finally {
ob_end_clean();
}
}
}
// Also check compiled views and clear if needed
$compiled_path = config('view.compiled');
if ($files->isDirectory($compiled_path)) {
$compiled_views = $files->glob("{$compiled_path}/*");
if (count($compiled_views) > 100) { // Only clear if there are many compiled views
ob_start();
try {
\Artisan::call('view:clear');
} catch (\Exception $e) {
// Silently ignore
} finally {
ob_end_clean();
}
}
}
}
}

View File

@@ -0,0 +1,19 @@
<?php
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>
*/
protected $except = [
'current_password',
'password',
'password_confirmation',
];
}

View File

@@ -0,0 +1,28 @@
<?php
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
*/
protected $proxies;
/**
* The headers that should be used to detect proxies.
*
* @var int
*/
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;
}

View File

@@ -0,0 +1,22 @@
<?php
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',
];
}

View File

@@ -0,0 +1,17 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as Middleware;
class VerifyCsrfToken extends Middleware
{
/**
* The URIs that should be excluded from CSRF verification.
*
* @var array<int, string>
*/
protected $except = [
//
];
}