Fix session resume and auth helper fallbacks
This commit is contained in:
@@ -1,5 +1,11 @@
|
|||||||
{
|
{
|
||||||
"sections": [
|
"sections": [
|
||||||
|
{
|
||||||
|
"date": "March 8, 2026",
|
||||||
|
"items": [
|
||||||
|
"Added a command palette with Shift+K, Shift+F10, or the Applications key to show all available commands."
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"date": "February 28, 2026",
|
"date": "February 28, 2026",
|
||||||
"items": [
|
"items": [
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// Maintainer-controlled web client version metadata.
|
// Maintainer-controlled web client version metadata.
|
||||||
window.CHGRID_RELEASE_VERSION = "0.1.0";
|
window.CHGRID_RELEASE_VERSION = "0.1.0";
|
||||||
window.CHGRID_BUILD_REVISION = "R344";
|
window.CHGRID_BUILD_REVISION = "R345";
|
||||||
window.CHGRID_WEB_VERSION = `${window.CHGRID_RELEASE_VERSION} ${window.CHGRID_BUILD_REVISION}`;
|
window.CHGRID_WEB_VERSION = `${window.CHGRID_RELEASE_VERSION} ${window.CHGRID_BUILD_REVISION}`;
|
||||||
// Optional display timezone for timestamps. Falls back to America/Detroit if unset/invalid.
|
// Optional display timezone for timestamps. Falls back to America/Detroit if unset/invalid.
|
||||||
window.CHGRID_TIME_ZONE = "America/Detroit";
|
window.CHGRID_TIME_ZONE = "America/Detroit";
|
||||||
|
|||||||
@@ -156,6 +156,14 @@ export function createAuthController(deps: AuthControllerDeps): {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function resetSavedSessionHint(): void {
|
||||||
|
authUserId = '';
|
||||||
|
authUsername = '';
|
||||||
|
deps.saveAuthUsername('');
|
||||||
|
deps.dom.authUsername.value = '';
|
||||||
|
deps.dom.registerUsername.value = '';
|
||||||
|
}
|
||||||
|
|
||||||
function updateConnectAvailability(): void {
|
function updateConnectAvailability(): void {
|
||||||
const hasSavedSessionHint = sanitizeAuthUsername(authUsername).length > 0;
|
const hasSavedSessionHint = sanitizeAuthUsername(authUsername).length > 0;
|
||||||
const showLogout = deps.isRunning() || hasSavedSessionHint;
|
const showLogout = deps.isRunning() || hasSavedSessionHint;
|
||||||
@@ -256,6 +264,10 @@ export function createAuthController(deps: AuthControllerDeps): {
|
|||||||
deps.signalingSend(packet);
|
deps.signalingSend(packet);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (sanitizeAuthUsername(authUsername).length > 0) {
|
||||||
|
resetSavedSessionHint();
|
||||||
|
setAuthMode('login');
|
||||||
|
}
|
||||||
deps.setConnecting(false);
|
deps.setConnecting(false);
|
||||||
updateConnectAvailability();
|
updateConnectAvailability();
|
||||||
}
|
}
|
||||||
@@ -311,6 +323,7 @@ export function createAuthController(deps: AuthControllerDeps): {
|
|||||||
deps.dom.registerPassword.value = '';
|
deps.dom.registerPassword.value = '';
|
||||||
deps.dom.registerPasswordConfirm.value = '';
|
deps.dom.registerPasswordConfirm.value = '';
|
||||||
if (message.message.toLowerCase().includes('session')) {
|
if (message.message.toLowerCase().includes('session')) {
|
||||||
|
resetSavedSessionHint();
|
||||||
void clearHttpOnlySessionCookie();
|
void clearHttpOnlySessionCookie();
|
||||||
}
|
}
|
||||||
applyAuthPermissions('user', []);
|
applyAuthPermissions('user', []);
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import uuid
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Literal
|
from typing import Literal
|
||||||
from urllib.error import URLError
|
from urllib.error import URLError
|
||||||
|
from urllib.parse import urlsplit, urlunsplit
|
||||||
from zoneinfo import ZoneInfo
|
from zoneinfo import ZoneInfo
|
||||||
|
|
||||||
from pydantic import ValidationError, TypeAdapter
|
from pydantic import ValidationError, TypeAdapter
|
||||||
@@ -330,14 +331,27 @@ class SignalingServer:
|
|||||||
if not self.host_origin:
|
if not self.host_origin:
|
||||||
return False
|
return False
|
||||||
raw_origin = str(request.headers.get("Origin", "")).strip()
|
raw_origin = str(request.headers.get("Origin", "")).strip()
|
||||||
if not raw_origin:
|
if raw_origin:
|
||||||
return False
|
|
||||||
try:
|
try:
|
||||||
origin = normalize_origin(raw_origin)
|
origin = normalize_origin(raw_origin)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return False
|
return False
|
||||||
return origin == self.host_origin
|
return origin == self.host_origin
|
||||||
|
|
||||||
|
fetch_site = str(request.headers.get("Sec-Fetch-Site", "")).strip().lower()
|
||||||
|
if fetch_site == "same-origin":
|
||||||
|
return True
|
||||||
|
|
||||||
|
raw_referer = str(request.headers.get("Referer", "")).strip()
|
||||||
|
if not raw_referer:
|
||||||
|
return False
|
||||||
|
try:
|
||||||
|
parts = urlsplit(raw_referer)
|
||||||
|
referer_origin = urlunsplit((parts.scheme, parts.netloc, "", "", ""))
|
||||||
|
return normalize_origin(referer_origin, field_name="referer") == self.host_origin
|
||||||
|
except ValueError:
|
||||||
|
return False
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _cookie_value(cookie_header: str, name: str) -> str:
|
def _cookie_value(cookie_header: str, name: str) -> str:
|
||||||
"""Extract one cookie value by name from a Cookie header."""
|
"""Extract one cookie value by name from a Cookie header."""
|
||||||
|
|||||||
@@ -118,6 +118,23 @@ async def test_session_cookie_helpers_reject_wrong_origin() -> None:
|
|||||||
assert response.status_code == 403
|
assert response.status_code == 403
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_session_cookie_helpers_accept_same_origin_referer_without_origin() -> None:
|
||||||
|
server = _server()
|
||||||
|
request = _request(
|
||||||
|
server.auth_session_cookie_clear_path,
|
||||||
|
headers={
|
||||||
|
AUTH_SESSION_COOKIE_CLIENT_HEADER: "1",
|
||||||
|
"Referer": "https://example.com/chgrid/",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
response = await server._process_http_request(SimpleNamespace(), request)
|
||||||
|
|
||||||
|
assert response is not None
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
def test_session_token_from_websocket_cookie_reads_named_cookie() -> None:
|
def test_session_token_from_websocket_cookie_reads_named_cookie() -> None:
|
||||||
server = SignalingServer("127.0.0.1", 8765, None, None, base_path="/chgrid/")
|
server = SignalingServer("127.0.0.1", 8765, None, None, base_path="/chgrid/")
|
||||||
websocket = SimpleNamespace(
|
websocket = SimpleNamespace(
|
||||||
|
|||||||
Reference in New Issue
Block a user