;; lib/host/session.sx — durable, signed sessions for the host. ;; Backs Dream's session middleware ops (session/create|exists|get|set|clear) ;; with the SAME durable persist KV the blog uses, so a login survives restarts. ;; The session cookie carries only a signed sid (dream-sessions-signed): the sid ;; itself is a persisted monotonic counter ("s1", "s2", …) — cheap and ordered — ;; and the HMAC signature (dr/sess-hash, keyed by host/session-secret) makes a ;; guessed or forged cookie unusable. http-listen serialises handler calls under a ;; mutex, so the counter increment is race-free. ;; ;; Depends on lib/dream/session.sx (dream-sessions-signed + cookie helpers) and ;; lib/persist/* (the KV backend). Wired into host/make-app via host/sessions. ;; ── store (durable persist KV, injectable; mirrors host/blog-store) ── (define host/session-store (persist/open)) (define host/session-use-store! (fn (b) (set! host/session-store b))) ;; ── signing secret (override from $SX_SESSION_SECRET in serve.sh) ──── (define host/session-secret "rose-ash-host-dev-secret-change-me") (define host/session-set-secret! (fn (s) (set! host/session-secret s))) ;; ── keys ──────────────────────────────────────────────────────────── (define host/-sess-key (fn (sid) (str "session:" sid))) (define host/-sess-counter-key "session:-counter") ;; mint the next sid from a persisted counter (signature guards guessability) (define host/-sess-next-sid (fn () (let ((n (+ 1 (or (persist/backend-kv-get host/session-store host/-sess-counter-key) 0)))) (begin (persist/backend-kv-put host/session-store host/-sess-counter-key n) (str "s" n))))) ;; ── backend io fn: dispatch session/* ops onto the persist KV ─────── (define host/session-backend (fn (op) (let ((kind (get op :op))) (cond ((= kind "session/create") (let ((sid (host/-sess-next-sid))) (begin (persist/backend-kv-put host/session-store (host/-sess-key sid) {}) sid))) ((= kind "session/exists") (persist/backend-kv-has? host/session-store (host/-sess-key (get op :sid)))) ((= kind "session/get") (get (or (persist/backend-kv-get host/session-store (host/-sess-key (get op :sid))) {}) (get op :key))) ((= kind "session/set") (let ((sid (get op :sid))) (persist/backend-kv-put host/session-store (host/-sess-key sid) (assoc (or (persist/backend-kv-get host/session-store (host/-sess-key sid)) {}) (get op :key) (get op :val))))) ((= kind "session/load") (or (persist/backend-kv-get host/session-store (host/-sess-key (get op :sid))) {})) ((= kind "session/clear") (persist/backend-kv-delete host/session-store (host/-sess-key (get op :sid)))) (else nil))))) ;; ── middleware for the host pipeline: signed cookie + durable backend ─ (define host/sessions (fn () (dream-sessions-signed host/session-backend host/session-secret))) ;; ── handler-facing helpers ────────────────────────────────────────── ;; The logged-in principal (or nil), and login/logout writing the session field. (define host/current-principal (fn (req) (dream-session-field req :principal))) (define host/login! (fn (req principal) (dream-set-session-field req :principal principal))) (define host/logout! (fn (req) (dream-invalidate-session req)))