From 2bdaa0f6cc96d6f788fd524dc7dccea370845de9 Mon Sep 17 00:00:00 2001 From: Thomas Date: Tue, 18 Aug 2026 11:43:04 +0200 Subject: [PATCH] Admin-Accounts-Editor (CRUD inkl. 2FA-Mail) + Regel: Lehrer-Username = E-Mail (Maske + register), Schueler frei Co-Authored-By: Claude Opus 4.8 --- App/admin-accounts.html | 171 +++++++++++++++++++++++++++++++++ App/admin-levels.html | 1 + App/admin-licenses.html | 1 + App/admin-modules.html | 1 + App/admin-sitemap.html | 1 + App/admin-styleguide.html | 1 + App/login.php | 4 +- App/php/api/admin-accounts.php | 123 ++++++++++++++++++++++++ App/php/api/auth.php | 4 +- 9 files changed, 305 insertions(+), 2 deletions(-) create mode 100644 App/admin-accounts.html create mode 100644 App/php/api/admin-accounts.php diff --git a/App/admin-accounts.html b/App/admin-accounts.html new file mode 100644 index 0000000..874e4d0 --- /dev/null +++ b/App/admin-accounts.html @@ -0,0 +1,171 @@ + + + + + + GeoGraSim — Admin-Accounts + + + + + + + +
+ +
+
Neuen Admin-Account anlegen
+
+
+ + +
+
+ + +
+
+ + +
+ +
+
Der 4-stellige Login-PIN wird bei jeder Anmeldung an die hinterlegte 2FA-E-Mail geschickt.
+
+
+ +
+
Bestehende Admins
+
+
+
+ +
+ + + + diff --git a/App/admin-levels.html b/App/admin-levels.html index fff63b8..a5638fc 100644 --- a/App/admin-levels.html +++ b/App/admin-levels.html @@ -86,6 +86,7 @@ 🧩 Module 🔑 Lizenzen 🎨 Styleguide + 👤 Admins Logout diff --git a/App/admin-licenses.html b/App/admin-licenses.html index 21dff1b..a7c5e11 100644 --- a/App/admin-licenses.html +++ b/App/admin-licenses.html @@ -43,6 +43,7 @@ 🧩 Module 🎮 Level 🎨 Styleguide + 👤 Admins Logout diff --git a/App/admin-modules.html b/App/admin-modules.html index 3f8dab4..ee9ed23 100644 --- a/App/admin-modules.html +++ b/App/admin-modules.html @@ -77,6 +77,7 @@ 🎟️ Lizenzen 🎮 Level 🎨 Styleguide + 👤 Admins Logout diff --git a/App/admin-sitemap.html b/App/admin-sitemap.html index 87b3432..9073341 100644 --- a/App/admin-sitemap.html +++ b/App/admin-sitemap.html @@ -89,6 +89,7 @@ Module Level Lizenzen + 👤 Admins Startseite diff --git a/App/admin-styleguide.html b/App/admin-styleguide.html index f4ed1c7..56c6e03 100644 --- a/App/admin-styleguide.html +++ b/App/admin-styleguide.html @@ -54,6 +54,7 @@ 🔑 Lizenzen 🧩 Module 🎮 Level + 👤 Admins Logout diff --git a/App/login.php b/App/login.php index 49ac2b9..e3503bf 100644 --- a/App/login.php +++ b/App/login.php @@ -107,6 +107,7 @@ $activeMain = null;

Einloggen

+

Lehrpersonen melden sich mit ihrer E-Mail-Adresse an, Schüler*innen mit ihrem Benutzernamen.

👁
@@ -123,8 +124,9 @@ $activeMain = null;
- + +

Ihre E-Mail-Adresse ist zugleich Ihr Benutzername für die Anmeldung.

