342 lines
12 KiB
PHP
Executable File
342 lines
12 KiB
PHP
Executable File
<?php
|
|
declare(strict_types=1);
|
|
|
|
use Modules\MiningChecker\Support\ApiException as MiningApiException;
|
|
use Modules\MiningChecker\Support\DebugState as MiningDebugState;
|
|
|
|
// boot application (config, autoload, services)
|
|
require_once __DIR__ . '/../config/fileload.php';
|
|
|
|
$uriPath = parse_url($_SERVER['REQUEST_URI'] ?? '/', PHP_URL_PATH) ?: '/';
|
|
$uriPath = preg_replace('~/{2,}~', '/', $uriPath);
|
|
$uriPath = trim($uriPath, '/');
|
|
$projectRoot = dirname(__DIR__);
|
|
$auth = app()->auth();
|
|
|
|
// OIDC Auth
|
|
$publicPaths = [
|
|
'auth/login',
|
|
'auth/callback',
|
|
'auth/logout',
|
|
'auth/keycloak/login',
|
|
'auth/keycloak/callback',
|
|
'auth/keycloak/logout',
|
|
'auth/me',
|
|
'module/pi_control/terminal_info',
|
|
];
|
|
$requiresGlobalAuth = in_array($uriPath, ['settings', 'users', 'modules', 'modules/install', 'modules/sql-import', 'debug', 'exports/database.sql'], true)
|
|
|| str_starts_with($uriPath, 'modules/setup/')
|
|
|| str_starts_with($uriPath, 'modules/access/');
|
|
if (defined('APP_AUTH_ENABLED') && APP_AUTH_ENABLED && $requiresGlobalAuth && !in_array($uriPath, $publicPaths, true)) {
|
|
$user = auth_user();
|
|
if (!$user) {
|
|
header('Location: /auth/login', true, 302);
|
|
exit;
|
|
}
|
|
}
|
|
|
|
// Sicherheitscheck
|
|
if (str_contains($uriPath, '..')) {
|
|
http_response_code(400);
|
|
exit('Bad request');
|
|
}
|
|
|
|
if ($uriPath === 'auth/keycloak/login') {
|
|
$returnTo = (string)($_GET['return_to'] ?? '/');
|
|
$auth->login($returnTo);
|
|
}
|
|
|
|
if ($uriPath === 'auth/keycloak/callback') {
|
|
$uriPath = 'auth/callback';
|
|
}
|
|
|
|
if ($uriPath === 'auth/keycloak/logout') {
|
|
$uriPath = 'auth/logout';
|
|
}
|
|
|
|
if ($uriPath === 'auth/me') {
|
|
header('Content-Type: application/json; charset=utf-8');
|
|
echo json_encode([
|
|
'authenticated' => $auth->isAuthenticated(),
|
|
'user' => $auth->user(),
|
|
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
|
exit;
|
|
}
|
|
|
|
if ($uriPath === 'exports/database.sql') {
|
|
require_admin();
|
|
$pdo = app()->basePdo() ?: app()->pdo();
|
|
if (!$pdo instanceof PDO) {
|
|
http_response_code(500);
|
|
exit('Keine Datenbankverbindung fuer den Export verfuegbar.');
|
|
}
|
|
|
|
$filename = 'nexus-export-' . gmdate('Ymd-His') . '.sql';
|
|
$sql = (new \App\SqlDataExporter())->export($pdo, 'nexus');
|
|
header('Content-Type: application/sql; charset=utf-8');
|
|
header('Content-Disposition: attachment; filename="' . $filename . '"');
|
|
header('X-Content-Type-Options: nosniff');
|
|
echo $sql;
|
|
exit;
|
|
}
|
|
|
|
if (preg_match('~^api/module-auth/([a-zA-Z0-9_-]+)$~', $uriPath, $moduleAuthMatches)) {
|
|
$moduleName = $moduleAuthMatches[1];
|
|
$moduleMeta = app()->modules()->get($moduleName);
|
|
if ($moduleMeta === null) {
|
|
http_response_code(404);
|
|
header('Content-Type: application/json; charset=utf-8');
|
|
echo json_encode(['error' => 'module_not_found'], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
|
exit;
|
|
}
|
|
if (!$auth->isAuthenticated()) {
|
|
http_response_code(401);
|
|
header('Content-Type: application/json; charset=utf-8');
|
|
echo json_encode(['error' => 'auth_required'], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
|
exit;
|
|
}
|
|
if (!auth_is_admin()) {
|
|
http_response_code(403);
|
|
header('Content-Type: application/json; charset=utf-8');
|
|
echo json_encode(['error' => 'forbidden'], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
|
exit;
|
|
}
|
|
|
|
header('Content-Type: application/json; charset=utf-8');
|
|
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
|
|
echo json_encode(['data' => ($moduleMeta['auth'] ?? ['required' => false, 'users' => [], 'groups' => []])], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
|
exit;
|
|
}
|
|
if ($_SERVER['REQUEST_METHOD'] === 'PUT') {
|
|
$input = json_decode((string)file_get_contents('php://input'), true);
|
|
if (!is_array($input)) {
|
|
$input = [];
|
|
}
|
|
echo json_encode(['data' => app()->modules()->saveAuth($moduleName, $input)], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
|
exit;
|
|
}
|
|
|
|
http_response_code(405);
|
|
echo json_encode(['error' => 'method_not_allowed'], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
|
exit;
|
|
}
|
|
|
|
if (preg_match('~^api/mining-checker(?:/(.*))?$~', $uriPath, $apiMatches)) {
|
|
$moduleMeta = app()->modules()->get('mining-checker') ?? ['auth' => ['required' => false]];
|
|
if (!$auth->canAccessModule($moduleMeta)) {
|
|
http_response_code($auth->isAuthenticated() ? 403 : 401);
|
|
header('Content-Type: application/json; charset=utf-8');
|
|
echo json_encode([
|
|
'error' => $auth->isAuthenticated() ? 'forbidden' : 'auth_required',
|
|
'login_url' => '/auth/login',
|
|
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
|
exit;
|
|
}
|
|
|
|
require_once $projectRoot . '/modules/mining-checker/bootstrap.php';
|
|
|
|
try {
|
|
(new Modules\MiningChecker\Api\Router($projectRoot . '/modules/mining-checker'))->handle($apiMatches[1] ?? '');
|
|
} catch (MiningApiException $exception) {
|
|
$debugTrace = MiningDebugState::export();
|
|
http_response_code($exception->statusCode());
|
|
header('Content-Type: application/json; charset=utf-8');
|
|
echo json_encode([
|
|
'error' => $exception->getMessage(),
|
|
'context' => $exception->context(),
|
|
'debug' => $debugTrace !== [] ? $debugTrace : null,
|
|
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
|
exit;
|
|
} catch (Throwable $exception) {
|
|
$debugTrace = MiningDebugState::export();
|
|
http_response_code(500);
|
|
header('Content-Type: application/json; charset=utf-8');
|
|
echo json_encode([
|
|
'error' => 'Unerwarteter Mining-Checker Fehler.',
|
|
'context' => ['message' => $exception->getMessage()],
|
|
'debug' => $debugTrace !== [] ? $debugTrace : null,
|
|
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
|
exit;
|
|
}
|
|
}
|
|
|
|
if (preg_match('~^api/fx-rates(?:/(.*))?$~', $uriPath, $apiMatches)) {
|
|
$moduleMeta = app()->modules()->get('fx-rates') ?? ['auth' => ['required' => false]];
|
|
if (!$auth->canAccessModule($moduleMeta)) {
|
|
http_response_code($auth->isAuthenticated() ? 403 : 401);
|
|
header('Content-Type: application/json; charset=utf-8');
|
|
echo json_encode([
|
|
'error' => $auth->isAuthenticated() ? 'forbidden' : 'auth_required',
|
|
'login_url' => '/auth/login',
|
|
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
|
exit;
|
|
}
|
|
|
|
require_once $projectRoot . '/modules/fx-rates/bootstrap.php';
|
|
app()->modules()->runDueIntervalTasks('fx-rates');
|
|
|
|
try {
|
|
$service = module_fn('fx-rates', 'service');
|
|
(new \Modules\FxRates\Api\Router($service))->handle($apiMatches[1] ?? '');
|
|
} catch (Throwable $exception) {
|
|
http_response_code(500);
|
|
header('Content-Type: application/json; charset=utf-8');
|
|
echo json_encode([
|
|
'error' => 'Unerwarteter FX-Module Fehler.',
|
|
'context' => ['message' => $exception->getMessage()],
|
|
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
|
exit;
|
|
}
|
|
}
|
|
|
|
if (preg_match('~^module-assets/([a-zA-Z0-9_-]+)/(.*)$~', $uriPath, $assetMatches)) {
|
|
$module = $assetMatches[1];
|
|
$relativeAssetPath = trim($assetMatches[2], '/');
|
|
if ($relativeAssetPath === '' || str_contains($relativeAssetPath, '..')) {
|
|
http_response_code(400);
|
|
exit('Bad request');
|
|
}
|
|
|
|
$assetFile = $projectRoot . '/modules/' . $module . '/assets/' . $relativeAssetPath;
|
|
if (!is_file($assetFile)) {
|
|
http_response_code(404);
|
|
exit('Asset not found');
|
|
}
|
|
|
|
$extension = strtolower(pathinfo($assetFile, PATHINFO_EXTENSION));
|
|
$contentType = match ($extension) {
|
|
'css' => 'text/css; charset=utf-8',
|
|
'js' => 'application/javascript; charset=utf-8',
|
|
'json' => 'application/json; charset=utf-8',
|
|
'png' => 'image/png',
|
|
'jpg', 'jpeg' => 'image/jpeg',
|
|
'webp' => 'image/webp',
|
|
'svg' => 'image/svg+xml',
|
|
default => 'application/octet-stream',
|
|
};
|
|
|
|
header('Content-Type: ' . $contentType);
|
|
readfile($assetFile);
|
|
exit;
|
|
}
|
|
|
|
// Basispfad fuer Landingpages
|
|
$pagesBase = realpath(__DIR__ . '/../partials/landingpages') ?: (__DIR__ . '/../partials/landingpages');
|
|
$page404 = $pagesBase . '/errorpages/404.php';
|
|
|
|
// Spezialrouten für Module
|
|
if (str_starts_with($uriPath, 'modules/install')) {
|
|
$target = $pagesBase . '/modules/install.php';
|
|
} elseif (str_starts_with($uriPath, 'modules/setup/')) {
|
|
$_GET['module'] = trim(substr($uriPath, strlen('modules/setup/')), '/');
|
|
$target = $pagesBase . '/modules/setup.php';
|
|
} elseif (str_starts_with($uriPath, 'modules/access/')) {
|
|
$_GET['module'] = trim(substr($uriPath, strlen('modules/access/')), '/');
|
|
$target = $pagesBase . '/modules/access.php';
|
|
} elseif ($uriPath === 'modules/sql-import') {
|
|
$target = $pagesBase . '/modules/sql_import.php';
|
|
} elseif ($uriPath === 'auth/login') {
|
|
$target = $pagesBase . '/auth/login.php';
|
|
} elseif ($uriPath === 'auth/callback') {
|
|
$target = $pagesBase . '/auth/callback.php';
|
|
} elseif ($uriPath === 'auth/logout') {
|
|
$target = $pagesBase . '/auth/logout.php';
|
|
} elseif ($uriPath === 'settings') {
|
|
$target = $pagesBase . '/users/settings.php';
|
|
} elseif ($uriPath === 'users') {
|
|
$target = $pagesBase . '/users/index.php';
|
|
} elseif ($uriPath === 'debug') {
|
|
$target = $pagesBase . '/retool/debug.php';
|
|
} elseif (preg_match('~^module/([a-zA-Z0-9_-]+)(?:/(.+))?$~', $uriPath, $m)) {
|
|
$module = $m[1];
|
|
$page = isset($m[2]) && $m[2] !== '' ? trim($m[2], '/') : 'index';
|
|
$moduleMeta = app()->modules()->get($module);
|
|
if ($moduleMeta !== null) {
|
|
$auth->requireModuleAccess($moduleMeta);
|
|
}
|
|
$modulePage = app()->modules()->resolvePage($module, $page);
|
|
$moduleBootstrap = $projectRoot . '/modules/' . $module . '/bootstrap.php';
|
|
if (is_file($moduleBootstrap)) {
|
|
require_once $moduleBootstrap;
|
|
}
|
|
if ($modulePage) {
|
|
app()->modules()->runDueIntervalTasks($module);
|
|
$target = $modulePage;
|
|
} else {
|
|
http_response_code(404);
|
|
$target = $page404;
|
|
}
|
|
} elseif ($uriPath === '' || $uriPath === 'index' || $uriPath === 'index.php') {
|
|
$target = $pagesBase . '/index.php';
|
|
} else {
|
|
$base = $pagesBase . '/' . $uriPath;
|
|
// 1) Verzeichnis mit index.php
|
|
if (is_dir($base) && is_file($base . '/index.php')) {
|
|
$target = $base . '/index.php';
|
|
}
|
|
// 2) Datei
|
|
elseif (is_file($base . '.php')) {
|
|
|
|
$target = $base . '.php';
|
|
}
|
|
// 3) 404
|
|
elseif (is_file($base)) {
|
|
|
|
$target = $base;
|
|
}
|
|
// 3) 404
|
|
else {
|
|
http_response_code(404);
|
|
$target = $page404;
|
|
}
|
|
}
|
|
// ------------------------------------
|
|
// Layout-Regel
|
|
// ------------------------------------
|
|
$skipLayout = false;
|
|
$targetReal = realpath($target);
|
|
$retoolBase = realpath($pagesBase . '/retool/raw');
|
|
|
|
// Beispiel: alles unter landingpages/retool/* ohne Layout
|
|
if ($targetReal && $retoolBase && str_starts_with($targetReal, $retoolBase)) {
|
|
$skipLayout = true;
|
|
}
|
|
|
|
// ------------------------------------
|
|
// Ausgabe
|
|
// ------------------------------------
|
|
// Erst Inhalt laden (ohne Ausgabe), damit Header/Redirects vor HTML funktionieren
|
|
ob_start();
|
|
try {
|
|
require $target;
|
|
$content = ob_get_clean();
|
|
} catch (\App\ModuleConfigException $e) {
|
|
ob_end_clean();
|
|
http_response_code(412);
|
|
$moduleName = $e->module();
|
|
$module = app()->modules()->get($moduleName);
|
|
$title = $module['title'] ?? $moduleName;
|
|
$setupUrl = '/modules/setup/' . rawurlencode($moduleName);
|
|
$content = '<div class="card">' .
|
|
'<div class="pill">' . e($title) . '</div>' .
|
|
'<h1 style="margin-top:.75rem;">Setup erforderlich</h1>' .
|
|
'<p class="muted">' . e($e->getMessage()) . '</p>' .
|
|
'<div style="margin-top:1rem;"><a class="nav-link" href="' . e($setupUrl) . '">Zum Setup</a></div>' .
|
|
'</div>';
|
|
}
|
|
|
|
// Wenn bereits Header gesendet wurden (z. B. eigener Redirect/Content-Type), Layout überspringen
|
|
if (headers_sent()) {
|
|
$skipLayout = true;
|
|
}
|
|
|
|
if (!$skipLayout) {
|
|
tpl('layout_start', 'structure');
|
|
}
|
|
|
|
echo $content;
|
|
|
|
if (!$skipLayout) {
|
|
tpl('layout_end', 'structure');
|
|
}
|