require_once __DIR__ . "/bootstrap.php"; require_once __DIR__ . "/instances.php"; function manageApiSendJson(int $status, array $payload): void { http_response_code($status); header("Content-Type: application/json; charset=utf-8"); header("Cache-Control: no-store"); header("X-Content-Type-Options: nosniff"); echo json_encode( $payload, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE, ); exit; } function manageApiFail(int $status, string $error, array $extra = []): void { manageApiSendJson($status, array_merge([ "success" => false, "error" => $error, ], $extra)); } function manageApiRequireMethod(string $method): void { if (($_SERVER["REQUEST_METHOD"] ?? "") !== $method) { header("Allow: " . $method); manageApiFail(405, $method . " erforderlich."); } } // Reads a request header regardless of SAPI. Apache with mod_php exposes // X-Manage-Token as HTTP_X_MANAGE_TOKEN; some setups only fill getallheaders(). function manageApiHeader(string $name): string { $key = "HTTP_" . strtoupper(str_replace("-", "_", $name)); if (isset($_SERVER[$key])) { return trim((string) $_SERVER[$key]); } if (function_exists("getallheaders")) { foreach (getallheaders() ?: [] as $headerName => $value) { if (strcasecmp((string) $headerName, $name) === 0) { return trim((string) $value); } } } return ""; } /** * Authenticates the calling instance or terminates the request. * * Failures are rate-limited per IP and answered with the same generic message, * so the endpoint cannot be used to enumerate valid instance ids. * * @return array the instance record */ function manageApiAuthenticate(): array { if (!manageRateLimitTryConsume( "api-auth", (int) MANAGE_API_RATE_LIMIT_MAX, (int) MANAGE_API_RATE_LIMIT_WINDOW, )) { manageApiFail(429, "Zu viele Anfragen. Bitte später erneut versuchen."); } $id = manageApiHeader("X-Manage-Instance"); $token = manageApiHeader("X-Manage-Token"); if ($id === "" || $token === "") { manageApiFail(401, "Authentifizierung erforderlich."); } try { $id = manageInstanceValidateId($id); } catch (Throwable $exception) { manageApiFail(401, "Authentifizierung fehlgeschlagen."); } $instance = manageInstanceAuthenticate($id, $token); if ($instance === null) { manageLogError("API authentication failed", ["instance" => $id]); manageApiFail(401, "Authentifizierung fehlgeschlagen."); } if (!$instance["enabled"]) { manageApiFail(403, "Diese Instanz ist deaktiviert."); } // A valid token clears the failure budget for this IP. manageRateLimitClearIp("api-auth"); return $instance; } // Decodes a JSON request body. Returns an empty array for an empty body so // optional payloads do not need a special case at every call site. function manageApiReadJsonBody(): array { $raw = file_get_contents("php://input"); if (!is_string($raw) || trim($raw) === "") { return []; } $decoded = json_decode($raw, true); if (!is_array($decoded)) { manageApiFail(400, "Anfrage-Body ist kein gültiges JSON."); } return $decoded; }