togethere.cloud/public_html/includes/session_bootstrap.php

394 lines
11 KiB
PHP

<?php
declare(strict_types=1);
if (defined('OG_SESSION_BOOTSTRAP_LOADED')) {
return;
}
define('OG_SESSION_BOOTSTRAP_LOADED', true);
define('OG_SESSION_TIMEOUT_DEFAULT', 24 * 60 * 60);
define('OG_SESSION_TIMEOUT_REMEMBER', 7 * 24 * 60 * 60);
if (!defined('OG_FATAL_LOGGER_REGISTERED')) {
define('OG_FATAL_LOGGER_REGISTERED', true);
register_shutdown_function(static function (): void {
$error = error_get_last();
if (!is_array($error)) {
return;
}
$fatalTypes = [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR];
if (!in_array($error['type'] ?? 0, $fatalTypes, true)) {
return;
}
$documentRoot = isset($_SERVER['DOCUMENT_ROOT']) ? rtrim((string) $_SERVER['DOCUMENT_ROOT'], '/\\') : '';
$logTargets = [
sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'togethere_php_fatal.log',
];
if ($documentRoot !== '') {
$projectRoot = dirname($documentRoot);
$logTargets[] = $projectRoot . DIRECTORY_SEPARATOR . 'private_html' . DIRECTORY_SEPARATOR . 'php_fatal.log';
$logTargets[] = $documentRoot . DIRECTORY_SEPARATOR . 'administration' . DIRECTORY_SEPARATOR . 'admin_fatal.log';
}
$line = sprintf(
"[%s] uri=%s ip=%s file=%s line=%s message=%s%s",
date('Y-m-d H:i:s'),
isset($_SERVER['REQUEST_URI']) ? (string) $_SERVER['REQUEST_URI'] : '',
isset($_SERVER['REMOTE_ADDR']) ? (string) $_SERVER['REMOTE_ADDR'] : '',
(string) ($error['file'] ?? ''),
(string) ($error['line'] ?? ''),
trim((string) ($error['message'] ?? 'unknown fatal error')),
PHP_EOL
);
foreach (array_unique($logTargets) as $logPath) {
if (@file_put_contents($logPath, $line, FILE_APPEND | LOCK_EX) !== false) {
break;
}
}
});
}
function og_session_is_secure_request(): bool
{
if (!empty($_SERVER['HTTPS']) && strtolower((string) $_SERVER['HTTPS']) !== 'off') {
return true;
}
if (isset($_SERVER['SERVER_PORT']) && (int) $_SERVER['SERVER_PORT'] === 443) {
return true;
}
return false;
}
function og_session_cookie_options(int $lifetime): array
{
return [
'lifetime' => $lifetime,
'path' => '/',
'secure' => og_session_is_secure_request(),
'httponly' => true,
'samesite' => 'Lax',
];
}
function og_session_setcookie_options(int $expiresAt): array
{
$options = og_session_cookie_options(0);
unset($options['lifetime']);
$options['expires'] = $expiresAt;
return $options;
}
function og_session_configure(int $timeout): void
{
if (session_status() === PHP_SESSION_ACTIVE) {
return;
}
ini_set('session.gc_maxlifetime', (string) $timeout);
ini_set('session.cookie_httponly', '1');
ini_set('session.use_strict_mode', '1');
if (PHP_VERSION_ID >= 70300) {
session_set_cookie_params(og_session_cookie_options($timeout));
return;
}
$path = '/; samesite=Lax';
session_set_cookie_params($timeout, $path, '', og_session_is_secure_request(), true);
}
function og_session_get_pdo(): ?PDO
{
static $pdo = null;
if ($pdo instanceof PDO) {
return $pdo;
}
try {
$pdo = new PDO(
'mysql:host=localhost;dbname=togethere_cloud;charset=utf8mb4',
'root',
'HasloDoSQL',
[
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]
);
$pdo->exec('SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci');
return $pdo;
} catch (Throwable $e) {
return null;
}
}
function og_session_ensure_remember_tokens_table(?PDO $pdo = null): bool
{
$pdo = $pdo ?: og_session_get_pdo();
if (!$pdo instanceof PDO) {
return false;
}
try {
$pdo->exec(
'CREATE TABLE IF NOT EXISTS remember_tokens (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL,
token VARCHAR(255) NOT NULL,
expires_at DATETIME NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY uniq_remember_token (token),
KEY idx_remember_user (user_id),
KEY idx_remember_expires (expires_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci'
);
} catch (Throwable $e) {
return false;
}
return true;
}
function og_session_normalize_username(?string $username): string
{
return trim((string) $username);
}
function og_session_is_valid_username(?string $username): bool
{
$normalized = og_session_normalize_username($username);
if ($normalized === '') {
return false;
}
return preg_match('/^[A-Za-z0-9_&!]{1,20}$/', $normalized) === 1;
}
function og_session_has_valid_username(): bool
{
return og_session_is_valid_username($_SESSION['username'] ?? null);
}
function og_session_remember_cookie_value(): string
{
return isset($_COOKIE['remember_token']) ? trim((string) $_COOKIE['remember_token']) : '';
}
function og_session_uses_remember_me(): bool
{
if (!empty($_SESSION['remember_me'])) {
return true;
}
return og_session_remember_cookie_value() !== '';
}
function og_session_timeout_seconds(): int
{
return og_session_uses_remember_me() ? OG_SESSION_TIMEOUT_REMEMBER : OG_SESSION_TIMEOUT_DEFAULT;
}
function og_session_refresh_cookie(string $name, string $value, int $lifetime): void
{
if (headers_sent()) {
return;
}
$expiresAt = time() + $lifetime;
if (PHP_VERSION_ID >= 70300) {
setcookie($name, $value, og_session_setcookie_options($expiresAt));
return;
}
setcookie($name, $value, $expiresAt, '/; samesite=Lax', '', og_session_is_secure_request(), true);
}
function og_session_clear_cookie(string $name): void
{
if (headers_sent()) {
return;
}
if (PHP_VERSION_ID >= 70300) {
setcookie($name, '', og_session_setcookie_options(time() - 3600));
return;
}
setcookie($name, '', time() - 3600, '/; samesite=Lax', '', og_session_is_secure_request(), true);
}
function og_session_refresh_remember_token(): void
{
if (empty($_SESSION['remember_me'])) {
return;
}
$token = og_session_remember_cookie_value();
if ($token === '') {
return;
}
$pdo = og_session_get_pdo();
if (!$pdo instanceof PDO || !og_session_ensure_remember_tokens_table($pdo)) {
return;
}
try {
$expiresAt = date('Y-m-d H:i:s', time() + OG_SESSION_TIMEOUT_REMEMBER);
$stmt = $pdo->prepare('UPDATE remember_tokens SET expires_at = :expires WHERE token = :token');
$stmt->execute([
':expires' => $expiresAt,
':token' => hash('sha256', $token),
]);
} catch (Throwable $e) {
return;
}
og_session_refresh_cookie('remember_token', $token, OG_SESSION_TIMEOUT_REMEMBER);
}
function og_session_clear_remember_token(): void
{
$token = og_session_remember_cookie_value();
if ($token !== '') {
$pdo = og_session_get_pdo();
if ($pdo instanceof PDO && og_session_ensure_remember_tokens_table($pdo)) {
try {
$stmt = $pdo->prepare('DELETE FROM remember_tokens WHERE token = :token');
$stmt->execute([':token' => hash('sha256', $token)]);
} catch (Throwable $e) {
}
}
}
og_session_clear_cookie('remember_token');
}
function og_session_destroy_auth(bool $clearRememberToken = false): void
{
$_SESSION = [];
if ($clearRememberToken) {
og_session_clear_remember_token();
}
if (session_status() === PHP_SESSION_ACTIVE) {
if (!headers_sent()) {
og_session_clear_cookie(session_name());
}
session_destroy();
}
}
function og_session_find_remember_user(PDO $pdo, string $token): ?array
{
if ($token === '') {
return null;
}
if (!og_session_ensure_remember_tokens_table($pdo)) {
return null;
}
try {
$stmt = $pdo->prepare(
'SELECT u.id, u.username, u.email, COALESCE(u.role, "user") AS role
FROM remember_tokens rt
INNER JOIN users u ON u.id = rt.user_id
WHERE rt.token = :token
AND rt.expires_at > NOW()
LIMIT 1'
);
$stmt->execute([':token' => hash('sha256', $token)]);
$row = $stmt->fetch();
} catch (Throwable $e) {
return null;
}
return is_array($row) ? $row : null;
}
function og_session_restore_from_remember_cookie(): void
{
if (!empty($_SESSION['logged_in']) && !empty($_SESSION['user_id'])) {
return;
}
$token = og_session_remember_cookie_value();
if ($token === '') {
return;
}
$pdo = og_session_get_pdo();
if (!$pdo instanceof PDO) {
return;
}
$user = og_session_find_remember_user($pdo, $token);
if (!$user) {
og_session_clear_remember_token();
return;
}
session_regenerate_id(true);
$_SESSION['logged_in'] = true;
$_SESSION['user_id'] = (int) $user['id'];
$_SESSION['username'] = (string) $user['username'];
$_SESSION['email'] = (string) ($user['email'] ?? '');
$_SESSION['role'] = (string) ($user['role'] ?? 'user');
$_SESSION['remember_me'] = true;
$_SESSION['last_activity'] = time();
og_session_refresh_remember_token();
}
function og_session_enforce_inactivity_timeout(): void
{
if (empty($_SESSION['logged_in']) || empty($_SESSION['user_id'])) {
return;
}
$lastActivity = isset($_SESSION['last_activity']) ? (int) $_SESSION['last_activity'] : 0;
if ($lastActivity <= 0) {
return;
}
if ((time() - $lastActivity) > og_session_timeout_seconds()) {
og_session_destroy_auth(og_session_uses_remember_me());
}
}
function og_session_touch(): void
{
if (empty($_SESSION['logged_in']) || empty($_SESSION['user_id'])) {
return;
}
$_SESSION['last_activity'] = time();
og_session_refresh_cookie(session_name(), session_id(), og_session_timeout_seconds());
og_session_refresh_remember_token();
}
$preSessionTimeout = og_session_remember_cookie_value() !== ''
? OG_SESSION_TIMEOUT_REMEMBER
: OG_SESSION_TIMEOUT_DEFAULT;
og_session_configure($preSessionTimeout);
if (session_status() !== PHP_SESSION_ACTIVE) {
session_start();
}
og_session_restore_from_remember_cookie();
og_session_enforce_inactivity_timeout();
og_session_touch();