Add user registration and login functionality for /adminer
This commit is contained in:
parent
68bbf405e4
commit
d8814a1dfc
2
.env
2
.env
@ -3,7 +3,7 @@ DB_SERVERNAME=localhost
|
||||
DB_PORT=3306
|
||||
DB_USERNAME=FSST
|
||||
DB_PASSWORD=L9wUNZZ9Qkbt
|
||||
DB_DATABASE=FSS_T
|
||||
DB_DATABASE=FabianWebsite
|
||||
|
||||
# Optional: Basis-URL (wenn du was dynamisch bauen willst)
|
||||
APP_URL=https://fabianschieder.com
|
||||
|
||||
18
.env.example
18
.env.example
@ -2,12 +2,20 @@
|
||||
DB_SERVERNAME=localhost
|
||||
DB_PORT=3306
|
||||
DB_USERNAME=FSST
|
||||
DB_PASSWORD=L9wUNZZ9Qkbt
|
||||
DB_DATABASE=FSS_T
|
||||
DB_PASSWORD=
|
||||
DB_DATABASE=FabianWebsite
|
||||
|
||||
# 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=L9wUNZZ9Qkbt
|
||||
# (Deprecated) Basic Auth für /adminer – wird nicht mehr verwendet
|
||||
ADMINER_BASIC_USER=
|
||||
ADMINER_BASIC_PASS=
|
||||
|
||||
# App-Login für /adminer
|
||||
# Registrierung erlauben? 1=ja, 0=nein
|
||||
ADMINER_ALLOW_REGISTER=1
|
||||
|
||||
# Optional: initialen User automatisch anlegen (einmalig)
|
||||
ADMINER_APP_SEED_USER=
|
||||
ADMINER_APP_SEED_PASS=
|
||||
|
||||
32
adminer/_smoke_register.php
Normal file
32
adminer/_smoke_register.php
Normal file
@ -0,0 +1,32 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
require_once __DIR__ . '/user_auth.php';
|
||||
|
||||
// CLI smoke test: php adminer/_smoke_register.php username password
|
||||
// Uses FabianWebsite via user_auth.php config.
|
||||
|
||||
$username = $argv[1] ?? 'smoketest_user';
|
||||
$password = $argv[2] ?? 'Smoketest123!';
|
||||
|
||||
try {
|
||||
adminer_app_bootstrap();
|
||||
|
||||
$r = adminer_app_try_register($username, $password, $password);
|
||||
if (!empty($r['ok'])) {
|
||||
echo "REGISTER OK\n";
|
||||
} else {
|
||||
echo "REGISTER FAIL: " . ($r['error'] ?? 'unknown') . "\n";
|
||||
}
|
||||
|
||||
$l = adminer_app_try_login($username, $password);
|
||||
if (!empty($l['ok'])) {
|
||||
echo "LOGIN OK\n";
|
||||
} else {
|
||||
echo "LOGIN FAIL: " . ($l['error'] ?? 'unknown') . "\n";
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
echo "EXCEPTION: " . $e->getMessage() . "\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
@ -14,6 +14,7 @@ try {
|
||||
exit;
|
||||
}
|
||||
|
||||
$appPage = (string)($_GET['page'] ?? 'login'); // login|register
|
||||
$appAction = (string)($_GET['auth'] ?? '');
|
||||
if ($appAction === 'logout') {
|
||||
adminer_app_logout();
|
||||
@ -32,27 +33,81 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && (string)($_POST['action'] ?? '') ==
|
||||
exit;
|
||||
}
|
||||
$appError = (string)($res['error'] ?? 'Login fehlgeschlagen.');
|
||||
$appPage = 'login';
|
||||
}
|
||||
|
||||
// Handle registration
|
||||
$appRegError = null;
|
||||
$appRegOk = null;
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && (string)($_POST['action'] ?? '') === 'app_register') {
|
||||
$u = (string)($_POST['username'] ?? '');
|
||||
$p1 = (string)($_POST['password'] ?? '');
|
||||
$p2 = (string)($_POST['password2'] ?? '');
|
||||
|
||||
$res = adminer_app_try_register($u, $p1, $p2);
|
||||
if (!empty($res['ok'])) {
|
||||
// Auto-login after register
|
||||
$loginRes = adminer_app_try_login($u, $p1);
|
||||
if (!empty($loginRes['ok'])) {
|
||||
header('Location: /adminer', true, 302);
|
||||
exit;
|
||||
}
|
||||
$appRegOk = 'Registrierung erfolgreich. Bitte einloggen.';
|
||||
$appPage = 'login';
|
||||
} else {
|
||||
$appRegError = (string)($res['error'] ?? 'Registrierung fehlgeschlagen.');
|
||||
$appPage = 'register';
|
||||
}
|
||||
}
|
||||
|
||||
if (!adminer_app_is_logged_in()) {
|
||||
$canRegister = adminer_app_allow_register();
|
||||
|
||||
$body = '<div class="top"><h1>DB-Verwaltung</h1><span class="pill">Login</span></div>';
|
||||
$body .= '<div class="card">';
|
||||
$body .= '<p class="muted">Bitte melde dich an, um die DB-Verwaltung zu öffnen.</p>';
|
||||
|
||||
if ($appError) {
|
||||
$body .= '<div class="err">' . h($appError) . '</div><br>';
|
||||
// Tabs
|
||||
$body .= '<div style="display:flex;gap:10px;margin-bottom:14px">'
|
||||
. '<a class="btn secondary" href="/adminer?page=login" style="text-decoration:none">Login</a>'
|
||||
. ($canRegister ? '<a class="btn secondary" href="/adminer?page=register" style="text-decoration:none">Registrieren</a>' : '')
|
||||
. '</div>';
|
||||
|
||||
if ($appRegOk) {
|
||||
$body .= '<div class="ok">' . h($appRegOk) . '</div><br>';
|
||||
}
|
||||
|
||||
$body .= '<form method="post">'
|
||||
. '<input type="hidden" name="action" value="app_login">'
|
||||
. '<label>Benutzername<br><input name="username" autocomplete="username"></label><br><br>'
|
||||
. '<label>Passwort<br><input name="password" type="password" autocomplete="current-password"></label><br><br>'
|
||||
. '<button class="btn" type="submit">Anmelden</button>'
|
||||
. '</form>';
|
||||
if ($appPage === 'register') {
|
||||
if (!$canRegister) {
|
||||
$body .= '<div class="err">Registrierung ist deaktiviert.</div>';
|
||||
} else {
|
||||
if ($appRegError) $body .= '<div class="err">' . h($appRegError) . '</div><br>';
|
||||
|
||||
$body .= '<hr style="border:0;border-top:1px solid rgba(255,255,255,.10);margin:16px 0">';
|
||||
$body .= '<div class="muted">Admin-Hinweis: Du kannst initial einen User per .env seeden: '
|
||||
. '<code>ADMINER_APP_SEED_USER</code> + <code>ADMINER_APP_SEED_PASS</code> (einmalig; wird nicht überschrieben).</div>';
|
||||
$body .= '<h3>Registrieren</h3>';
|
||||
$body .= '<form method="post">'
|
||||
. '<input type="hidden" name="action" value="app_register">'
|
||||
. '<label>Benutzername<br><input name="username" autocomplete="username"></label><br><br>'
|
||||
. '<label>Passwort<br><input name="password" type="password" autocomplete="new-password"></label><br><br>'
|
||||
. '<label>Passwort wiederholen<br><input name="password2" type="password" autocomplete="new-password"></label><br><br>'
|
||||
. '<button class="btn" type="submit">Account erstellen</button>'
|
||||
. '</form>';
|
||||
|
||||
$body .= '<div class="muted" style="margin-top:12px">Dein Account wird in <code>FabianWebsite.adminer_users</code> gespeichert.</div>';
|
||||
}
|
||||
} else {
|
||||
if ($appError) $body .= '<div class="err">' . h($appError) . '</div><br>';
|
||||
|
||||
$body .= '<h3>Login</h3>';
|
||||
$body .= '<form method="post">'
|
||||
. '<input type="hidden" name="action" value="app_login">'
|
||||
. '<label>Benutzername<br><input name="username" autocomplete="username"></label><br><br>'
|
||||
. '<label>Passwort<br><input name="password" type="password" autocomplete="current-password"></label><br><br>'
|
||||
. '<button class="btn" type="submit">Anmelden</button>'
|
||||
. '</form>';
|
||||
|
||||
if ($canRegister) {
|
||||
$body .= '<div class="muted" style="margin-top:12px">Noch kein Account? <a href="/adminer?page=register">Registrieren</a></div>';
|
||||
}
|
||||
}
|
||||
|
||||
$body .= '</div>';
|
||||
|
||||
|
||||
@ -1,18 +1,22 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
require_once __DIR__ . '/auth.php';
|
||||
require_once __DIR__ . '/env.php';
|
||||
|
||||
/**
|
||||
* App-Login (schöne Login-Seite) für /adminer.
|
||||
* Nutzer werden in einer MySQL-Tabelle gespeichert.
|
||||
*
|
||||
* Wir nutzen die DB-Verbindungsdaten aus .env (DB_*), um die User-Tabelle zu hosten.
|
||||
* Alles liegt in der DB "FabianWebsite".
|
||||
*/
|
||||
|
||||
function adminer_app_session_start()
|
||||
{
|
||||
// CLI: keine Sessions/Headers
|
||||
if (PHP_SAPI === 'cli') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (session_status() === PHP_SESSION_NONE) {
|
||||
ini_set('session.cookie_httponly', '1');
|
||||
if (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') {
|
||||
@ -22,32 +26,54 @@ function adminer_app_session_start()
|
||||
}
|
||||
}
|
||||
|
||||
function adminer_app_pdo()
|
||||
function adminer_app_config()
|
||||
{
|
||||
$vars = env_load(dirname(__DIR__) . '/.env');
|
||||
$host = env_get($vars, 'DB_SERVERNAME', 'localhost');
|
||||
$port = (int)env_get($vars, 'DB_PORT', '3306');
|
||||
$user = env_get($vars, 'DB_USERNAME', '');
|
||||
$pass = env_get($vars, 'DB_PASSWORD', '');
|
||||
$db = env_get($vars, 'DB_DATABASE', '');
|
||||
|
||||
if ($host === '' || $port <= 0 || $user === '' || $db === '') {
|
||||
// DB fest auf FabianWebsite, wie gewünscht.
|
||||
$cfg = [
|
||||
'host' => (string)env_get($vars, 'DB_SERVERNAME', 'localhost'),
|
||||
'port' => (int)env_get($vars, 'DB_PORT', '3306'),
|
||||
'user' => (string)env_get($vars, 'DB_USERNAME', ''),
|
||||
'pass' => (string)env_get($vars, 'DB_PASSWORD', ''),
|
||||
'db' => 'FabianWebsite',
|
||||
// Registrierung optional abschaltbar
|
||||
'allow_register' => ((string)env_get($vars, 'ADMINER_ALLOW_REGISTER', '1')) !== '0',
|
||||
];
|
||||
|
||||
return $cfg;
|
||||
}
|
||||
|
||||
function adminer_app_pdo()
|
||||
{
|
||||
$cfg = adminer_app_config();
|
||||
|
||||
if ($cfg['host'] === '' || (int)$cfg['port'] <= 0 || $cfg['user'] === '' || $cfg['db'] === '') {
|
||||
throw new RuntimeException('DB_* ist in .env nicht vollständig gesetzt (für Admin-Login-User-Store).');
|
||||
}
|
||||
|
||||
$dsn = sprintf('mysql:host=%s;port=%d;dbname=%s;charset=utf8mb4', $host, $port, $db);
|
||||
$dsn = sprintf('mysql:host=%s;port=%d;dbname=%s;charset=utf8mb4', $cfg['host'], (int)$cfg['port'], $cfg['db']);
|
||||
|
||||
return new PDO($dsn, $user, $pass, [
|
||||
return new PDO($dsn, $cfg['user'], $cfg['pass'], [
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
]);
|
||||
}
|
||||
|
||||
function adminer_validate_username($u)
|
||||
{
|
||||
$u = trim((string)$u);
|
||||
if ($u === '') return [false, 'Benutzername darf nicht leer sein.'];
|
||||
if (strlen($u) < 3) return [false, 'Benutzername ist zu kurz (min. 3 Zeichen).'];
|
||||
if (strlen($u) > 50) return [false, 'Benutzername ist zu lang (max. 50 Zeichen).'];
|
||||
if (!preg_match('/^[A-Za-z0-9_.-]+$/', $u)) return [false, 'Nur Buchstaben, Zahlen, Punkt, Unterstrich und Minus sind erlaubt.'];
|
||||
return [true, null];
|
||||
}
|
||||
|
||||
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,'
|
||||
@ -64,12 +90,12 @@ function adminer_app_bootstrap()
|
||||
|
||||
if ($seedUser !== '' && $seedPass !== '') {
|
||||
$stmt = $pdo->prepare('SELECT id FROM adminer_users WHERE username = ?');
|
||||
$stmt->execute([$seedUser]);
|
||||
$stmt->execute([(string)$seedUser]);
|
||||
$exists = (bool)$stmt->fetchColumn();
|
||||
if (!$exists) {
|
||||
$hash = password_hash($seedPass, PASSWORD_DEFAULT);
|
||||
$hash = password_hash((string)$seedPass, PASSWORD_DEFAULT);
|
||||
$ins = $pdo->prepare('INSERT INTO adminer_users (username, password_hash) VALUES (?, ?)');
|
||||
$ins->execute([$seedUser, $hash]);
|
||||
$ins->execute([(string)$seedUser, $hash]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -77,12 +103,18 @@ function adminer_app_bootstrap()
|
||||
function adminer_app_is_logged_in()
|
||||
{
|
||||
adminer_app_session_start();
|
||||
if (PHP_SAPI === 'cli') {
|
||||
return false;
|
||||
}
|
||||
return !empty($_SESSION['adminer_app']['ok']);
|
||||
}
|
||||
|
||||
function adminer_app_logout()
|
||||
{
|
||||
adminer_app_session_start();
|
||||
if (PHP_SAPI === 'cli') {
|
||||
return;
|
||||
}
|
||||
unset($_SESSION['adminer_app']);
|
||||
}
|
||||
|
||||
@ -105,11 +137,54 @@ function adminer_app_try_login($username, $password)
|
||||
}
|
||||
|
||||
adminer_app_session_start();
|
||||
$_SESSION['adminer_app'] = [
|
||||
'ok' => true,
|
||||
'username' => $username,
|
||||
'uid' => (int)$row['id'],
|
||||
];
|
||||
if (PHP_SAPI !== 'cli') {
|
||||
$_SESSION['adminer_app'] = [
|
||||
'ok' => true,
|
||||
'username' => $username,
|
||||
'uid' => (int)$row['id'],
|
||||
];
|
||||
}
|
||||
|
||||
return ['ok' => true, 'error' => null];
|
||||
}
|
||||
|
||||
function adminer_app_allow_register()
|
||||
{
|
||||
$cfg = adminer_app_config();
|
||||
return !empty($cfg['allow_register']);
|
||||
}
|
||||
|
||||
function adminer_app_try_register($username, $password, $password2)
|
||||
{
|
||||
if (!adminer_app_allow_register()) {
|
||||
return ['ok' => false, 'error' => 'Registrierung ist deaktiviert.'];
|
||||
}
|
||||
|
||||
list($ok, $msg) = adminer_validate_username($username);
|
||||
if (!$ok) return ['ok' => false, 'error' => $msg];
|
||||
|
||||
$password = (string)$password;
|
||||
$password2 = (string)$password2;
|
||||
|
||||
if (strlen($password) < 8) {
|
||||
return ['ok' => false, 'error' => 'Passwort ist zu kurz (min. 8 Zeichen).'];
|
||||
}
|
||||
if ($password !== $password2) {
|
||||
return ['ok' => false, 'error' => 'Passwörter stimmen nicht überein.'];
|
||||
}
|
||||
|
||||
$pdo = adminer_app_pdo();
|
||||
|
||||
// Unique check
|
||||
$stmt = $pdo->prepare('SELECT id FROM adminer_users WHERE username = ?');
|
||||
$stmt->execute([trim((string)$username)]);
|
||||
if ($stmt->fetchColumn()) {
|
||||
return ['ok' => false, 'error' => 'Benutzername ist bereits vergeben.'];
|
||||
}
|
||||
|
||||
$hash = password_hash($password, PASSWORD_DEFAULT);
|
||||
$ins = $pdo->prepare('INSERT INTO adminer_users (username, password_hash) VALUES (?, ?)');
|
||||
$ins->execute([trim((string)$username), $hash]);
|
||||
|
||||
return ['ok' => true, 'error' => null];
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user