From d8814a1dfcea32b96c33ad699d6911cc02db0771 Mon Sep 17 00:00:00 2001 From: Fabian Schieder Date: Sat, 28 Feb 2026 20:09:00 +0100 Subject: [PATCH] Add user registration and login functionality for /adminer --- .env | 2 +- .env.example | 18 ++++-- adminer/_smoke_register.php | 32 ++++++++++ adminer/index.php | 79 +++++++++++++++++++++---- adminer/user_auth.php | 115 +++++++++++++++++++++++++++++------- 5 files changed, 208 insertions(+), 38 deletions(-) create mode 100644 adminer/_smoke_register.php diff --git a/.env b/.env index 011aeee..aaa2e34 100644 --- a/.env +++ b/.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 diff --git a/.env.example b/.env.example index 011aeee..7e9faa2 100644 --- a/.env.example +++ b/.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= diff --git a/adminer/_smoke_register.php b/adminer/_smoke_register.php new file mode 100644 index 0000000..4411441 --- /dev/null +++ b/adminer/_smoke_register.php @@ -0,0 +1,32 @@ +getMessage() . "\n"; + exit(1); +} + diff --git a/adminer/index.php b/adminer/index.php index 91336c8..25c86bf 100644 --- a/adminer/index.php +++ b/adminer/index.php @@ -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 = '

DB-Verwaltung

Login
'; $body .= '
'; - $body .= '

Bitte melde dich an, um die DB-Verwaltung zu öffnen.

'; - if ($appError) { - $body .= '
' . h($appError) . '

'; + // Tabs + $body .= '
' + . 'Login' + . ($canRegister ? 'Registrieren' : '') + . '
'; + + if ($appRegOk) { + $body .= '
' . h($appRegOk) . '

'; } - $body .= '
' - . '' - . '

' - . '

' - . '' - . '
'; + if ($appPage === 'register') { + if (!$canRegister) { + $body .= '
Registrierung ist deaktiviert.
'; + } else { + if ($appRegError) $body .= '
' . h($appRegError) . '

'; - $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 .= '

Registrieren

'; + $body .= '
' + . '' + . '

' + . '

' + . '

' + . '' + . '
'; + + $body .= '
Dein Account wird in FabianWebsite.adminer_users gespeichert.
'; + } + } else { + if ($appError) $body .= '
' . h($appError) . '

'; + + $body .= '

Login

'; + $body .= '
' + . '' + . '

' + . '

' + . '' + . '
'; + + if ($canRegister) { + $body .= '
Noch kein Account? Registrieren
'; + } + } $body .= '
'; diff --git a/adminer/user_auth.php b/adminer/user_auth.php index 300d3bb..ce0b099 100644 --- a/adminer/user_auth.php +++ b/adminer/user_auth.php @@ -1,18 +1,22 @@ (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]; }