@nifrajs/auth 5.2.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 CHANGED
@@ -1,5 +1,19 @@
1
1
  # @nifrajs/auth
2
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
+
3
17
  ## 5.2.0
4
18
 
5
19
  ### Patch Changes
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 { SessionStore } from "./store.js";
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. */
@@ -1 +1 @@
1
- {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"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"}
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
- const record = await store.get(unsigned);
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 || state.expiresAt === undefined ? now() + maxAge * 1000 : state.expiresAt;
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 store.delete(state.id); // drop the pre-rotation session
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
- if (state.id === undefined)
172
- state.id = randomId();
206
+ const loaded = state.id !== undefined;
207
+ state.id ??= randomId();
173
208
  await store.set(state.id, { data, expiresAt });
174
- writeCookie(c, await sign(state.id), expiresAt);
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
- writeCookie(c, await sign(JSON.stringify({ data, expiresAt })), expiresAt);
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 = states.get(session)?.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 store.delete(id);
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 ?? "/",
@@ -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>;
@@ -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;;;;GAIG;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,MAAM,WAAW,yBAAyB;IACxC;4CACwC;IACxC,QAAQ,CAAC,iBAAiB,CAAC,EAAE,OAAO,CAAA;IACpC,4FAA4F;IAC5F,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CACtB;AAED;;;;GAIG;AACH,qBAAa,kBAAmB,YAAW,YAAY;IACrD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAmC;IAC5D,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAQ;gBAEhB,OAAO,GAAE,yBAA8B;IAenD,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;IAWrD,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"}
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.sessions.get(id));
36
+ return Promise.resolve(this.mapFor(id).get(id));
29
37
  }
30
38
  set(id, record) {
31
- this.sessions.delete(id); // re-insert at the tail so Map order tracks recency for the bounded evict
32
- this.sessions.set(id, record);
33
- while (this.sessions.size > this.max) {
34
- const oldest = this.sessions.keys().next().value;
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
- this.sessions.delete(oldest);
46
+ entries.delete(oldest);
38
47
  }
39
48
  return Promise.resolve();
40
49
  }
41
50
  delete(id) {
42
- this.sessions.delete(id);
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":"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"}
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.2.0",
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",
@@ -41,7 +41,7 @@
41
41
  "access": "public"
42
42
  },
43
43
  "peerDependencies": {
44
- "@nifrajs/core": "^5.2.0"
44
+ "@nifrajs/core": "^5.3.0"
45
45
  },
46
46
  "devDependencies": {
47
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 { SessionRecord, SessionStore } from "./store.ts"
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
- const record = await store.get(unsigned)
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 || state.expiresAt === undefined ? now() + maxAge * 1000 : state.expiresAt
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 store.delete(state.id) // drop the pre-rotation session
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
- if (state.id === undefined) state.id = randomId()
316
+ const loaded = state.id !== undefined
317
+ state.id ??= randomId()
281
318
  await store.set(state.id, { data, expiresAt } satisfies SessionRecord)
282
- writeCookie(c, await sign(state.id), expiresAt)
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
- writeCookie(c, await sign(JSON.stringify({ data, expiresAt } satisfies Payload)), expiresAt)
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 = states.get(session)?.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 store.delete(id)
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.sessions.get(id))
76
+ return Promise.resolve(this.mapFor(id).get(id))
63
77
  }
64
78
 
65
79
  set(id: string, record: SessionRecord): Promise<void> {
66
- this.sessions.delete(id) // re-insert at the tail so Map order tracks recency for the bounded evict
67
- this.sessions.set(id, record)
68
- while (this.sessions.size > this.max) {
69
- const oldest = this.sessions.keys().next().value
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
- this.sessions.delete(oldest)
86
+ entries.delete(oldest)
72
87
  }
73
88
  return Promise.resolve()
74
89
  }
75
90
 
76
91
  delete(id: string): Promise<void> {
77
- this.sessions.delete(id)
92
+ this.mapFor(id).delete(id)
78
93
  return Promise.resolve()
79
94
  }
80
95
  }