Skip to content

Add SQLite-backed server-side sessions with explicit lifecycle and bounds #203

Description

@lukewilliamboswell

Summary

Add a Session module implemented over the existing bounded SQLite pool. The browser receives only an opaque bearer identifier; application session data remains in SQLite.

Illustrative API:

store = Session.init!({
    db,
    max_payload_bytes: 16 * 1024,
    idle_timeout_seconds: 30 * 60,
    absolute_timeout_seconds: 24 * 60 * 60,
})?

created = Session.create!({ store, now, payload })?
loaded = Session.load!({ store, now, id })?
rotated = Session.rotate!({ store, now, id })?
Session.invalidate!({ store, id })?
Session.prune!({ store, now, max_rows: 500 })?

Dependencies

Why this belongs in the platform

Server-side sessions are a conventional web framework facility and fit the platform's explicit preference for durable domain state in SQLite rather than arbitrary mutable process-local state.

The existing Sqlite.Db already provides bounded pooling, timeouts, typed errors, and transactions. The session facility can therefore be a Roc module over SQLite rather than a new host subsystem.

Required contract

  • Depend on Random.bytes! and the typed cookie module.
  • Generate identifiers from at least 32 OS-random bytes.
  • Encode them as a fixed, validated cookie-safe representation.
  • Make the identifier opaque and redact it from normal inspection.
  • Store only a one-way digest of the bearer identifier in SQLite.
  • Store application data as a bounded List(U8) payload; serialization is application policy.
  • Use a fixed, namespaced, versioned table schema.
  • Schema creation or migration occurs only through an explicit Session.init! call.
  • Enforce idle and absolute expiry server-side.
  • Atomically rotate the identifier and invalidate the old identifier to prevent session fixation.
  • Support explicit invalidation/logout.
  • Provide bounded pruning rather than a detached cleanup worker.
  • Use SQLite transactions for each lifecycle operation.
  • Document that session operations are not automatically atomic with arbitrary application-table updates in the first version.
  • Return typed missing, expired, malformed, saturated, timeout, and SQLite failures.
  • Never use an in-memory session cache as the source of truth.

The cookie helper should be used to emit an explicitly configured Secure, HttpOnly, SameSite cookie. The session module should not guess whether the public deployment uses HTTPS.

Tests

  • Create, load, update, rotate, invalidate, and expire.
  • Idle versus absolute expiry.
  • Concurrent loads, rotations, and invalidations.
  • Old identifier unusable immediately after rotation.
  • Payload exact-limit and over-limit behavior.
  • Bounded prune batches.
  • SQLite busy, timeout, and rollback paths.
  • Restart and multi-process behavior against the same database.
  • No identifier values in diagnostics.

Non-goals

  • Users, authentication, roles, or authorization
  • In-memory sessions
  • A generic key/value map with hidden serialization
  • Client-side encrypted session cookies
  • Background cleanup jobs
  • Automatic session creation on every request
  • Flash messages or login redirects

Acceptance criteria

  • Session state is durable SQLite state, not process-local mutable state.
  • Identifiers are cryptographically random, opaque, rotated, and explicitly invalidated.
  • Idle and absolute timeouts are enforced by the server-side store.
  • Payload, queries, and cleanup work are bounded.
  • Cookie policy remains explicit at the application boundary.
  • Concurrency and restart behavior are covered by real SQLite tests.

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions