http-nu 0.17.2

The surprisingly performant, Nushell-scriptable, cross.stream-powered, Datastar-ready HTTP server that fits in your back pocket.
Documentation
# Session auth, on top of cross.stream frames.
#
# Cookie (secret) -> session frame -> user_id (public)
#   session=<token>  ->  session.<token> {meta: {user_id}}  ->  user_id
#
# - <token> is a CSPRNG-backed `random uuid` (cookie secret; never
#   stamped on any other frame).
# - <session_id> is the *frame id* of the session frame -- a SCRU128,
#   auditable, public-safe. Stamped on write frames (moves) so per-
#   session activity can be correlated without revealing the cookie.
# - <user_id> is the stable, public identity. Lives at /by/<user_id>.
#
# Old `player=<uuid>` cookies are honoured ONCE: the first request from
# such a visitor mints a fresh session bound to that user_id, then the
# legacy cookie is cleared. After that, only `session` is read.

use http-nu/http *

# Resolve an incoming request to a session record, claiming a legacy
# player cookie if that is all we have.
#
# Returns either null (anonymous) or:
#   {user_id, session_id, token, fresh: bool, claimed: bool}
#
# - fresh = true  when this call just minted the session frame
# - claimed = true when we minted FROM a legacy `player` cookie (callers
#   should pipe responses through `session-cookies set` to install the
#   new session cookie and delete the legacy one)
export def resolve-session [req: record]: nothing -> any {
  let cookies = $req | cookie parse
  let token = $cookies | get session? | default ""
  if ($token | is-not-empty) {
    let frame = try { .last $"session.($token)" } catch { null }
    if $frame != null {
      let user_id = $frame.meta | get user_id? | default ""
      if ($user_id | is-not-empty) {
        return {
          user_id: $user_id
          session_id: $frame.id
          token: $token
          fresh: false
          claimed: false
        }
      }
    }
  }
  let legacy = $cookies | get player? | default ""
  if ($legacy | is-not-empty) {
    let minted = mint-session $legacy
    return ($minted | upsert claimed true)
  }
  null
}

# Mint a fresh session bound to a user_id. Appends a `session.<token>`
# frame (ttl: last:1 so only the active binding is kept). Returns
# {token, session_id, user_id, fresh: true, claimed: false}.
export def mint-session [user_id: string]: nothing -> record {
  let token = random uuid
  let frame = (null | .append $"session.($token)" --meta {user_id: $user_id} --ttl last:1)
  {
    token: $token
    session_id: $frame.id
    user_id: $user_id
    fresh: true
    claimed: false
  }
}

# Pipeline helper. Sets the `session` cookie (1yr sliding window) and
# clears any legacy `player` cookie on the response. Implicit pipeline
# input (no `$in`) so http.response metadata threads through cleanly --
# `$in` collects the value and seems to strip the merged-metadata
# instruction set upstream.
export def "session-cookies set" [session: record]: any -> any {
  cookie set "session" $session.token --max-age 31536000
  | cookie delete "player"
}