1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
//! Session capability trait and types.
//!
//! A session store owns the lifecycle of session records. The trait is
//! deliberately storage-agnostic: it accommodates both server-backed stores
//! (e.g. Redis, Postgres) where the cookie carries only an opaque id, and
//! stateless client-side stores (e.g. signed/encrypted cookie) where the
//! "id" is itself the ciphertext and no server state is kept.
//!
//! Design notes:
//!
//! - `SessionId` is an opaque string. Server-backed stores put a uuid or
//! similar random token there. Cookie-only stores put the encrypted +
//! signed payload there. The host treats it as opaque and writes it into
//! the session cookie; backends know how to interpret it on read.
//!
//! - `create` and `update` both return a `SessionId`. For server-backed
//! stores the id is stable across updates; for cookie-only stores the id
//! changes on every mutation (because the ciphertext changes) and the
//! host must re-set the cookie. Callers should always write back the id
//! returned by `update`.
//!
//! - Session data is passed as a JSON string so the trait surface stays
//! WASM-ABI friendly, matching the style used in `lifecycle.rs`. The
//! schema inside the blob is defined by the caller (typically an Auth
//! plugin storing a user id + claims; an app storing cart state; etc.).
//!
//! - `user_id` is optional so anonymous sessions (pre-login carts, guest
//! checkout, CSRF tokens) are first-class. Auth plugins that require a
//! logged-in user enforce that themselves.
//!
//! - This trait deliberately does not import any Auth types. Session is
//! the substrate Auth sits on, not the other way round. An Auth plugin
//! uses a `SessionPlugin` to persist proof-of-identity; the session store
//! does not know or care what that proof looks like.
use HashMap;
/// Opaque session identifier written into the session cookie.
///
/// Server-backed stores use this as a lookup key (e.g. a uuid). Cookie-only
/// stores encode the entire (encrypted, signed) session payload here. The
/// host treats it as an opaque string.
;
/// A session record as observed by callers.
///
/// `data_json` is a JSON-encoded object whose schema is defined by the
/// caller. `user_id` is `None` for anonymous sessions. `expires_at_ms` is
/// the absolute unix millisecond at which the session is considered
/// expired; backends may garbage-collect after that point.
/// Options for creating a new session.
/// Errors a session store can return. Kept as a flat enum so the shape is
/// stable across backends; backend-specific detail goes in the wrapped
/// message.
/// Storage backend for sessions.
///
/// Implementations fall into two families:
///
/// 1. **Server-backed** (`@bext/session-redis`, `@bext/session-pg`). The
/// `SessionId` is a short random token used as a lookup key into an
/// external store. `update` returns the same id it received; `delete`
/// removes the record; `touch` bumps the TTL cheaply.
///
/// 2. **Client-backed** (`@bext/session-cookie`). The `SessionId` is the
/// encrypted, signed session payload itself — there is no server state.
/// `update` returns a *new* id (the freshly-encrypted payload), `delete`
/// is a no-op from the store's perspective (the host clears the cookie),
/// and `touch` can re-sign the payload with a refreshed expiry.
///
/// Callers MUST treat the id returned by `create` / `update` / `touch` as
/// authoritative and write it back into the session cookie. Assuming the id
/// is stable breaks cookie-only stores.
///
/// All methods take `&self` — implementations are expected to hold any
/// mutable state behind their own synchronisation (e.g. a connection pool).
/// Convenience helper for tests and simple callers. Not part of the trait
/// so it does not force a particular JSON shape on backends.
/// Convenience helper for constructing a `HashMap`-backed data blob when
/// the caller does not want to pull in serde_json directly. Returns a JSON
/// object string.