394 lines
11 KiB
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(); |