chore: восстановление репозитория из снапшота v0.3.1

Прежняя git-история утрачена при переносе проекта на машину владельца
(снапшот без .git). Хэши коммитов в docs/reports/* относятся к утраченной
истории.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
sab.code.lab 2026-08-07 09:34:02 +02:00
commit 1c85091186
184 changed files with 33303 additions and 0 deletions

99
server/lib/auth.php Normal file
View file

@ -0,0 +1,99 @@
<?php
declare(strict_types=1);
/**
* Сессии и текущий пользователь.
* PHP native-сессии, cookie HttpOnly, SameSite=Lax, Secure при HTTPS.
*/
/**
* Старт сессии с параметрами cookie по контракту.
* Идемпотентно (повторный вызов безопасен).
*/
function session_start_secure(): void
{
if (session_status() === PHP_SESSION_ACTIVE) {
return;
}
// Fallback save_path (пустой/несуществующий/недоступный — типично для
// ручных сборок и части shared-хостингов): уходим в sys_get_temp_dir().
$savePath = (string)ini_get('session.save_path');
if (
$savePath === ''
|| !is_dir($savePath)
|| !is_writable($savePath)
) {
ini_set('session.save_path', sys_get_temp_dir());
}
$https = !empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off';
session_set_cookie_params([
'path' => '/',
'httponly' => true,
'samesite' => 'Lax',
'secure' => $https,
]);
session_start();
}
/**
* Текущий пользователь по сессии или null.
*
* @return array{id: int, email: string}|null
*/
function current_user(PDO $pdo): ?array
{
session_start_secure();
$id = $_SESSION['user_id'] ?? null;
if (!is_int($id)) {
return null;
}
$stmt = $pdo->prepare('SELECT id, email FROM users WHERE id = ?');
$stmt->execute([$id]);
$user = $stmt->fetch();
if (!is_array($user)) {
return null;
}
return ['id' => (int)$user['id'], 'email' => (string)$user['email']];
}
/**
* Текущий пользователь или 401.
*
* @return array{id: int, email: string}
*/
function require_user(PDO $pdo): array
{
$user = current_user($pdo);
if ($user === null) {
error_out(401, 'unauthorized');
}
return $user;
}
/** Установить сессию после успешного login (id перегенерируется). */
function login_user(int $id): void
{
session_start_secure();
session_regenerate_id(true);
$_SESSION['user_id'] = $id;
}
/** Полный выход: очистка $_SESSION, cookie и уничтожение сессии. */
function logout_user(): void
{
session_start_secure();
$_SESSION = [];
if (ini_get('session.use_cookies')) {
$params = session_get_cookie_params();
setcookie(session_name(), '', [
'expires' => time() - 42000,
'path' => $params['path'],
'domain' => $params['domain'],
'secure' => $params['secure'],
'httponly' => $params['httponly'],
'samesite' => $params['samesite'] ?? 'Lax',
]);
}
session_destroy();
}