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)
|
# 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
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
|
<?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);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user