Upload via GUI (360 Dateien)

This commit is contained in:
2026-10-30 21:50:25 +00:00
parent b322e13aef
commit 515db6b5e2
4 changed files with 50 additions and 3 deletions
+1 -1
View File
@@ -57,7 +57,7 @@ if ($method === 'POST') {
$note = mb_substr($note, 0, 2000); $note = mb_substr($note, 0, 2000);
if (!in_array($ticket['status'], ['resolved', 'closed'], true)) { if (!in_array($ticket['status'], ['resolved', 'closed'], true)) {
$now = db_now(); $now = db_now();
$pdo->prepare("INSERT INTO ticket_threads (ticket_id, author_type, message, is_internal_note, created_at) VALUES (?, 'system', ?, 1, ?)")->execute([$ticket['id'], e($note), $now]); $pdo->prepare("INSERT INTO ticket_threads (ticket_id, author_type, message, is_internal_note, created_at) VALUES (?, 'system', ?, 1, ?)")->execute([$ticket['id'], $note, $now]);
$pdo->prepare("UPDATE tickets SET status = 'resolved', resolved_at = ?, updated_at = ? WHERE id = ?")->execute([$now, $now, $ticket['id']]); $pdo->prepare("UPDATE tickets SET status = 'resolved', resolved_at = ?, updated_at = ? WHERE id = ?")->execute([$now, $now, $ticket['id']]);
log_activity($pdo, (int) $ticket['id'], null, 'Über die API gelöst: ' . $note); log_activity($pdo, (int) $ticket['id'], null, 'Über die API gelöst: ' . $note);
try { webhook_dispatch($pdo, 'ticket.status_changed', (int) $ticket['id'], ['previousStatus' => (string) $ticket['status']]); } catch (Throwable $e) { /* nachrangig */ } try { webhook_dispatch($pdo, 'ticket.status_changed', (int) $ticket['id'], ['previousStatus' => (string) $ticket['status']]); } catch (Throwable $e) { /* nachrangig */ }
+5 -1
View File
@@ -13,7 +13,8 @@ if (!filter_var($email, FILTER_VALIDATE_EMAIL) || $password === '') {
api_error(422, 'CREDENTIALS_REQUIRED', 'E-Mail-Adresse und Passwort sind erforderlich.'); api_error(422, 'CREDENTIALS_REQUIRED', 'E-Mail-Adresse und Passwort sind erforderlich.');
} }
if (!rate_limit_check('mobile_staff_login_ip', client_ip(), 10, 15) if (!rate_limit_check('mobile_staff_login_ip', client_ip(), 10, 15)
|| !rate_limit_check('mobile_staff_login_email', strtolower($email), 5, 15)) { || !rate_limit_check('mobile_staff_login_email', strtolower($email), login_guard_max_attempts(), login_guard_lock_minutes())) {
login_guard_locked($email, 'app');
api_error(429, 'RATE_LIMITED', 'Zu viele Anmeldeversuche. Bitte versuchen Sie es später erneut.'); api_error(429, 'RATE_LIMITED', 'Zu viele Anmeldeversuche. Bitte versuchen Sie es später erneut.');
} }
@@ -22,6 +23,7 @@ $stmt = $pdo->prepare('SELECT * FROM staff WHERE LOWER(email) = LOWER(?) AND is_
$stmt->execute([$email]); $stmt->execute([$email]);
$staff = $stmt->fetch(); $staff = $stmt->fetch();
if (!$staff || !password_verify($password, $staff['password_hash'])) { if (!$staff || !password_verify($password, $staff['password_hash'])) {
login_guard_failed($staff ?: null, $email, 'app', $staff ? 'Falsches Passwort' : 'Unbekannte E-Mail-Adresse');
api_error(401, 'INVALID_CREDENTIALS', 'E-Mail-Adresse oder Passwort ist falsch.'); api_error(401, 'INVALID_CREDENTIALS', 'E-Mail-Adresse oder Passwort ist falsch.');
} }
if (!empty($staff['totp_enabled'])) { if (!empty($staff['totp_enabled'])) {
@@ -29,6 +31,7 @@ if (!empty($staff['totp_enabled'])) {
api_json(['requiresTotp' => true], 202); api_json(['requiresTotp' => true], 202);
} }
if (!preg_match('/^\d{6}$/', $totpCode) || !totp_verify($staff['totp_secret'], $totpCode)) { if (!preg_match('/^\d{6}$/', $totpCode) || !totp_verify($staff['totp_secret'], $totpCode)) {
login_guard_failed($staff, $email, 'app', 'Falscher 2FA-Code');
api_error(401, 'INVALID_TOTP', 'Der Authenticator-Code ist ungültig.'); api_error(401, 'INVALID_TOTP', 'Der Authenticator-Code ist ungültig.');
} }
} }
@@ -37,6 +40,7 @@ rate_limit_reset('mobile_staff_login_email', strtolower($email));
rate_limit_reset('mobile_staff_login_ip', client_ip()); rate_limit_reset('mobile_staff_login_ip', client_ip());
$issued = api_issue_token($pdo, (int) $staff['id'], isset($body['deviceName']) ? (string) $body['deviceName'] : null); $issued = api_issue_token($pdo, (int) $staff['id'], isset($body['deviceName']) ? (string) $body['deviceName'] : null);
audit_log((int) $staff['id'], 'Mobile App: Anmeldung'); audit_log((int) $staff['id'], 'Mobile App: Anmeldung');
login_guard_success($staff, 'app', isset($body['deviceName']) ? (string) $body['deviceName'] : null);
api_json([ api_json([
'token' => $issued['token'], 'token' => $issued['token'],
'expiresAt' => $issued['expires_at'], 'expiresAt' => $issued['expires_at'],
+43
View File
@@ -0,0 +1,43 @@
<?php
/**
* plugin_login.php
*
* Gemeinsamer Einstieg für Kunden-Anmeldungen über Plugins (zum Beispiel authentik): GET ?p=<Plugin>&e=<Name>. Das Plugin braucht die
* Berechtigung "auth.customer" und meldet die Adresse mit plugin_register_customer_login() an. Hier steht nur, was für alle gleich ist:
* Weiterleitung, Anmeldung (und, wenn das Plugin es erlaubt, Anlegen des Kundenkontos), Fehler und Begrenzung der Aufrufe.
* Die Rückkehr vom Dienst kann auch über staff/plugin_login.php kommen (gleiche Rückkehr-Adresse für alle).
*/
require_once __DIR__ . '/includes/bootstrap.php';
header('Cache-Control: no-store');
header('Referrer-Policy: no-referrer');
function plugin_customer_login_back(string $message): never
{
flash('error', $message);
redirect('/login.php');
}
if (current_user()) redirect('/home.php');
$slug = (string) ($_GET['p'] ?? '');
$endpoint = (string) ($_GET['e'] ?? '');
$handler = plugin_state()['customer_logins'][$slug][$endpoint] ?? null;
if (!is_callable($handler)) {
http_response_code(404);
echo 'Unbekannte Anmelde-Adresse.';
exit;
}
if (!rate_limit_check('plugin_customer_login', client_ip(), 40, 15)) plugin_customer_login_back('Zu viele Anmeldeversuche. Bitte versuchen Sie es in einigen Minuten erneut.');
$result = plugin_guard($slug, fn() => $handler(['query' => $_GET, 'ip' => client_ip()]), null);
if (!is_array($result)) plugin_customer_login_back('Die Anmeldung ist gerade nicht möglich. Bitte versuchen Sie es später noch einmal oder melden Sie sich mit E-Mail und Passwort an.');
if (!empty($result['redirect'])) {
$url = (string) $result['redirect'];
if (!plugin_login_external_url_ok($url)) plugin_customer_login_back('Die Adresse des Anmeldedienstes ist ungültig.');
header('Location: ' . $url, true, 302);
exit;
}
if (!empty($result['customer']) && is_array($result['customer'])) plugin_customer_login_finish($slug, $result['customer']);
plugin_customer_login_back(mb_substr((string) ($result['error'] ?? 'Die Anmeldung wurde abgelehnt.'), 0, 300));
+1 -1
View File
@@ -1,7 +1,7 @@
<?php <?php
if (defined('DESKHUB_VERSION')) return; if (defined('DESKHUB_VERSION')) return;
define('DESKHUB_VERSION', '1.0.50'); define('DESKHUB_VERSION', '1.0.60');
define('DESKHUB_RELEASE_URL', 'https://git.viper.ipv64.net/M_Viper/deskhub/releases'); 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 // Kein eingebautes Zugangstoken: Das Update liest die öffentlichen Releases. Ist das Repository privat, kann ein Token