Add basic authentication for /adminer with configurable credentials
This commit is contained in:
parent
76764e2995
commit
2711e5b27c
@ -8,3 +8,6 @@ DB_DATABASE=FSS_T
|
||||
# Optional: Basis-URL (wenn du was dynamisch bauen willst)
|
||||
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
49
adminer/basic_auth.php
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,9 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
require_once __DIR__ . '/basic_auth.php';
|
||||
adminer_require_basic_auth();
|
||||
|
||||
require_once __DIR__ . '/auth.php';
|
||||
require_once __DIR__ . '/views.php';
|
||||
|
||||
@ -13,9 +16,48 @@ if ($action === 'logout') {
|
||||
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;
|
||||
$success = null;
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && (string)($_POST['action'] ?? '') === 'login') {
|
||||
$host = trim((string)($_POST['host'] ?? ''));
|
||||
$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);
|
||||
if ($res['ok']) {
|
||||
$success = 'Login erfolgreich.';
|
||||
header('Location: /adminer', true, 302);
|
||||
exit;
|
||||
}
|
||||
@ -33,38 +74,80 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && (string)($_POST['action'] ?? '') ==
|
||||
}
|
||||
|
||||
$defaults = admin_default_creds();
|
||||
$selectState = is_array($_SESSION['db_admin_select']) ? $_SESSION['db_admin_select'] : [];
|
||||
|
||||
// UI when not 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=\"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>';
|
||||
|
||||
// Step 1: Probe
|
||||
$body .= "<h3>1) Verbindung testen & Datenbanken laden</h3>";
|
||||
$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>";
|
||||
$body .= "<label>Host<br><input name=\"host\" value=\"" . h((string)$defaults['host']) . "\"></label><br><br>";
|
||||
$body .= "<label>Port<br><input name=\"port\" type=\"number\" value=\"" . h((string)$defaults['port']) . "\"></label><br><br>";
|
||||
$body .= "<label>Benutzer<br><input name=\"user\" value=\"" . h((string)$defaults['user']) . "\"></label><br><br>";
|
||||
$body .= "<label>Passwort<br><input name=\"pass\" type=\"password\" value=\"\"></label><br><br>";
|
||||
$body .= "<label>Datenbank<br><input name=\"db\" value=\"" . h((string)$defaults['db']) . "\"></label><br><br>";
|
||||
$body .= "<button class=\"btn\" type=\"submit\">Login</button>";
|
||||
$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>";
|
||||
$body .= "<button class=\"btn secondary\" type=\"submit\">Datenbanken laden</button>";
|
||||
$body .= "</div>";
|
||||
|
||||
$body .= "<div>";
|
||||
$body .= "<h3>Hinweise</h3>";
|
||||
$body .= "<ul class=\"muted\">";
|
||||
$body .= "<li>Dieses Tool ist bewusst minimal (Tabellenliste, Browse, SQL Query).</li>";
|
||||
$body .= "<li>Für produktive Nutzung bitte zusätzlich absichern (Basic Auth / IP-Allowlist).</li>";
|
||||
$body .= "<li>Basic Auth ist aktiv (Credentials in <code>.env</code>).</li>";
|
||||
$body .= "<li>Für produktive Nutzung zusätzlich mit IP-Allowlist kombinieren.</li>";
|
||||
$body .= "</ul>";
|
||||
$body .= "</div>";
|
||||
|
||||
$body .= "</div>";
|
||||
$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>";
|
||||
|
||||
admin_layout('DB-Verwaltung', $body);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user