Files
usbcheck.it/src/auth.php
2025-11-19 00:49:53 +01:00

285 lines
8.4 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?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 332 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();
}