testing
This commit is contained in:
284
src/Auth.php
Normal file
284
src/Auth.php
Normal file
@@ -0,0 +1,284 @@
|
||||
<?php
|
||||
// src/auth.php
|
||||
// Zentrale Auth-Logik: Session, CSRF, Login, Registrierung, aktueller Nutzer
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
require_once __DIR__ . '/../config/db.php'; // Stellt $pdo (PDO) bereit
|
||||
|
||||
// --- Session Setup ---
|
||||
if (session_status() !== PHP_SESSION_ACTIVE) {
|
||||
$secure = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off');
|
||||
session_set_cookie_params([
|
||||
'lifetime' => 0,
|
||||
'path' => '/',
|
||||
'domain' => '', // Standard: aktuelle Domain
|
||||
'secure' => $secure,
|
||||
'httponly' => true,
|
||||
'samesite' => 'Lax',
|
||||
]);
|
||||
session_start();
|
||||
}
|
||||
|
||||
// --- Sprache ermitteln / speichern ---
|
||||
function auth_get_lang(): string {
|
||||
if (!empty($_GET['lang'])) {
|
||||
$_SESSION['lang'] = $_GET['lang'];
|
||||
}
|
||||
if (!empty($_SESSION['lang'])) {
|
||||
return $_SESSION['lang'];
|
||||
}
|
||||
return 'en';
|
||||
}
|
||||
|
||||
// --- CSRF-Token ---
|
||||
function auth_csrf_token(): string {
|
||||
if (empty($_SESSION['csrf_token'])) {
|
||||
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
|
||||
}
|
||||
return $_SESSION['csrf_token'];
|
||||
}
|
||||
|
||||
function auth_verify_csrf(?string $token): bool {
|
||||
if (empty($token) || empty($_SESSION['csrf_token'])) {
|
||||
return false;
|
||||
}
|
||||
return hash_equals($_SESSION['csrf_token'], $token);
|
||||
}
|
||||
|
||||
// --- PDO Helper ---
|
||||
function auth_pdo(): PDO {
|
||||
// $pdo kommt aus config/db.php
|
||||
global $pdo;
|
||||
if (!$pdo instanceof PDO) {
|
||||
throw new RuntimeException('Database connection not available.');
|
||||
}
|
||||
return $pdo;
|
||||
}
|
||||
|
||||
// --- Aktueller User ---
|
||||
function auth_current_user(): ?array {
|
||||
if (!empty($_SESSION['user_cache']) && is_array($_SESSION['user_cache'])) {
|
||||
return $_SESSION['user_cache'];
|
||||
}
|
||||
if (empty($_SESSION['user_id'])) {
|
||||
return null;
|
||||
}
|
||||
$pdo = auth_pdo();
|
||||
$stmt = $pdo->prepare('SELECT * FROM users WHERE id = :id LIMIT 1');
|
||||
$stmt->execute([':id' => $_SESSION['user_id']]);
|
||||
$user = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
if (!$user) {
|
||||
return null;
|
||||
}
|
||||
$_SESSION['user_cache'] = $user;
|
||||
return $user;
|
||||
}
|
||||
|
||||
function auth_require_login(): void {
|
||||
if (!auth_current_user()) {
|
||||
$lang = auth_get_lang();
|
||||
header('Location: /login.php?lang=' . urlencode($lang));
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
// --- Avatar-Helfer ---
|
||||
function auth_user_initials(array $user): string {
|
||||
$name = $user['full_name'] ?? '';
|
||||
if (trim($name) === '') {
|
||||
$name = $user['username'] ?? $user['email'] ?? 'U';
|
||||
}
|
||||
$parts = preg_split('/\s+/', trim($name));
|
||||
$initials = strtoupper(mb_substr($parts[0], 0, 1));
|
||||
if (count($parts) > 1) {
|
||||
$initials .= strtoupper(mb_substr(end($parts), 0, 1));
|
||||
}
|
||||
return $initials;
|
||||
}
|
||||
|
||||
function auth_user_avatar_url(array $user): ?string {
|
||||
if (!empty($user['avatar_path'])) {
|
||||
return '/uploads/avatars/' . ltrim($user['avatar_path'], '/');
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// --- Registrierung ---
|
||||
function auth_register_user(
|
||||
string $email,
|
||||
string $username,
|
||||
string $fullName,
|
||||
string $password,
|
||||
string $passwordConfirm,
|
||||
string $preferredLang
|
||||
): array {
|
||||
$pdo = auth_pdo();
|
||||
$errors = [];
|
||||
|
||||
$email = trim($email);
|
||||
$username = trim($username);
|
||||
$fullName = trim($fullName);
|
||||
|
||||
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||||
$errors['email'] = 'Bitte eine gültige E-Mail-Adresse eingeben.';
|
||||
}
|
||||
|
||||
if ($username === '' || !preg_match('/^[a-zA-Z0-9_.-]{3,32}$/', $username)) {
|
||||
$errors['username'] = 'Username muss 3–32 Zeichen lang sein und darf nur Buchstaben, Zahlen, ., _, - enthalten.';
|
||||
}
|
||||
|
||||
if (mb_strlen($fullName) < 3) {
|
||||
$errors['full_name'] = 'Bitte einen vollständigen Namen angeben.';
|
||||
}
|
||||
|
||||
if (mb_strlen($password) < 10) {
|
||||
$errors['password'] = 'Passwort muss mindestens 10 Zeichen lang sein.';
|
||||
}
|
||||
|
||||
if ($password !== $passwordConfirm) {
|
||||
$errors['password_confirm'] = 'Passwörter stimmen nicht überein.';
|
||||
}
|
||||
|
||||
$allowedLangs = ['de', 'en', 'it', 'fr'];
|
||||
if (!in_array($preferredLang, $allowedLangs, true)) {
|
||||
$preferredLang = 'en';
|
||||
}
|
||||
|
||||
// E-Mail / Username bereits vergeben?
|
||||
if (!$errors) {
|
||||
$stmt = $pdo->prepare('SELECT email, username FROM users WHERE email = :email OR username = :username LIMIT 1');
|
||||
$stmt->execute([
|
||||
':email' => $email,
|
||||
':username' => $username,
|
||||
]);
|
||||
$existing = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
if ($existing) {
|
||||
if (strcasecmp($existing['email'], $email) === 0) {
|
||||
$errors['email'] = 'Diese E-Mail-Adresse wird bereits verwendet.';
|
||||
}
|
||||
if (strcasecmp($existing['username'], $username) === 0) {
|
||||
$errors['username'] = 'Dieser Username ist bereits vergeben.';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($errors) {
|
||||
return ['success' => false, 'errors' => $errors];
|
||||
}
|
||||
|
||||
$hash = password_hash($password, PASSWORD_DEFAULT);
|
||||
$now = (new DateTimeImmutable('now', new DateTimeZone('UTC')))->format('Y-m-d H:i:s');
|
||||
|
||||
$stmt = $pdo->prepare('
|
||||
INSERT INTO users (email, username, full_name, password_hash, preferred_lang, created_at, updated_at)
|
||||
VALUES (:email, :username, :full_name, :password_hash, :preferred_lang, :created_at, :updated_at)
|
||||
');
|
||||
$stmt->execute([
|
||||
':email' => $email,
|
||||
':username' => $username,
|
||||
':full_name' => $fullName,
|
||||
':password_hash' => $hash,
|
||||
':preferred_lang'=> $preferredLang,
|
||||
':created_at' => $now,
|
||||
':updated_at' => $now,
|
||||
]);
|
||||
|
||||
$userId = (int)$pdo->lastInsertId();
|
||||
$_SESSION['user_id'] = $userId;
|
||||
unset($_SESSION['user_cache']); // neu laden beim nächsten Zugriff
|
||||
$_SESSION['lang'] = $preferredLang;
|
||||
|
||||
return ['success' => true, 'errors' => []];
|
||||
}
|
||||
|
||||
// --- Login ---
|
||||
function auth_login(string $login, string $password): array {
|
||||
$pdo = auth_pdo();
|
||||
$errors = [];
|
||||
|
||||
$login = trim($login);
|
||||
|
||||
if ($login === '' || $password === '') {
|
||||
$errors['login'] = 'Bitte Zugangsdaten vollständig ausfüllen.';
|
||||
return ['success' => false, 'errors' => $errors];
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare('
|
||||
SELECT * FROM users
|
||||
WHERE email = :login OR username = :login
|
||||
LIMIT 1
|
||||
');
|
||||
$stmt->execute([':login' => $login]);
|
||||
$user = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$user || !password_verify($password, $user['password_hash'])) {
|
||||
$errors['login'] = 'E-Mail/Username oder Passwort ist falsch.';
|
||||
return ['success' => false, 'errors' => $errors];
|
||||
}
|
||||
|
||||
$_SESSION['user_id'] = (int)$user['id'];
|
||||
unset($_SESSION['user_cache']);
|
||||
if (!empty($user['preferred_lang'])) {
|
||||
$_SESSION['lang'] = $user['preferred_lang'];
|
||||
}
|
||||
|
||||
// Option: Password-Rehash, wenn Algorithmus veraltet
|
||||
if (password_needs_rehash($user['password_hash'], PASSWORD_DEFAULT)) {
|
||||
$newHash = password_hash($password, PASSWORD_DEFAULT);
|
||||
$upd = $pdo->prepare('UPDATE users SET password_hash = :hash WHERE id = :id');
|
||||
$upd->execute([':hash' => $newHash, ':id' => $user['id']]);
|
||||
}
|
||||
|
||||
return ['success' => true, 'errors' => []];
|
||||
}
|
||||
|
||||
// --- Profil aktualisieren (Name, Sprache) ---
|
||||
function auth_update_profile(int $userId, string $fullName, string $preferredLang): array {
|
||||
$pdo = auth_pdo();
|
||||
$errors = [];
|
||||
|
||||
$fullName = trim($fullName);
|
||||
if (mb_strlen($fullName) < 3) {
|
||||
$errors['full_name'] = 'Bitte einen gültigen Namen angeben.';
|
||||
}
|
||||
|
||||
$allowedLangs = ['de', 'en', 'it', 'fr'];
|
||||
if (!in_array($preferredLang, $allowedLangs, true)) {
|
||||
$preferredLang = 'en';
|
||||
}
|
||||
|
||||
if ($errors) {
|
||||
return ['success' => false, 'errors' => $errors];
|
||||
}
|
||||
|
||||
$now = (new DateTimeImmutable('now', new DateTimeZone('UTC')))->format('Y-m-d H:i:s');
|
||||
$stmt = $pdo->prepare('
|
||||
UPDATE users
|
||||
SET full_name = :full_name,
|
||||
preferred_lang = :preferred_lang,
|
||||
updated_at = :updated_at
|
||||
WHERE id = :id
|
||||
');
|
||||
$stmt->execute([
|
||||
':full_name' => $fullName,
|
||||
':preferred_lang'=> $preferredLang,
|
||||
':updated_at' => $now,
|
||||
':id' => $userId,
|
||||
]);
|
||||
|
||||
unset($_SESSION['user_cache']);
|
||||
$_SESSION['lang'] = $preferredLang;
|
||||
|
||||
return ['success' => true, 'errors' => []];
|
||||
}
|
||||
|
||||
// --- Logout ---
|
||||
function auth_logout(): void {
|
||||
$_SESSION = [];
|
||||
if (ini_get('session.use_cookies')) {
|
||||
$params = session_get_cookie_params();
|
||||
setcookie(session_name(), '', time() - 42000, $params['path'], $params['domain'], $params['secure'], $params['httponly']);
|
||||
}
|
||||
session_destroy();
|
||||
}
|
||||
25
src/Database.php
Normal file
25
src/Database.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
// src/Database.php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
class Database
|
||||
{
|
||||
private static ?PDO $pdo = null;
|
||||
|
||||
public static function getConnection(): PDO
|
||||
{
|
||||
if (self::$pdo === null) {
|
||||
$config = require __DIR__ . '/../config/database.php';
|
||||
|
||||
self::$pdo = new PDO(
|
||||
$config['dsn'],
|
||||
$config['user'],
|
||||
$config['password'],
|
||||
$config['options']
|
||||
);
|
||||
}
|
||||
|
||||
return self::$pdo;
|
||||
}
|
||||
}
|
||||
87
src/Session.php
Normal file
87
src/Session.php
Normal file
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
// src/Session.php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
class Session
|
||||
{
|
||||
public static function start(): void
|
||||
{
|
||||
if (session_status() !== PHP_SESSION_ACTIVE) {
|
||||
// Etwas härtere Session-Cookies
|
||||
session_set_cookie_params([
|
||||
'lifetime' => 0,
|
||||
'path' => '/',
|
||||
'secure' => isset($_SERVER['HTTPS']),
|
||||
'httponly' => true,
|
||||
'samesite' => 'Lax',
|
||||
]);
|
||||
session_start();
|
||||
}
|
||||
}
|
||||
|
||||
public static function regenerate(): void
|
||||
{
|
||||
if (session_status() === PHP_SESSION_ACTIVE) {
|
||||
session_regenerate_id(true);
|
||||
}
|
||||
}
|
||||
|
||||
public static function set(string $key, mixed $value): void
|
||||
{
|
||||
$_SESSION[$key] = $value;
|
||||
}
|
||||
|
||||
public static function get(string $key, mixed $default = null): mixed
|
||||
{
|
||||
return $_SESSION[$key] ?? $default;
|
||||
}
|
||||
|
||||
public static function remove(string $key): void
|
||||
{
|
||||
unset($_SESSION[$key]);
|
||||
}
|
||||
|
||||
public static function destroy(): void
|
||||
{
|
||||
if (session_status() === PHP_SESSION_ACTIVE) {
|
||||
$_SESSION = [];
|
||||
if (ini_get("session.use_cookies")) {
|
||||
$params = session_get_cookie_params();
|
||||
setcookie(
|
||||
session_name(),
|
||||
'',
|
||||
time() - 42000,
|
||||
$params["path"],
|
||||
$params["domain"],
|
||||
$params["secure"],
|
||||
$params["httponly"]
|
||||
);
|
||||
}
|
||||
session_destroy();
|
||||
}
|
||||
}
|
||||
|
||||
public static function csrfToken(): string
|
||||
{
|
||||
self::start();
|
||||
if (!isset($_SESSION['_csrf_token'])) {
|
||||
$_SESSION['_csrf_token'] = bin2hex(random_bytes(32));
|
||||
}
|
||||
return $_SESSION['_csrf_token'];
|
||||
}
|
||||
|
||||
public static function validateCsrf(?string $token): bool
|
||||
{
|
||||
self::start();
|
||||
if (!isset($_SESSION['_csrf_token']) || !$token) {
|
||||
return false;
|
||||
}
|
||||
$valid = hash_equals($_SESSION['_csrf_token'], $token);
|
||||
if ($valid) {
|
||||
// Optional: Token nach Benutzung rotieren
|
||||
unset($_SESSION['_csrf_token']);
|
||||
}
|
||||
return $valid;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user