Fix session resume and auth helper fallbacks

This commit is contained in:
Jage9
2026-03-08 23:12:02 -04:00
parent b229e20ae2
commit d111146554
5 changed files with 54 additions and 4 deletions

View File

@@ -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": [

View File

@@ -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";

View File

@@ -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', []);

View File

@@ -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,13 +331,26 @@ 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:
try:
origin = normalize_origin(raw_origin)
except ValueError:
return False
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 return False
try: try:
origin = normalize_origin(raw_origin) parts = urlsplit(raw_referer)
referer_origin = urlunsplit((parts.scheme, parts.netloc, "", "", ""))
return normalize_origin(referer_origin, field_name="referer") == self.host_origin
except ValueError: except ValueError:
return False return False
return origin == self.host_origin
@staticmethod @staticmethod
def _cookie_value(cookie_header: str, name: str) -> str: def _cookie_value(cookie_header: str, name: str) -> str:

View File

@@ -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(