Ein von dir gesetzter Status bleibt, bis der Mitarbeiter ihn selbst ändert oder du ihn erneut änderst.
-
+
Status der Mitarbeiter ändern (Admin)
+
Ein von dir gesetzter Status bleibt, bis der Mitarbeiter ihn selbst ändert oder du ihn erneut änderst.
+
+
Mitarbeiter
Status und Notiz
+
-
-
= e($ag['name']) ?>
-
-
-
+
+
+
= csrf_field() ?>
-
- Minuten ohne Aktivität
+
+
+ Minuten ohne AktivitätSpeichern
- 0 = aus. Bei neuer Aktivität kommt der vorherige Status zurück.
+ 0 = aus. Bei neuer Aktivität kommt der vorherige Status zurück.
@@ -309,18 +317,7 @@ var statusUpdateTimer;
function setMyStatus(s) {
currentStatus = s;
document.querySelectorAll('.status-btn').forEach(function(btn) {
- var active = btn.dataset.status === s;
- var colors = {
- available: ['#22c55e','#22c55e18'],
- busy: ['#f59e0b','#f59e0b18'],
- break: ['#6366f1','#6366f118'],
- offline: ['#94a3b8','#94a3b818'],
- };
- var c = colors[btn.dataset.status] || ['var(--border)','transparent'];
- btn.style.borderColor = active ? c[0] : 'var(--border)';
- btn.style.background = active ? c[1] : 'transparent';
- btn.style.color = active ? c[0] : 'var(--ink-soft)';
- btn.style.fontWeight = active ? '600' : '400';
+ btn.classList.toggle('active', btn.dataset.status === s);
});
sendStatusUpdate();
}
@@ -360,7 +357,7 @@ function refreshTeamStatus() {
var colors = { available:'#22c55e', busy:'#f59e0b', break:'#6366f1', offline:'#94a3b8' };
var labels = { available:'Verfügbar', busy:'Beschäftigt', break:'Pause', offline:'Offline' };
var s = ag.is_on_vacation ? 'offline' : (ag.status || 'offline');
- var label = ag.is_on_vacation ? '🌴 Urlaub' : labels[s];
+ var label = ag.is_on_vacation ? 'Urlaub' : labels[s];
var color = colors[s] || '#94a3b8';
el.querySelector('.status-dot').style.background = color;
el.querySelector('.status-label').textContent = label;
diff --git a/DeskHub-Webseite/staff/login.php b/DeskHub-Webseite/staff/login.php
index 0525fc89..f8e1b280 100644
--- a/DeskHub-Webseite/staff/login.php
+++ b/DeskHub-Webseite/staff/login.php
@@ -24,21 +24,30 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && !csrf_valid()) {
$stmt = get_db()->prepare('SELECT * FROM staff WHERE id = ? AND is_active = 1');
$stmt->execute([$pendingId]);
$staff = $stmt->fetch();
- if ($staff && totp_verify($staff['totp_secret'], $code)) {
+ if ($staff && !rate_limit_check('staff_totp_attempt', (string) $pendingId, login_guard_max_attempts(), login_guard_lock_minutes())) {
+ // Code-Raten bremsen: nach zu vielen Fehlversuchen muss das Passwort neu eingegeben werden
unset($_SESSION['totp_pending_staff_id']);
+ login_guard_locked((string) $staff['email'], 'web');
+ $errors[] = 'Zu viele falsche Codes. Bitte warten Sie einige Minuten und melden Sie sich neu an.';
+ } elseif ($staff && totp_verify($staff['totp_secret'], $code)) {
+ unset($_SESSION['totp_pending_staff_id']);
+ rate_limit_reset('staff_totp_attempt', (string) $pendingId);
staff_login($staff);
redirect('/staff/reports.php');
+ } else {
+ if ($staff) login_guard_failed($staff, (string) $staff['email'], 'web', 'Falscher 2FA-Code');
+ $errors[] = 'Ungültiger Code. Bitte erneut versuchen.';
+ $need2fa = true;
}
- $errors[] = 'Ungültiger Code. Bitte erneut versuchen.';
- $need2fa = true;
}
} else {
$email = trim($_POST['email'] ?? '');
$pass = $_POST['password'] ?? '';
if (!rate_limit_check('staff_login_attempt', client_ip(), 10, 15)) {
$errors[] = 'Zu viele Anmeldeversuche. Bitte versuchen Sie es in einigen Minuten erneut.';
- } elseif ($email && !rate_limit_check('staff_login_email', strtolower($email), 5, 15)) {
+ } elseif ($email && !rate_limit_check('staff_login_email', strtolower($email), login_guard_max_attempts(), login_guard_lock_minutes())) {
// Konto-basierte Sperre – verhindert Brute-Force über wechselnde IPs
+ login_guard_locked($email, 'web');
$errors[] = 'Zu viele Anmeldeversuche für dieses Konto. Bitte warten Sie einige Minuten.';
} else {
$stmt = get_db()->prepare('SELECT * FROM staff WHERE LOWER(email) = LOWER(?) AND is_active = 1');
@@ -55,6 +64,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && !csrf_valid()) {
redirect('/staff/reports.php');
}
} else {
+ login_guard_failed($staff ?: null, $email, 'web', $staff ? 'Falsches Passwort' : 'Unbekannte E-Mail-Adresse');
$errors[] = 'E-Mail-Adresse oder Passwort ist falsch.';
}
}
@@ -141,6 +151,12 @@ $displayName = get_setting('display_name', config()['site']['name']);
Waren Sie das nicht? Dann sichern Sie Ihr Konto: Das Passwort wird ungültig, alle App-Anmeldungen und gemerkten Geräte werden beendet und die Administratoren erfahren davon. Danach legen Sie ein neues Passwort fest.
+
+ = csrf_field() ?>
+
+
Das war ich nicht – Konto sichern
+
+
Waren Sie es doch? Dann schließen Sie diese Seite einfach.
+
+
+
+
+
diff --git a/DeskHub-Webseite/staff/login_log.php b/DeskHub-Webseite/staff/login_log.php
new file mode 100644
index 00000000..bb46d00b
--- /dev/null
+++ b/DeskHub-Webseite/staff/login_log.php
@@ -0,0 +1,118 @@
+exec('DELETE FROM login_events');
+ audit_log($admin['id'], 'Anmelde-Wächter: Protokoll geleert (' . (int) $n . ' Einträge)');
+ flash('success', 'Protokoll geleert.');
+ } elseif ($action === 'forget_devices') {
+ $sid = (int) ($_POST['staff_id'] ?? 0);
+ if ($sid > 0) {
+ $pdo->prepare('DELETE FROM login_devices WHERE staff_id = ?')->execute([$sid]);
+ $pdo->prepare('DELETE FROM login_places WHERE staff_id = ?')->execute([$sid]);
+ audit_log($admin['id'], 'Anmelde-Wächter: gemerkte Geräte und Orte vergessen (Mitarbeiter ' . $sid . ')');
+ flash('success', 'Geräte und Orte vergessen. Die nächste Anmeldung zählt wieder als erste.');
+ }
+ }
+ redirect('/staff/login_log.php');
+}
+
+$labels = login_guard_event_labels();
+$filter = (string) ($_GET['event'] ?? '');
+if (!isset($labels[$filter])) $filter = '';
+$q = trim((string) ($_GET['q'] ?? ''));
+$where = ['1=1'];
+$params = [];
+if ($filter !== '') { $where[] = 'e.event = ?'; $params[] = $filter; }
+if ($q !== '') { $where[] = '(e.email LIKE ? OR e.ip LIKE ? OR e.device LIKE ? OR e.place LIKE ?)'; $like = '%' . $q . '%'; array_push($params, $like, $like, $like, $like); }
+$st = $pdo->prepare('SELECT e.*, s.name AS staff_name FROM login_events e LEFT JOIN staff s ON s.id = e.staff_id WHERE ' . implode(' AND ', $where) . ' ORDER BY e.id DESC LIMIT 300');
+$st->execute($params);
+$rows = $st->fetchAll();
+
+$since = date('Y-m-d H:i:s', time() - 86400);
+$cnt = $pdo->prepare('SELECT event, COUNT(*) AS n FROM login_events WHERE created_at >= ? GROUP BY event');
+$cnt->execute([$since]);
+$counts = array_fill_keys(array_keys($labels), 0);
+foreach ($cnt->fetchAll() as $r) $counts[$r['event']] = (int) $r['n'];
+
+$pageTitle = 'Anmelde-Wächter';
+$activePage = 'login_log';
+$colors = ['success' => '#15803d', 'new_device' => '#b45309', 'new_place' => '#b45309', 'failed' => '#b91c1c', 'locked' => '#b91c1c', 'revoked' => '#7c3aed'];
+require __DIR__ . '/_header.php';
+?>
+
Anmelde-Wächter
+
Der Wächter merkt sich die Geräte und Orte, von denen sich Mitarbeiter anmelden (Web und App). Meldet sich jemand von einem neuen Gerät oder neuen Ort an, bekommt er eine Mail mit dem Link „Das war ich nicht“. Der Link macht das Passwort ungültig, beendet App-Anmeldungen und Browser-Sitzungen und informiert die Administratoren. Nach zu vielen Fehlversuchen wird die Anmeldung gesperrt; auch das steht im Protokoll und geht per Mail an Kontoinhaber und Administratoren. Als Ort gilt das Land (wenn Cloudflare es mitschickt), sonst das Netz der IP-Adresse; die erste Anmeldung eines Kontos wird still gemerkt.
+
+
+
+
= e($labels[$k]) ?> (24 Std.)
= (int) $counts[$k] ?>
+
+
+
+
= csrf_field() ?>
+
Einstellungen
+
+
+
+
+
+
+
+
+
Speichern
+
+
+
+
+
Protokoll
+
+
+
+ Filtern
+
+
= csrf_field() ?>>Protokoll leeren
+
+
+
Keine Einträge= ($filter !== '' || $q !== '') ? ' zu diesem Filter' : '' ?>.
Hier könnt ihr eigene Plugins als ZIP installieren oder aktualisieren, auch ohne Signatur. Diese Plugins sind nicht vom Herausgeber geprüft und laufen mit allen Rechten des Systems, auch mit Zugriff auf die gesamte Datenbank. Installiert nur Code, den ihr selbst geschrieben oder geprüft habt.
-
- = csrf_field() ?>
-
-
-
-
-
-
-
-
Hochladen & installieren
-
-
Das Plugin wird nach der Installation nicht automatisch aktiviert. Vor dem Aktivieren bestätigt ihr die Berechtigungen, die es anfordert.
-
-
-
-
-
Katalog und Herausgeber-Schlüssel
-
- = csrf_field() ?>
-
-
-
-
-
-
Den Schlüssel bekommst du von der Plugin-Webseite. Ist er hinterlegt, prüft DeskHub bei jeder Installation, ob das Paket wirklich vom Herausgeber stammt und unverändert ist.
>= $installed ? 'Kein Plugin passt zu Filter oder Suche.' : 'Noch keine Plugins installiert. Unter „Plugin hinzufügen“ findest du den Katalog und den ZIP-Upload.' ?>
Es ist noch keine Katalog-Adresse hinterlegt. Trage unter „Katalog & Schlüssel“ die Adresse der Plugin-Webseite ein, dann könnt ihr Plugins direkt von dort installieren.
Hier könnt ihr eigene Plugins als ZIP installieren oder aktualisieren, auch ohne Signatur. Diese Plugins sind nicht vom Herausgeber geprüft und laufen mit allen Rechten des Systems, auch mit Zugriff auf die gesamte Datenbank. Installiert nur Code, den ihr selbst geschrieben oder geprüft habt.
Das Plugin wird nach der Installation nicht automatisch aktiviert. Vor dem Aktivieren bestätigt ihr die Berechtigungen, die es anfordert.
+
+
+
+
+
+>
+
+
Katalog und Herausgeber-Schlüssel
+
+ = csrf_field() ?>
+
+
+
+
+
+
Den Schlüssel bekommst du von der Plugin-Webseite. Ist er hinterlegt, prüft DeskHub bei jeder Installation, ob das Paket wirklich vom Herausgeber stammt und unverändert ist.