@nifrajs/auth 5.1.0 → 5.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +387 -0
- package/dist/session.d.ts +6 -2
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +66 -11
- package/dist/session.js.map +1 -1
- package/dist/store.d.ts +11 -3
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +17 -8
- package/dist/store.js.map +1 -1
- package/package.json +4 -3
- package/src/session.ts +68 -11
- package/src/store.ts +25 -10
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
# @nifrajs/auth
|
|
2
|
+
|
|
3
|
+
## 5.3.0
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Refuse renewal or rotation of a loaded session after its original expiry, including when a KV revocation marker has expired. Rejected commits keep their previous expiry so retrying cannot restore a logged-out session.
|
|
8
|
+
|
|
9
|
+
- a32b33e: A store-mode session ended by `destroy` or rotated by `regenerate` stays ended. A request that loaded the session before the logout and committed after it (a rolling renewal, a slow handler) used to write the record back, so a copy of the cookie taken before logout worked again. Logout and rotation now also write a revocation marker under `<id>.revoked`, every load checks it, and a commit of a revoked session removes its write and sets no cookie, so it cannot replace a session the browser obtained since. A custom `SessionStore` must accept that key like any other; loading a store-mode session reads two keys in parallel.
|
|
10
|
+
- 88f91aa: `MemorySessionStore` keeps revocation markers apart from sessions, each bounded by `max`, so sessions written after a logout can no longer evict its marker. Once `max` newer sessions had been written, a request that loaded the session before the logout and committed after it brought the logged-out session back and re-issued its cookie. Markers also no longer take capacity from live sessions. The `SessionStore` contract now says a custom store must keep a marker until its `expiresAt`.
|
|
11
|
+
- 55b9a41: `destroy(c, session)` empties and detaches the session object it is passed. Committing that object later in the same request now starts a new session holding only what was set after the call. In store mode, that commit used to vanish against the revocation marker, so a flash message set after logout was lost without an error. In cookie mode, it signed the logged-out data, user id included, into a fresh cookie. The 5.3.0 upgrade notes say so.
|
|
12
|
+
|
|
13
|
+
## 5.2.1
|
|
14
|
+
|
|
15
|
+
No changes in this release.
|
|
16
|
+
|
|
17
|
+
## 5.2.0
|
|
18
|
+
|
|
19
|
+
### Patch Changes
|
|
20
|
+
|
|
21
|
+
- 9498cf8: The package ships its `CHANGELOG.md`, so the installed package has the notes for every version, as the GitHub release does. Before, the tarball held only the code, `README.md`, `LICENSE` and `package.json`.
|
|
22
|
+
|
|
23
|
+
## 5.1.0
|
|
24
|
+
|
|
25
|
+
### Patch Changes
|
|
26
|
+
|
|
27
|
+
- efd2102: `createSessions` no longer accepts a session cookie signed in the pre-5.0 form (without the cookie name in the signature) unless you pass the new `acceptLegacyCookies: true`. Before, that fallback was always on, so any value the app signed with the session secret through `@nifrajs/core`'s `signValue` that parsed as a cookie-mode session (a signed preference cookie built from a form field, say) read as a session of the attacker's choosing, with an expiry of its own. A cookie-mode session's expiry is now also capped at `now + maxAge` on load, so no cookie can name a longer life. Migration: if you are upgrading from 4.x, or from 5.0 with sessions that have not been committed since, set `acceptLegacyCookies: true` to keep those users signed in, and remove it once one `maxAge` (7 days by default) has passed; each such session is re-signed in the current form on its next commit. Without it, those users are signed out once.
|
|
28
|
+
|
|
29
|
+
## 5.0.0
|
|
30
|
+
|
|
31
|
+
### Minor Changes
|
|
32
|
+
|
|
33
|
+
- 7a169c9: The session guards take `unauthenticated: { status, body, headers }` to set the response they deny a missing session with, in place of the default `401` JSON: `requireSession` and `requireUser` in `@nifrajs/auth`, `requireSession`, `requirePrincipal` and `authed` in `@nifrajs/better-auth`, and `requireAuthUser` in `@nifrajs/authjs`. The status must be 400-599, and a guard given both `redirectTo` and `unauthenticated` throws. `UnauthenticatedResponse` is exported from each package.
|
|
34
|
+
|
|
35
|
+
### Patch Changes
|
|
36
|
+
|
|
37
|
+
- 2e331b1: Session cookies are now signed together with their cookie name, so when two session managers share a secret, a cookie issued by one no longer verifies as the other's. Cookies signed by earlier versions still load and are re-signed on their next commit, so upgrading signs no one out.
|
|
38
|
+
- 8606fa4: `csrf()` (middleware and auth) accepts a same-origin HTML form POST sent under `Referrer-Policy: no-referrer`, which `securityHeaders()` sets by default. Such a form arrives with `Origin: null`, and every one was refused with 403. A request with `Origin: null` now passes only when the browser's own `Sec-Fetch-Site` header says `same-origin`, and only in the default same-origin mode; an explicit `origins` allowlist still refuses it.
|
|
39
|
+
- Updated dependencies [a0a02f4]
|
|
40
|
+
- Updated dependencies [0aed425]
|
|
41
|
+
- Updated dependencies [eaf030b]
|
|
42
|
+
- Updated dependencies [7de0397]
|
|
43
|
+
- Updated dependencies [4199ff3]
|
|
44
|
+
- Updated dependencies [90d9ee6]
|
|
45
|
+
- Updated dependencies [364a234]
|
|
46
|
+
- Updated dependencies [c06cba5]
|
|
47
|
+
- Updated dependencies [f4d3852]
|
|
48
|
+
- Updated dependencies [4a82ae0]
|
|
49
|
+
- Updated dependencies [0ff867a]
|
|
50
|
+
- Updated dependencies [e7391bf]
|
|
51
|
+
- Updated dependencies [0b80a59]
|
|
52
|
+
- Updated dependencies [c64db12]
|
|
53
|
+
- Updated dependencies [3753104]
|
|
54
|
+
- Updated dependencies [4b3b1fa]
|
|
55
|
+
- Updated dependencies [3562557]
|
|
56
|
+
- Updated dependencies [53087f3]
|
|
57
|
+
- Updated dependencies [2def6c7]
|
|
58
|
+
- Updated dependencies [b5b5bc3]
|
|
59
|
+
- Updated dependencies [22e1777]
|
|
60
|
+
- Updated dependencies [3a483cd]
|
|
61
|
+
- Updated dependencies [35f52c7]
|
|
62
|
+
- Updated dependencies [343cb81]
|
|
63
|
+
- Updated dependencies [d91fd44]
|
|
64
|
+
- Updated dependencies [b13e071]
|
|
65
|
+
- Updated dependencies [077657f]
|
|
66
|
+
- Updated dependencies [a5963cb]
|
|
67
|
+
- Updated dependencies [05aac0e]
|
|
68
|
+
- Updated dependencies [1a6616f]
|
|
69
|
+
- Updated dependencies [7775c2b]
|
|
70
|
+
- Updated dependencies [846e2f6]
|
|
71
|
+
- Updated dependencies [aadd9fc]
|
|
72
|
+
- Updated dependencies [ea16748]
|
|
73
|
+
- Updated dependencies [fc0284a]
|
|
74
|
+
- Updated dependencies [5b22f4d]
|
|
75
|
+
- Updated dependencies [1b94593]
|
|
76
|
+
- Updated dependencies [9f58295]
|
|
77
|
+
- Updated dependencies [b9608ac]
|
|
78
|
+
- Updated dependencies [7a165de]
|
|
79
|
+
- Updated dependencies [4064f36]
|
|
80
|
+
- Updated dependencies [48aad8c]
|
|
81
|
+
- Updated dependencies [41050ed]
|
|
82
|
+
- Updated dependencies [fc837cb]
|
|
83
|
+
- Updated dependencies [b80ce77]
|
|
84
|
+
- Updated dependencies [9b1acc9]
|
|
85
|
+
- Updated dependencies [740109f]
|
|
86
|
+
- Updated dependencies [e0a25fe]
|
|
87
|
+
- Updated dependencies [3630b6e]
|
|
88
|
+
- Updated dependencies [1290c6d]
|
|
89
|
+
- Updated dependencies [d0a7d58]
|
|
90
|
+
- @nifrajs/core@5.0.0
|
|
91
|
+
|
|
92
|
+
## 4.0.2
|
|
93
|
+
|
|
94
|
+
## 4.0.1
|
|
95
|
+
|
|
96
|
+
## 4.0.0
|
|
97
|
+
|
|
98
|
+
### Patch Changes
|
|
99
|
+
|
|
100
|
+
- 6daaa05: Each guard you build is its own plugin: `bearer()`, `basicAuth()`, `jwt()`, `ipRestriction()`, `rateLimit()`, `csrf()` and `bodyLimit()`. A second, stricter one before later routes or inside a `group()` applies alongside the first. The same instance passed to `use()` twice still applies once.
|
|
101
|
+
- 8e52704: fix(middleware, auth): CSRF behind TLS proxies, CORS caching, and token parsing
|
|
102
|
+
|
|
103
|
+
The CSRF same-origin default in `csrf()` from both packages accepts an `https:` Origin on an `http:`
|
|
104
|
+
request URL, which is what a TLS-terminating proxy presents. A downgrade or another host is still
|
|
105
|
+
rejected.
|
|
106
|
+
|
|
107
|
+
`cors()` with an allowlist or predicate sends `Vary: Origin` on every response, including ones that
|
|
108
|
+
carry no `Access-Control-Allow-Origin`. An existing `Origin` member is not repeated, and `Vary: *` is
|
|
109
|
+
kept as it is.
|
|
110
|
+
|
|
111
|
+
`jwt()` and `verifyCsrfToken()` accept only canonical base64url signatures. `jwt()` and `bearer()`
|
|
112
|
+
match the `Bearer` auth-scheme case-insensitively.
|
|
113
|
+
|
|
114
|
+
- Updated dependencies [dde125b]
|
|
115
|
+
- Updated dependencies [72b62fa]
|
|
116
|
+
- Updated dependencies [aa44e93]
|
|
117
|
+
- Updated dependencies [4a3ee60]
|
|
118
|
+
- Updated dependencies [aad6297]
|
|
119
|
+
- Updated dependencies [dad0d41]
|
|
120
|
+
- Updated dependencies [538adc2]
|
|
121
|
+
- Updated dependencies [f47edd1]
|
|
122
|
+
- Updated dependencies [df9530a]
|
|
123
|
+
- Updated dependencies [3e6973f]
|
|
124
|
+
- Updated dependencies [25e8edf]
|
|
125
|
+
- Updated dependencies [2b5e5fc]
|
|
126
|
+
- Updated dependencies [3b090de]
|
|
127
|
+
- Updated dependencies [da7d792]
|
|
128
|
+
- Updated dependencies [612a296]
|
|
129
|
+
- Updated dependencies [fb14dfa]
|
|
130
|
+
- Updated dependencies [8ae97f6]
|
|
131
|
+
- Updated dependencies [4af6f39]
|
|
132
|
+
- Updated dependencies [ca8b50d]
|
|
133
|
+
- Updated dependencies [b00a889]
|
|
134
|
+
- Updated dependencies [b53d64f]
|
|
135
|
+
- Updated dependencies [66fd712]
|
|
136
|
+
- Updated dependencies [9c3d524]
|
|
137
|
+
- Updated dependencies [738e7a1]
|
|
138
|
+
- Updated dependencies [4801cac]
|
|
139
|
+
- Updated dependencies [1b2d53a]
|
|
140
|
+
- Updated dependencies [25fe13d]
|
|
141
|
+
- Updated dependencies [0852290]
|
|
142
|
+
- Updated dependencies [0589dbe]
|
|
143
|
+
- Updated dependencies [2e2d8c0]
|
|
144
|
+
- Updated dependencies [856f5ce]
|
|
145
|
+
- Updated dependencies [18aa5aa]
|
|
146
|
+
- Updated dependencies [cfd86b3]
|
|
147
|
+
- Updated dependencies [8ff96c9]
|
|
148
|
+
- Updated dependencies [4c46199]
|
|
149
|
+
- Updated dependencies [eef4932]
|
|
150
|
+
- Updated dependencies [6de8686]
|
|
151
|
+
- Updated dependencies [d7892ea]
|
|
152
|
+
- Updated dependencies [4936309]
|
|
153
|
+
- Updated dependencies [ff5a779]
|
|
154
|
+
- Updated dependencies [bbdc5a1]
|
|
155
|
+
- Updated dependencies [10bc446]
|
|
156
|
+
- Updated dependencies [e8270d9]
|
|
157
|
+
- Updated dependencies [ff4a062]
|
|
158
|
+
- Updated dependencies [43ba944]
|
|
159
|
+
- Updated dependencies [46c741a]
|
|
160
|
+
- Updated dependencies [7bfa25e]
|
|
161
|
+
- Updated dependencies [4936309]
|
|
162
|
+
- Updated dependencies [6e257a6]
|
|
163
|
+
- Updated dependencies [4a03d30]
|
|
164
|
+
- Updated dependencies [8e30090]
|
|
165
|
+
- Updated dependencies [28f3aaf]
|
|
166
|
+
- Updated dependencies [6d20355]
|
|
167
|
+
- Updated dependencies [6907cbe]
|
|
168
|
+
- Updated dependencies [b64c3ee]
|
|
169
|
+
- Updated dependencies [bda9637]
|
|
170
|
+
- Updated dependencies [81c720e]
|
|
171
|
+
- Updated dependencies [ed60b23]
|
|
172
|
+
- Updated dependencies [a158b74]
|
|
173
|
+
- Updated dependencies [ff25d68]
|
|
174
|
+
- @nifrajs/core@4.0.0
|
|
175
|
+
|
|
176
|
+
## 3.5.0
|
|
177
|
+
|
|
178
|
+
### Patch Changes
|
|
179
|
+
|
|
180
|
+
- d5b7c22: Harden request boundaries, error handling, resource limits, signing, and cross-runtime adapters for safer production releases.
|
|
181
|
+
|
|
182
|
+
## 3.4.0
|
|
183
|
+
|
|
184
|
+
## 3.3.0
|
|
185
|
+
|
|
186
|
+
## 3.2.0
|
|
187
|
+
|
|
188
|
+
### Patch Changes
|
|
189
|
+
|
|
190
|
+
- 7551709: Harden runtime boundaries and defaults: clean up subprocess abort listeners, support short Cloudflare
|
|
191
|
+
KV sessions, bound and incrementally sweep the default memory cache, make image reads and cancellation
|
|
192
|
+
safe, emit content-derived image validators, require trusted forwarded hosts, avoid caching dynamic SSR
|
|
193
|
+
metadata, and reject invalid upload or image limits.
|
|
194
|
+
|
|
195
|
+
## 3.1.0
|
|
196
|
+
|
|
197
|
+
## 3.0.0
|
|
198
|
+
|
|
199
|
+
### Minor Changes
|
|
200
|
+
|
|
201
|
+
- 99fc683: A thrown `status(...)` now costs close to what a returned one costs, and the guards throw one.
|
|
202
|
+
|
|
203
|
+
`status(...)` is meant to be **returned** - from a `beforeHandle`, from a `derive`, from the handler. The one place a return cannot work is a helper called for effect: `requireSession(c)` decides the request is over from inside a call the handler makes, and only unwinding gets out of a half-finished handler. That is the whole remaining use of `throw`, and it now runs on the same lane as the return:
|
|
204
|
+
|
|
205
|
+
- The lifecycle error path is synchronous again. It was `async`, so a thrown `status(...)` - which needs no `await` at all - still allocated a promise and resumed a microtask later. The `onError` hook loop moved to its own async method, so only routes that registered a hook pay for one.
|
|
206
|
+
- The two remaining sites that turned a thrown `status(...)` into a `Response` before rendering it (the bare and contextless fast paths) now render it as the plain data it is, through the request's own finalizer.
|
|
207
|
+
- `requireSession` / `requireUser` / `requireAuthorization` throw a `status(...)` render instead of building a `Response`. The bytes on the wire are unchanged - a 401 JSON envelope, or a 302 with a `location` - and on Node they now carry a `content-length` instead of being drained back out as a stream.
|
|
208
|
+
|
|
209
|
+
**Behavior change:** what the guards throw is a `status(...)` render, not a `Response`. Code that catches a guard and tests `err instanceof Response` needs to stop doing that; nifra itself treats the two identically as control flow, so a guard thrown through a route behaves exactly as before.
|
|
210
|
+
|
|
211
|
+
Measured on the Linux rig (4 server cores, 50 connections, medians of 5 x 2s; a `derive` that rejects, returning vs throwing the same `status(401, ...)`). Only within-runtime deltas are readable - the host was under other load, and the return arm is included in both columns as the control:
|
|
212
|
+
|
|
213
|
+
| runtime | return | throw, before | throw, after |
|
|
214
|
+
| ------- | ------ | ------------- | ------------ |
|
|
215
|
+
| node | 85904 | 74471 | 80136 |
|
|
216
|
+
| deno | 139477 | 85970 | 90355 |
|
|
217
|
+
|
|
218
|
+
On Node a throw now lands within the run's own spread of a return. On Deno it does not: a throw still costs ~35%, and it costs the same whether the thrown value is a `status(...)` render or a `Response` - so what remains there is the unwind itself, not the rendering.
|
|
219
|
+
|
|
220
|
+
That remainder is the runtime's, not the framework's. The same arms with no framework at all - a bare `Deno.serve` / `Bun.serve` / `node:http` handler answering identical bytes, one returning its payload and one throwing it - reproduce it, in CPU-microseconds per request:
|
|
221
|
+
|
|
222
|
+
| | return | throw | throw across await |
|
|
223
|
+
| ---- | ------ | ----- | ------------------ |
|
|
224
|
+
| node | 49.35 | +3.85 | +2.85 |
|
|
225
|
+
| bun | 22.55 | +1.83 | +3.99 |
|
|
226
|
+
| deno | 18.89 | +7.62 | +11.03 |
|
|
227
|
+
|
|
228
|
+
Against that, nifra's own rejection arms cost +3.35 (node), +3.41 (bun), +15.59 (deno). On node and bun the framework's entire remaining throw penalty _is_ the runtime's throw. On deno the runtime's throw-across-await alone accounts for ~70% of it, and the percentage reads worse than the others only because deno's baseline request is the cheapest of the three. Two candidate explanations were tested and ruled out: `Error.stackTraceLimit = 0` changes nothing (so it is not stack capture), and throwing three frames below the handler is no cheaper (so it is not proximity to the runtime boundary).
|
|
229
|
+
|
|
230
|
+
### Patch Changes
|
|
231
|
+
|
|
232
|
+
- f0fd370: The same-origin check behind `redirect()` and the guards' `redirectTo` now rejects the paths a URL parser resolves onto another origin, and lives in one place.
|
|
233
|
+
|
|
234
|
+
A leading `/` that is not `//` is not sufficient to keep a destination on this origin. Under a special scheme a backslash parses as a path separator, and tab, CR and LF are stripped from the input before parsing, so `/\evil.example` and `/<TAB>/evil.example` both pass a `//` test and then resolve to the host `evil.example` - an open redirect reachable from any unvalidated `?next=` parameter. Both forms are now refused: `redirect()` throws as it already did for `//host`, and an auth guard falls back to its configured destination instead of honouring the value.
|
|
235
|
+
|
|
236
|
+
New export `isSameOriginPath` from `@nifrajs/core/server`, which is the single implementation the three gates now share - a security predicate kept in three copies is three chances for one of them to be hardened alone. It answers about a path, so an absolute URL is false even when it names the current origin: the point of the gate is that the value never got to name a host at all.
|
|
237
|
+
|
|
238
|
+
A percent-encoded backslash (`/%5Cevil.example`) is still a same-origin path, because that is what it resolves to.
|
|
239
|
+
|
|
240
|
+
- Updated dependencies [f3d2a35]
|
|
241
|
+
- Updated dependencies [6e43c15]
|
|
242
|
+
- Updated dependencies [f0fd370]
|
|
243
|
+
- Updated dependencies [86a555b]
|
|
244
|
+
- Updated dependencies [8c5f4cf]
|
|
245
|
+
- Updated dependencies [f0fd370]
|
|
246
|
+
- Updated dependencies [381bbf3]
|
|
247
|
+
- Updated dependencies [36801ae]
|
|
248
|
+
- Updated dependencies [9acadba]
|
|
249
|
+
- Updated dependencies [99fc683]
|
|
250
|
+
- Updated dependencies [73d894d]
|
|
251
|
+
- @nifrajs/core@3.0.0
|
|
252
|
+
|
|
253
|
+
## 2.14.1
|
|
254
|
+
|
|
255
|
+
## 2.14.0
|
|
256
|
+
|
|
257
|
+
## 2.13.0
|
|
258
|
+
|
|
259
|
+
## 2.12.1
|
|
260
|
+
|
|
261
|
+
## 2.12.0
|
|
262
|
+
|
|
263
|
+
### Minor Changes
|
|
264
|
+
|
|
265
|
+
- dbc0b79: Signing-secret rotation. `signValue`/`unsignValue` (and the new `CookieSecret` type), session `secret`, and CSRF `secret` now also accept a rotation list: the first secret signs, any listed secret verifies, so keys rotate without invalidating live cookies, sessions, or CSRF tokens. Every listed secret must meet the 32-byte floor and an empty list throws; the single-secret path is unchanged.
|
|
266
|
+
|
|
267
|
+
### Patch Changes
|
|
268
|
+
|
|
269
|
+
- 023891a: `destroy(c)` called without a `Session` object now revokes the stored record addressed by the request's
|
|
270
|
+
signed session cookie, instead of only clearing the cookie. A logout handler that had not first loaded
|
|
271
|
+
the session cleared the browser's copy while the server-side record stayed valid for its full TTL, so a
|
|
272
|
+
copy of the cookie taken before logout still authenticated. The id comes from the signed cookie, so
|
|
273
|
+
only a session the caller actually presented can be deleted.
|
|
274
|
+
|
|
275
|
+
## 2.11.0
|
|
276
|
+
|
|
277
|
+
## 2.10.0
|
|
278
|
+
|
|
279
|
+
## 2.9.1
|
|
280
|
+
|
|
281
|
+
## 2.9.0
|
|
282
|
+
|
|
283
|
+
## 2.8.2
|
|
284
|
+
|
|
285
|
+
### Patch Changes
|
|
286
|
+
|
|
287
|
+
- f7d68e8: Numeric limit options (body/payload byte caps, TTLs, cache sizes, concurrency, ISR revalidate windows) are now validated at construction and throw a `RangeError` on non-finite or out-of-range values instead of silently disabling the bound - a `NaN` cap previously made `size > max` comparisons fail open. JWT `requiredClaims` now checks own properties only, so inherited names like `toString` no longer satisfy a required claim. `@nifrajs/mcp-db` gates multi-statement input with a real tokenizer, bounds `run_query` materialization to `maxRows + 1` via a wrapping subquery, and skips SQLite planner pseudo-nodes when verifying the table allowlist. `nifra scaffold` refuses to write through symlinked route directories.
|
|
288
|
+
|
|
289
|
+
## 2.8.1
|
|
290
|
+
|
|
291
|
+
## 2.8.0
|
|
292
|
+
|
|
293
|
+
## 2.7.1
|
|
294
|
+
|
|
295
|
+
## 2.7.0
|
|
296
|
+
|
|
297
|
+
## 2.6.1
|
|
298
|
+
|
|
299
|
+
## 2.6.0
|
|
300
|
+
|
|
301
|
+
## 2.5.0
|
|
302
|
+
|
|
303
|
+
## 2.4.0
|
|
304
|
+
|
|
305
|
+
## 2.3.0
|
|
306
|
+
|
|
307
|
+
## 2.2.0
|
|
308
|
+
|
|
309
|
+
## 2.1.0
|
|
310
|
+
|
|
311
|
+
## 2.0.0
|
|
312
|
+
|
|
313
|
+
### Patch Changes
|
|
314
|
+
|
|
315
|
+
- ade0c7a: Add a curated `@nifrajs/core/server` entry for the common HTTP runtime and dedicated subpaths for
|
|
316
|
+
contracts, classification, cookies, logging, routing, Standard Schema, SEO, SSE, and webhooks. The
|
|
317
|
+
package root remains backwards compatible, while new scaffolds and first-party runtime packages avoid
|
|
318
|
+
eagerly parsing opt-in causality, invariant, manifest, reflection, capability, and assurance tooling.
|
|
319
|
+
- Updated dependencies [a7b1d60]
|
|
320
|
+
- Updated dependencies [eaac3d7]
|
|
321
|
+
- Updated dependencies [ade0c7a]
|
|
322
|
+
- Updated dependencies [82676e0]
|
|
323
|
+
- Updated dependencies [1522d06]
|
|
324
|
+
- Updated dependencies [a7b1d60]
|
|
325
|
+
- Updated dependencies [a7b1d60]
|
|
326
|
+
- @nifrajs/core@2.0.0
|
|
327
|
+
|
|
328
|
+
## 1.13.0
|
|
329
|
+
|
|
330
|
+
## 1.12.0
|
|
331
|
+
|
|
332
|
+
## 1.11.0
|
|
333
|
+
|
|
334
|
+
## 1.10.0
|
|
335
|
+
|
|
336
|
+
## 1.9.1
|
|
337
|
+
|
|
338
|
+
## 1.9.0
|
|
339
|
+
|
|
340
|
+
## 1.8.0
|
|
341
|
+
|
|
342
|
+
## 1.7.0
|
|
343
|
+
|
|
344
|
+
## 1.6.0
|
|
345
|
+
|
|
346
|
+
## 1.5.0
|
|
347
|
+
|
|
348
|
+
## 1.4.0
|
|
349
|
+
|
|
350
|
+
## 1.3.1
|
|
351
|
+
|
|
352
|
+
## 1.3.0
|
|
353
|
+
|
|
354
|
+
## 1.2.2
|
|
355
|
+
|
|
356
|
+
## 1.2.1
|
|
357
|
+
|
|
358
|
+
## 1.2.0
|
|
359
|
+
|
|
360
|
+
## 1.1.0
|
|
361
|
+
|
|
362
|
+
## 1.0.0
|
|
363
|
+
|
|
364
|
+
### Patch Changes
|
|
365
|
+
|
|
366
|
+
- Updated dependencies [f1f0e18]
|
|
367
|
+
- Updated dependencies [3efb7cd]
|
|
368
|
+
- Updated dependencies [de9675b]
|
|
369
|
+
- @nifrajs/core@1.0.0
|
|
370
|
+
|
|
371
|
+
## 1.0.0-beta.4
|
|
372
|
+
|
|
373
|
+
### Patch Changes
|
|
374
|
+
|
|
375
|
+
- @nifrajs/core@1.0.0-beta.4
|
|
376
|
+
|
|
377
|
+
## 1.0.0-beta.3
|
|
378
|
+
|
|
379
|
+
### Patch Changes
|
|
380
|
+
|
|
381
|
+
- @nifrajs/core@1.0.0-beta.3
|
|
382
|
+
|
|
383
|
+
## 0.1.0-beta.2
|
|
384
|
+
|
|
385
|
+
### Patch Changes
|
|
386
|
+
|
|
387
|
+
- @nifrajs/core@0.1.0-beta.2
|
package/dist/session.d.ts
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* anonymous session, never an error.
|
|
11
11
|
*/
|
|
12
12
|
import { type CookieOptions } from "@nifrajs/core/cookies";
|
|
13
|
-
import type
|
|
13
|
+
import { type SessionStore } from "./store.js";
|
|
14
14
|
/** The cookie + response surface the manager needs - a structural subset of nifra's `Context`, so any
|
|
15
15
|
* `c` satisfies it and it's testable with a stub. */
|
|
16
16
|
export interface SessionContext {
|
|
@@ -76,7 +76,11 @@ export interface SessionManager<Data extends Record<string, unknown> = Record<st
|
|
|
76
76
|
read(request: Request): Promise<Session<Data>>;
|
|
77
77
|
/** Persist the session: write the store (store mode) + the signed cookie. */
|
|
78
78
|
commit(c: SessionContext, session: Session<Data>): Promise<void>;
|
|
79
|
-
/** End the session: drop the store record (store mode) + clear the cookie.
|
|
79
|
+
/** End the session: drop the store record (store mode) + clear the cookie. In store mode the id is
|
|
80
|
+
* also marked revoked, so a request that loaded the session earlier cannot write it back. The
|
|
81
|
+
* `session` passed in is emptied and detached: committing it later in the same request starts a
|
|
82
|
+
* new session holding only what was set since. A session object not passed in stays bound to the
|
|
83
|
+
* ended id, and committing it writes nothing. */
|
|
80
84
|
destroy(c: SessionContext, session?: Session<Data>): Promise<void>;
|
|
81
85
|
/** Rotate the session id on the next commit (call on privilege change - login - to defend against
|
|
82
86
|
* session fixation). Data is preserved; the old store record is dropped. */
|
package/dist/session.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EACL,KAAK,aAAa,EAKnB,MAAM,uBAAuB,CAAA;AAC9B,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"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"}
|
package/dist/session.js
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* anonymous session, never an error.
|
|
11
11
|
*/
|
|
12
12
|
import { hasDuplicateCookie, parseCookies, signValue, unsignValue, } from "@nifrajs/core/cookies";
|
|
13
|
+
import { REVOKED_SUFFIX } from "./store.js";
|
|
13
14
|
const DAY_SECONDS = 86_400;
|
|
14
15
|
// 256-bit floor, matching @nifrajs/core's signed-cookie/webhook guard. Sessions sign via core's
|
|
15
16
|
// signValue (which enforces the same bar); this early check gives an auth-specific message before
|
|
@@ -24,6 +25,10 @@ const encodeBase64Url = (bytes) => {
|
|
|
24
25
|
};
|
|
25
26
|
/** A 256-bit random session id (base64url). High-entropy + unguessable - the cookie also signs it. */
|
|
26
27
|
const randomId = () => encodeBase64Url(crypto.getRandomValues(new Uint8Array(32)));
|
|
28
|
+
// A revoked id keeps a marker under its own key. A request that loaded the session before logout and
|
|
29
|
+
// commits after it rewrites the record, and a blind `set` cannot be made conditional on every store,
|
|
30
|
+
// but it cannot overwrite a key it never writes. `.` is outside the base64url id alphabet.
|
|
31
|
+
const revokedKey = (id) => `${id}${REVOKED_SUFFIX}`;
|
|
27
32
|
/** Validate a cookie-mode payload at the trust boundary - malformed JSON / shape → `null` (no session). */
|
|
28
33
|
const parsePayload = (json) => {
|
|
29
34
|
let value;
|
|
@@ -125,9 +130,19 @@ export function createSessions(options) {
|
|
|
125
130
|
if (unsigned === null)
|
|
126
131
|
return make({}); // tampered / wrong secret → fail closed (anonymous)
|
|
127
132
|
if (store !== undefined) {
|
|
128
|
-
|
|
133
|
+
// Only reachable with a value the app signed itself (legacy mode): never read a marker as a session.
|
|
134
|
+
if (unsigned.endsWith(REVOKED_SUFFIX))
|
|
135
|
+
return make({});
|
|
136
|
+
const [record, revoked] = await Promise.all([
|
|
137
|
+
store.get(unsigned),
|
|
138
|
+
store.get(revokedKey(unsigned)),
|
|
139
|
+
]);
|
|
129
140
|
if (record === undefined)
|
|
130
141
|
return make({});
|
|
142
|
+
if (revoked !== undefined) {
|
|
143
|
+
await store.delete(unsigned); // a commit that raced the logout wrote it back
|
|
144
|
+
return make({});
|
|
145
|
+
}
|
|
131
146
|
if (record.expiresAt <= now()) {
|
|
132
147
|
await store.delete(unsigned); // expired - evict + treat as no session
|
|
133
148
|
return make({});
|
|
@@ -155,33 +170,65 @@ export function createSessions(options) {
|
|
|
155
170
|
const maxAgeSeconds = Math.max(0, Math.floor((expiresAt - now()) / 1000));
|
|
156
171
|
c.set.cookie(cookieName, value, { ...cookieOpts, maxAge: maxAgeSeconds });
|
|
157
172
|
};
|
|
173
|
+
// Marker first: a racing commit's write either precedes the delete below or follows the marker.
|
|
174
|
+
const revoke = async (id) => {
|
|
175
|
+
if (store === undefined)
|
|
176
|
+
return;
|
|
177
|
+
await store.set(revokedKey(id), { data: {}, expiresAt: now() + maxAge * 1000 });
|
|
178
|
+
await store.delete(id);
|
|
179
|
+
};
|
|
158
180
|
const commit = async (c, session) => {
|
|
159
181
|
const state = stateOf(session, "commit");
|
|
182
|
+
const previousExpiry = state.expiresAt;
|
|
183
|
+
// Capture before store reads so a later logout's marker outlives this renewal.
|
|
184
|
+
const committedAt = now();
|
|
185
|
+
// The loaded expiry still fences a request after its revocation marker has been collected.
|
|
186
|
+
if (previousExpiry !== undefined && previousExpiry <= committedAt)
|
|
187
|
+
return;
|
|
160
188
|
// Rolling → slide the expiry; absolute → keep the original (set on the first commit).
|
|
161
|
-
const expiresAt = rolling ||
|
|
162
|
-
state.expiresAt = expiresAt;
|
|
189
|
+
const expiresAt = rolling || previousExpiry === undefined ? committedAt + maxAge * 1000 : previousExpiry;
|
|
163
190
|
const data = Object.fromEntries(state.data);
|
|
164
191
|
if (store !== undefined) {
|
|
192
|
+
if (state.id !== undefined) {
|
|
193
|
+
const revoked = await store.get(revokedKey(state.id));
|
|
194
|
+
// Check again after the read: a delayed store operation can outlive both session and marker.
|
|
195
|
+
if (revoked !== undefined || (previousExpiry !== undefined && previousExpiry <= now()))
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
165
198
|
if (state.regenerate) {
|
|
166
199
|
if (state.id !== undefined)
|
|
167
|
-
await
|
|
200
|
+
await revoke(state.id); // drop the pre-rotation session
|
|
201
|
+
if (previousExpiry !== undefined && previousExpiry <= now())
|
|
202
|
+
return;
|
|
168
203
|
state.id = undefined;
|
|
169
204
|
state.regenerate = false;
|
|
170
205
|
}
|
|
171
|
-
|
|
172
|
-
|
|
206
|
+
const loaded = state.id !== undefined;
|
|
207
|
+
state.id ??= randomId();
|
|
173
208
|
await store.set(state.id, { data, expiresAt });
|
|
174
|
-
|
|
209
|
+
// Checked after the write: a revocation that lands before it is seen here, one that lands after
|
|
210
|
+
// it deletes the record itself. Either way the cookie is left alone - the browser may hold a
|
|
211
|
+
// newer session by now, and this response must not replace or clear it.
|
|
212
|
+
if (loaded && (await store.get(revokedKey(state.id))) !== undefined) {
|
|
213
|
+
await store.delete(state.id);
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
const signed = await sign(state.id);
|
|
217
|
+
state.expiresAt = expiresAt;
|
|
218
|
+
writeCookie(c, signed, expiresAt);
|
|
175
219
|
return;
|
|
176
220
|
}
|
|
177
221
|
// Cookie mode: the (signed) payload IS the cookie. serializeCookie enforces the 4 KB cap.
|
|
178
|
-
|
|
222
|
+
const signed = await sign(JSON.stringify({ data, expiresAt }));
|
|
223
|
+
state.expiresAt = expiresAt;
|
|
224
|
+
writeCookie(c, signed, expiresAt);
|
|
179
225
|
};
|
|
180
226
|
const destroy = async (c, session) => {
|
|
227
|
+
const state = session === undefined ? undefined : states.get(session);
|
|
181
228
|
if (store !== undefined) {
|
|
182
229
|
let id;
|
|
183
230
|
if (session !== undefined) {
|
|
184
|
-
id =
|
|
231
|
+
id = state?.id;
|
|
185
232
|
}
|
|
186
233
|
else {
|
|
187
234
|
// Revoke the record addressed by the signed request cookie even when the caller did not
|
|
@@ -190,8 +237,16 @@ export function createSessions(options) {
|
|
|
190
237
|
if (raw !== undefined)
|
|
191
238
|
id = (await unsign(raw)) ?? undefined;
|
|
192
239
|
}
|
|
193
|
-
if (id !== undefined)
|
|
194
|
-
await
|
|
240
|
+
if (id !== undefined && !id.endsWith(REVOKED_SUFFIX))
|
|
241
|
+
await revoke(id);
|
|
242
|
+
}
|
|
243
|
+
// A later commit of this object must neither write the ended data back (cookie mode) nor vanish
|
|
244
|
+
// against the revocation marker (store mode).
|
|
245
|
+
if (state !== undefined) {
|
|
246
|
+
state.id = undefined;
|
|
247
|
+
state.expiresAt = undefined;
|
|
248
|
+
state.regenerate = false;
|
|
249
|
+
state.data.clear();
|
|
195
250
|
}
|
|
196
251
|
c.set.deleteCookie(cookieName, {
|
|
197
252
|
path: cookieOpts.path ?? "/",
|
package/dist/session.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session.js","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"session.js","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"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"}
|
package/dist/store.d.ts
CHANGED
|
@@ -13,7 +13,10 @@ export interface SessionRecord {
|
|
|
13
13
|
/**
|
|
14
14
|
* Pluggable session backend (store mode). Async so a network store (KV/Redis) fits the same shape.
|
|
15
15
|
* **Production needs a shared/durable store** so sessions hold across instances - {@link MemorySessionStore}
|
|
16
|
-
* prod-guards against the per-instance footgun.
|
|
16
|
+
* prod-guards against the per-instance footgun. Logout and rotation also write a revocation marker, an
|
|
17
|
+
* empty record under `<id>.revoked`, so a store must accept that key like any other, and keep it until
|
|
18
|
+
* its `expiresAt`: a marker dropped earlier lets a request that loaded the session before the logout
|
|
19
|
+
* write it back.
|
|
17
20
|
*/
|
|
18
21
|
export interface SessionStore {
|
|
19
22
|
/** The record for `id`, or `undefined` on a miss. */
|
|
@@ -23,22 +26,27 @@ export interface SessionStore {
|
|
|
23
26
|
/** Drop `id` (logout / regeneration). A no-op if absent. */
|
|
24
27
|
delete(id: string): Promise<void>;
|
|
25
28
|
}
|
|
29
|
+
/** The suffix of a revocation marker's key: `<id>.revoked`. */
|
|
30
|
+
export declare const REVOKED_SUFFIX = ".revoked";
|
|
26
31
|
export interface MemorySessionStoreOptions {
|
|
27
32
|
/** Allow the in-memory store in production. Off by default - per-instance sessions don't hold across
|
|
28
33
|
* instances and are lost on restart. */
|
|
29
34
|
readonly allowInProduction?: boolean;
|
|
30
|
-
/** Hard cap on stored sessions; the oldest-inserted is evicted past it (default 10_000).
|
|
35
|
+
/** Hard cap on stored sessions; the oldest-inserted is evicted past it (default 10_000). Revocation
|
|
36
|
+
* markers are capped by it separately, so new sessions never evict one. */
|
|
31
37
|
readonly max?: number;
|
|
32
38
|
}
|
|
33
39
|
/**
|
|
34
40
|
* In-process session store. Refuses to run in production unless explicitly allowed (mirrors the ISR
|
|
35
41
|
* `MemoryCacheStore` + the rate-limit `MemoryStore` - a per-instance store is unsafe across instances).
|
|
36
|
-
* Bounded: oldest-inserted entries evict past `max
|
|
42
|
+
* Bounded: oldest-inserted entries evict past `max`, sessions and revocation markers each on their own.
|
|
37
43
|
*/
|
|
38
44
|
export declare class MemorySessionStore implements SessionStore {
|
|
39
45
|
private readonly sessions;
|
|
46
|
+
private readonly markers;
|
|
40
47
|
private readonly max;
|
|
41
48
|
constructor(options?: MemorySessionStoreOptions);
|
|
49
|
+
private mapFor;
|
|
42
50
|
get(id: string): Promise<SessionRecord | undefined>;
|
|
43
51
|
set(id: string, record: SessionRecord): Promise<void>;
|
|
44
52
|
delete(id: string): Promise<void>;
|
package/dist/store.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,yEAAyE;AACzE,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACtC,uFAAuF;IACvF,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;CAC3B;AAED
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,yEAAyE;AACzE,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACtC,uFAAuF;IACvF,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;CAC3B;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,YAAY;IAC3B,qDAAqD;IACrD,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,SAAS,CAAC,CAAA;IACnD,gDAAgD;IAChD,GAAG,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACrD,4DAA4D;IAC5D,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAClC;AAED,+DAA+D;AAC/D,eAAO,MAAM,cAAc,aAAa,CAAA;AAExC,MAAM,WAAW,yBAAyB;IACxC;4CACwC;IACxC,QAAQ,CAAC,iBAAiB,CAAC,EAAE,OAAO,CAAA;IACpC;+EAC2E;IAC3E,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CACtB;AAED;;;;GAIG;AACH,qBAAa,kBAAmB,YAAW,YAAY;IACrD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAmC;IAG5D,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAmC;IAC3D,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAQ;gBAEhB,OAAO,GAAE,yBAA8B;IAenD,OAAO,CAAC,MAAM;IAId,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,SAAS,CAAC;IAInD,GAAG,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IAYrD,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAIlC;AAED;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAA;IACxC,8FAA8F;IAC9F,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC1F,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CACnC;AAgBD;;;;;;GAMG;AACH,qBAAa,cAAe,YAAW,YAAY;IACjD,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAiB;IACpC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAc;gBAEtB,EAAE,EAAE,eAAe,EAAE,OAAO,GAAE;QAAE,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;KAAO;IAKxE,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,SAAS,CAAC;IAYnD,GAAG,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IAWrD,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAGxC"}
|
package/dist/store.js
CHANGED
|
@@ -4,13 +4,18 @@
|
|
|
4
4
|
* (the durable, shared production path). The store is dumb persistence: the session *manager* owns the
|
|
5
5
|
* clock and authoritatively enforces expiry, so the store stays clock-free and trivially testable.
|
|
6
6
|
*/
|
|
7
|
+
/** The suffix of a revocation marker's key: `<id>.revoked`. */
|
|
8
|
+
export const REVOKED_SUFFIX = ".revoked";
|
|
7
9
|
/**
|
|
8
10
|
* In-process session store. Refuses to run in production unless explicitly allowed (mirrors the ISR
|
|
9
11
|
* `MemoryCacheStore` + the rate-limit `MemoryStore` - a per-instance store is unsafe across instances).
|
|
10
|
-
* Bounded: oldest-inserted entries evict past `max
|
|
12
|
+
* Bounded: oldest-inserted entries evict past `max`, sessions and revocation markers each on their own.
|
|
11
13
|
*/
|
|
12
14
|
export class MemorySessionStore {
|
|
13
15
|
sessions = new Map();
|
|
16
|
+
// Apart from the sessions: anonymous sessions written after a logout must not push its marker out.
|
|
17
|
+
// Markers share one lifetime, so the oldest-inserted is the first to expire.
|
|
18
|
+
markers = new Map();
|
|
14
19
|
max;
|
|
15
20
|
constructor(options = {}) {
|
|
16
21
|
const isProd = typeof process !== "undefined" && process.env?.NODE_ENV === "production";
|
|
@@ -24,22 +29,26 @@ export class MemorySessionStore {
|
|
|
24
29
|
throw new RangeError("[nifra/auth] MemorySessionStore max must be a positive safe integer");
|
|
25
30
|
}
|
|
26
31
|
}
|
|
32
|
+
mapFor(id) {
|
|
33
|
+
return id.endsWith(REVOKED_SUFFIX) ? this.markers : this.sessions;
|
|
34
|
+
}
|
|
27
35
|
get(id) {
|
|
28
|
-
return Promise.resolve(this.
|
|
36
|
+
return Promise.resolve(this.mapFor(id).get(id));
|
|
29
37
|
}
|
|
30
38
|
set(id, record) {
|
|
31
|
-
this.
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
39
|
+
const entries = this.mapFor(id);
|
|
40
|
+
entries.delete(id); // re-insert at the tail so Map order tracks recency for the bounded evict
|
|
41
|
+
entries.set(id, record);
|
|
42
|
+
while (entries.size > this.max) {
|
|
43
|
+
const oldest = entries.keys().next().value;
|
|
35
44
|
if (oldest === undefined)
|
|
36
45
|
break;
|
|
37
|
-
|
|
46
|
+
entries.delete(oldest);
|
|
38
47
|
}
|
|
39
48
|
return Promise.resolve();
|
|
40
49
|
}
|
|
41
50
|
delete(id) {
|
|
42
|
-
this.
|
|
51
|
+
this.mapFor(id).delete(id);
|
|
43
52
|
return Promise.resolve();
|
|
44
53
|
}
|
|
45
54
|
}
|
package/dist/store.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store.js","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nifrajs/auth",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.3.0",
|
|
4
4
|
"description": "Session primitives for nifra - signed-cookie + server-store sessions, CSRF, and route guards. Bring your own identity (Better Auth / Lucia / OAuth); nifra owns the session.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"bun",
|
|
@@ -29,7 +29,8 @@
|
|
|
29
29
|
},
|
|
30
30
|
"files": [
|
|
31
31
|
"dist",
|
|
32
|
-
"src"
|
|
32
|
+
"src",
|
|
33
|
+
"CHANGELOG.md"
|
|
33
34
|
],
|
|
34
35
|
"sideEffects": false,
|
|
35
36
|
"scripts": {
|
|
@@ -40,7 +41,7 @@
|
|
|
40
41
|
"access": "public"
|
|
41
42
|
},
|
|
42
43
|
"peerDependencies": {
|
|
43
|
-
"@nifrajs/core": "^5.
|
|
44
|
+
"@nifrajs/core": "^5.3.0"
|
|
44
45
|
},
|
|
45
46
|
"devDependencies": {
|
|
46
47
|
"@nifrajs/core": "workspace:*"
|
package/src/session.ts
CHANGED
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
signValue,
|
|
17
17
|
unsignValue,
|
|
18
18
|
} from "@nifrajs/core/cookies"
|
|
19
|
-
import type
|
|
19
|
+
import { REVOKED_SUFFIX, type SessionRecord, type SessionStore } from "./store.ts"
|
|
20
20
|
|
|
21
21
|
/** The cookie + response surface the manager needs - a structural subset of nifra's `Context`, so any
|
|
22
22
|
* `c` satisfies it and it's testable with a stub. */
|
|
@@ -84,7 +84,11 @@ export interface SessionManager<Data extends Record<string, unknown> = Record<st
|
|
|
84
84
|
read(request: Request): Promise<Session<Data>>
|
|
85
85
|
/** Persist the session: write the store (store mode) + the signed cookie. */
|
|
86
86
|
commit(c: SessionContext, session: Session<Data>): Promise<void>
|
|
87
|
-
/** End the session: drop the store record (store mode) + clear the cookie.
|
|
87
|
+
/** End the session: drop the store record (store mode) + clear the cookie. In store mode the id is
|
|
88
|
+
* also marked revoked, so a request that loaded the session earlier cannot write it back. The
|
|
89
|
+
* `session` passed in is emptied and detached: committing it later in the same request starts a
|
|
90
|
+
* new session holding only what was set since. A session object not passed in stays bound to the
|
|
91
|
+
* ended id, and committing it writes nothing. */
|
|
88
92
|
destroy(c: SessionContext, session?: Session<Data>): Promise<void>
|
|
89
93
|
/** Rotate the session id on the next commit (call on privilege change - login - to defend against
|
|
90
94
|
* session fixation). Data is preserved; the old store record is dropped. */
|
|
@@ -107,6 +111,11 @@ const encodeBase64Url = (bytes: Uint8Array): string => {
|
|
|
107
111
|
/** A 256-bit random session id (base64url). High-entropy + unguessable - the cookie also signs it. */
|
|
108
112
|
const randomId = (): string => encodeBase64Url(crypto.getRandomValues(new Uint8Array(32)))
|
|
109
113
|
|
|
114
|
+
// A revoked id keeps a marker under its own key. A request that loaded the session before logout and
|
|
115
|
+
// commits after it rewrites the record, and a blind `set` cannot be made conditional on every store,
|
|
116
|
+
// but it cannot overwrite a key it never writes. `.` is outside the base64url id alphabet.
|
|
117
|
+
const revokedKey = (id: string): string => `${id}${REVOKED_SUFFIX}`
|
|
118
|
+
|
|
110
119
|
interface Payload {
|
|
111
120
|
data: Record<string, unknown>
|
|
112
121
|
expiresAt: number
|
|
@@ -227,8 +236,17 @@ export function createSessions<Data extends Record<string, unknown> = Record<str
|
|
|
227
236
|
if (unsigned === null) return make({}) // tampered / wrong secret → fail closed (anonymous)
|
|
228
237
|
|
|
229
238
|
if (store !== undefined) {
|
|
230
|
-
|
|
239
|
+
// Only reachable with a value the app signed itself (legacy mode): never read a marker as a session.
|
|
240
|
+
if (unsigned.endsWith(REVOKED_SUFFIX)) return make({})
|
|
241
|
+
const [record, revoked] = await Promise.all([
|
|
242
|
+
store.get(unsigned),
|
|
243
|
+
store.get(revokedKey(unsigned)),
|
|
244
|
+
])
|
|
231
245
|
if (record === undefined) return make({})
|
|
246
|
+
if (revoked !== undefined) {
|
|
247
|
+
await store.delete(unsigned) // a commit that raced the logout wrote it back
|
|
248
|
+
return make({})
|
|
249
|
+
}
|
|
232
250
|
if (record.expiresAt <= now()) {
|
|
233
251
|
await store.delete(unsigned) // expired - evict + treat as no session
|
|
234
252
|
return make({})
|
|
@@ -263,41 +281,80 @@ export function createSessions<Data extends Record<string, unknown> = Record<str
|
|
|
263
281
|
c.set.cookie(cookieName, value, { ...cookieOpts, maxAge: maxAgeSeconds })
|
|
264
282
|
}
|
|
265
283
|
|
|
284
|
+
// Marker first: a racing commit's write either precedes the delete below or follows the marker.
|
|
285
|
+
const revoke = async (id: string): Promise<void> => {
|
|
286
|
+
if (store === undefined) return
|
|
287
|
+
await store.set(revokedKey(id), { data: {}, expiresAt: now() + maxAge * 1000 })
|
|
288
|
+
await store.delete(id)
|
|
289
|
+
}
|
|
290
|
+
|
|
266
291
|
const commit = async (c: SessionContext, session: Session<Data>): Promise<void> => {
|
|
267
292
|
const state = stateOf(session, "commit")
|
|
293
|
+
const previousExpiry = state.expiresAt
|
|
294
|
+
// Capture before store reads so a later logout's marker outlives this renewal.
|
|
295
|
+
const committedAt = now()
|
|
296
|
+
// The loaded expiry still fences a request after its revocation marker has been collected.
|
|
297
|
+
if (previousExpiry !== undefined && previousExpiry <= committedAt) return
|
|
268
298
|
// Rolling → slide the expiry; absolute → keep the original (set on the first commit).
|
|
269
299
|
const expiresAt =
|
|
270
|
-
rolling ||
|
|
271
|
-
state.expiresAt = expiresAt
|
|
300
|
+
rolling || previousExpiry === undefined ? committedAt + maxAge * 1000 : previousExpiry
|
|
272
301
|
const data = Object.fromEntries(state.data) as Record<string, unknown>
|
|
273
302
|
|
|
274
303
|
if (store !== undefined) {
|
|
304
|
+
if (state.id !== undefined) {
|
|
305
|
+
const revoked = await store.get(revokedKey(state.id))
|
|
306
|
+
// Check again after the read: a delayed store operation can outlive both session and marker.
|
|
307
|
+
if (revoked !== undefined || (previousExpiry !== undefined && previousExpiry <= now()))
|
|
308
|
+
return
|
|
309
|
+
}
|
|
275
310
|
if (state.regenerate) {
|
|
276
|
-
if (state.id !== undefined) await
|
|
311
|
+
if (state.id !== undefined) await revoke(state.id) // drop the pre-rotation session
|
|
312
|
+
if (previousExpiry !== undefined && previousExpiry <= now()) return
|
|
277
313
|
state.id = undefined
|
|
278
314
|
state.regenerate = false
|
|
279
315
|
}
|
|
280
|
-
|
|
316
|
+
const loaded = state.id !== undefined
|
|
317
|
+
state.id ??= randomId()
|
|
281
318
|
await store.set(state.id, { data, expiresAt } satisfies SessionRecord)
|
|
282
|
-
|
|
319
|
+
// Checked after the write: a revocation that lands before it is seen here, one that lands after
|
|
320
|
+
// it deletes the record itself. Either way the cookie is left alone - the browser may hold a
|
|
321
|
+
// newer session by now, and this response must not replace or clear it.
|
|
322
|
+
if (loaded && (await store.get(revokedKey(state.id))) !== undefined) {
|
|
323
|
+
await store.delete(state.id)
|
|
324
|
+
return
|
|
325
|
+
}
|
|
326
|
+
const signed = await sign(state.id)
|
|
327
|
+
state.expiresAt = expiresAt
|
|
328
|
+
writeCookie(c, signed, expiresAt)
|
|
283
329
|
return
|
|
284
330
|
}
|
|
285
331
|
// Cookie mode: the (signed) payload IS the cookie. serializeCookie enforces the 4 KB cap.
|
|
286
|
-
|
|
332
|
+
const signed = await sign(JSON.stringify({ data, expiresAt } satisfies Payload))
|
|
333
|
+
state.expiresAt = expiresAt
|
|
334
|
+
writeCookie(c, signed, expiresAt)
|
|
287
335
|
}
|
|
288
336
|
|
|
289
337
|
const destroy = async (c: SessionContext, session?: Session<Data>): Promise<void> => {
|
|
338
|
+
const state = session === undefined ? undefined : states.get(session)
|
|
290
339
|
if (store !== undefined) {
|
|
291
340
|
let id: string | undefined
|
|
292
341
|
if (session !== undefined) {
|
|
293
|
-
id =
|
|
342
|
+
id = state?.id
|
|
294
343
|
} else {
|
|
295
344
|
// Revoke the record addressed by the signed request cookie even when the caller did not
|
|
296
345
|
// first load a Session object. The signature check prevents deleting attacker-chosen IDs.
|
|
297
346
|
const raw = sessionCookie(c)
|
|
298
347
|
if (raw !== undefined) id = (await unsign(raw)) ?? undefined
|
|
299
348
|
}
|
|
300
|
-
if (id !== undefined) await
|
|
349
|
+
if (id !== undefined && !id.endsWith(REVOKED_SUFFIX)) await revoke(id)
|
|
350
|
+
}
|
|
351
|
+
// A later commit of this object must neither write the ended data back (cookie mode) nor vanish
|
|
352
|
+
// against the revocation marker (store mode).
|
|
353
|
+
if (state !== undefined) {
|
|
354
|
+
state.id = undefined
|
|
355
|
+
state.expiresAt = undefined
|
|
356
|
+
state.regenerate = false
|
|
357
|
+
state.data.clear()
|
|
301
358
|
}
|
|
302
359
|
c.set.deleteCookie(cookieName, {
|
|
303
360
|
path: cookieOpts.path ?? "/",
|
package/src/store.ts
CHANGED
|
@@ -15,7 +15,10 @@ export interface SessionRecord {
|
|
|
15
15
|
/**
|
|
16
16
|
* Pluggable session backend (store mode). Async so a network store (KV/Redis) fits the same shape.
|
|
17
17
|
* **Production needs a shared/durable store** so sessions hold across instances - {@link MemorySessionStore}
|
|
18
|
-
* prod-guards against the per-instance footgun.
|
|
18
|
+
* prod-guards against the per-instance footgun. Logout and rotation also write a revocation marker, an
|
|
19
|
+
* empty record under `<id>.revoked`, so a store must accept that key like any other, and keep it until
|
|
20
|
+
* its `expiresAt`: a marker dropped earlier lets a request that loaded the session before the logout
|
|
21
|
+
* write it back.
|
|
19
22
|
*/
|
|
20
23
|
export interface SessionStore {
|
|
21
24
|
/** The record for `id`, or `undefined` on a miss. */
|
|
@@ -26,21 +29,28 @@ export interface SessionStore {
|
|
|
26
29
|
delete(id: string): Promise<void>
|
|
27
30
|
}
|
|
28
31
|
|
|
32
|
+
/** The suffix of a revocation marker's key: `<id>.revoked`. */
|
|
33
|
+
export const REVOKED_SUFFIX = ".revoked"
|
|
34
|
+
|
|
29
35
|
export interface MemorySessionStoreOptions {
|
|
30
36
|
/** Allow the in-memory store in production. Off by default - per-instance sessions don't hold across
|
|
31
37
|
* instances and are lost on restart. */
|
|
32
38
|
readonly allowInProduction?: boolean
|
|
33
|
-
/** Hard cap on stored sessions; the oldest-inserted is evicted past it (default 10_000).
|
|
39
|
+
/** Hard cap on stored sessions; the oldest-inserted is evicted past it (default 10_000). Revocation
|
|
40
|
+
* markers are capped by it separately, so new sessions never evict one. */
|
|
34
41
|
readonly max?: number
|
|
35
42
|
}
|
|
36
43
|
|
|
37
44
|
/**
|
|
38
45
|
* In-process session store. Refuses to run in production unless explicitly allowed (mirrors the ISR
|
|
39
46
|
* `MemoryCacheStore` + the rate-limit `MemoryStore` - a per-instance store is unsafe across instances).
|
|
40
|
-
* Bounded: oldest-inserted entries evict past `max
|
|
47
|
+
* Bounded: oldest-inserted entries evict past `max`, sessions and revocation markers each on their own.
|
|
41
48
|
*/
|
|
42
49
|
export class MemorySessionStore implements SessionStore {
|
|
43
50
|
private readonly sessions = new Map<string, SessionRecord>()
|
|
51
|
+
// Apart from the sessions: anonymous sessions written after a logout must not push its marker out.
|
|
52
|
+
// Markers share one lifetime, so the oldest-inserted is the first to expire.
|
|
53
|
+
private readonly markers = new Map<string, SessionRecord>()
|
|
44
54
|
private readonly max: number
|
|
45
55
|
|
|
46
56
|
constructor(options: MemorySessionStoreOptions = {}) {
|
|
@@ -58,23 +68,28 @@ export class MemorySessionStore implements SessionStore {
|
|
|
58
68
|
}
|
|
59
69
|
}
|
|
60
70
|
|
|
71
|
+
private mapFor(id: string): Map<string, SessionRecord> {
|
|
72
|
+
return id.endsWith(REVOKED_SUFFIX) ? this.markers : this.sessions
|
|
73
|
+
}
|
|
74
|
+
|
|
61
75
|
get(id: string): Promise<SessionRecord | undefined> {
|
|
62
|
-
return Promise.resolve(this.
|
|
76
|
+
return Promise.resolve(this.mapFor(id).get(id))
|
|
63
77
|
}
|
|
64
78
|
|
|
65
79
|
set(id: string, record: SessionRecord): Promise<void> {
|
|
66
|
-
this.
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
80
|
+
const entries = this.mapFor(id)
|
|
81
|
+
entries.delete(id) // re-insert at the tail so Map order tracks recency for the bounded evict
|
|
82
|
+
entries.set(id, record)
|
|
83
|
+
while (entries.size > this.max) {
|
|
84
|
+
const oldest = entries.keys().next().value
|
|
70
85
|
if (oldest === undefined) break
|
|
71
|
-
|
|
86
|
+
entries.delete(oldest)
|
|
72
87
|
}
|
|
73
88
|
return Promise.resolve()
|
|
74
89
|
}
|
|
75
90
|
|
|
76
91
|
delete(id: string): Promise<void> {
|
|
77
|
-
this.
|
|
92
|
+
this.mapFor(id).delete(id)
|
|
78
93
|
return Promise.resolve()
|
|
79
94
|
}
|
|
80
95
|
}
|