From d3d5f5a3c85dae2c73cba0ccf7b42a74316361d8 Mon Sep 17 00:00:00 2001 From: Leo Vasanko Date: Wed, 28 Jan 2026 02:14:34 +0000 Subject: [PATCH] Remove get_session_context setting of host (now read only op as expected). Make session host, ip and user_agent always set (the ua potentially empty string). --- paskia/db/jsonl.py | 6 +++--- paskia/db/migrations.py | 8 ++++---- paskia/db/operations.py | 38 +++++++++++++++++++------------------- paskia/db/structs.py | 6 +++--- paskia/fastapi/session.py | 4 ++-- paskia/fastapi/ws.py | 8 ++++---- 6 files changed, 35 insertions(+), 35 deletions(-) diff --git a/paskia/db/jsonl.py b/paskia/db/jsonl.py index 005df16..674f34e 100644 --- a/paskia/db/jsonl.py +++ b/paskia/db/jsonl.py @@ -176,17 +176,17 @@ class JsonlStore: if data_dict: # Preserve original state before migrations (deep copy for nested dicts) original_dict = copy.deepcopy(data_dict) - + # Apply schema migrations (modifies data_dict in place) migrated = apply_migrations(data_dict) decoder = msgspec.json.Decoder(DB) self.db = decoder.decode(msgspec.json.encode(data_dict)) self.db._store = self - + # Update previous state to migrated data FIRST (to avoid transaction hardening reset) self._previous_builtins = data_dict - + # Persist migration by manually computing and queueing the diff if migrated: diff = compute_diff(original_dict, data_dict) diff --git a/paskia/db/migrations.py b/paskia/db/migrations.py index eabbf47..a08888f 100644 --- a/paskia/db/migrations.py +++ b/paskia/db/migrations.py @@ -12,16 +12,16 @@ _logger = logging.getLogger(__name__) def apply_migrations(data_dict: dict) -> bool: """Apply any pending schema migrations to the database dictionary. - + Args: data_dict: The raw database dictionary loaded from JSONL - + Returns: True if any migrations were applied, False otherwise """ db_version = data_dict.get("v", 0) migrated = False - + if db_version == 0: # Migration v0 -> v1: Remove created_at from orgs (field removed from schema) if "orgs" in data_dict: @@ -30,5 +30,5 @@ def apply_migrations(data_dict: dict) -> bool: data_dict["v"] = 1 migrated = True _logger.info("Applied schema migration: v0 -> v1 (removed org.created_at)") - + return migrated diff --git a/paskia/db/operations.py b/paskia/db/operations.py index 1c37d75..43f438c 100644 --- a/paskia/db/operations.py +++ b/paskia/db/operations.py @@ -41,16 +41,21 @@ _logger = logging.getLogger(__name__) _db = DB() _store = JsonlStore(_db) _db._store = _store +_initialized = False async def init(*args, **kwargs): """Load database from JSONL file.""" - global _db + global _db, _initialized + if _initialized: + _logger.debug("Database already initialized, skipping reload") + return db_path = os.environ.get("PASKIA_DB", DB_PATH_DEFAULT) if db_path.startswith("json:"): db_path = db_path[5:] await _store.load(db_path) _db = _store.db + _initialized = True # ------------------------------------------------------------------------- @@ -150,15 +155,10 @@ def get_session_context( if s.expiry < datetime.now(timezone.utc): return None - # Handle host binding - if host is not None: - if s.host is None: - # Bind session to this host - with _db.transaction("bind_session_host"): - s.host = host - elif s.host != host: - # Session bound to different host - return None + # Validate host matches (sessions are always created with a host) + if host is not None and s.host != host: + # Session bound to different host + return None # Validate user exists if s.user not in _db.users: @@ -558,9 +558,9 @@ def create_session( key: str, user_uuid: UUID, credential_uuid: UUID, - host: str | None, - ip: str | None, - user_agent: str | None, + host: str, + ip: str, + user_agent: str, expiry: datetime, *, ctx: SessionContext | None = None, @@ -708,9 +708,9 @@ def _create_token() -> str: def login( user_uuid: UUID, credential: Credential, - host: str | None, - ip: str | None, - user_agent: str | None, + host: str, + ip: str, + user_agent: str, expiry: datetime, ) -> str: """Update user/credential on login and create session in a single transaction. @@ -755,9 +755,9 @@ def login( def create_credential_session( user_uuid: UUID, credential: Credential, - host: str | None, - ip: str | None, - user_agent: str | None, + host: str, + ip: str, + user_agent: str, display_name: str | None = None, reset_key: bytes | None = None, ) -> str: diff --git a/paskia/db/structs.py b/paskia/db/structs.py index 9276fb2..c6d7e32 100644 --- a/paskia/db/structs.py +++ b/paskia/db/structs.py @@ -190,9 +190,9 @@ class Session(msgspec.Struct, dict=True): user: UUID credential: UUID - host: str | None - ip: str | None - user_agent: str | None + host: str + ip: str + user_agent: str expiry: datetime def __post_init__(self): diff --git a/paskia/fastapi/session.py b/paskia/fastapi/session.py index 778b72f..e86b207 100644 --- a/paskia/fastapi/session.py +++ b/paskia/fastapi/session.py @@ -19,8 +19,8 @@ AUTH_COOKIE = Cookie(None, alias=AUTH_COOKIE_NAME) def infodict(request: Request | WebSocket, type: str) -> dict: """Extract client information from request.""" return { - "ip": request.client.host if request.client else None, - "user_agent": request.headers.get("user-agent", "")[:500] or None, + "ip": request.client.host if request.client else "", + "user_agent": request.headers.get("user-agent", "")[:500], "session_type": type, } diff --git a/paskia/fastapi/ws.py b/paskia/fastapi/ws.py index 89d6362..5e80ee5 100644 --- a/paskia/fastapi/ws.py +++ b/paskia/fastapi/ws.py @@ -65,8 +65,8 @@ async def websocket_register_add( reset_key=(s.key if reset is not None else None), display_name=user_name, host=host, - ip=metadata.get("ip"), - user_agent=metadata.get("user_agent"), + ip=metadata["ip"], + user_agent=metadata["user_agent"], ) auth = token @@ -117,8 +117,8 @@ async def websocket_authenticate(ws: WebSocket, auth=AUTH_COOKIE): user_uuid=cred.user, credential=cred, host=normalized_host, - ip=metadata.get("ip") or "", - user_agent=metadata.get("user_agent") or "", + ip=metadata["ip"], + user_agent=metadata["user_agent"], expiry=expires(), )