From 25beaea671124fed1f6fe5637810e64a13b6b20c Mon Sep 17 00:00:00 2001 From: M_Viper Date: Sun, 1 Nov 2026 11:16:11 +0000 Subject: [PATCH] Upload via GUI (364 Dateien) --- DeskHub-Webseite/api/public/v1/tickets.php | 32 ++++++++++++++++++++- DeskHub-Webseite/api/staff/v1/common.php | 19 +++++++++++- DeskHub-Webseite/api/staff/v1/live_chat.php | 2 +- DeskHub-Webseite/api/staff/v1/login.php | 7 +++-- DeskHub-Webseite/status.php | 2 +- DeskHub-Webseite/version.php | 2 +- 6 files changed, 56 insertions(+), 8 deletions(-) diff --git a/DeskHub-Webseite/api/public/v1/tickets.php b/DeskHub-Webseite/api/public/v1/tickets.php index bd5a80a9..9a56b41a 100644 --- a/DeskHub-Webseite/api/public/v1/tickets.php +++ b/DeskHub-Webseite/api/public/v1/tickets.php @@ -8,6 +8,9 @@ declare(strict_types=1); * POST /api/public/v1/tickets.php Ticket lösen (Recht "Tickets anlegen") * JSON: { "action": "resolve", "number", "email", "message"? } nur für Tickets, die über die API angelegt wurden, mit passender E-Mail-Adresse * GET /api/public/v1/tickets.php?number=…&email=… Status + öffentlicher Verlauf (Recht "Tickets lesen") + * GET /api/public/v1/tickets.php?topics=1 Öffentliche Hilfethemen (Recht "Tickets anlegen") + * GET /api/public/v1/tickets.php?form=1&helpTopicId=… Kundenformular samt eigener Felder (Recht "Tickets anlegen") + * Beim Anlegen nimmt "fields": { "": "Wert", … } die Angaben dieses Formulars entgegen (Pflichtfelder werden geprüft). * * Authentifizierung: Header "X-API-Key: dh_…" oder "Authorization: Bearer dh_…". * Schlüssel verwaltet ein Administrator unter Verwalten → API-Schlüssel. @@ -34,7 +37,12 @@ $key = trim((string) ($_SERVER['HTTP_X_API_KEY'] ?? '')); if ($key === '' && preg_match('/^Bearer\s+(\S+)$/i', (string) ($_SERVER['HTTP_AUTHORIZATION'] ?? ''), $m)) $key = $m[1]; $pdo = get_db(); $apiKey = $key !== '' ? api_key_verify($pdo, $key, client_ip()) : null; -if (!$apiKey) public_api_fail(401, 'UNAUTHENTICATED', 'Ein gültiger API-Schlüssel ist erforderlich (Header X-API-Key).'); +if (!$apiKey) { + if ($key !== '' && api_key_ip_blocked($pdo, $key, client_ip())) { + public_api_fail(401, 'IP_NOT_ALLOWED', 'Der Schlüssel ist gültig, aber für die IP-Adresse ' . client_ip() . ' nicht freigegeben. Diese Adresse bei „Erlaubte IPs“ des Schlüssels eintragen (oder das Feld leer lassen).'); + } + public_api_fail(401, 'UNAUTHENTICATED', 'Ein gültiger API-Schlüssel ist erforderlich (Header X-API-Key).'); +} if ($method === 'POST') { if (!$apiKey['can_create_tickets']) public_api_fail(403, 'FORBIDDEN', 'Dieser Schlüssel darf keine Tickets anlegen.'); @@ -80,6 +88,28 @@ if ($method === 'POST') { exit; } +// GET ?topics=1 (Hilfethemen) und GET ?form=1&helpTopicId=…|departmentId=… (Kundenformular mit eigenen Feldern) für Website-Formulare. +// Das ist dasselbe, was Kunden im Kundenportal sehen; es braucht das Recht "Tickets anlegen". +if (isset($_GET['topics']) || isset($_GET['form'])) { + if (!$apiKey['can_create_tickets']) public_api_fail(403, 'FORBIDDEN', 'Dieser Schlüssel darf keine Tickets anlegen.'); + if (isset($_GET['topics'])) { + echo json_encode(['topics' => array_map(fn(array $t) => [ + 'id' => (int) $t['id'], 'name' => (string) $t['name'], 'path' => (string) $t['path'], 'depth' => (int) $t['depth'], + 'parentId' => !empty($t['parent_id']) ? (int) $t['parent_id'] : null, 'departmentId' => (int) $t['department_id'], + ], help_topic_options($pdo, true))], JSON_UNESCAPED_UNICODE); + exit; + } + try { + $topicId = am_int_or_null($_GET, 'helpTopicId'); + $deptId = am_int_or_null($_GET, 'departmentId'); + $def = public_form_definition($pdo, $topicId, $topicId === null ? ($deptId ?: (int) get_setting('default_department_id', '1')) : $deptId); + } catch (AdminModelException $e) { + public_api_fail($e->httpStatus(), $e->errorCode(), $e->getMessage()); + } + echo json_encode(['form' => public_form_payload($def)], JSON_UNESCAPED_UNICODE); + exit; +} + // GET: Status eines Tickets (nur mit passender E-Mail-Adresse des Anfragenden) if (!$apiKey['can_read_tickets']) public_api_fail(403, 'FORBIDDEN', 'Dieser Schlüssel darf keine Tickets lesen.'); $number = trim((string) ($_GET['number'] ?? '')); diff --git a/DeskHub-Webseite/api/staff/v1/common.php b/DeskHub-Webseite/api/staff/v1/common.php index 326ae5b4..304d4438 100644 --- a/DeskHub-Webseite/api/staff/v1/common.php +++ b/DeskHub-Webseite/api/staff/v1/common.php @@ -9,7 +9,24 @@ header('Content-Type: application/json; charset=utf-8'); header('Cache-Control: no-store'); header('Pragma: no-cache'); -const MOBILE_API_STATUSES = ['open', 'pending', 'answered', 'on_hold', 'resolved', 'closed']; +// Browser-Erweiterungen (Chrome/Edge/Brave: chrome-extension://…, Firefox: moz-extension://…) dürfen die API aufrufen. Ohne diese Antwort hängt der +// Zugriff davon ab, ob der Browser die Server-Berechtigung der Erweiterung schon angewendet hat (nach der ersten Erlaubnis oft erst nach einem Neustart). +// Es werden keine Cookies gesendet; wer die API nutzt, braucht weiterhin das Token bzw. Passwort. +$__origin = (string) ($_SERVER['HTTP_ORIGIN'] ?? ''); +if ($__origin !== '' && preg_match('#^(chrome|moz)-extension://[a-z0-9-]{8,64}$#', $__origin)) { + header('Access-Control-Allow-Origin: ' . $__origin); + header('Vary: Origin'); + header('Access-Control-Allow-Headers: Authorization, X-DeskHub-Authorization, Content-Type, Cache-Control, Accept'); + header('Access-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETE, OPTIONS'); + header('Access-Control-Max-Age: 600'); + if (($_SERVER['REQUEST_METHOD'] ?? '') === 'OPTIONS') { + http_response_code(204); + exit; + } +} +unset($__origin); + +const MOBILE_API_STATUSES =['open', 'pending', 'answered', 'on_hold', 'resolved', 'closed']; set_exception_handler(function (Throwable $error): void { error_log('Mobile Staff API: ' . $error->getMessage()); diff --git a/DeskHub-Webseite/api/staff/v1/live_chat.php b/DeskHub-Webseite/api/staff/v1/live_chat.php index 7dc6d3e7..cc9ba7a1 100644 --- a/DeskHub-Webseite/api/staff/v1/live_chat.php +++ b/DeskHub-Webseite/api/staff/v1/live_chat.php @@ -118,7 +118,7 @@ if (!$ticket) api_error(404, 'TICKET_NOT_FOUND', 'Chat nicht gefunden oder kein if ($action === 'typing') { if ($ticket['status'] !== 'closed' && !live_chat_locked_by_other($ticket, (int) $staff['id'], $pdo)) { - $pdo->prepare('UPDATE tickets SET typing_staff_until = ? WHERE id = ?') + $pdo->prepare('UPDATE tickets SET typing_staff_until = ?, updated_at = updated_at WHERE id = ?') ->execute([date('Y-m-d H:i:s', time() + 6), $ticket['id']]); } api_json(['ok' => true]); diff --git a/DeskHub-Webseite/api/staff/v1/login.php b/DeskHub-Webseite/api/staff/v1/login.php index 1b01e741..bb88e468 100644 --- a/DeskHub-Webseite/api/staff/v1/login.php +++ b/DeskHub-Webseite/api/staff/v1/login.php @@ -12,9 +12,10 @@ $totpCode = trim((string) ($body['totpCode'] ?? '')); if (!filter_var($email, FILTER_VALIDATE_EMAIL) || $password === '') { api_error(422, 'CREDENTIALS_REQUIRED', 'E-Mail-Adresse und Passwort sind erforderlich.'); } -if (!rate_limit_check('mobile_staff_login_ip', client_ip(), 10, 15) - || !rate_limit_check('mobile_staff_login_email', strtolower($email), login_guard_max_attempts(), login_guard_lock_minutes())) { - login_guard_locked($email, 'app'); +$ipOk = rate_limit_check('mobile_staff_login_ip', client_ip(), 10, 15); +$accountOk = !$ipOk || rate_limit_check('mobile_staff_login_email', strtolower($email), login_guard_max_attempts(), login_guard_lock_minutes()); +if (!$ipOk || !$accountOk) { + if (!$accountOk) login_guard_locked($email, 'app'); // das IP-Limit allein sperrt kein Konto und löst keine Mail an den Kontoinhaber aus api_error(429, 'RATE_LIMITED', 'Zu viele Anmeldeversuche. Bitte versuchen Sie es später erneut.'); } diff --git a/DeskHub-Webseite/status.php b/DeskHub-Webseite/status.php index 7823f491..1a9c91e4 100644 --- a/DeskHub-Webseite/status.php +++ b/DeskHub-Webseite/status.php @@ -18,7 +18,7 @@ if (!status_page_enabled()) { $states = status_component_states(); $overall = status_overall($pdo); -$components = status_components($pdo); +$components = status_components($pdo, false, true); foreach ($components as &$component) { $component['timeline'] = status_component_timeline($pdo, (int) $component['id'], 30); } diff --git a/DeskHub-Webseite/version.php b/DeskHub-Webseite/version.php index 238ab7d4..f4d90f79 100644 --- a/DeskHub-Webseite/version.php +++ b/DeskHub-Webseite/version.php @@ -1,7 +1,7 @@