5.4.0
Restructure New Journal entries Colour tabs update with wind
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
// ─── SunScope — Session auth ──────────────────────────────────────────────
|
||||
// Opaque DB-backed session token in an HttpOnly cookie, not $_SESSION —
|
||||
// keeps the door open for a future magic-link flow without a rewrite, and
|
||||
// means a session can be revoked by deleting one row.
|
||||
//
|
||||
// The cookie holds the plaintext token; only its SHA-256 hash is stored,
|
||||
// same pattern as a password hash, so a DB read alone can't forge a login.
|
||||
|
||||
const SESSION_COOKIE = 'ss_session';
|
||||
const SESSION_DAYS = 30;
|
||||
|
||||
function new_public_id(): string {
|
||||
return rtrim(strtr(base64_encode(random_bytes(16)), '+/', '-_'), '=');
|
||||
}
|
||||
|
||||
function issue_session(int $userId): void {
|
||||
$token = rtrim(strtr(base64_encode(random_bytes(32)), '+/', '-_'), '=');
|
||||
$hash = hash('sha256', $token, true);
|
||||
$expiresAt = (new DateTime())->modify('+' . SESSION_DAYS . ' days')->format('Y-m-d H:i:s');
|
||||
|
||||
$stmt = db()->prepare(
|
||||
'INSERT INTO sessions (user_id, token_hash, expires_at) VALUES (?, ?, ?)'
|
||||
);
|
||||
$stmt->execute([$userId, $hash, $expiresAt]);
|
||||
|
||||
setcookie(SESSION_COOKIE, $token, [
|
||||
'expires' => time() + SESSION_DAYS * 86400,
|
||||
'path' => '/',
|
||||
'httponly' => true,
|
||||
'samesite' => 'Lax',
|
||||
// 'secure' left off for local XAMPP http:// dev; set true behind HTTPS in production.
|
||||
]);
|
||||
}
|
||||
|
||||
function clear_session(): void {
|
||||
$token = $_COOKIE[SESSION_COOKIE] ?? null;
|
||||
if ($token) {
|
||||
$hash = hash('sha256', $token, true);
|
||||
$stmt = db()->prepare('DELETE FROM sessions WHERE token_hash = ?');
|
||||
$stmt->execute([$hash]);
|
||||
}
|
||||
setcookie(SESSION_COOKIE, '', ['expires' => time() - 3600, 'path' => '/']);
|
||||
}
|
||||
|
||||
// Returns the logged-in user's row, or null if there is no valid session.
|
||||
function current_user(): ?array {
|
||||
$token = $_COOKIE[SESSION_COOKIE] ?? null;
|
||||
if (!$token) return null;
|
||||
|
||||
$hash = hash('sha256', $token, true);
|
||||
$stmt = db()->prepare(
|
||||
'SELECT u.id, u.public_id, u.email
|
||||
FROM sessions s
|
||||
JOIN users u ON u.id = s.user_id
|
||||
WHERE s.token_hash = ? AND s.expires_at > NOW()'
|
||||
);
|
||||
$stmt->execute([$hash]);
|
||||
$row = $stmt->fetch();
|
||||
return $row ?: null;
|
||||
}
|
||||
|
||||
// Ends the request with 401 if not logged in; otherwise returns the user row.
|
||||
function require_user(): array {
|
||||
$user = current_user();
|
||||
if (!$user) json_error('Not signed in', 401);
|
||||
return $user;
|
||||
}
|
||||
Reference in New Issue
Block a user