# kaya-session Session management for the Kaya web framework. Provides server-side, identity-agnostic HTTP sessions via a session cookie. The session data is accessible from request handlers as `ctx.session`. ## Usage ```python from kaya.core import KayaApp, HttpContext from kaya.session import SessionMixin, InMemorySessionStore session = SessionMixin(InMemorySessionStore()) app = KayaApp(mixins=[session]) @app.GET('/') async def home(ctx: HttpContext): n = ctx.session.get('visits', 0) + 1 ctx.session['visits'] = n await ctx.send_str(200, f'visits: {n}') ``` Sessions are created lazily: a cookie is only set when the handler modifies the session. `SessionMixin` is a `KayaMixin`, so the app stays a `KayaApp` and both ASGI and RSGI keep working. ## WebSocket sessions The same session is available in websocket handlers as `ws.session`: ```python @app.websocket('/ws/visits') async def ws_visits(ws: WebSocket): await ws.accept() visits = ws.session.get('visits', 0) await ws.send_text(f'visits: {visits}') ``` The session is loaded from the cookie when the connection is opened and persisted when the connection closes, if it was modified. **Important:** although the ASGI spec allows custom headers on the WebSocket handshake response (`websocket.accept` headers, spec 2.1+), most ASGI servers in practice — including **Granian** and **Daphne** — do not forward them into the HTTP `101` response. RSGI websocket handshakes cannot carry response headers at all. Therefore, in real deployments a session cookie can only be set or refreshed by an HTTP response. Use an HTTP endpoint to establish or update the session before opening the WebSocket, and read the existing session in the WebSocket handler. ## Session expiry The cookie sent to the browser has a `Max-Age` (default 14 days), but that is only a client-side hint. The real boundary is the store's server-side TTL, which the mixin keeps in sync with the cookie `Max-Age`. For `InMemorySessionStore`, a session expires if it is idle for longer than `max_age`. Active sessions have their expiry slid forward on every access, so a user that keeps visiting stays logged in. If the client ignores the cookie's `Max-Age` and replays an old cookie value, the store rejects the expired session and creates a fresh empty one. Set `max_age=None` to disable server-side expiry (and the `Max-Age` cookie attribute) entirely. ## Session indexes Authentication layers can attach reverse-index metadata to a session without adding it to the user-visible session dictionary: ```python ctx.session.add_index('oidc:default:sub', 'user-123') ``` Index-capable stores persist these memberships on `save()` and maintain them across session ID rotation and deletion: ```python session_ids = await store.find_sessions('oidc:default:sub', 'user-123') deleted = await store.delete_by_index('oidc:default:sub', 'user-123') ``` Use `session.remove_index(name, value)` to remove a membership while keeping the session. `SessionMixin.store` exposes the backing store for features such as back-channel logout. `InMemorySessionStore`, `RedisSessionStore`, and `MemcacheSessionStore` set `supports_indexes = True`. Custom stores can opt in by setting that flag, implementing `find_sessions()`, maintaining reverse memberships on `save()`, and removing them on `delete()`. `delete_by_index()` has a generic default implementation based on `find_sessions()` and `delete()`. Remote stores serialize an envelope containing both the session dictionary and its index metadata. Existing plain-dict payloads remain readable and are upgraded on the next save. ## Features - `Session`: dict-like session object with modification tracking - `SessionStore`: abstract store interface - `InMemorySessionStore`: simple in-memory store for development/single-process - `SessionMixin`: composable Kaya mixin managing session cookies and persistence - Session ID regeneration (`session.regenerate_id()`) and invalidation (`session.invalidate()`) for authentication layers - Reverse session indexes for looking up and invalidating sessions by metadata - WebSocket support: the session is exposed as `ws.session` in websocket handlers, loaded at connect time and persisted on close ## Notes - `InMemorySessionStore` does not survive process restarts and is not shared across processes. Production deployments should use a store backed by a shared storage system (planned).