Upload via GUI (364 Dateien)
This commit is contained in:
@@ -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": { "<Feld-ID>": "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'] ?? ''));
|
||||
|
||||
@@ -9,6 +9,23 @@ header('Content-Type: application/json; charset=utf-8');
|
||||
header('Cache-Control: no-store');
|
||||
header('Pragma: no-cache');
|
||||
|
||||
// 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 {
|
||||
|
||||
@@ -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]);
|
||||
|
||||
@@ -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.');
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?php
|
||||
if (defined('DESKHUB_VERSION')) return;
|
||||
|
||||
define('DESKHUB_VERSION', '1.0.60');
|
||||
define('DESKHUB_VERSION', '1.0.66');
|
||||
define('DESKHUB_RELEASE_URL', 'https://git.viper.ipv64.net/M_Viper/deskhub/releases');
|
||||
|
||||
// Kein eingebautes Zugangstoken: Das Update liest die öffentlichen Releases. Ist das Repository privat, kann ein Token
|
||||
|
||||
Reference in New Issue
Block a user