118 lines
3.7 KiB
PHP
118 lines
3.7 KiB
PHP
<?php
|
|
// Sessions, auth, CSRF. Included by every page via bootstrap.php.
|
|
|
|
function session_boot(): void {
|
|
if (session_status() === PHP_SESSION_ACTIVE) return;
|
|
// WAP gateways often strip cookies -> accept the sid from the query string.
|
|
ini_set('session.use_only_cookies', '0');
|
|
ini_set('session.use_trans_sid', '0');
|
|
session_name('WAPSID');
|
|
if (!empty($_REQUEST['WAPSID']) && preg_match('/^[A-Za-z0-9,\-]{8,64}$/', $_REQUEST['WAPSID'])) {
|
|
session_id($_REQUEST['WAPSID']);
|
|
}
|
|
session_start();
|
|
}
|
|
|
|
function csrf_token(): string {
|
|
if (empty($_SESSION['csrf'])) $_SESSION['csrf'] = bin2hex(random_bytes(16));
|
|
return $_SESSION['csrf'];
|
|
}
|
|
|
|
function csrf_ok(): bool {
|
|
$t = $_POST['csrf'] ?? $_GET['csrf'] ?? '';
|
|
return $t !== '' && !empty($_SESSION['csrf']) && hash_equals($_SESSION['csrf'], $t);
|
|
}
|
|
|
|
function require_csrf(): void {
|
|
if (!csrf_ok()) bail('Error', 'Session expired or bad token. Please try again.');
|
|
}
|
|
|
|
function current_user(): ?array {
|
|
static $cache = null;
|
|
static $done = false;
|
|
if ($done) return $cache;
|
|
$done = true;
|
|
if (empty($_SESSION['uid'])) return $cache = null;
|
|
$s = db()->prepare("SELECT * FROM users WHERE id=?");
|
|
$s->execute([$_SESSION['uid']]);
|
|
$u = $s->fetch();
|
|
if (!$u || $u['is_banned']) { $_SESSION['uid'] = null; return $cache = null; }
|
|
// throttle last_seen writes to once a minute
|
|
if (time() - (int)$u['last_seen'] > 60) {
|
|
db()->prepare("UPDATE users SET last_seen=? WHERE id=?")->execute([time(), $u['id']]);
|
|
}
|
|
return $cache = $u;
|
|
}
|
|
|
|
function require_login(): array {
|
|
$u = current_user();
|
|
if (!$u) {
|
|
page_start('Login needed', ['back' => '/index.php']);
|
|
p_para('You must log in to use this feature.');
|
|
p_links([['/login.php', 'Login'], ['/signup.php', 'Sign up'], ['/index.php', 'Home']]);
|
|
page_end();
|
|
exit;
|
|
}
|
|
return $u;
|
|
}
|
|
|
|
function require_admin(): array {
|
|
$u = require_login();
|
|
if (!$u['is_admin']) bail('Denied', 'Admin only.');
|
|
return $u;
|
|
}
|
|
|
|
function user_login(string $name, string $pass): ?array {
|
|
$s = db()->prepare("SELECT * FROM users WHERE username=?");
|
|
$s->execute([$name]);
|
|
$u = $s->fetch();
|
|
if (!$u || !password_verify($pass, $u['pass_hash'])) return null;
|
|
if ($u['is_banned']) return null;
|
|
$_SESSION['uid'] = (int)$u['id'];
|
|
return $u;
|
|
}
|
|
|
|
function user_create(string $name, string $pass, int $admin = 0): int {
|
|
$s = db()->prepare("INSERT INTO users (username,pass_hash,is_admin,created_at)
|
|
VALUES (?,?,?,?)");
|
|
$s->execute([$name, password_hash($pass, PASSWORD_DEFAULT), $admin, time()]);
|
|
return (int)db()->lastInsertId();
|
|
}
|
|
|
|
function username_taken(string $n): bool {
|
|
$s = db()->prepare("SELECT 1 FROM users WHERE username=?");
|
|
$s->execute([$n]);
|
|
return (bool)$s->fetchColumn();
|
|
}
|
|
|
|
function valid_username(string $n): bool {
|
|
return (bool)preg_match('/^[A-Za-z0-9_]{3,16}$/', $n);
|
|
}
|
|
|
|
function user_by_id(int $id): ?array {
|
|
$s = db()->prepare("SELECT * FROM users WHERE id=?");
|
|
$s->execute([$id]);
|
|
return $s->fetch() ?: null;
|
|
}
|
|
|
|
function user_by_name(string $n): ?array {
|
|
$s = db()->prepare("SELECT * FROM users WHERE username=?");
|
|
$s->execute([$n]);
|
|
return $s->fetch() ?: null;
|
|
}
|
|
|
|
function unread_count(int $uid): int {
|
|
$s = db()->prepare("SELECT COUNT(*) FROM messages WHERE to_id=? AND is_read=0");
|
|
$s->execute([$uid]);
|
|
return (int)$s->fetchColumn();
|
|
}
|
|
|
|
function ago(int $ts): string {
|
|
$d = max(0, time() - $ts);
|
|
if ($d < 60) return $d . 's';
|
|
if ($d < 3600) return floor($d / 60) . 'm';
|
|
if ($d < 86400) return floor($d / 3600) . 'h';
|
|
if ($d < 2592000) return floor($d / 86400) . 'd';
|
|
return date('d/m/y', $ts);
|
|
}
|