diff --git a/adminer/README.md b/adminer/README.md
index 7343427..03f325e 100644
--- a/adminer/README.md
+++ b/adminer/README.md
@@ -2,17 +2,32 @@
Dieses Projekt enthält eine kleine, selbst implementierte DB-Verwaltung unter `/adminer`.
+## Schutz / Login
+Statt Browser-Basic-Auth gibt es eine eigene Login-Seite.
+
+- User werden in der Datenbanktabelle `adminer_users` gespeichert.
+- Passwörter werden gehasht (`password_hash`).
+
+### Initialen User anlegen (Seed)
+Setze in deiner lokalen `.env` (Projekt-Root) einmalig:
+
+- `ADMINER_APP_SEED_USER=...`
+- `ADMINER_APP_SEED_PASS=...`
+
+Beim ersten Aufruf von `/adminer` wird (falls der User noch nicht existiert) automatisch ein Nutzer angelegt.
+
## Setup
1. Erstelle eine lokale `.env` im Projekt-Root (siehe `.env.example`).
-2. Trage dort deine DB-Zugangsdaten ein.
+2. Trage dort deine DB-Zugangsdaten ein (`DB_*`).
> Wichtig: `.env` wird durch `.gitignore` ignoriert.
## Nutzung
- Öffne im Browser: `/adminer`
-- Login erfolgt über das Formular.
+- Schritt 1: App-Login
+- Schritt 2: DB-Verbindung testen und Datenbanken laden
+- Schritt 3: In gewünschte Datenbank einloggen
## Hinweise
- Das Tool ist bewusst minimal (Tabellenliste + Browse + einfache SQL-Query).
-- Für produktive Nutzung bitte zusätzlich absichern (z.B. Basic Auth / IP-Allowlist).
-
+- Für produktive Nutzung bitte zusätzlich absichern (z.B. IP-Allowlist, VPN).
diff --git a/adminer/index.php b/adminer/index.php
index 0997f07..91336c8 100644
--- a/adminer/index.php
+++ b/adminer/index.php
@@ -1,11 +1,66 @@
DB-Verwaltung
' . h($e->getMessage()) . '
');
+ exit;
+}
+
+$appAction = (string)($_GET['auth'] ?? '');
+if ($appAction === 'logout') {
+ adminer_app_logout();
+ header('Location: /adminer', true, 302);
+ exit;
+}
+
+// Handle app login
+$appError = null;
+if ($_SERVER['REQUEST_METHOD'] === 'POST' && (string)($_POST['action'] ?? '') === 'app_login') {
+ $u = (string)($_POST['username'] ?? '');
+ $p = (string)($_POST['password'] ?? '');
+ $res = adminer_app_try_login($u, $p);
+ if (!empty($res['ok'])) {
+ header('Location: /adminer', true, 302);
+ exit;
+ }
+ $appError = (string)($res['error'] ?? 'Login fehlgeschlagen.');
+}
+
+if (!adminer_app_is_logged_in()) {
+ $body = 'DB-Verwaltung
Login';
+ $body .= '';
+ $body .= '
Bitte melde dich an, um die DB-Verwaltung zu öffnen.
';
+
+ if ($appError) {
+ $body .= '
' . h($appError) . '
';
+ }
+
+ $body .= '
';
+
+ $body .= '
';
+ $body .= '
Admin-Hinweis: Du kannst initial einen User per .env seeden: '
+ . 'ADMINER_APP_SEED_USER + ADMINER_APP_SEED_PASS (einmalig; wird nicht überschrieben).
';
+
+ $body .= '
';
+
+ admin_layout('DB-Verwaltung', $body);
+ exit;
+}
require_once __DIR__ . '/auth.php';
-require_once __DIR__ . '/views.php';
admin_session_start();
@@ -109,7 +164,7 @@ if (!admin_is_logged_in()) {
$body .= "";
$body .= "
Hinweise
";
$body .= "
";
- $body .= "- Basic Auth ist aktiv (Credentials in
.env). ";
+ $body .= "- Der Zugriff ist durch den App-Login geschützt.
";
$body .= "- Für produktive Nutzung zusätzlich mit IP-Allowlist kombinieren.
";
$body .= "
";
$body .= "
";
@@ -195,7 +250,10 @@ try {
$body = '';
$body .= '';
diff --git a/adminer/user_auth.php b/adminer/user_auth.php
new file mode 100644
index 0000000..300d3bb
--- /dev/null
+++ b/adminer/user_auth.php
@@ -0,0 +1,116 @@
+ PDO::ERRMODE_EXCEPTION,
+ PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
+ ]);
+}
+
+function adminer_app_bootstrap()
+{
+ $pdo = adminer_app_pdo();
+
+ // Minimal users table
+ $pdo->exec(
+ 'CREATE TABLE IF NOT EXISTS adminer_users ('
+ . 'id INT AUTO_INCREMENT PRIMARY KEY,'
+ . 'username VARCHAR(190) NOT NULL UNIQUE,'
+ . 'password_hash VARCHAR(255) NOT NULL,'
+ . 'created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP'
+ . ') ENGINE=InnoDB DEFAULT CHARSET=utf8mb4'
+ );
+
+ // Optional: auto-seed from .env ADMINER_APP_SEED_USER/PASS
+ $vars = env_load(dirname(__DIR__) . '/.env');
+ $seedUser = env_get($vars, 'ADMINER_APP_SEED_USER', '');
+ $seedPass = env_get($vars, 'ADMINER_APP_SEED_PASS', '');
+
+ if ($seedUser !== '' && $seedPass !== '') {
+ $stmt = $pdo->prepare('SELECT id FROM adminer_users WHERE username = ?');
+ $stmt->execute([$seedUser]);
+ $exists = (bool)$stmt->fetchColumn();
+ if (!$exists) {
+ $hash = password_hash($seedPass, PASSWORD_DEFAULT);
+ $ins = $pdo->prepare('INSERT INTO adminer_users (username, password_hash) VALUES (?, ?)');
+ $ins->execute([$seedUser, $hash]);
+ }
+ }
+}
+
+function adminer_app_is_logged_in()
+{
+ adminer_app_session_start();
+ return !empty($_SESSION['adminer_app']['ok']);
+}
+
+function adminer_app_logout()
+{
+ adminer_app_session_start();
+ unset($_SESSION['adminer_app']);
+}
+
+function adminer_app_try_login($username, $password)
+{
+ $username = trim((string)$username);
+ $password = (string)$password;
+
+ if ($username === '' || $password === '') {
+ return ['ok' => false, 'error' => 'Bitte Benutzername und Passwort eingeben.'];
+ }
+
+ $pdo = adminer_app_pdo();
+ $stmt = $pdo->prepare('SELECT id, password_hash FROM adminer_users WHERE username = ?');
+ $stmt->execute([$username]);
+ $row = $stmt->fetch();
+
+ if (!$row || empty($row['password_hash']) || !password_verify($password, (string)$row['password_hash'])) {
+ return ['ok' => false, 'error' => 'Login fehlgeschlagen.'];
+ }
+
+ adminer_app_session_start();
+ $_SESSION['adminer_app'] = [
+ 'ok' => true,
+ 'username' => $username,
+ 'uid' => (int)$row['id'],
+ ];
+
+ return ['ok' => true, 'error' => null];
+}
+