Fix IDE service routing and path normalization Refactor IDE services and add session rotation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
194 lines
6.3 KiB
PHP
Executable File
194 lines
6.3 KiB
PHP
Executable File
<?php
|
|
/**
|
|
* IDE Service Authentication
|
|
*
|
|
* SECURITY-CRITICAL: This file must be executed BEFORE any IDE service logic.
|
|
* All /_ide/service/* requests must pass through this authentication check.
|
|
*
|
|
* Two authentication modes:
|
|
* 1. Session-based auth with signature verification (production/remote)
|
|
* 2. Localhost bypass (development only, strict requirements)
|
|
*/
|
|
|
|
// Base path - framework is in system/ subdirectory
|
|
// @REALPATH-EXCEPTION - Bootstrap file: runs before helpers loaded, needs PHP's realpath()
|
|
$system_path = realpath(__DIR__ . '/../../../..');
|
|
// @REALPATH-EXCEPTION - Bootstrap file: runs before helpers loaded, needs PHP's realpath()
|
|
define('IDE_AUTH_BASE_PATH', realpath($system_path . '/..')); // Project root
|
|
define('IDE_AUTH_SYSTEM_PATH', $system_path); // Framework root
|
|
|
|
// Helper to get framework paths
|
|
function ide_auth_framework_path($relative_path) {
|
|
return IDE_AUTH_SYSTEM_PATH . '/' . ltrim($relative_path, '/');
|
|
}
|
|
|
|
// JSON response helper
|
|
function ide_auth_json_response($data, $code = 200) {
|
|
http_response_code($code);
|
|
header('Content-Type: application/json');
|
|
echo json_encode($data);
|
|
exit;
|
|
}
|
|
|
|
// Error response helper
|
|
function ide_auth_error_response($message, $code = 400) {
|
|
ide_auth_json_response(['success' => false, 'error' => $message], $code);
|
|
}
|
|
|
|
// Check if IDE services are enabled
|
|
$env_file = IDE_AUTH_BASE_PATH . '/.env';
|
|
if (file_exists($env_file)) {
|
|
$env_content = file_get_contents($env_file);
|
|
// Check if production
|
|
if (preg_match('/^APP_ENV=production$/m', $env_content)) {
|
|
// Check if explicitly enabled in production
|
|
if (!preg_match('/^RSX_IDE_SERVICES_ENABLED=true$/m', $env_content)) {
|
|
ide_auth_error_response('IDE services disabled in production', 403);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Parse request URI to get service
|
|
$request_uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
|
|
$service_path = str_replace('/_ide/service', '', $request_uri);
|
|
$service_path = trim($service_path, '/');
|
|
|
|
// Get request body for signature verification
|
|
$request_body = file_get_contents('php://input');
|
|
|
|
// Handle authentication
|
|
$auth_data = [];
|
|
$session_header = $_SERVER['HTTP_X_SESSION'] ?? null;
|
|
$signature_header = $_SERVER['HTTP_X_SIGNATURE'] ?? null;
|
|
|
|
// Check if this is auth/create request (no auth required for session creation)
|
|
if ($service_path === 'auth/create' && $_SERVER['REQUEST_METHOD'] === 'POST') {
|
|
// Generate new auth session
|
|
$session = bin2hex(random_bytes(20));
|
|
$client_key = bin2hex(random_bytes(20));
|
|
$server_key = bin2hex(random_bytes(20));
|
|
|
|
$auth_data_new = [
|
|
'session' => $session,
|
|
'client_key' => $client_key,
|
|
'server_key' => $server_key,
|
|
'created' => time()
|
|
];
|
|
|
|
// Save auth file
|
|
$bridge_dir = ide_auth_framework_path('storage/rsx-ide-bridge');
|
|
if (!is_dir($bridge_dir)) {
|
|
mkdir($bridge_dir, 0755, true);
|
|
}
|
|
|
|
// Rotate old sessions - keep max 100 auth files
|
|
$auth_files = glob($bridge_dir . '/auth-*.json');
|
|
if (count($auth_files) >= 100) {
|
|
// Sort by modification time (oldest first)
|
|
usort($auth_files, function($a, $b) {
|
|
return filemtime($a) - filemtime($b);
|
|
});
|
|
|
|
// Delete oldest files to get down to 99 (so we can add the new one)
|
|
$files_to_delete = count($auth_files) - 99;
|
|
for ($i = 0; $i < $files_to_delete; $i++) {
|
|
unlink($auth_files[$i]);
|
|
}
|
|
}
|
|
|
|
$auth_file = $bridge_dir . '/auth-' . $session . '.json';
|
|
file_put_contents($auth_file, json_encode($auth_data_new, JSON_PRETTY_PRINT));
|
|
|
|
// Return auth data (unsigned response since client doesn't have keys yet)
|
|
http_response_code(200);
|
|
header('Content-Type: application/json');
|
|
echo json_encode([
|
|
'success' => true,
|
|
'session' => $session,
|
|
'client_key' => $client_key,
|
|
'server_key' => $server_key
|
|
]);
|
|
exit;
|
|
}
|
|
|
|
// Authentication required for all other requests
|
|
// Handle authentication with localhost bypass
|
|
|
|
// Localhost bypass for IDE integration
|
|
$is_localhost_bypass = false;
|
|
$request_host = $_SERVER['HTTP_HOST'] ?? '';
|
|
$request_scheme = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http';
|
|
$remote_addr = $_SERVER['REMOTE_ADDR'] ?? '';
|
|
|
|
// Check for proxy headers
|
|
$has_proxy_headers = false;
|
|
foreach ($_SERVER as $key => $value) {
|
|
if (str_starts_with($key, 'HTTP_X_')) {
|
|
if ($key === 'HTTP_X_SESSION' || $key === 'HTTP_X_SIGNATURE') {
|
|
continue;
|
|
}
|
|
$has_proxy_headers = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Check if request is from loopback
|
|
$is_loopback_ip = (
|
|
!$has_proxy_headers &&
|
|
($remote_addr === '127.0.0.1' ||
|
|
$remote_addr === '::1' ||
|
|
str_starts_with($remote_addr, '127.'))
|
|
);
|
|
|
|
// Check if not in production
|
|
$is_not_production = true;
|
|
if (file_exists($env_file)) {
|
|
$env_content = file_get_contents($env_file);
|
|
if (preg_match('/^APP_ENV=production$/m', $env_content)) {
|
|
$is_not_production = false;
|
|
}
|
|
}
|
|
|
|
// All conditions must be true for bypass
|
|
if (
|
|
$request_host === 'localhost' &&
|
|
$request_scheme === 'http' &&
|
|
$is_loopback_ip &&
|
|
$is_not_production
|
|
) {
|
|
$is_localhost_bypass = true;
|
|
$auth_data = [
|
|
'session' => 'localhost-bypass',
|
|
'client_key' => '',
|
|
'server_key' => ''
|
|
];
|
|
}
|
|
|
|
if (!$is_localhost_bypass) {
|
|
// Require authentication for non-localhost
|
|
if (!$session_header || !$signature_header) {
|
|
ide_auth_error_response('Authentication required', 401);
|
|
}
|
|
|
|
// Load and validate session
|
|
$auth_file = ide_auth_framework_path('storage/rsx-ide-bridge/auth-' . $session_header . '.json');
|
|
if (!file_exists($auth_file)) {
|
|
ide_auth_error_response('Session not found', 401);
|
|
}
|
|
|
|
$auth_data = json_decode(file_get_contents($auth_file), true);
|
|
$expected_signature = sha1($request_body . $auth_data['client_key']);
|
|
if ($signature_header !== $expected_signature) {
|
|
ide_auth_error_response('Invalid signature', 401);
|
|
}
|
|
}
|
|
|
|
// Authentication passed - store auth data for handlers to use if needed
|
|
define('IDE_AUTH_PASSED', true);
|
|
define('IDE_AUTH_DATA', json_encode($auth_data));
|
|
define('IDE_AUTH_IS_LOCALHOST_BYPASS', $is_localhost_bypass);
|
|
|
|
// Suppress console_debug output for IDE service requests
|
|
// These are programmatic API calls, not user-facing pages
|
|
define('SUPPRESS_CONSOLE_DEBUG_OUTPUT', true);
|