iron-session 9.0.0-beta.0 → 9.0.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/MIGRATION.md +24 -9
- package/README.md +74 -3
- package/dist/index.js +34 -5
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/MIGRATION.md
CHANGED
|
@@ -2,15 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
## v8 to v9
|
|
4
4
|
|
|
5
|
+
```sh
|
|
6
|
+
pnpm add iron-session # latest
|
|
7
|
+
pnpm add iron-session@beta # prereleases
|
|
8
|
+
```
|
|
9
|
+
|
|
5
10
|
Most apps need two changes: Node 22 and one line if you store a `Date` in the
|
|
6
11
|
session. Everything else below is either a type error TypeScript will point at,
|
|
7
12
|
or a bug fix you want.
|
|
8
13
|
|
|
9
14
|
### Requirements
|
|
10
15
|
|
|
11
|
-
- **Node 22.
|
|
16
|
+
- **Node 22.13 or later.** Node 20 reached end of life in April 2026.
|
|
12
17
|
- **The package is ESM-only.** There is no CommonJS build. `require()` still
|
|
13
|
-
works on Node 22.
|
|
18
|
+
works on Node 22.13+, which supports `require()` of an ES module, so most CJS
|
|
14
19
|
code keeps working. If you are bundling for an older target, keep v8.
|
|
15
20
|
|
|
16
21
|
### Do this
|
|
@@ -39,11 +44,21 @@ A session that does not exist yet is an empty object. The old type claimed
|
|
|
39
44
|
otherwise, so `session.user.id` compiled and threw on a first visit, on an
|
|
40
45
|
expired cookie, and after `destroy()`.
|
|
41
46
|
|
|
42
|
-
**3. Do not
|
|
47
|
+
**3. Do not write to a session after `destroy()`.**
|
|
48
|
+
|
|
49
|
+
`destroy()` is terminal now. A bare `save()` after it is ignored, so a logout
|
|
50
|
+
handler that calls both keeps working and the user ends up signed out. Writing
|
|
51
|
+
fields back in and then saving throws:
|
|
52
|
+
|
|
53
|
+
```diff
|
|
54
|
+
session.destroy();
|
|
55
|
+
- session.lastSeen = Date.now();
|
|
56
|
+
- await session.save();
|
|
57
|
+
```
|
|
43
58
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
59
|
+
Before, that save re-sealed the session and the browser kept the last
|
|
60
|
+
`Set-Cookie`, so the logout silently did not happen. A wrapper refreshing a
|
|
61
|
+
rolling expiry at the end of every request cancelled every logout in the app.
|
|
47
62
|
|
|
48
63
|
**4. Pass a real password to `updateConfig()`.**
|
|
49
64
|
|
|
@@ -87,13 +102,13 @@ relying on that, you were not rotating anything.
|
|
|
87
102
|
- **Adapters** for the runtimes that needed them:
|
|
88
103
|
- `nextProxyCookies(request, response)` for Next.js `proxy.ts` (called
|
|
89
104
|
`middleware.ts` before Next 16). This is the fix if saving a session in
|
|
90
|
-
middleware never seemed to take effect.
|
|
105
|
+
Proxy (middleware) never seemed to take effect.
|
|
91
106
|
- `nodeCookies(req, res)` and `webCookies(request, responseOrHeaders)` if you
|
|
92
107
|
want to be explicit instead of relying on the `(req, res, options)` form.
|
|
93
108
|
|
|
94
|
-
### Rotating a session in Next.js middleware
|
|
109
|
+
### Rotating a session in Next.js Proxy (middleware)
|
|
95
110
|
|
|
96
|
-
This did not work before. A cookie written in middleware only reaches the
|
|
111
|
+
This did not work before. A cookie written in Proxy (middleware) only reaches the
|
|
97
112
|
current render when it goes through `response.cookies.set()`, so `session.save()`
|
|
98
113
|
appeared to succeed and then vanished.
|
|
99
114
|
|
package/README.md
CHANGED
|
@@ -14,6 +14,7 @@ The session data is stored in signed and encrypted cookies which are decoded by
|
|
|
14
14
|
|
|
15
15
|
- [Table of Contents](#table-of-contents)
|
|
16
16
|
- [Installation](#installation)
|
|
17
|
+
- [Upgrading to v9](#upgrading-to-v9)
|
|
17
18
|
- [Usage](#usage)
|
|
18
19
|
- [Examples](#examples)
|
|
19
20
|
- [Runtimes](#runtimes)
|
|
@@ -25,6 +26,7 @@ The session data is stored in signed and encrypted cookies which are decoded by
|
|
|
25
26
|
- [API](#api)
|
|
26
27
|
- [`getIronSession<T>(req, res, sessionOptions): Promise<IronSession<T>>`](#getironsessiontreq-res-sessionoptions-promiseironsessiont)
|
|
27
28
|
- [`getIronSession<T>(cookieStore, sessionOptions): Promise<IronSession<T>>`](#getironsessiontcookiestore-sessionoptions-promiseironsessiont)
|
|
29
|
+
- [`nodeCookies`, `webCookies`, `nextProxyCookies`](#nodecookiesreq-res-webcookiesrequest-responseorheaders-nextproxycookiesrequest-response)
|
|
28
30
|
- [`session.save(): Promise<void>`](#sessionsave-promisevoid)
|
|
29
31
|
- [`session.destroy(): void`](#sessiondestroy-void)
|
|
30
32
|
- [`session.updateConfig(sessionOptions: SessionOptions): void`](#sessionupdateconfigsessionoptions-sessionoptions-void)
|
|
@@ -44,6 +46,57 @@ The session data is stored in signed and encrypted cookies which are decoded by
|
|
|
44
46
|
pnpm add iron-session
|
|
45
47
|
```
|
|
46
48
|
|
|
49
|
+
Prereleases are published under the `beta` tag:
|
|
50
|
+
|
|
51
|
+
```sh
|
|
52
|
+
pnpm add iron-session@beta
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
v9 needs **Node 22.13 or later** and is **ESM-only**. `require()` still works on
|
|
56
|
+
Node 22.13+, which supports `require()` of an ES module. If you are stuck on an
|
|
57
|
+
older Node, stay on v8: `pnpm add iron-session@8`.
|
|
58
|
+
|
|
59
|
+
## Upgrading to v9
|
|
60
|
+
|
|
61
|
+
Most apps change two things. Both are things v8 got wrong quietly.
|
|
62
|
+
|
|
63
|
+
**1. Store timestamps, not `Date` objects.**
|
|
64
|
+
|
|
65
|
+
```diff
|
|
66
|
+
- session.lastSeen = new Date();
|
|
67
|
+
+ session.lastSeen = Date.now();
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
v8 turned a `Date` into a string when sealing, so the type you wrote was not the
|
|
71
|
+
type you read back. v9 throws and names the field.
|
|
72
|
+
|
|
73
|
+
**2. Handle a session that does not exist yet.**
|
|
74
|
+
|
|
75
|
+
```diff
|
|
76
|
+
- const userId = session.user.id;
|
|
77
|
+
+ const userId = session.user?.id;
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Reads are typed as `Partial<T>` now. A first visit, an expired cookie and a
|
|
81
|
+
`destroy()` all leave you an empty object, so the old type let this compile and
|
|
82
|
+
then throw at runtime.
|
|
83
|
+
|
|
84
|
+
Nothing else is required. `getIronSession(req, res, options)` and
|
|
85
|
+
`getIronSession(await cookies(), options)` both still work, v9 reads v8 cookies
|
|
86
|
+
and v8 reads v9 cookies, so you can roll a deploy back without signing everyone
|
|
87
|
+
out. If you had `as any` on `await cookies()`, delete it.
|
|
88
|
+
|
|
89
|
+
Worth adopting while you are here:
|
|
90
|
+
|
|
91
|
+
- [`nextProxyCookies`](#runtimes) if you ever tried to save a session in Next.js
|
|
92
|
+
middleware and it did not stick.
|
|
93
|
+
- [`onUnsealError`](#watching-for-unreadable-cookies) to see why cookies get
|
|
94
|
+
rejected instead of guessing.
|
|
95
|
+
- [`chunk: true`](#session-size) if your session outgrew one cookie.
|
|
96
|
+
|
|
97
|
+
The full guide, including the removed APIs and the security fix that signs pre-v8
|
|
98
|
+
cookies out once, is in [MIGRATION.md](./MIGRATION.md).
|
|
99
|
+
|
|
47
100
|
## Usage
|
|
48
101
|
|
|
49
102
|
_We have extensive examples here too: https://get-iron-session.vercel.app/._
|
|
@@ -135,7 +188,7 @@ else, pass an adapter instead:
|
|
|
135
188
|
| ---------------------------------------- | -------------------------------------------------------------------------- |
|
|
136
189
|
| `nodeCookies(req, res)` | Node `http`, Express, Connect, Next.js API routes |
|
|
137
190
|
| `webCookies(request, responseOrHeaders)` | Anything web-standard: Hono, Bun, Deno, Cloudflare Workers, Route Handlers |
|
|
138
|
-
| `nextProxyCookies(request, response)` | Next.js
|
|
191
|
+
| `nextProxyCookies(request, response)` | Next.js Proxy (middleware), `proxy.ts` |
|
|
139
192
|
|
|
140
193
|
Anything with `get(name)` and `set(name, value, options)`, like Next's
|
|
141
194
|
`cookies()`, can be passed directly. If your framework has neither, a cookie jar
|
|
@@ -234,7 +287,7 @@ Two options are required: `password` and `cookieName`. Everything else is automa
|
|
|
234
287
|
httpOnly: true,
|
|
235
288
|
secure: true, // set this to false in local (non-HTTPS) development
|
|
236
289
|
sameSite: "lax",// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite#lax
|
|
237
|
-
maxAge: (ttl === 0 ? 2147483647 : ttl) - 60, // Expire cookie before the session expires.
|
|
290
|
+
maxAge: (ttl === 0 ? 2147483647 : ttl) - 60, // Expire cookie before the session expires. A ttl of 60 or less keeps its full value.
|
|
238
291
|
path: "/",
|
|
239
292
|
}
|
|
240
293
|
```
|
|
@@ -258,7 +311,21 @@ type SessionData = {
|
|
|
258
311
|
// Your data
|
|
259
312
|
};
|
|
260
313
|
|
|
261
|
-
const session = await getIronSession<SessionData>(cookies(), sessionOptions);
|
|
314
|
+
const session = await getIronSession<SessionData>(await cookies(), sessionOptions);
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
Reads are typed as `Partial<T>`, because a session that does not exist yet is an
|
|
318
|
+
empty object. Use optional chaining, or narrow once and pass the result around.
|
|
319
|
+
|
|
320
|
+
### `nodeCookies(req, res)`, `webCookies(request, responseOrHeaders)`, `nextProxyCookies(request, response)`
|
|
321
|
+
|
|
322
|
+
Cookie jars you pass in place of `req, res`, for when the shorthand cannot tell
|
|
323
|
+
what your framework wants. See [Runtimes](#runtimes).
|
|
324
|
+
|
|
325
|
+
```ts
|
|
326
|
+
import { getIronSession, nextProxyCookies } from "iron-session";
|
|
327
|
+
|
|
328
|
+
const session = await getIronSession(nextProxyCookies(request, response), sessionOptions);
|
|
262
329
|
```
|
|
263
330
|
|
|
264
331
|
### `session.save(): Promise<void>`
|
|
@@ -277,10 +344,14 @@ Destroys the session. This is a synchronous operation as it only removes the coo
|
|
|
277
344
|
session.destroy();
|
|
278
345
|
```
|
|
279
346
|
|
|
347
|
+
`destroy()` is terminal. A `save()` after it is ignored, so a logout handler that calls both still signs the user out. Writing fields back into the session and then saving throws, because the last `Set-Cookie` would win and leave the user signed in.
|
|
348
|
+
|
|
280
349
|
### `session.updateConfig(sessionOptions: SessionOptions): void`
|
|
281
350
|
|
|
282
351
|
Updates the configuration of the session with new session options. You still need to call save() if you want them to be applied.
|
|
283
352
|
|
|
353
|
+
It rebuilds the whole configuration, including the password, so this is what you use to rotate a password mid-request. In v8 a new password passed here was ignored.
|
|
354
|
+
|
|
284
355
|
### `sealData(data: unknown, { password, ttl }): Promise<string>`
|
|
285
356
|
|
|
286
357
|
This is the underlying method and seal mechanism that powers `iron-session`. You can use it to seal any `data` you want and pass it around. One usecase are magic links: you generate a seal that contains a user id to login and send it to a route on your website (like `/magic-login`). Once received, you can safely decode the seal with `unsealData` and log the user in.
|
package/dist/index.js
CHANGED
|
@@ -32,7 +32,26 @@ function stripVersion(seal) {
|
|
|
32
32
|
}
|
|
33
33
|
function computeCookieMaxAge(ttl) {
|
|
34
34
|
if (ttl === 0) return 2147483647;
|
|
35
|
-
return
|
|
35
|
+
return ttl > timestampSkewSec ? ttl - timestampSkewSec : ttl;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Names the value that iron-webcrypto refused, so the error points at
|
|
39
|
+
* `session.lastSeen` instead of leaving you to find it.
|
|
40
|
+
*/
|
|
41
|
+
function describeUnserializable(data, path = "session", depth = 0) {
|
|
42
|
+
const type = typeof data;
|
|
43
|
+
if (type === "bigint" || type === "function" || type === "symbol") return ` (${path} is a ${type})`;
|
|
44
|
+
if (type === "number" && !Number.isFinite(data)) return ` (${path} is ${String(data)})`;
|
|
45
|
+
if (type !== "object" || data === null || depth > 5) return "";
|
|
46
|
+
const object = data;
|
|
47
|
+
const name = object.constructor?.name;
|
|
48
|
+
if (!Array.isArray(object) && name !== void 0 && name !== "Object") return ` (${path} is a ${name})`;
|
|
49
|
+
const entries = Array.isArray(object) ? object.map((value, index) => [`[${index}]`, value]) : Object.entries(object).map(([key, value]) => [`.${key}`, value]);
|
|
50
|
+
for (const [suffix, value] of entries) {
|
|
51
|
+
const found = describeUnserializable(value, `${path}${suffix}`, depth + 1);
|
|
52
|
+
if (found) return found;
|
|
53
|
+
}
|
|
54
|
+
return "";
|
|
36
55
|
}
|
|
37
56
|
async function sealData(data, { password, ttl = fourteenDaysInSeconds }) {
|
|
38
57
|
const passwordsMap = normalizeStringPasswordToMap(password);
|
|
@@ -50,7 +69,7 @@ async function sealData(data, { password, ttl = fourteenDaysInSeconds }) {
|
|
|
50
69
|
ttl: ttl * 1e3
|
|
51
70
|
});
|
|
52
71
|
} catch (error) {
|
|
53
|
-
if (error instanceof Error && error.message === "Data is not JSON serializable") throw new Error(
|
|
72
|
+
if (error instanceof Error && error.message === "Data is not JSON serializable") throw new Error(`iron-session: The session data is not JSON serializable${describeUnserializable(data)}. Store plain JSON values only: a Date must be stored as a timestamp (Date.now()) or an ISO string, and Map/Set/BigInt/functions are not supported.`, { cause: error });
|
|
54
73
|
throw error;
|
|
55
74
|
}
|
|
56
75
|
return `${seal$1}${versionDelimiter}${currentMajorVersion}`;
|
|
@@ -207,11 +226,17 @@ function getSessionConfig(sessionOptions) {
|
|
|
207
226
|
if (sessionOptions.cookieOptions && "maxAge" in sessionOptions.cookieOptions) {
|
|
208
227
|
if (sessionOptions.cookieOptions.maxAge === void 0) options.ttl = 0;
|
|
209
228
|
} else options.cookieOptions.maxAge = computeCookieMaxAge(options.ttl);
|
|
210
|
-
const { expires } = options.cookieOptions;
|
|
211
|
-
if (expires instanceof Date && expires.getTime() < Date.now()) throw new Error(`iron-session: Bad usage. cookieOptions.expires is in the past (${expires.toISOString()}), so the browser will discard the cookie and the session will never persist. This usually means the Date was created once at module scope. Use \`ttl\` instead.`);
|
|
212
229
|
return options;
|
|
213
230
|
}
|
|
214
231
|
/**
|
|
232
|
+
* Only checked when writing: reading a session does not set a cookie, so a
|
|
233
|
+
* stale `expires` should not break a page that just reads one.
|
|
234
|
+
*/
|
|
235
|
+
function assertCookieCanPersist(cookieOptions) {
|
|
236
|
+
const { expires } = cookieOptions;
|
|
237
|
+
if (expires instanceof Date && expires.getTime() < Date.now()) throw new Error(`iron-session: Bad usage. cookieOptions.expires is in the past (${expires.toISOString()}), so the browser will discard the cookie and the session will never persist. This usually means the Date was created once at module scope. Use \`ttl\` instead.`);
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
215
240
|
* The single session implementation. Reads the cookie through the jar, and
|
|
216
241
|
* defines save/destroy/updateConfig against that same jar.
|
|
217
242
|
*/
|
|
@@ -231,8 +256,12 @@ async function createSession(jar, sessionOptions) {
|
|
|
231
256
|
onUnsealError = newSessionOptions.onUnsealError;
|
|
232
257
|
} },
|
|
233
258
|
save: { value: async function save() {
|
|
234
|
-
if (destroyed)
|
|
259
|
+
if (destroyed) {
|
|
260
|
+
if (Object.keys(session).length === 0) return;
|
|
261
|
+
throw new Error("iron-session: Cannot save a destroyed session that has data written back into it. session.destroy() signs the user out, and saving these fields would restore the cookie you just cleared, leaving the user signed in. Get a fresh session if you need to write one.");
|
|
262
|
+
}
|
|
235
263
|
jar.assertWritable?.();
|
|
264
|
+
assertCookieCanPersist(config.cookieOptions);
|
|
236
265
|
const seal = await sealData(session, {
|
|
237
266
|
password: config.passwordsMap,
|
|
238
267
|
ttl: config.ttl
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["seal","ironSeal","ironDefaults","ironUnseal"],"sources":["../src/core.ts"],"sourcesContent":["import type { IncomingMessage, ServerResponse } from \"node:http\";\nimport { parseCookie, stringifySetCookie, type SetCookie } from \"cookie\";\nimport { defaults as ironDefaults, seal as ironSeal, unseal as ironUnseal } from \"iron-webcrypto\";\n\ntype PasswordsMap = Record<string, string>;\ntype Password = PasswordsMap | string;\ntype RequestType = IncomingMessage | Request;\ntype ResponseType = Response | ServerResponse;\n\n/**\n * {@link https://wicg.github.io/cookie-store/#dictdef-cookielistitem CookieListItem}\n * as specified by W3C.\n */\ninterface CookieListItem extends Pick<SetCookie, \"domain\" | \"path\" | \"sameSite\" | \"secure\"> {\n /** A string with the name of a cookie. */\n name: string;\n /** A string containing the value of the cookie. */\n value: string;\n /** A number of milliseconds or Date interface containing the expires of the cookie. */\n expires?: SetCookie[\"expires\"] | number;\n}\n\n/**\n * Superset of {@link CookieListItem} extending it with\n * the `httpOnly`, `maxAge` and `priority` properties.\n */\ntype ResponseCookie = CookieListItem &\n Pick<SetCookie, \"httpOnly\" | \"priority\"> & {\n maxAge?: number | undefined;\n };\n\n/**\n * What iron-session needs from a cookie store. This is deliberately the shape\n * of `await cookies()` from `next/headers`, so it can be passed in directly.\n *\n * `getAll` is optional: it is only used to find cookie chunks, and stores that\n * cannot enumerate are probed by name instead.\n */\nexport interface CookieStore {\n get: (name: string) => { name: string; value: string } | undefined;\n getAll?: () => { name: string; value: string }[];\n /**\n * One signature with a required third argument, returning `unknown`.\n *\n * Every part of that matters for `getIronSession(await cookies(), ...)` to\n * typecheck against Next.js, which is what #840 was about:\n *\n * - not an overload pair. Next declares a single signature over a tuple\n * union, and a single signature is not assignable to an overload pair.\n * - the third argument is required, not optional. Next's tuple element is\n * `cookie?: Partial<ResponseCookie>`, and under\n * `exactOptionalPropertyTypes` our optional parameter widened to\n * `Partial<ResponseCookie> | undefined`, which their tuple rejects. We\n * always pass cookie options anyway.\n * - `unknown` return. Next returns the cookie store itself, not void.\n */\n set: (name: string, value: string, cookie: Partial<ResponseCookie>) => unknown;\n}\n\n/**\n * Set-Cookie attributes. `name` and `value` are owned by iron-session\n * (`cookieName` and the seal), so they are not configurable here.\n *\n * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie\n * @see https://developer.chrome.com/docs/devtools/application/cookies/\n */\ntype CookieOptions = Omit<SetCookie, \"name\" | \"value\" | \"maxAge\"> & {\n /**\n * Explicitly allows `undefined` so that `cookieOptions: { maxAge: undefined }`\n * keeps working under `exactOptionalPropertyTypes`. That is the documented way\n * to ask for a \"session cookie\" that the browser drops when it closes.\n */\n maxAge?: number | undefined;\n};\n\n/**\n * Why an existing cookie could not be read. Every reason results in a fresh,\n * empty session: a session library cannot tell a tampered cookie from a\n * badly rotated password, so the only safe outcome is to start over.\n * Use `onUnsealError` to observe these, they are otherwise invisible.\n */\nexport type UnsealErrorReason =\n /** The seal is past its expiration. Normal, this is how sessions end. */\n | \"expired\"\n /** Integrity check failed, or the value is not a seal at all. Possible tampering. */\n | \"invalid\"\n /** The seal references a password id that is not in the password map. Usually a rotation mistake. */\n | \"unknown-password\";\n\nexport interface SessionOptions {\n /**\n * The cookie name that will be used inside the browser. Make sure it's unique\n * given your application.\n *\n * @example 'vercel-session'\n */\n cookieName: string;\n\n /**\n * The password(s) that will be used to encrypt the cookie. Can either be a string\n * or an object.\n *\n * When you provide multiple passwords then all of them will be used to decrypt\n * the cookie. But only the most recent (`= highest key`, `2` in the example)\n * password will be used to encrypt the cookie. This allows password rotation.\n *\n * @example { 1: 'password-1', 2: 'password-2' }\n */\n password: Password;\n\n /**\n * The time (in seconds) that the session will be valid for. Also sets the\n * `max-age` attribute of the cookie automatically (`= ttl - 60s`, so that the\n * cookie always expire before the session).\n *\n * `ttl = 0` means no expiration. Do not use it for authentication: the seal\n * is then accepted forever and there is no way to revoke it.\n *\n * @default 1209600\n */\n ttl?: number;\n\n /**\n * The options that will be passed to the cookie library.\n *\n * If you want to use \"session cookies\" (cookies that are deleted when the browser\n * is closed) then you need to pass `cookieOptions: { maxAge: undefined }`\n *\n * @see https://github.com/jshttp/cookie#options-1\n */\n cookieOptions?: CookieOptions;\n\n /**\n * Called when an existing cookie could not be read, right before the session\n * is reset to an empty object.\n *\n * A stateless session library cannot tell a tampered cookie from a badly\n * rotated password, so it always starts a new session rather than throwing.\n * That is the safe default, but it means genuine problems are invisible:\n * a `\"unknown-password\"` burst usually means a broken password rotation, and\n * an `\"invalid\"` burst can mean someone is probing your cookies. Log them.\n *\n * This hook must not throw. It is not a place to deny access: the session is\n * empty either way.\n *\n * @example\n * onUnsealError: (reason, error) => {\n * if (reason !== \"expired\") logger.warn({ reason, error }, \"session cookie rejected\");\n * }\n */\n onUnsealError?: (reason: UnsealErrorReason, error: unknown) => void;\n\n /**\n * Split a session that does not fit in one cookie across several cookies,\n * named `<cookieName>.0`, `<cookieName>.1` and so on.\n *\n * Off by default, and reading works either way: turning this on or off does\n * not sign anyone out.\n *\n * Read the size limits before reaching for this. A browser caps one cookie at\n * 4096 bytes, but the real constraint is the request side: every cookie is\n * sent on every request, and proxies cap the whole `Cookie` header well below\n * what four chunks can produce. nginx allows 8 KB by default, and a CDN or\n * load balancer in front of it may allow less. Going over that returns 400 or\n * 431 at the edge, before your app runs, which is much harder to debug than\n * an error from us. iron-session refuses more than {@link MAX_CHUNKS} chunks\n * for that reason.\n *\n * Chunking is an escape hatch for a session slightly over the limit. If you\n * need a lot of room, store an id in the session and keep the data in your\n * database.\n *\n * @default false\n */\n chunk?: boolean;\n}\n\n/**\n * A session object: your data, plus `save`, `destroy` and `updateConfig`.\n *\n * `T` is wrapped in `Partial` because a session that does not exist yet reads as\n * an empty object. Declaring `IronSession<{ user: User }>` used to promise that\n * `session.user` was there, so `session.user.id` typechecked and then threw at\n * runtime on the first visit, on an expired cookie, and after `destroy()`.\n */\nexport type IronSession<T> = Partial<T> & {\n /**\n * Encrypts the session data and sets the cookie.\n */\n readonly save: () => Promise<void>;\n\n /**\n * Destroys the session data and removes the cookie.\n */\n readonly destroy: () => void;\n\n /**\n * Update the session configuration. You still need to call save() to send the new cookie.\n */\n readonly updateConfig: (newSessionOptions: SessionOptions) => void;\n};\n\n// default time allowed to check for iron seal validity when ttl passed\n// see https://hapi.dev/module/iron/api/?v=7.0.1#options\nconst timestampSkewSec = 60;\nconst fourteenDaysInSeconds = 14 * 24 * 3600;\n\n// We store a token major version to handle data format changes so that the cookies\n// can be kept alive between upgrades, no need to disconnect everyone.\nconst currentMajorVersion = 2;\nconst versionDelimiter = \"~\";\n\nconst defaultOptions: Required<Pick<SessionOptions, \"ttl\" | \"cookieOptions\" | \"chunk\">> = {\n ttl: fourteenDaysInSeconds,\n cookieOptions: { httpOnly: true, secure: true, sameSite: \"lax\", path: \"/\" },\n chunk: false,\n};\n\nfunction normalizeStringPasswordToMap(password: Password): PasswordsMap {\n return typeof password === \"string\" ? { 1: password } : password;\n}\n\n/**\n * Removes the trailing `~<version>` marker from a seal.\n *\n * The marker sits outside the seal's HMAC, so it is attacker-controlled and its\n * value must never select a code path. iron-session v8 used it to unwrap a\n * `persistent` key from v6-era cookies; v9 drops that format, so the marker is\n * now inert metadata that we strip and ignore.\n */\nfunction stripVersion(seal: string): string {\n const delimiterIndex = seal.indexOf(versionDelimiter);\n return delimiterIndex === -1 ? seal : seal.slice(0, delimiterIndex);\n}\n\nfunction computeCookieMaxAge(ttl: number): number {\n if (ttl === 0) {\n // ttl = 0 means no expiration\n // but in reality cookies have to expire (can't have no max-age)\n // 2147483647 is the max value for max-age in cookies\n // see https://stackoverflow.com/a/11685301/147079\n return 2147483647;\n }\n\n // Expire the cookie slightly before the seal, and allow for a 60 second clock\n // difference between server and client. Clamped to 1: a short ttl used to\n // produce `Max-Age=0` or a negative value, which makes the browser drop the\n // cookie on arrival while save() still reported success.\n return Math.max(1, ttl - timestampSkewSec);\n}\n\nexport async function sealData(\n data: unknown,\n { password, ttl = fourteenDaysInSeconds }: { password: Password; ttl?: number },\n): Promise<string> {\n const passwordsMap = normalizeStringPasswordToMap(password);\n\n const mostRecentPasswordId = Math.max(...Object.keys(passwordsMap).map(Number));\n const secret = passwordsMap[mostRecentPasswordId];\n\n if (secret === undefined) {\n throw new Error(\n \"iron-session: Bad usage. The password map has no usable entry, it must be a non-empty object keyed by numbers, for example { 1: 'your-password' }.\",\n );\n }\n\n const passwordForSeal = { id: mostRecentPasswordId.toString(), secret };\n\n let seal: string;\n try {\n // Spread into a plain object: iron-webcrypto v2 refuses to encode the\n // non-enumerable save/destroy/updateConfig properties we define on sessions.\n seal = await ironSeal(\n data !== null && typeof data === \"object\" ? { ...data } : data,\n passwordForSeal,\n { ...ironDefaults, ttl: ttl * 1000 },\n );\n } catch (error) {\n if (error instanceof Error && error.message === \"Data is not JSON serializable\") {\n throw new Error(\n \"iron-session: The session data is not JSON serializable. Store plain JSON values only: a Date must be stored as a timestamp (Date.now()) or an ISO string, and Map/Set/BigInt/undefined/functions are not supported.\",\n { cause: error },\n );\n }\n throw error;\n }\n\n return `${seal}${versionDelimiter}${currentMajorVersion}`;\n}\n\nfunction classifyUnsealError(error: unknown): UnsealErrorReason {\n if (!(error instanceof Error)) return \"invalid\";\n if (error.message.startsWith(\"Expired seal\")) return \"expired\";\n if (error.message.startsWith(\"Cannot find password\")) return \"unknown-password\";\n // Everything else means \"this string is not a seal we can read\": bad hmac,\n // wrong mac prefix, invalid expiration, wrong component count, bad base64.\n // We deliberately do not enumerate iron-webcrypto's messages here: an\n // unrecognised failure must still reset the session rather than throw a 500\n // on every request from a browser holding a poisoned cookie.\n return \"invalid\";\n}\n\nexport async function unsealData<T>(\n seal: string,\n {\n password,\n ttl = fourteenDaysInSeconds,\n onUnsealError,\n }: {\n password: Password;\n ttl?: number;\n onUnsealError?: (reason: UnsealErrorReason, error: unknown) => void;\n },\n): Promise<T> {\n const passwordsMap = normalizeStringPasswordToMap(password);\n const sealWithoutVersion = stripVersion(seal);\n\n try {\n const data =\n (await ironUnseal(sealWithoutVersion, passwordsMap, {\n ...ironDefaults,\n ttl: ttl * 1000,\n })) ?? {};\n\n return data as T;\n } catch (error) {\n onUnsealError?.(classifyUnsealError(error), error);\n return {} as T;\n }\n}\n\n/**\n * The one thing iron-session needs from a runtime: read a cookie by name, and\n * write a cookie back. Everything else (Node req/res, web Request/Response,\n * Next's `cookies()`, Next's proxy) is an adapter that produces one of these.\n *\n * There used to be two copies of the read/save/destroy logic, one per calling\n * convention, and they drifted: the cookie size limit was computed differently\n * in each, and only one of them checked whether it was still possible to send a\n * header. Everything now goes through a single implementation.\n */\nexport interface CookieJar {\n read: (name: string) => string | undefined;\n write: (name: string, value: string, options: CookieOptions) => void;\n /**\n * Names of the cookies present on the request, when the runtime can list\n * them. Used to find cookie chunks.\n */\n names?: () => string[];\n /**\n * Throws if a cookie can no longer be sent, for runtimes where writing after\n * a certain point is silently dropped. Losing a session cookie without an\n * error is much worse than a loud failure.\n */\n assertWritable?: () => void;\n}\n\nfunction isWebRequest(req: RequestType): req is Request {\n return \"headers\" in req && typeof (req as Request).headers.get === \"function\";\n}\n\nfunction readCookieHeader(req: RequestType, name: string): string | undefined {\n const header = isWebRequest(req) ? req.headers.get(\"cookie\") : req.headers.cookie;\n return parseCookie(header ?? \"\")[name];\n}\n\nfunction cookieHeaderNames(req: RequestType): string[] {\n const header = isWebRequest(req) ? req.headers.get(\"cookie\") : req.headers.cookie;\n return Object.keys(parseCookie(header ?? \"\"));\n}\n\n/**\n * `cookie@2` rejects an explicit `maxAge: undefined` under\n * `exactOptionalPropertyTypes`, and an absent `Max-Age` is exactly what\n * `maxAge: undefined` is meant to produce, so the key is dropped instead of\n * forwarded.\n */\nfunction serializeCookie(\n name: string,\n value: string,\n { maxAge, ...cookieOptions }: CookieOptions,\n): string {\n return stringifySetCookie({\n ...cookieOptions,\n ...(maxAge === undefined ? {} : { maxAge }),\n name,\n value,\n });\n}\n\n/**\n * Node's `http` server, which is also Express, Connect and Next.js API routes.\n *\n * @example\n * const session = await getIronSession(nodeCookies(req, res), options);\n */\nexport function nodeCookies(req: IncomingMessage, res: ServerResponse): CookieJar {\n return {\n read: (name) => readCookieHeader(req, name),\n names: () => cookieHeaderNames(req),\n assertWritable: () => {\n if (res.headersSent) {\n throw new Error(\n \"iron-session: Cannot set session cookie: session.save() was called after headers were sent. Make sure to call it before any res.send() or res.end()\",\n );\n }\n },\n write: (name, value, options) => {\n const existing = res.getHeader(\"set-cookie\") ?? [];\n const previous = Array.isArray(existing) ? existing : [existing.toString()];\n res.setHeader(\"set-cookie\", [...previous, serializeCookie(name, value, options)]);\n },\n };\n}\n\n/**\n * Web-standard `Request` plus anything with appendable headers: a `Response`,\n * a bare `Headers`, or Next's `NextResponse`.\n *\n * @example\n * const headers = new Headers();\n * const session = await getIronSession(webCookies(request, headers), options);\n * return new Response(body, { headers });\n */\nexport function webCookies(request: Request, response: Response | Headers): CookieJar {\n const headers = response instanceof Headers ? response : response.headers;\n\n return {\n read: (name) => readCookieHeader(request, name),\n names: () => cookieHeaderNames(request),\n assertWritable: () => {\n // A Response the runtime has already started sending ignores header\n // mutations without complaining, which loses the session silently.\n if (response instanceof Response && response.bodyUsed) {\n throw new Error(\n \"iron-session: Cannot set session cookie: the response body has already been consumed, so the Set-Cookie header would be dropped. Call session.save() before returning or streaming the response.\",\n );\n }\n },\n write: (name, value, options) => {\n headers.append(\"set-cookie\", serializeCookie(name, value, options));\n },\n };\n}\n\n/**\n * The parts of `NextRequest`/`NextResponse` we touch, structurally typed so\n * `next` is not a dependency.\n *\n * Request and response cookies are not the same type in Next: request cookies\n * take `(name, value)` only, because attributes are meaningless on an incoming\n * cookie, while response cookies take `(name, value, options)`. Declaring one\n * shared interface for both is what made `NextRequest` fail to assign.\n */\ninterface NextRequestCookies {\n get: (name: string) => { name: string; value: string } | undefined;\n getAll?: () => { name: string; value: string }[];\n set: (name: string, value: string) => unknown;\n}\n\ninterface NextResponseCookies {\n set: (name: string, value: string, options: Partial<ResponseCookie>) => unknown;\n}\n\n/**\n * Next.js `proxy.ts` (called `middleware.ts` before Next 16).\n *\n * Writing a raw `set-cookie` header here does not work the way you would\n * expect: Next only merges a cookie into the current render when it goes\n * through `response.cookies.set()`, so `session.save()` in middleware appeared\n * to succeed and then vanished. Writing to `request.cookies` as well makes the\n * new value visible to code that reads the session later in the same request,\n * which is what makes rotation work.\n *\n * @example\n * export async function proxy(request: NextRequest) {\n * const response = NextResponse.next();\n * const session = await getIronSession(nextProxyCookies(request, response), options);\n * session.lastSeen = Date.now();\n * await session.save();\n * return response;\n * }\n */\nexport function nextProxyCookies(\n request: { cookies: NextRequestCookies },\n response: { cookies: NextResponseCookies },\n): CookieJar {\n return {\n read: (name) => request.cookies.get(name)?.value,\n names: () => request.cookies.getAll?.().map((cookie) => cookie.name) ?? [],\n write: (name, value, options) => {\n // The response carries the cookie to the browser.\n response.cookies.set(name, value, options);\n // The request makes it visible to the rest of this same request.\n // No attributes here on purpose, they mean nothing on an incoming cookie\n // and Next's request cookies only accept a name and a value.\n request.cookies.set(name, value);\n },\n };\n}\n\n/** Wraps a `cookies()`-style store (Next's App Router) in a jar. */\nfunction cookieStoreJar(cookieStore: CookieStore): CookieJar {\n return {\n read: (name) => cookieStore.get(name)?.value,\n names: () => cookieStore.getAll?.().map((cookie) => cookie.name) ?? [],\n write: (name, value, options) => {\n cookieStore.set(name, value, options);\n },\n };\n}\n\nfunction isCookieJar(value: unknown): value is CookieJar {\n return (\n typeof value === \"object\" &&\n value !== null &&\n typeof (value as CookieJar).read === \"function\" &&\n typeof (value as CookieJar).write === \"function\"\n );\n}\n\nfunction isCookieStore(value: unknown): value is CookieStore {\n return (\n typeof value === \"object\" &&\n value !== null &&\n typeof (value as CookieStore).get === \"function\" &&\n typeof (value as CookieStore).set === \"function\"\n );\n}\n\n/** The options that actually shape the cookie, with defaults applied. */\ntype SessionConfig = Required<\n Pick<SessionOptions, \"cookieName\" | \"password\" | \"ttl\" | \"cookieOptions\" | \"chunk\">\n> & {\n passwordsMap: PasswordsMap;\n};\n\nfunction getSessionConfig(sessionOptions: SessionOptions): SessionConfig {\n if (!sessionOptions.cookieName) {\n throw new Error(\"iron-session: Bad usage. Missing cookie name.\");\n }\n\n if (!sessionOptions.password) {\n throw new Error(\"iron-session: Bad usage. Missing password.\");\n }\n\n const passwordsMap = normalizeStringPasswordToMap(sessionOptions.password);\n\n if (Object.keys(passwordsMap).length === 0) {\n throw new Error(\n \"iron-session: Bad usage. The password map is empty, it must be keyed by numbers, for example { 1: 'your-password' }.\",\n );\n }\n\n for (const [id, password] of Object.entries(passwordsMap)) {\n if (!Number.isInteger(Number(id))) {\n throw new Error(\n `iron-session: Bad usage. Password ids must be integers, got ${JSON.stringify(id)}. Use { 1: '...', 2: '...' }.`,\n );\n }\n if (typeof password !== \"string\" || password.length < 32) {\n throw new Error(\"iron-session: Bad usage. Password must be at least 32 characters long.\");\n }\n }\n\n const options = {\n ...defaultOptions,\n ...sessionOptions,\n passwordsMap,\n cookieOptions: { ...defaultOptions.cookieOptions, ...sessionOptions.cookieOptions },\n };\n\n if (sessionOptions.cookieOptions && \"maxAge\" in sessionOptions.cookieOptions) {\n if (sessionOptions.cookieOptions.maxAge === undefined) {\n // session cookies, do not set maxAge, consider token as infinite\n options.ttl = 0;\n }\n } else {\n options.cookieOptions.maxAge = computeCookieMaxAge(options.ttl);\n }\n\n const { expires } = options.cookieOptions;\n if (expires instanceof Date && expires.getTime() < Date.now()) {\n throw new Error(\n `iron-session: Bad usage. cookieOptions.expires is in the past (${expires.toISOString()}), so the browser will discard the cookie and the session will never persist. This usually means the Date was created once at module scope. Use \\`ttl\\` instead.`,\n );\n }\n\n return options;\n}\n\n/**\n * The single session implementation. Reads the cookie through the jar, and\n * defines save/destroy/updateConfig against that same jar.\n */\nasync function createSession<T extends object>(\n jar: CookieJar,\n sessionOptions: SessionOptions,\n): Promise<IronSession<T>> {\n let config = getSessionConfig(sessionOptions);\n let onUnsealError = sessionOptions.onUnsealError;\n\n const sealFromCookies = readSeal(jar, config.cookieName);\n const session = sealFromCookies\n ? await unsealData<T>(sealFromCookies, {\n password: config.passwordsMap,\n ttl: config.ttl,\n ...(onUnsealError ? { onUnsealError } : {}),\n })\n : ({} as T);\n\n // `destroy()` used to only clear the object and queue an expired cookie, so a\n // later `save()` re-sealed the same session and the last Set-Cookie won. A\n // wrapper that refreshed a rolling expiry at end of request silently\n // cancelled every logout in the app.\n let destroyed = false;\n\n Object.defineProperties(session, {\n updateConfig: {\n value: function updateConfig(newSessionOptions: SessionOptions) {\n // Rebuilt in full, including the password map. It used to only refresh\n // the cookie options, so passing a new password here silently kept\n // sealing with the old one and skipped the length check entirely.\n config = getSessionConfig(newSessionOptions);\n onUnsealError = newSessionOptions.onUnsealError;\n },\n },\n save: {\n value: async function save() {\n if (destroyed) {\n throw new Error(\n \"iron-session: Cannot save a destroyed session. session.destroy() signs the user out, and saving afterwards would restore the cookie you just cleared. Get a fresh session if you need to write one.\",\n );\n }\n\n jar.assertWritable?.();\n\n const seal = await sealData(session, {\n password: config.passwordsMap,\n ttl: config.ttl,\n });\n\n writeSeal(jar, config, seal);\n },\n },\n destroy: {\n value: function destroy() {\n destroyed = true;\n for (const key of Object.keys(session)) {\n delete (session as Record<string, unknown>)[key];\n }\n jar.write(config.cookieName, \"\", { ...config.cookieOptions, maxAge: 0 });\n // Leaving chunks behind would let a later read reassemble a stale seal.\n clearStaleCookies(jar, config.cookieName, config.cookieOptions, {\n whole: true,\n chunksUpTo: 0,\n });\n },\n },\n });\n\n return session as IronSession<T>;\n}\n\n/** Browsers cap one cookie at 4096 bytes over the whole `Set-Cookie` value. */\nconst MAX_COOKIE_BYTES = 4096;\n\n/**\n * Hard cap on chunks, not configurable on purpose.\n *\n * Four chunks is already ~16 KB of `Cookie` header on every single request, and\n * nginx's default `large_client_header_buffers` is 8 KB. Letting people raise\n * this just moves the failure from our error message to a 400 at their CDN.\n */\nconst MAX_CHUNKS = 4;\n\nconst chunkName = (cookieName: string, index: number): string => `${cookieName}.${index}`;\n\nfunction cookieBytes(name: string, value: string, cookieOptions: CookieOptions): number {\n return new TextEncoder().encode(serializeCookie(name, value, cookieOptions)).length;\n}\n\n/**\n * Reads the seal, whether it was stored in one cookie or split across several.\n *\n * Both shapes are always accepted regardless of the `chunk` setting, so turning\n * chunking on or off does not invalidate cookies that are already out there.\n *\n * Chunk discovery probes `name.0`, `name.1`, ... and stops at the first gap,\n * bounded by {@link MAX_CHUNKS}. There is deliberately no cookie holding the\n * chunk count: that value would be attacker-controlled, and a `name.count` of\n * 99999999 is free CPU amplification before anyone is authenticated.\n *\n * The chunks are concatenated and handed to `unseal` whole. Nothing here\n * inspects, validates or trusts an individual chunk, and that is what makes\n * reassembly safe: the HMAC already covers the entire seal string, so a deleted\n * chunk, reordered chunks, or a chunk swapped in from a different session all\n * fail integrity and land in the unreadable-cookie path.\n */\nfunction readSeal(jar: CookieJar, cookieName: string): string {\n const whole = jar.read(cookieName);\n if (whole) {\n return whole;\n }\n\n let seal = \"\";\n for (let index = 0; index < MAX_CHUNKS; index += 1) {\n const part = jar.read(chunkName(cookieName, index));\n if (!part) {\n break;\n }\n seal += part;\n }\n\n return seal;\n}\n\n/**\n * Expires cookies that are no longer part of the session.\n *\n * This is the bug chunking would otherwise ship with. A session that shrinks\n * from three chunks to one leaves `name.1` and `name.2` in the browser, the next\n * read concatenates the new chunk 0 with the two stale ones, the HMAC fails, and\n * the user is signed out on every request from then on while `save()` keeps\n * reporting success. There is no error anywhere in that loop.\n *\n * Only cookies actually present on the request are expired, so a normal\n * unchunked save does not emit four pointless `Set-Cookie` headers. The expiry\n * reuses the same `path` and `domain` as the write, otherwise the browser treats\n * it as a different cookie and the delete does nothing.\n */\nfunction clearStaleCookies(\n jar: CookieJar,\n cookieName: string,\n cookieOptions: CookieOptions,\n keep: { whole: boolean; chunksUpTo: number },\n): void {\n const expire = (name: string): void => {\n if (jar.read(name)) {\n jar.write(name, \"\", { ...cookieOptions, maxAge: 0 });\n }\n };\n\n if (!keep.whole) {\n expire(cookieName);\n }\n\n for (let index = keep.chunksUpTo; index < MAX_CHUNKS; index += 1) {\n expire(chunkName(cookieName, index));\n }\n}\n\n/**\n * Writes the seal, splitting it across cookies when it does not fit and\n * chunking is enabled.\n *\n * The seal is split as an opaque string, after the version suffix is applied to\n * the whole thing. Rejoining is a plain concatenation with no separator.\n */\nfunction writeSeal(jar: CookieJar, config: SessionConfig, seal: string): void {\n const { cookieName, cookieOptions } = config;\n const wholeBytes = cookieBytes(cookieName, seal, cookieOptions);\n\n if (wholeBytes <= MAX_COOKIE_BYTES) {\n jar.write(cookieName, seal, cookieOptions);\n clearStaleCookies(jar, cookieName, cookieOptions, { whole: true, chunksUpTo: 0 });\n return;\n }\n\n if (!config.chunk) {\n // If the session we just read was itself chunked, the problem is almost\n // certainly a second options object somewhere without `chunk` set, rather\n // than a session that suddenly grew. Say so: otherwise this reads as \"it\n // works in my route handler but not in my middleware\".\n const wasChunked = Boolean(jar.read(chunkName(cookieName, 0)));\n\n throw new Error(\n wasChunked\n ? `iron-session: Cookie length is too big (${wholeBytes} bytes) and \\`chunk\\` is not enabled here, but this session is already stored across several cookies. You have more than one options object and only some of them set \\`chunk: true\\`. Use the same options everywhere you call getIronSession, including middleware.`\n : `iron-session: Cookie length is too big (${wholeBytes} bytes), browsers will refuse it. Remove some data from the session, or set \\`chunk: true\\` to split it across several cookies.`,\n );\n }\n\n // Every chunk index is a single digit because MAX_CHUNKS is 4, so all chunk\n // names are the same length and one budget works for all of them.\n const perChunkOverhead = cookieBytes(chunkName(cookieName, 0), \"\", cookieOptions);\n const budget = MAX_COOKIE_BYTES - perChunkOverhead;\n\n if (budget <= 0) {\n throw new Error(\n `iron-session: The cookie name and options alone take ${perChunkOverhead} bytes, which leaves no room for session data. Use a shorter cookie name.`,\n );\n }\n\n const chunks: string[] = [];\n for (let offset = 0; offset < seal.length; offset += budget) {\n chunks.push(seal.slice(offset, offset + budget));\n }\n\n if (chunks.length > MAX_CHUNKS) {\n throw new Error(\n `iron-session: The session needs ${chunks.length} cookies and the maximum is ${MAX_CHUNKS}. Even at ${MAX_CHUNKS} the whole Cookie header is sent on every request and proxies commonly cap it at 8 KB, so raising this would fail at your CDN instead. Store an id in the session and keep the data in your database.`,\n );\n }\n\n chunks.forEach((value, index) => {\n jar.write(chunkName(cookieName, index), value, cookieOptions);\n });\n\n clearStaleCookies(jar, cookieName, cookieOptions, {\n whole: false,\n chunksUpTo: chunks.length,\n });\n}\n\nconst badUsageMessage =\n \"iron-session: Bad usage: use getIronSession(req, res, options) or getIronSession(cookieStore, options).\";\n\nexport async function getIronSession<T extends object>(\n cookies: CookieStore | CookieJar,\n sessionOptions: SessionOptions,\n): Promise<IronSession<T>>;\nexport async function getIronSession<T extends object>(\n req: RequestType,\n res: ResponseType,\n sessionOptions: SessionOptions,\n): Promise<IronSession<T>>;\nexport async function getIronSession<T extends object>(\n first: RequestType | CookieStore | CookieJar,\n second: ResponseType | SessionOptions,\n third?: SessionOptions,\n): Promise<IronSession<T>> {\n if (!first || !second) {\n throw new Error(badUsageMessage);\n }\n\n // getIronSession(cookieStoreOrJar, options)\n if (!third) {\n const options = second as SessionOptions;\n\n if (isCookieJar(first)) {\n return createSession<T>(first, options);\n }\n\n if (isCookieStore(first)) {\n return createSession<T>(cookieStoreJar(first), options);\n }\n\n throw new Error(badUsageMessage);\n }\n\n // getIronSession(req, res, options), kept so Node and Express keep working\n // without a code change. It just picks the matching adapter.\n const req = first as RequestType;\n const res = second as ResponseType;\n const jar = isWebRequest(req)\n ? webCookies(req, res as Response)\n : nodeCookies(req, res as ServerResponse);\n\n return createSession<T>(jar, third);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["seal","ironSeal","ironDefaults","ironUnseal"],"sources":["../src/core.ts"],"sourcesContent":["import type { IncomingMessage, ServerResponse } from \"node:http\";\nimport { parseCookie, stringifySetCookie, type SetCookie } from \"cookie\";\nimport { defaults as ironDefaults, seal as ironSeal, unseal as ironUnseal } from \"iron-webcrypto\";\n\ntype PasswordsMap = Record<string, string>;\ntype Password = PasswordsMap | string;\ntype RequestType = IncomingMessage | Request;\ntype ResponseType = Response | ServerResponse;\n\n/**\n * {@link https://wicg.github.io/cookie-store/#dictdef-cookielistitem CookieListItem}\n * as specified by W3C.\n */\ninterface CookieListItem extends Pick<SetCookie, \"domain\" | \"path\" | \"sameSite\" | \"secure\"> {\n /** A string with the name of a cookie. */\n name: string;\n /** A string containing the value of the cookie. */\n value: string;\n /** A number of milliseconds or Date interface containing the expires of the cookie. */\n expires?: SetCookie[\"expires\"] | number;\n}\n\n/**\n * Superset of {@link CookieListItem} extending it with\n * the `httpOnly`, `maxAge` and `priority` properties.\n */\ntype ResponseCookie = CookieListItem &\n Pick<SetCookie, \"httpOnly\" | \"priority\"> & {\n maxAge?: number | undefined;\n };\n\n/**\n * What iron-session needs from a cookie store. This is deliberately the shape\n * of `await cookies()` from `next/headers`, so it can be passed in directly.\n *\n * `getAll` is optional: it is only used to find cookie chunks, and stores that\n * cannot enumerate are probed by name instead.\n */\nexport interface CookieStore {\n get: (name: string) => { name: string; value: string } | undefined;\n getAll?: () => { name: string; value: string }[];\n /**\n * One signature with a required third argument, returning `unknown`.\n *\n * Every part of that matters for `getIronSession(await cookies(), ...)` to\n * typecheck against Next.js, which is what #840 was about:\n *\n * - not an overload pair. Next declares a single signature over a tuple\n * union, and a single signature is not assignable to an overload pair.\n * - the third argument is required, not optional. Next's tuple element is\n * `cookie?: Partial<ResponseCookie>`, and under\n * `exactOptionalPropertyTypes` our optional parameter widened to\n * `Partial<ResponseCookie> | undefined`, which their tuple rejects. We\n * always pass cookie options anyway.\n * - `unknown` return. Next returns the cookie store itself, not void.\n */\n set: (name: string, value: string, cookie: Partial<ResponseCookie>) => unknown;\n}\n\n/**\n * Set-Cookie attributes. `name` and `value` are owned by iron-session\n * (`cookieName` and the seal), so they are not configurable here.\n *\n * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie\n * @see https://developer.chrome.com/docs/devtools/application/cookies/\n */\ntype CookieOptions = Omit<SetCookie, \"name\" | \"value\" | \"maxAge\"> & {\n /**\n * Explicitly allows `undefined` so that `cookieOptions: { maxAge: undefined }`\n * keeps working under `exactOptionalPropertyTypes`. That is the documented way\n * to ask for a \"session cookie\" that the browser drops when it closes.\n */\n maxAge?: number | undefined;\n};\n\n/**\n * Why an existing cookie could not be read. Every reason results in a fresh,\n * empty session: a session library cannot tell a tampered cookie from a\n * badly rotated password, so the only safe outcome is to start over.\n * Use `onUnsealError` to observe these, they are otherwise invisible.\n */\nexport type UnsealErrorReason =\n /** The seal is past its expiration. Normal, this is how sessions end. */\n | \"expired\"\n /** Integrity check failed, or the value is not a seal at all. Possible tampering. */\n | \"invalid\"\n /** The seal references a password id that is not in the password map. Usually a rotation mistake. */\n | \"unknown-password\";\n\nexport interface SessionOptions {\n /**\n * The cookie name that will be used inside the browser. Make sure it's unique\n * given your application.\n *\n * @example 'vercel-session'\n */\n cookieName: string;\n\n /**\n * The password(s) that will be used to encrypt the cookie. Can either be a string\n * or an object.\n *\n * When you provide multiple passwords then all of them will be used to decrypt\n * the cookie. But only the most recent (`= highest key`, `2` in the example)\n * password will be used to encrypt the cookie. This allows password rotation.\n *\n * @example { 1: 'password-1', 2: 'password-2' }\n */\n password: Password;\n\n /**\n * The time (in seconds) that the session will be valid for. Also sets the\n * `max-age` attribute of the cookie automatically (`= ttl - 60s`, so that the\n * cookie always expire before the session).\n *\n * `ttl = 0` means no expiration. Do not use it for authentication: the seal\n * is then accepted forever and there is no way to revoke it.\n *\n * @default 1209600\n */\n ttl?: number;\n\n /**\n * The options that will be passed to the cookie library.\n *\n * If you want to use \"session cookies\" (cookies that are deleted when the browser\n * is closed) then you need to pass `cookieOptions: { maxAge: undefined }`\n *\n * @see https://github.com/jshttp/cookie#options-1\n */\n cookieOptions?: CookieOptions;\n\n /**\n * Called when an existing cookie could not be read, right before the session\n * is reset to an empty object.\n *\n * A stateless session library cannot tell a tampered cookie from a badly\n * rotated password, so it always starts a new session rather than throwing.\n * That is the safe default, but it means genuine problems are invisible:\n * a `\"unknown-password\"` burst usually means a broken password rotation, and\n * an `\"invalid\"` burst can mean someone is probing your cookies. Log them.\n *\n * This hook must not throw. It is not a place to deny access: the session is\n * empty either way.\n *\n * @example\n * onUnsealError: (reason, error) => {\n * if (reason !== \"expired\") logger.warn({ reason, error }, \"session cookie rejected\");\n * }\n */\n onUnsealError?: (reason: UnsealErrorReason, error: unknown) => void;\n\n /**\n * Split a session that does not fit in one cookie across several cookies,\n * named `<cookieName>.0`, `<cookieName>.1` and so on.\n *\n * Off by default, and reading works either way: turning this on or off does\n * not sign anyone out.\n *\n * Read the size limits before reaching for this. A browser caps one cookie at\n * 4096 bytes, but the real constraint is the request side: every cookie is\n * sent on every request, and proxies cap the whole `Cookie` header well below\n * what four chunks can produce. nginx allows 8 KB by default, and a CDN or\n * load balancer in front of it may allow less. Going over that returns 400 or\n * 431 at the edge, before your app runs, which is much harder to debug than\n * an error from us. iron-session refuses more than {@link MAX_CHUNKS} chunks\n * for that reason.\n *\n * Chunking is an escape hatch for a session slightly over the limit. If you\n * need a lot of room, store an id in the session and keep the data in your\n * database.\n *\n * @default false\n */\n chunk?: boolean;\n}\n\n/**\n * A session object: your data, plus `save`, `destroy` and `updateConfig`.\n *\n * `T` is wrapped in `Partial` because a session that does not exist yet reads as\n * an empty object. Declaring `IronSession<{ user: User }>` used to promise that\n * `session.user` was there, so `session.user.id` typechecked and then threw at\n * runtime on the first visit, on an expired cookie, and after `destroy()`.\n */\nexport type IronSession<T> = Partial<T> & {\n /**\n * Encrypts the session data and sets the cookie.\n */\n readonly save: () => Promise<void>;\n\n /**\n * Destroys the session data and removes the cookie.\n */\n readonly destroy: () => void;\n\n /**\n * Update the session configuration. You still need to call save() to send the new cookie.\n */\n readonly updateConfig: (newSessionOptions: SessionOptions) => void;\n};\n\n// default time allowed to check for iron seal validity when ttl passed\n// see https://hapi.dev/module/iron/api/?v=7.0.1#options\nconst timestampSkewSec = 60;\nconst fourteenDaysInSeconds = 14 * 24 * 3600;\n\n// We store a token major version to handle data format changes so that the cookies\n// can be kept alive between upgrades, no need to disconnect everyone.\nconst currentMajorVersion = 2;\nconst versionDelimiter = \"~\";\n\nconst defaultOptions: Required<Pick<SessionOptions, \"ttl\" | \"cookieOptions\" | \"chunk\">> = {\n ttl: fourteenDaysInSeconds,\n cookieOptions: { httpOnly: true, secure: true, sameSite: \"lax\", path: \"/\" },\n chunk: false,\n};\n\nfunction normalizeStringPasswordToMap(password: Password): PasswordsMap {\n return typeof password === \"string\" ? { 1: password } : password;\n}\n\n/**\n * Removes the trailing `~<version>` marker from a seal.\n *\n * The marker sits outside the seal's HMAC, so it is attacker-controlled and its\n * value must never select a code path. iron-session v8 used it to unwrap a\n * `persistent` key from v6-era cookies; v9 drops that format, so the marker is\n * now inert metadata that we strip and ignore.\n */\nfunction stripVersion(seal: string): string {\n const delimiterIndex = seal.indexOf(versionDelimiter);\n return delimiterIndex === -1 ? seal : seal.slice(0, delimiterIndex);\n}\n\nfunction computeCookieMaxAge(ttl: number): number {\n if (ttl === 0) {\n // ttl = 0 means no expiration\n // but in reality cookies have to expire (can't have no max-age)\n // 2147483647 is the max value for max-age in cookies\n // see https://stackoverflow.com/a/11685301/147079\n return 2147483647;\n }\n\n // Expire the cookie slightly before the seal, and allow for a 60 second clock\n // difference between server and client. A ttl at or under that skew keeps its\n // full length: subtracting it produced `Max-Age=0` or a negative value, which\n // makes the browser drop the cookie on arrival while save() reported success.\n return ttl > timestampSkewSec ? ttl - timestampSkewSec : ttl;\n}\n\n/**\n * Names the value that iron-webcrypto refused, so the error points at\n * `session.lastSeen` instead of leaving you to find it.\n */\nfunction describeUnserializable(data: unknown, path = \"session\", depth = 0): string {\n const type = typeof data;\n\n if (type === \"bigint\" || type === \"function\" || type === \"symbol\") {\n return ` (${path} is a ${type})`;\n }\n if (type === \"number\" && !Number.isFinite(data)) return ` (${path} is ${String(data)})`;\n if (type !== \"object\" || data === null || depth > 5) return \"\";\n\n const object = data as object;\n\n // Date, Map, Set and class instances all land here: anything whose prototype\n // is not plain Object cannot survive a JSON round trip.\n const name = (object.constructor as { name?: string } | undefined)?.name;\n if (!Array.isArray(object) && name !== undefined && name !== \"Object\") {\n return ` (${path} is a ${name})`;\n }\n\n const entries: [string, unknown][] = Array.isArray(object)\n ? object.map((value, index) => [`[${index}]`, value])\n : Object.entries(object).map(([key, value]) => [`.${key}`, value]);\n\n for (const [suffix, value] of entries) {\n const found = describeUnserializable(value, `${path}${suffix}`, depth + 1);\n if (found) return found;\n }\n\n return \"\";\n}\n\nexport async function sealData(\n data: unknown,\n { password, ttl = fourteenDaysInSeconds }: { password: Password; ttl?: number },\n): Promise<string> {\n const passwordsMap = normalizeStringPasswordToMap(password);\n\n const mostRecentPasswordId = Math.max(...Object.keys(passwordsMap).map(Number));\n const secret = passwordsMap[mostRecentPasswordId];\n\n if (secret === undefined) {\n throw new Error(\n \"iron-session: Bad usage. The password map has no usable entry, it must be a non-empty object keyed by numbers, for example { 1: 'your-password' }.\",\n );\n }\n\n const passwordForSeal = { id: mostRecentPasswordId.toString(), secret };\n\n let seal: string;\n try {\n // Spread into a plain object: iron-webcrypto v2 refuses to encode the\n // non-enumerable save/destroy/updateConfig properties we define on sessions.\n seal = await ironSeal(\n data !== null && typeof data === \"object\" ? { ...data } : data,\n passwordForSeal,\n { ...ironDefaults, ttl: ttl * 1000 },\n );\n } catch (error) {\n if (error instanceof Error && error.message === \"Data is not JSON serializable\") {\n throw new Error(\n `iron-session: The session data is not JSON serializable${describeUnserializable(data)}. Store plain JSON values only: a Date must be stored as a timestamp (Date.now()) or an ISO string, and Map/Set/BigInt/functions are not supported.`,\n { cause: error },\n );\n }\n throw error;\n }\n\n return `${seal}${versionDelimiter}${currentMajorVersion}`;\n}\n\nfunction classifyUnsealError(error: unknown): UnsealErrorReason {\n if (!(error instanceof Error)) return \"invalid\";\n if (error.message.startsWith(\"Expired seal\")) return \"expired\";\n if (error.message.startsWith(\"Cannot find password\")) return \"unknown-password\";\n // Everything else means \"this string is not a seal we can read\": bad hmac,\n // wrong mac prefix, invalid expiration, wrong component count, bad base64.\n // We deliberately do not enumerate iron-webcrypto's messages here: an\n // unrecognised failure must still reset the session rather than throw a 500\n // on every request from a browser holding a poisoned cookie.\n return \"invalid\";\n}\n\nexport async function unsealData<T>(\n seal: string,\n {\n password,\n ttl = fourteenDaysInSeconds,\n onUnsealError,\n }: {\n password: Password;\n ttl?: number;\n onUnsealError?: (reason: UnsealErrorReason, error: unknown) => void;\n },\n): Promise<T> {\n const passwordsMap = normalizeStringPasswordToMap(password);\n const sealWithoutVersion = stripVersion(seal);\n\n try {\n const data =\n (await ironUnseal(sealWithoutVersion, passwordsMap, {\n ...ironDefaults,\n ttl: ttl * 1000,\n })) ?? {};\n\n return data as T;\n } catch (error) {\n onUnsealError?.(classifyUnsealError(error), error);\n return {} as T;\n }\n}\n\n/**\n * The one thing iron-session needs from a runtime: read a cookie by name, and\n * write a cookie back. Everything else (Node req/res, web Request/Response,\n * Next's `cookies()`, Next's proxy) is an adapter that produces one of these.\n *\n * There used to be two copies of the read/save/destroy logic, one per calling\n * convention, and they drifted: the cookie size limit was computed differently\n * in each, and only one of them checked whether it was still possible to send a\n * header. Everything now goes through a single implementation.\n */\nexport interface CookieJar {\n read: (name: string) => string | undefined;\n write: (name: string, value: string, options: CookieOptions) => void;\n /**\n * Names of the cookies present on the request, when the runtime can list\n * them. Used to find cookie chunks.\n */\n names?: () => string[];\n /**\n * Throws if a cookie can no longer be sent, for runtimes where writing after\n * a certain point is silently dropped. Losing a session cookie without an\n * error is much worse than a loud failure.\n */\n assertWritable?: () => void;\n}\n\nfunction isWebRequest(req: RequestType): req is Request {\n return \"headers\" in req && typeof (req as Request).headers.get === \"function\";\n}\n\nfunction readCookieHeader(req: RequestType, name: string): string | undefined {\n const header = isWebRequest(req) ? req.headers.get(\"cookie\") : req.headers.cookie;\n return parseCookie(header ?? \"\")[name];\n}\n\nfunction cookieHeaderNames(req: RequestType): string[] {\n const header = isWebRequest(req) ? req.headers.get(\"cookie\") : req.headers.cookie;\n return Object.keys(parseCookie(header ?? \"\"));\n}\n\n/**\n * `cookie@2` rejects an explicit `maxAge: undefined` under\n * `exactOptionalPropertyTypes`, and an absent `Max-Age` is exactly what\n * `maxAge: undefined` is meant to produce, so the key is dropped instead of\n * forwarded.\n */\nfunction serializeCookie(\n name: string,\n value: string,\n { maxAge, ...cookieOptions }: CookieOptions,\n): string {\n return stringifySetCookie({\n ...cookieOptions,\n ...(maxAge === undefined ? {} : { maxAge }),\n name,\n value,\n });\n}\n\n/**\n * Node's `http` server, which is also Express, Connect and Next.js API routes.\n *\n * @example\n * const session = await getIronSession(nodeCookies(req, res), options);\n */\nexport function nodeCookies(req: IncomingMessage, res: ServerResponse): CookieJar {\n return {\n read: (name) => readCookieHeader(req, name),\n names: () => cookieHeaderNames(req),\n assertWritable: () => {\n if (res.headersSent) {\n throw new Error(\n \"iron-session: Cannot set session cookie: session.save() was called after headers were sent. Make sure to call it before any res.send() or res.end()\",\n );\n }\n },\n write: (name, value, options) => {\n const existing = res.getHeader(\"set-cookie\") ?? [];\n const previous = Array.isArray(existing) ? existing : [existing.toString()];\n res.setHeader(\"set-cookie\", [...previous, serializeCookie(name, value, options)]);\n },\n };\n}\n\n/**\n * Web-standard `Request` plus anything with appendable headers: a `Response`,\n * a bare `Headers`, or Next's `NextResponse`.\n *\n * @example\n * const headers = new Headers();\n * const session = await getIronSession(webCookies(request, headers), options);\n * return new Response(body, { headers });\n */\nexport function webCookies(request: Request, response: Response | Headers): CookieJar {\n const headers = response instanceof Headers ? response : response.headers;\n\n return {\n read: (name) => readCookieHeader(request, name),\n names: () => cookieHeaderNames(request),\n assertWritable: () => {\n // A Response the runtime has already started sending ignores header\n // mutations without complaining, which loses the session silently.\n if (response instanceof Response && response.bodyUsed) {\n throw new Error(\n \"iron-session: Cannot set session cookie: the response body has already been consumed, so the Set-Cookie header would be dropped. Call session.save() before returning or streaming the response.\",\n );\n }\n },\n write: (name, value, options) => {\n headers.append(\"set-cookie\", serializeCookie(name, value, options));\n },\n };\n}\n\n/**\n * The parts of `NextRequest`/`NextResponse` we touch, structurally typed so\n * `next` is not a dependency.\n *\n * Request and response cookies are not the same type in Next: request cookies\n * take `(name, value)` only, because attributes are meaningless on an incoming\n * cookie, while response cookies take `(name, value, options)`. Declaring one\n * shared interface for both is what made `NextRequest` fail to assign.\n */\ninterface NextRequestCookies {\n get: (name: string) => { name: string; value: string } | undefined;\n getAll?: () => { name: string; value: string }[];\n set: (name: string, value: string) => unknown;\n}\n\ninterface NextResponseCookies {\n set: (name: string, value: string, options: Partial<ResponseCookie>) => unknown;\n}\n\n/**\n * Next.js `proxy.ts` (called `middleware.ts` before Next 16).\n *\n * Writing a raw `set-cookie` header here does not work the way you would\n * expect: Next only merges a cookie into the current render when it goes\n * through `response.cookies.set()`, so `session.save()` in middleware appeared\n * to succeed and then vanished. Writing to `request.cookies` as well makes the\n * new value visible to code that reads the session later in the same request,\n * which is what makes rotation work.\n *\n * @example\n * export async function proxy(request: NextRequest) {\n * const response = NextResponse.next();\n * const session = await getIronSession(nextProxyCookies(request, response), options);\n * session.lastSeen = Date.now();\n * await session.save();\n * return response;\n * }\n */\nexport function nextProxyCookies(\n request: { cookies: NextRequestCookies },\n response: { cookies: NextResponseCookies },\n): CookieJar {\n return {\n read: (name) => request.cookies.get(name)?.value,\n names: () => request.cookies.getAll?.().map((cookie) => cookie.name) ?? [],\n write: (name, value, options) => {\n // The response carries the cookie to the browser.\n response.cookies.set(name, value, options);\n // The request makes it visible to the rest of this same request.\n // No attributes here on purpose, they mean nothing on an incoming cookie\n // and Next's request cookies only accept a name and a value.\n request.cookies.set(name, value);\n },\n };\n}\n\n/** Wraps a `cookies()`-style store (Next's App Router) in a jar. */\nfunction cookieStoreJar(cookieStore: CookieStore): CookieJar {\n return {\n read: (name) => cookieStore.get(name)?.value,\n names: () => cookieStore.getAll?.().map((cookie) => cookie.name) ?? [],\n write: (name, value, options) => {\n cookieStore.set(name, value, options);\n },\n };\n}\n\nfunction isCookieJar(value: unknown): value is CookieJar {\n return (\n typeof value === \"object\" &&\n value !== null &&\n typeof (value as CookieJar).read === \"function\" &&\n typeof (value as CookieJar).write === \"function\"\n );\n}\n\nfunction isCookieStore(value: unknown): value is CookieStore {\n return (\n typeof value === \"object\" &&\n value !== null &&\n typeof (value as CookieStore).get === \"function\" &&\n typeof (value as CookieStore).set === \"function\"\n );\n}\n\n/** The options that actually shape the cookie, with defaults applied. */\ntype SessionConfig = Required<\n Pick<SessionOptions, \"cookieName\" | \"password\" | \"ttl\" | \"cookieOptions\" | \"chunk\">\n> & {\n passwordsMap: PasswordsMap;\n};\n\nfunction getSessionConfig(sessionOptions: SessionOptions): SessionConfig {\n if (!sessionOptions.cookieName) {\n throw new Error(\"iron-session: Bad usage. Missing cookie name.\");\n }\n\n if (!sessionOptions.password) {\n throw new Error(\"iron-session: Bad usage. Missing password.\");\n }\n\n const passwordsMap = normalizeStringPasswordToMap(sessionOptions.password);\n\n if (Object.keys(passwordsMap).length === 0) {\n throw new Error(\n \"iron-session: Bad usage. The password map is empty, it must be keyed by numbers, for example { 1: 'your-password' }.\",\n );\n }\n\n for (const [id, password] of Object.entries(passwordsMap)) {\n if (!Number.isInteger(Number(id))) {\n throw new Error(\n `iron-session: Bad usage. Password ids must be integers, got ${JSON.stringify(id)}. Use { 1: '...', 2: '...' }.`,\n );\n }\n if (typeof password !== \"string\" || password.length < 32) {\n throw new Error(\"iron-session: Bad usage. Password must be at least 32 characters long.\");\n }\n }\n\n const options = {\n ...defaultOptions,\n ...sessionOptions,\n passwordsMap,\n cookieOptions: { ...defaultOptions.cookieOptions, ...sessionOptions.cookieOptions },\n };\n\n if (sessionOptions.cookieOptions && \"maxAge\" in sessionOptions.cookieOptions) {\n if (sessionOptions.cookieOptions.maxAge === undefined) {\n // session cookies, do not set maxAge, consider token as infinite\n options.ttl = 0;\n }\n } else {\n options.cookieOptions.maxAge = computeCookieMaxAge(options.ttl);\n }\n\n return options;\n}\n\n/**\n * Only checked when writing: reading a session does not set a cookie, so a\n * stale `expires` should not break a page that just reads one.\n */\nfunction assertCookieCanPersist(cookieOptions: CookieOptions): void {\n const { expires } = cookieOptions;\n if (expires instanceof Date && expires.getTime() < Date.now()) {\n throw new Error(\n `iron-session: Bad usage. cookieOptions.expires is in the past (${expires.toISOString()}), so the browser will discard the cookie and the session will never persist. This usually means the Date was created once at module scope. Use \\`ttl\\` instead.`,\n );\n }\n}\n\n/**\n * The single session implementation. Reads the cookie through the jar, and\n * defines save/destroy/updateConfig against that same jar.\n */\nasync function createSession<T extends object>(\n jar: CookieJar,\n sessionOptions: SessionOptions,\n): Promise<IronSession<T>> {\n let config = getSessionConfig(sessionOptions);\n let onUnsealError = sessionOptions.onUnsealError;\n\n const sealFromCookies = readSeal(jar, config.cookieName);\n const session = sealFromCookies\n ? await unsealData<T>(sealFromCookies, {\n password: config.passwordsMap,\n ttl: config.ttl,\n ...(onUnsealError ? { onUnsealError } : {}),\n })\n : ({} as T);\n\n // `destroy()` used to only clear the object and queue an expired cookie, so a\n // later `save()` re-sealed the same session and the last Set-Cookie won. A\n // wrapper that refreshed a rolling expiry at end of request silently\n // cancelled every logout in the app.\n let destroyed = false;\n\n Object.defineProperties(session, {\n updateConfig: {\n value: function updateConfig(newSessionOptions: SessionOptions) {\n // Rebuilt in full, including the password map. It used to only refresh\n // the cookie options, so passing a new password here silently kept\n // sealing with the old one and skipped the length check entirely.\n config = getSessionConfig(newSessionOptions);\n onUnsealError = newSessionOptions.onUnsealError;\n },\n },\n save: {\n value: async function save() {\n if (destroyed) {\n // `destroy()` already cleared the cookie, so saving nothing on top of\n // it is redundant rather than wrong: plenty of logout handlers call\n // both, and that end state is the one they wanted. Writing data after\n // a destroy is the real bug, because the last Set-Cookie wins and the\n // user silently stays signed in.\n if (Object.keys(session).length === 0) {\n return;\n }\n\n throw new Error(\n \"iron-session: Cannot save a destroyed session that has data written back into it. session.destroy() signs the user out, and saving these fields would restore the cookie you just cleared, leaving the user signed in. Get a fresh session if you need to write one.\",\n );\n }\n\n jar.assertWritable?.();\n assertCookieCanPersist(config.cookieOptions);\n\n const seal = await sealData(session, {\n password: config.passwordsMap,\n ttl: config.ttl,\n });\n\n writeSeal(jar, config, seal);\n },\n },\n destroy: {\n value: function destroy() {\n destroyed = true;\n for (const key of Object.keys(session)) {\n delete (session as Record<string, unknown>)[key];\n }\n jar.write(config.cookieName, \"\", { ...config.cookieOptions, maxAge: 0 });\n // Leaving chunks behind would let a later read reassemble a stale seal.\n clearStaleCookies(jar, config.cookieName, config.cookieOptions, {\n whole: true,\n chunksUpTo: 0,\n });\n },\n },\n });\n\n return session as IronSession<T>;\n}\n\n/** Browsers cap one cookie at 4096 bytes over the whole `Set-Cookie` value. */\nconst MAX_COOKIE_BYTES = 4096;\n\n/**\n * Hard cap on chunks, not configurable on purpose.\n *\n * Four chunks is already ~16 KB of `Cookie` header on every single request, and\n * nginx's default `large_client_header_buffers` is 8 KB. Letting people raise\n * this just moves the failure from our error message to a 400 at their CDN.\n */\nconst MAX_CHUNKS = 4;\n\nconst chunkName = (cookieName: string, index: number): string => `${cookieName}.${index}`;\n\nfunction cookieBytes(name: string, value: string, cookieOptions: CookieOptions): number {\n return new TextEncoder().encode(serializeCookie(name, value, cookieOptions)).length;\n}\n\n/**\n * Reads the seal, whether it was stored in one cookie or split across several.\n *\n * Both shapes are always accepted regardless of the `chunk` setting, so turning\n * chunking on or off does not invalidate cookies that are already out there.\n *\n * Chunk discovery probes `name.0`, `name.1`, ... and stops at the first gap,\n * bounded by {@link MAX_CHUNKS}. There is deliberately no cookie holding the\n * chunk count: that value would be attacker-controlled, and a `name.count` of\n * 99999999 is free CPU amplification before anyone is authenticated.\n *\n * The chunks are concatenated and handed to `unseal` whole. Nothing here\n * inspects, validates or trusts an individual chunk, and that is what makes\n * reassembly safe: the HMAC already covers the entire seal string, so a deleted\n * chunk, reordered chunks, or a chunk swapped in from a different session all\n * fail integrity and land in the unreadable-cookie path.\n */\nfunction readSeal(jar: CookieJar, cookieName: string): string {\n const whole = jar.read(cookieName);\n if (whole) {\n return whole;\n }\n\n let seal = \"\";\n for (let index = 0; index < MAX_CHUNKS; index += 1) {\n const part = jar.read(chunkName(cookieName, index));\n if (!part) {\n break;\n }\n seal += part;\n }\n\n return seal;\n}\n\n/**\n * Expires cookies that are no longer part of the session.\n *\n * This is the bug chunking would otherwise ship with. A session that shrinks\n * from three chunks to one leaves `name.1` and `name.2` in the browser, the next\n * read concatenates the new chunk 0 with the two stale ones, the HMAC fails, and\n * the user is signed out on every request from then on while `save()` keeps\n * reporting success. There is no error anywhere in that loop.\n *\n * Only cookies actually present on the request are expired, so a normal\n * unchunked save does not emit four pointless `Set-Cookie` headers. The expiry\n * reuses the same `path` and `domain` as the write, otherwise the browser treats\n * it as a different cookie and the delete does nothing.\n */\nfunction clearStaleCookies(\n jar: CookieJar,\n cookieName: string,\n cookieOptions: CookieOptions,\n keep: { whole: boolean; chunksUpTo: number },\n): void {\n const expire = (name: string): void => {\n if (jar.read(name)) {\n jar.write(name, \"\", { ...cookieOptions, maxAge: 0 });\n }\n };\n\n if (!keep.whole) {\n expire(cookieName);\n }\n\n for (let index = keep.chunksUpTo; index < MAX_CHUNKS; index += 1) {\n expire(chunkName(cookieName, index));\n }\n}\n\n/**\n * Writes the seal, splitting it across cookies when it does not fit and\n * chunking is enabled.\n *\n * The seal is split as an opaque string, after the version suffix is applied to\n * the whole thing. Rejoining is a plain concatenation with no separator.\n */\nfunction writeSeal(jar: CookieJar, config: SessionConfig, seal: string): void {\n const { cookieName, cookieOptions } = config;\n const wholeBytes = cookieBytes(cookieName, seal, cookieOptions);\n\n if (wholeBytes <= MAX_COOKIE_BYTES) {\n jar.write(cookieName, seal, cookieOptions);\n clearStaleCookies(jar, cookieName, cookieOptions, { whole: true, chunksUpTo: 0 });\n return;\n }\n\n if (!config.chunk) {\n // If the session we just read was itself chunked, the problem is almost\n // certainly a second options object somewhere without `chunk` set, rather\n // than a session that suddenly grew. Say so: otherwise this reads as \"it\n // works in my route handler but not in my middleware\".\n const wasChunked = Boolean(jar.read(chunkName(cookieName, 0)));\n\n throw new Error(\n wasChunked\n ? `iron-session: Cookie length is too big (${wholeBytes} bytes) and \\`chunk\\` is not enabled here, but this session is already stored across several cookies. You have more than one options object and only some of them set \\`chunk: true\\`. Use the same options everywhere you call getIronSession, including middleware.`\n : `iron-session: Cookie length is too big (${wholeBytes} bytes), browsers will refuse it. Remove some data from the session, or set \\`chunk: true\\` to split it across several cookies.`,\n );\n }\n\n // Every chunk index is a single digit because MAX_CHUNKS is 4, so all chunk\n // names are the same length and one budget works for all of them.\n const perChunkOverhead = cookieBytes(chunkName(cookieName, 0), \"\", cookieOptions);\n const budget = MAX_COOKIE_BYTES - perChunkOverhead;\n\n if (budget <= 0) {\n throw new Error(\n `iron-session: The cookie name and options alone take ${perChunkOverhead} bytes, which leaves no room for session data. Use a shorter cookie name.`,\n );\n }\n\n const chunks: string[] = [];\n for (let offset = 0; offset < seal.length; offset += budget) {\n chunks.push(seal.slice(offset, offset + budget));\n }\n\n if (chunks.length > MAX_CHUNKS) {\n throw new Error(\n `iron-session: The session needs ${chunks.length} cookies and the maximum is ${MAX_CHUNKS}. Even at ${MAX_CHUNKS} the whole Cookie header is sent on every request and proxies commonly cap it at 8 KB, so raising this would fail at your CDN instead. Store an id in the session and keep the data in your database.`,\n );\n }\n\n chunks.forEach((value, index) => {\n jar.write(chunkName(cookieName, index), value, cookieOptions);\n });\n\n clearStaleCookies(jar, cookieName, cookieOptions, {\n whole: false,\n chunksUpTo: chunks.length,\n });\n}\n\nconst badUsageMessage =\n \"iron-session: Bad usage: use getIronSession(req, res, options) or getIronSession(cookieStore, options).\";\n\nexport async function getIronSession<T extends object>(\n cookies: CookieStore | CookieJar,\n sessionOptions: SessionOptions,\n): Promise<IronSession<T>>;\nexport async function getIronSession<T extends object>(\n req: RequestType,\n res: ResponseType,\n sessionOptions: SessionOptions,\n): Promise<IronSession<T>>;\nexport async function getIronSession<T extends object>(\n first: RequestType | CookieStore | CookieJar,\n second: ResponseType | SessionOptions,\n third?: SessionOptions,\n): Promise<IronSession<T>> {\n if (!first || !second) {\n throw new Error(badUsageMessage);\n }\n\n // getIronSession(cookieStoreOrJar, options)\n if (!third) {\n const options = second as SessionOptions;\n\n if (isCookieJar(first)) {\n return createSession<T>(first, options);\n }\n\n if (isCookieStore(first)) {\n return createSession<T>(cookieStoreJar(first), options);\n }\n\n throw new Error(badUsageMessage);\n }\n\n // getIronSession(req, res, options), kept so Node and Express keep working\n // without a code change. It just picks the matching adapter.\n const req = first as RequestType;\n const res = second as ResponseType;\n const jar = isWebRequest(req)\n ? webCookies(req, res as Response)\n : nodeCookies(req, res as ServerResponse);\n\n return createSession<T>(jar, third);\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "iron-session",
|
|
3
|
-
"version": "9.0.0
|
|
3
|
+
"version": "9.0.0",
|
|
4
4
|
"description": "Secure, stateless, and cookie-based session library for JavaScript",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cookie",
|
|
@@ -77,7 +77,7 @@
|
|
|
77
77
|
{
|
|
78
78
|
"name": "iron-session, with cookie + iron-webcrypto",
|
|
79
79
|
"path": "dist/index.js",
|
|
80
|
-
"limit": "6 kB",
|
|
80
|
+
"limit": "6.25 kB",
|
|
81
81
|
"gzip": true
|
|
82
82
|
},
|
|
83
83
|
{
|
|
@@ -88,7 +88,7 @@
|
|
|
88
88
|
"cookie",
|
|
89
89
|
"iron-webcrypto"
|
|
90
90
|
],
|
|
91
|
-
"limit": "3 kB",
|
|
91
|
+
"limit": "3.5 kB",
|
|
92
92
|
"gzip": true
|
|
93
93
|
}
|
|
94
94
|
],
|