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; }