Add basic authentication for /adminer with configurable credentials

This commit is contained in:
Fabian Schieder 2026-02-28 19:47:37 +01:00
parent 76764e2995
commit 2711e5b27c
3 changed files with 150 additions and 15 deletions

View File

@ -8,3 +8,6 @@ DB_DATABASE=FSS_T
# Optional: Basis-URL (wenn du was dynamisch bauen willst) # Optional: Basis-URL (wenn du was dynamisch bauen willst)
APP_URL=https://fabianschieder.com APP_URL=https://fabianschieder.com
# Basic Auth für /adminer (zusätzlicher Schutz)
ADMINER_BASIC_USER=admin
ADMINER_BASIC_PASS=change-me-too

49
adminer/basic_auth.php Normal file
View File

@ -0,0 +1,49 @@
<?php
declare(strict_types=1);
require_once __DIR__ . '/env.php';
/**
* Basic Auth für /adminer
* Credentials aus Projekt-Root .env:
* - ADMINER_BASIC_USER
* - ADMINER_BASIC_PASS
*/
function adminer_require_basic_auth()
{
$vars = env_load(dirname(__DIR__) . '/.env');
$user = env_get($vars, 'ADMINER_BASIC_USER', '');
$pass = env_get($vars, 'ADMINER_BASIC_PASS', '');
// Wenn nicht gesetzt, sperren wir trotzdem (fail-closed), damit du es nicht aus Versehen offen lässt.
if ($user === '' || $pass === '') {
header('Content-Type: text/plain; charset=utf-8');
http_response_code(500);
echo "Basic Auth ist nicht konfiguriert.\n";
echo "Bitte setze ADMINER_BASIC_USER und ADMINER_BASIC_PASS in deiner .env.\n";
exit;
}
$givenUser = null;
$givenPass = null;
if (isset($_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW'])) {
$givenUser = (string)$_SERVER['PHP_AUTH_USER'];
$givenPass = (string)$_SERVER['PHP_AUTH_PW'];
} elseif (!empty($_SERVER['HTTP_AUTHORIZATION']) && stripos((string)$_SERVER['HTTP_AUTHORIZATION'], 'basic ') === 0) {
// Fallback, falls PHP_AUTH_* nicht gesetzt wird
$decoded = base64_decode(substr((string)$_SERVER['HTTP_AUTHORIZATION'], 6));
if ($decoded !== false && strpos($decoded, ':') !== false) {
list($givenUser, $givenPass) = explode(':', $decoded, 2);
}
}
if ($givenUser === null || $givenPass === null || !hash_equals($user, $givenUser) || !hash_equals($pass, $givenPass)) {
header('WWW-Authenticate: Basic realm="DB-Verwaltung"');
header('Content-Type: text/plain; charset=utf-8');
http_response_code(401);
echo "Auth erforderlich.";
exit;
}
}

View File

@ -1,6 +1,9 @@
<?php <?php
declare(strict_types=1); declare(strict_types=1);
require_once __DIR__ . '/basic_auth.php';
adminer_require_basic_auth();
require_once __DIR__ . '/auth.php'; require_once __DIR__ . '/auth.php';
require_once __DIR__ . '/views.php'; require_once __DIR__ . '/views.php';
@ -13,9 +16,48 @@ if ($action === 'logout') {
exit; exit;
} }
// Login // ── DB-Auswahl (Step 1) ───────────────────────────────────────────────────
// Wir speichern Host/Port/User/Pass kurz in der Session, um die DB-Liste zu holen.
if (!isset($_SESSION['db_admin_select'])) {
$_SESSION['db_admin_select'] = [];
}
$selectError = null;
$selectMsg = null;
if ($_SERVER['REQUEST_METHOD'] === 'POST' && (string)($_POST['action'] ?? '') === 'probe') {
$host = trim((string)($_POST['host'] ?? ''));
$port = (int)($_POST['port'] ?? 3306);
$user = trim((string)($_POST['user'] ?? ''));
$pass = (string)($_POST['pass'] ?? '');
if ($host === '' || $port <= 0 || $user === '') {
$selectError = 'Bitte Host, Port und Benutzer angeben.';
} else {
try {
$dsn = sprintf('mysql:host=%s;port=%d;charset=utf8mb4', $host, $port);
$pdo = new PDO($dsn, $user, $pass, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]);
$dbs = $pdo->query('SHOW DATABASES')->fetchAll(PDO::FETCH_COLUMN, 0);
$_SESSION['db_admin_select'] = [
'host' => $host,
'port' => $port,
'user' => $user,
'pass' => $pass,
'dbs' => $dbs,
];
$selectMsg = 'Datenbanken geladen.';
} catch (Throwable $e) {
$selectError = 'Konnte Datenbanken nicht laden: ' . $e->getMessage();
}
}
}
// Login (Step 2)
$error = null; $error = null;
$success = null;
if ($_SERVER['REQUEST_METHOD'] === 'POST' && (string)($_POST['action'] ?? '') === 'login') { if ($_SERVER['REQUEST_METHOD'] === 'POST' && (string)($_POST['action'] ?? '') === 'login') {
$host = trim((string)($_POST['host'] ?? '')); $host = trim((string)($_POST['host'] ?? ''));
$port = (int)($_POST['port'] ?? 3306); $port = (int)($_POST['port'] ?? 3306);
@ -25,7 +67,6 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && (string)($_POST['action'] ?? '') ==
$res = admin_try_login($host, $port, $user, $pass, $db); $res = admin_try_login($host, $port, $user, $pass, $db);
if ($res['ok']) { if ($res['ok']) {
$success = 'Login erfolgreich.';
header('Location: /adminer', true, 302); header('Location: /adminer', true, 302);
exit; exit;
} }
@ -33,38 +74,80 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && (string)($_POST['action'] ?? '') ==
} }
$defaults = admin_default_creds(); $defaults = admin_default_creds();
$selectState = is_array($_SESSION['db_admin_select']) ? $_SESSION['db_admin_select'] : [];
// UI when not logged in // UI when not logged in
if (!admin_is_logged_in()) { if (!admin_is_logged_in()) {
$prefHost = isset($selectState['host']) ? (string)$selectState['host'] : (string)$defaults['host'];
$prefPort = isset($selectState['port']) ? (int)$selectState['port'] : (int)$defaults['port'];
$prefUser = isset($selectState['user']) ? (string)$selectState['user'] : (string)$defaults['user'];
$prefPass = isset($selectState['pass']) ? (string)$selectState['pass'] : '';
$dbList = isset($selectState['dbs']) && is_array($selectState['dbs']) ? $selectState['dbs'] : [];
$body = "<div class=\"top\"><h1>DB-Verwaltung</h1><span class=\"pill\">Mini-Admin</span></div>"; $body = "<div class=\"top\"><h1>DB-Verwaltung</h1><span class=\"pill\">Mini-Admin</span></div>";
$body .= "<div class=\"card\">"; $body .= "<div class=\"card\">";
$body .= "<p class=\"muted\">Login-Daten werden nur in der Session gespeichert. Für Defaults wird <code>.env</code> aus dem Projekt-Root gelesen.</p>"; $body .= "<p class=\"muted\">Zuerst Verbindungsdaten prüfen (ohne DB), dann bekommst du eine Datenbank-Auswahl. Defaults kommen aus <code>.env</code>.</p>";
if ($selectMsg) $body .= '<div class="ok">' . h($selectMsg) . '</div><br>';
if ($selectError) $body .= '<div class="err">' . h($selectError) . '</div><br>';
if ($error) $body .= '<div class="err">' . h($error) . '</div><br>'; if ($error) $body .= '<div class="err">' . h($error) . '</div><br>';
// Step 1: Probe
$body .= "<h3>1) Verbindung testen & Datenbanken laden</h3>";
$body .= "<form method=\"post\">"; $body .= "<form method=\"post\">";
$body .= "<input type=\"hidden\" name=\"action\" value=\"login\">"; $body .= "<input type=\"hidden\" name=\"action\" value=\"probe\">";
$body .= "<div class=\"grid\">"; $body .= "<div class=\"grid\">";
$body .= "<div>"; $body .= "<div>";
$body .= "<label>Host<br><input name=\"host\" value=\"" . h((string)$defaults['host']) . "\"></label><br><br>"; $body .= "<label>Host<br><input name=\"host\" value=\"" . h($prefHost) . "\"></label><br><br>";
$body .= "<label>Port<br><input name=\"port\" type=\"number\" value=\"" . h((string)$defaults['port']) . "\"></label><br><br>"; $body .= "<label>Port<br><input name=\"port\" type=\"number\" value=\"" . h((string)$prefPort) . "\"></label><br><br>";
$body .= "<label>Benutzer<br><input name=\"user\" value=\"" . h((string)$defaults['user']) . "\"></label><br><br>"; $body .= "<label>Benutzer<br><input name=\"user\" value=\"" . h($prefUser) . "\"></label><br><br>";
$body .= "<label>Passwort<br><input name=\"pass\" type=\"password\" value=\"\"></label><br><br>"; $body .= "<label>Passwort<br><input name=\"pass\" type=\"password\" value=\"" . h($prefPass) . "\"></label><br><br>";
$body .= "<label>Datenbank<br><input name=\"db\" value=\"" . h((string)$defaults['db']) . "\"></label><br><br>"; $body .= "<button class=\"btn secondary\" type=\"submit\">Datenbanken laden</button>";
$body .= "<button class=\"btn\" type=\"submit\">Login</button>";
$body .= "</div>"; $body .= "</div>";
$body .= "<div>"; $body .= "<div>";
$body .= "<h3>Hinweise</h3>"; $body .= "<h3>Hinweise</h3>";
$body .= "<ul class=\"muted\">"; $body .= "<ul class=\"muted\">";
$body .= "<li>Dieses Tool ist bewusst minimal (Tabellenliste, Browse, SQL Query).</li>"; $body .= "<li>Basic Auth ist aktiv (Credentials in <code>.env</code>).</li>";
$body .= "<li>Für produktive Nutzung bitte zusätzlich absichern (Basic Auth / IP-Allowlist).</li>"; $body .= "<li>Für produktive Nutzung zusätzlich mit IP-Allowlist kombinieren.</li>";
$body .= "</ul>"; $body .= "</ul>";
$body .= "</div>"; $body .= "</div>";
$body .= "</div>"; $body .= "</div>";
$body .= "</form>"; $body .= "</form>";
// Step 2: Login
$body .= "<hr style=\"border:0;border-top:1px solid rgba(255,255,255,.10);margin:16px 0\">";
$body .= "<h3>2) Login in Datenbank</h3>";
$body .= "<form method=\"post\">";
$body .= "<input type=\"hidden\" name=\"action\" value=\"login\">";
$body .= "<div class=\"grid\">";
$body .= "<div>";
$body .= "<label>Host<br><input name=\"host\" value=\"" . h($prefHost) . "\"></label><br><br>";
$body .= "<label>Port<br><input name=\"port\" type=\"number\" value=\"" . h((string)$prefPort) . "\"></label><br><br>";
$body .= "<label>Benutzer<br><input name=\"user\" value=\"" . h($prefUser) . "\"></label><br><br>";
$body .= "<label>Passwort<br><input name=\"pass\" type=\"password\" value=\"" . h($prefPass) . "\"></label><br><br>";
if (!empty($dbList)) {
$body .= "<label>Datenbank<br><select name=\"db\" style=\"width:100%;box-sizing:border-box;background:rgba(0,0,0,.35);color:#e5e7eb;border:1px solid rgba(255,255,255,.14);border-radius:10px;padding:10px\">";
$selectedDb = (string)$defaults['db'];
foreach ($dbList as $dbName) {
$dbName = (string)$dbName;
$sel = ($dbName === $selectedDb) ? ' selected' : '';
$body .= '<option value="' . h($dbName) . '"' . $sel . '>' . h($dbName) . '</option>';
}
$body .= "</select></label><br><br>";
} else {
$body .= "<label>Datenbank<br><input name=\"db\" value=\"" . h((string)$defaults['db']) . "\" placeholder=\"z.B. mydb\"></label><br><br>";
$body .= "<div class=\"muted\" style=\"margin-top:-8px;margin-bottom:12px\">Tipp: Erst oben \"Datenbanken laden\" klicken für Vorschläge.</div>";
}
$body .= "<button class=\"btn\" type=\"submit\">Login</button>";
$body .= "</div>";
$body .= "</div>";
$body .= "</form>";
$body .= "</div>"; $body .= "</div>";
admin_layout('DB-Verwaltung', $body); admin_layout('DB-Verwaltung', $body);