👁
diff --git a/App/php/api/admin-accounts.php b/App/php/api/admin-accounts.php new file mode 100644 index 0000000..18ab7e0 --- /dev/null +++ b/App/php/api/admin-accounts.php @@ -0,0 +1,123 @@ + 'Nicht eingeloggt']); + exit; +} + +$meId = (int)$_SESSION['admin_id']; +$db = Database::get(); + +if ($_SERVER['REQUEST_METHOD'] !== 'POST') { + Response::error('Nur POST erlaubt', 405); +} + +$body = json_decode(file_get_contents('php://input'), true) ?: []; +$action = $body['action'] ?? ''; + +// === LISTE === +if ($action === 'list') { + $rows = $db->fetchAll('SELECT id, username, email_2fa, created_at FROM admin_users ORDER BY id'); + foreach ($rows as &$r) { + $r['id'] = (int)$r['id']; + $r['is_self'] = ($r['id'] === $meId); + } + unset($r); + Response::ok(['admins' => $rows]); +} + +// === ANLEGEN === +if ($action === 'create') { + $username = trim($body['username'] ?? ''); + $email = trim($body['email_2fa'] ?? ''); + $password = (string)($body['password'] ?? ''); + + if ($username === '' || $email === '' || $password === '') { + Response::error('Benutzername, 2FA-E-Mail und Passwort sind erforderlich.'); + } + if (!preg_match('/^[A-Za-z0-9._\-]{3,64}$/', $username)) { + Response::error('Benutzername: 3–64 Zeichen, nur Buchstaben, Ziffern, Punkt, Unterstrich, Bindestrich.'); + } + if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { + Response::error('Ungültige 2FA-E-Mail-Adresse.'); + } + if (strlen($password) < 10) { + Response::error('Passwort muss mindestens 10 Zeichen haben.'); + } + if ($db->fetchOne('SELECT id FROM admin_users WHERE username = ?', [$username])) { + Response::error('Dieser Benutzername ist bereits vergeben.'); + } + + $hash = password_hash($password, PASSWORD_DEFAULT); + $db->execute( + 'INSERT INTO admin_users (username, password, email_2fa) VALUES (?, ?, ?)', + [$username, $hash, $email] + ); + Response::ok(['message' => 'Admin-Account angelegt.']); +} + +// === AKTUALISIEREN (2FA-Mail und/oder Passwort) === +if ($action === 'update') { + $id = (int)($body['id'] ?? 0); + if (!$id) Response::error('ID fehlt.'); + if (!$db->fetchOne('SELECT id FROM admin_users WHERE id = ?', [$id])) { + Response::error('Admin nicht gefunden.', 404); + } + + $didSomething = false; + + if (array_key_exists('email_2fa', $body)) { + $email = trim((string)$body['email_2fa']); + if ($email === '' || !filter_var($email, FILTER_VALIDATE_EMAIL)) { + Response::error('Ungültige 2FA-E-Mail-Adresse.'); + } + $db->execute('UPDATE admin_users SET email_2fa = ? WHERE id = ?', [$email, $id]); + $didSomething = true; + } + + if (array_key_exists('password', $body) && (string)$body['password'] !== '') { + $password = (string)$body['password']; + if (strlen($password) < 10) { + Response::error('Passwort muss mindestens 10 Zeichen haben.'); + } + $db->execute( + 'UPDATE admin_users SET password = ? WHERE id = ?', + [password_hash($password, PASSWORD_DEFAULT), $id] + ); + $didSomething = true; + } + + if (!$didSomething) Response::error('Nichts zu ändern.'); + Response::ok(['message' => 'Admin-Account aktualisiert.']); +} + +// === LÖSCHEN === +if ($action === 'delete') { + $id = (int)($body['id'] ?? 0); + if (!$id) Response::error('ID fehlt.'); + if ($id === $meId) Response::error('Du kannst deinen eigenen Account nicht löschen.'); + + $row = $db->fetchOne('SELECT COUNT(*) AS c FROM admin_users'); + if ((int)($row['c'] ?? 0) <= 1) { + Response::error('Der letzte Admin-Account kann nicht gelöscht werden.'); + } + + $db->execute('DELETE FROM admin_users WHERE id = ?', [$id]); + $db->execute('DELETE FROM admin_pins WHERE admin_id = ?', [$id]); // verwaiste PINs aufräumen + Response::ok(['message' => 'Admin-Account gelöscht.']); +} + +Response::error('Unbekannte Aktion.'); diff --git a/App/php/api/auth.php b/App/php/api/auth.php index b5dd939..22b03dd 100644 --- a/App/php/api/auth.php +++ b/App/php/api/auth.php @@ -27,7 +27,9 @@ if ($action === 'register') { $existing = $db->fetchOne('SELECT id FROM teachers WHERE email = ?', [$email]); if ($existing) Response::error('Diese E-Mail-Adresse ist bereits registriert'); - $username = strtolower(explode('@', $email)[0]) . '_' . rand(100, 999); + // Regel (ab 2026-08): Der Benutzername einer Lehrperson IST ihre E-Mail-Adresse. + // Bestehende, abweichende Benutzernamen bleiben gültig (Login akzeptiert E-Mail ODER Benutzername). + $username = strtolower($email); $hash = password_hash($password, PASSWORD_DEFAULT); $db->execute(