104 lines
4.7 KiB
PHP
104 lines
4.7 KiB
PHP
<?php
|
|
/** Shared authentication state for the dashboard, API and inline editor. */
|
|
|
|
function raijuSessionStart(): void {
|
|
if (session_status() !== PHP_SESSION_NONE || headers_sent()) return;
|
|
ini_set('session.use_strict_mode', '1');
|
|
session_set_cookie_params([
|
|
'lifetime' => defined('SESSION_LIFETIME') ? SESSION_LIFETIME : 3600,
|
|
'path' => '/',
|
|
'secure' => !empty($_SERVER['HTTPS']) && strtolower((string)$_SERVER['HTTPS']) !== 'off',
|
|
'httponly' => true,
|
|
'samesite' => 'Strict',
|
|
]);
|
|
session_start();
|
|
}
|
|
|
|
function raijuSessionIsLoopback(string $host): bool {
|
|
$host = strtolower(trim($host));
|
|
if (str_starts_with($host, '[')) {
|
|
$host = substr($host, 1, strpos($host, ']') - 1);
|
|
} elseif (substr_count($host, ':') === 1) {
|
|
$host = explode(':', $host, 2)[0];
|
|
}
|
|
return in_array($host, ['localhost', '::1', '0:0:0:0:0:0:0:1'], true)
|
|
|| (filter_var($host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) && str_starts_with($host, '127.'));
|
|
}
|
|
|
|
function raijuSessionDevLoginAllowed(): bool {
|
|
return (!defined('RAIJU_DEV_LOGIN') || RAIJU_DEV_LOGIN === true)
|
|
&& raijuSessionIsLoopback((string)($_SERVER['REMOTE_ADDR'] ?? ''))
|
|
&& raijuSessionIsLoopback((string)($_SERVER['HTTP_HOST'] ?? $_SERVER['SERVER_NAME'] ?? ''));
|
|
}
|
|
|
|
function raijuSessionForgetLogin(): void {
|
|
foreach (['admin_logged_in', 'admin_login_time', 'admin_user_id', 'admin_username',
|
|
'admin_role', 'admin_password_fingerprint', 'raiju_dev_login', 'csrf_token',
|
|
'password_warning', 'email_missing'] as $key) {
|
|
unset($_SESSION[$key]);
|
|
}
|
|
}
|
|
|
|
function raijuSessionValidate(bool $refresh = false): bool {
|
|
if (($_SESSION['admin_logged_in'] ?? false) !== true) return false;
|
|
$config = dirname(__DIR__) . '/admin/config.php';
|
|
if (!defined('SESSION_LIFETIME') && is_file($config)) require_once $config;
|
|
$lastActivity = (int)($_SESSION['admin_login_time'] ?? 0);
|
|
$lifetime = defined('SESSION_LIFETIME') ? SESSION_LIFETIME : 3600;
|
|
if ($lastActivity <= 0 || time() - $lastActivity > $lifetime) {
|
|
raijuSessionForgetLogin();
|
|
return false;
|
|
}
|
|
|
|
$id = (string)($_SESSION['admin_user_id'] ?? '');
|
|
if (!empty($_SESSION['raiju_dev_login']) && !raijuSessionDevLoginAllowed()) {
|
|
raijuSessionForgetLogin();
|
|
return false;
|
|
}
|
|
if ($id === 'dev_admin' && !empty($_SESSION['raiju_dev_login'])) {
|
|
$_SESSION['admin_role'] = 'admin';
|
|
$_SESSION['admin_login_time'] = time();
|
|
return true;
|
|
}
|
|
|
|
require_once dirname(__DIR__) . '/admin/users.php';
|
|
// Cache only within this request; the next request re-reads the account.
|
|
static $users = [];
|
|
if ($refresh || !array_key_exists($id, $users)) $users[$id] = findUserById($id);
|
|
$user = $users[$id];
|
|
$fingerprint = (string)($_SESSION['admin_password_fingerprint'] ?? '');
|
|
if (!$user || !in_array($user['role'] ?? '', ['admin', 'editor'], true)
|
|
|| $fingerprint === ''
|
|
|| !hash_equals(hash('sha256', (string)($user['passwordHash'] ?? '')), $fingerprint)) {
|
|
raijuSessionForgetLogin();
|
|
return false;
|
|
}
|
|
$_SESSION['admin_username'] = $user['username'];
|
|
$_SESSION['admin_role'] = $user['role'];
|
|
$_SESSION['admin_login_time'] = time();
|
|
return true;
|
|
}
|
|
|
|
/** Return a site-relative login destination; reject browser URL ambiguities. */
|
|
function raijuSessionRedirectUrl(string $url): string {
|
|
$url = trim($url);
|
|
if ($url === '' || preg_match('/[\x00-\x20\x7f\\\\]/', $url)) return '/';
|
|
$parts = parse_url($url);
|
|
if (!is_array($parts) || isset($parts['user']) || isset($parts['pass'])) return '/';
|
|
if (isset($parts['scheme']) || isset($parts['host'])) {
|
|
if (!in_array(strtolower($parts['scheme'] ?? ''), ['http', 'https'], true)) return '/';
|
|
$origin = parse_url('http://' . ($_SERVER['HTTP_HOST'] ?? ''));
|
|
if (strtolower($parts['host'] ?? '') !== strtolower($origin['host'] ?? '')
|
|
|| (int)($parts['port'] ?? (strtolower($parts['scheme']) === 'https' ? 443 : 80))
|
|
!== (int)($origin['port'] ?? (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' ? 443 : 80))) return '/';
|
|
$url = ($parts['path'] ?? '/') . (isset($parts['query']) ? '?' . $parts['query'] : '')
|
|
. (isset($parts['fragment']) ? '#' . $parts['fragment'] : '');
|
|
}
|
|
$path = rawurldecode((string)($parts['path'] ?? ''));
|
|
if (str_starts_with($url, '//') || preg_match('/[\x00-\x20\x7f\\\\]/', $path)
|
|
|| preg_match('#(^|/)\.\.?(/|$)#', $path)) return '/';
|
|
$path = '/' . ltrim($path, '/');
|
|
if ($path === '/admin' || str_starts_with($path, '/admin/') || $path === '/login' || $path === '/login/') return '/';
|
|
return str_starts_with($url, '/') || str_starts_with($url, '#') ? $url : '/' . $url;
|
|
}
|