285 lines
8.4 KiB
PHP
285 lines
8.4 KiB
PHP
<?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();
|
||
}
|