iron-session 8.0.0-beta.6 → 8.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/README.md CHANGED
@@ -2,7 +2,9 @@
2
2
 
3
3
  **`iron-session` is a secure, stateless, and cookie-based session library for JavaScript.**
4
4
 
5
- The session data is stored in signed and encrypted cookies which are decoded by your server code in a stateless fashion (= no I/O involved). This is the same technique used by frameworks like
5
+ <p align="center">Online demo: <a href="https://get-iron-session.vercel.app/">https://get-iron-session.vercel.app</a> 👀</p>
6
+
7
+ The session data is stored in signed and encrypted cookies which are decoded by your server code in a stateless fashion (= no network involved). This is the same technique used by frameworks like
6
8
  [Ruby On Rails](https://guides.rubyonrails.org/security.html#session-storage).
7
9
 
8
10
  <p align="center"><i>⭐️ Featured in the <a href="https://nextjs.org/docs/authentication">Next.js documentation</a></i></p>
@@ -12,12 +14,21 @@ The session data is stored in signed and encrypted cookies which are decoded by
12
14
  - [Table of Contents](#table-of-contents)
13
15
  - [Installation](#installation)
14
16
  - [Usage](#usage)
17
+ - [Examples](#examples)
18
+ - [Project status](#project-status)
15
19
  - [Session options](#session-options)
16
20
  - [API](#api)
17
21
  - [`getIronSession<T>(req, res, sessionOptions): Promise<IronSession<T>>`](#getironsessiontreq-res-sessionoptions-promiseironsessiont)
18
22
  - [`getIronSession<T>(cookieStore, sessionOptions): Promise<IronSession<T>>`](#getironsessiontcookiestore-sessionoptions-promiseironsessiont)
19
- - [session.save()](#sessionsave)
20
- - [session.destroy()](#sessiondestroy)
23
+ - [`session.save(): Promise<void>`](#sessionsave-promisevoid)
24
+ - [`session.destroy(): void`](#sessiondestroy-void)
25
+ - [`sealData(data: unknown, { password, ttl }): Promise<string>`](#sealdatadata-unknown--password-ttl--promisestring)
26
+ - [`unSealData<T>(seal: string, { password, ttl }): Promise<T>`](#unsealdatatseal-string--password-ttl--promiset)
27
+ - [FAQ](#faq)
28
+ - [Why use pure cookies for sessions?](#why-use-pure-cookies-for-sessions)
29
+ - [How to invalidate sessions?](#how-to-invalidate-sessions)
30
+ - [Can I use something else than cookies?](#can-i-use-something-else-than-cookies)
31
+ - [How is this different from JWT?](#how-is-this-different-from-jwt)
21
32
  - [Credits](#credits)
22
33
  - [Good Reads](#good-reads)
23
34
 
@@ -32,7 +43,7 @@ pnpm add iron-session
32
43
  To get a session, there's a single method to know: `getIronSession`.
33
44
 
34
45
  ```ts
35
- // Next.js Pages with API Route and Node.js/Express/Connect.
46
+ // Next.js API Routes and Node.js/Express/Connect.
36
47
  import { getIronSession } from 'iron-session';
37
48
 
38
49
  export function get(req, res) {
@@ -47,7 +58,7 @@ export function post(req, res) {
47
58
  ```
48
59
 
49
60
  ```ts
50
- // Next.js App Router with route handlers
61
+ // Next.js Route Handlers (App Router)
51
62
  import { cookies } from 'next/header';
52
63
  import { getIronSession } from 'iron-session';
53
64
 
@@ -56,14 +67,14 @@ export function GET() {
56
67
  }
57
68
 
58
69
  export function POST() {
59
- const session = getIronSession(req, res, { password: "...", cookieName: "..." });
70
+ const session = getIronSession(cookies(), { password: "...", cookieName: "..." });
60
71
  session.username = "Alison";
61
72
  await session.save();
62
73
  }
63
74
  ```
64
75
 
65
- ```ts
66
- // Next.js App Router with server component or server action
76
+ ```tsx
77
+ // Next.js Server Components and Server Actions (App Router)
67
78
  import { cookies } from 'next/header';
68
79
  import { getIronSession } from 'iron-session';
69
80
 
@@ -78,10 +89,17 @@ function Profile() {
78
89
  }
79
90
  ```
80
91
 
92
+ ## Examples
93
+
94
+ We have many different patterns and examples on the online demo, have a look: https://get-iron-session.vercel.app/.
95
+
96
+ ## Project status
97
+
98
+ ✅ Production ready and maintained.
81
99
 
82
100
  ## Session options
83
101
 
84
- Two options are required: `password` and `cookieName`. Everything else is automatically computed and usually doesn't need to be changed.
102
+ Two options are required: `password` and `cookieName`. Everything else is automatically computed and usually doesn't need to be changed.****
85
103
 
86
104
  - `password`, **required**: Private key used to encrypt the cookie. It has to be at least 32 characters long. Use <https://1password.com/password-generator/> to generate strong passwords. `password` can be either a `string` or an `array` of objects like this: `[{id: 2, password: "..."}, {id: 1, password: "..."}]` to allow for password rotation.
87
105
  - `cookieName`, **required**: Name of the cookie to be stored
@@ -112,22 +130,58 @@ const session = getIronSession<SessionData>(req, res, sessionOptions);
112
130
  const session = getIronSession<SessionData>(cookies(), sessionOptions);
113
131
  ```
114
132
 
115
- ### session.save()
133
+ ### `session.save(): Promise<void>`
116
134
 
117
- Saves the session.
135
+ Saves the session. This is an asynchronous operation. It must be done and awaited before headers are sent to the client.
118
136
 
119
137
  ```ts
120
138
  await session.save()
121
139
  ```
122
140
 
123
- ### session.destroy()
141
+ ### `session.destroy(): void`
124
142
 
125
- Destroys the session.
143
+ Destroys the session. This is a synchronous operation as it only removes the cookie. It must be done before headers are sent to the client.
126
144
 
127
145
  ```ts
128
146
  await session.destroy()
129
147
  ```
130
148
 
149
+ ### `sealData(data: unknown, { password, ttl }): Promise<string>`
150
+
151
+ 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.
152
+
153
+ ### `unSealData<T>(seal: string, { password, ttl }): Promise<T>`
154
+
155
+ This is the opposite of `sealData` and allow you to decode a seal to get the original data back.
156
+
157
+ ## FAQ
158
+
159
+ ### Why use pure cookies for sessions?
160
+
161
+ This makes your sessions stateless: since the data is passed around in cookies, you do not need any server or service to store session data.
162
+
163
+ More information can also be found on the [Ruby On Rails website](https://guides.rubyonrails.org/security.html#session-storage) which uses the same technique.
164
+
165
+ ### How to invalidate sessions?
166
+
167
+ Sessions cannot be instantly invalidated (or "disconnect this customer") as there is typically no state stored about sessions on the server by default. However, in most applications, the first step upon receiving an authenticated request is to validate the user and their permissions in the database. So, to easily disconnect customers (or invalidate sessions), you can add an `isBlocked`` state in the database and create a UI to block customers.
168
+
169
+ Then, every time a request is received that involves reading or altering sensitive data, make sure to check this flag.
170
+
171
+ ### Can I use something else than cookies?
172
+
173
+ Yes, we expose `sealData` and `unsealData` which are not tied to cookies. This way you can seal and unseal any object in your application and move seals around to login users.
174
+
175
+ ### How is this different from [JWT](https://jwt.io/)?
176
+
177
+ Not so much:
178
+
179
+ - JWT is a standard, it stores metadata in the JWT token themselves to ensure communication between different systems is flawless.
180
+ - JWT tokens are not encrypted, the payload is visible by customers if they manage to inspect the seal. You would have to use [JWE](https://tools.ietf.org/html/rfc7516) to achieve the same.
181
+ - @hapi/iron mechanism is not a standard, it's a way to sign and encrypt data into seals
182
+
183
+ Depending on your own needs and preferences, `iron-session` may or may not fit you.
184
+
131
185
  ## Credits
132
186
 
133
187
  - [Eran Hammer and hapi.js contributors](https://github.com/hapijs/iron/graphs/contributors)
package/dist/index.cjs CHANGED
@@ -85,7 +85,6 @@ function createSealData(_crypto) {
85
85
  );
86
86
  const passwordForSeal = {
87
87
  id: mostRecentPasswordId.toString(),
88
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
89
88
  secret: passwordsMap[mostRecentPasswordId]
90
89
  };
91
90
  const seal = await ironWebcrypto.seal(_crypto, data, passwordForSeal, {
@@ -121,15 +120,13 @@ function createUnsealData(_crypto) {
121
120
  }
122
121
  };
123
122
  }
124
- function mergeOptions(sessionOptions, overrides) {
123
+ function getSessionConfig(sessionOptions) {
125
124
  const options = {
126
125
  ...defaultOptions,
127
126
  ...sessionOptions,
128
- ...overrides,
129
127
  cookieOptions: {
130
128
  ...defaultOptions.cookieOptions,
131
- ...sessionOptions.cookieOptions,
132
- ...overrides?.cookieOptions
129
+ ...sessionOptions.cookieOptions || {}
133
130
  }
134
131
  };
135
132
  if (sessionOptions.cookieOptions && "maxAge" in sessionOptions.cookieOptions) {
@@ -141,7 +138,7 @@ function mergeOptions(sessionOptions, overrides) {
141
138
  }
142
139
  return options;
143
140
  }
144
- var badUsageMessage = "iron-session: Bad usage: use getIronSession(req, res, options) or getIronSession(cookies, options).";
141
+ var badUsageMessage = "iron-session: Bad usage: use getIronSession(req, res, options) or getIronSession(cookieStore, options).";
145
142
  function createGetIronSession(sealData2, unsealData2) {
146
143
  return getIronSession2;
147
144
  async function getIronSession2(reqOrCookieStore, resOrsessionOptions, sessionOptions) {
@@ -162,7 +159,7 @@ function createGetIronSession(sealData2, unsealData2) {
162
159
  const req = reqOrCookieStore;
163
160
  const res = resOrsessionOptions;
164
161
  if (!sessionOptions) {
165
- throw new Error("iron-session: Bad usage. Missing options.");
162
+ throw new Error(badUsageMessage);
166
163
  }
167
164
  if (!sessionOptions.cookieName) {
168
165
  throw new Error("iron-session: Bad usage. Missing cookie name.");
@@ -176,29 +173,33 @@ function createGetIronSession(sealData2, unsealData2) {
176
173
  "iron-session: Bad usage. Password must be at least 32 characters long."
177
174
  );
178
175
  }
179
- const options = mergeOptions(sessionOptions);
180
- const sealFromCookies = getCookie(req, options.cookieName);
176
+ let sessionConfig = getSessionConfig(sessionOptions);
177
+ const sealFromCookies = getCookie(req, sessionConfig.cookieName);
181
178
  const session = sealFromCookies ? await unsealData2(sealFromCookies, {
182
179
  password: passwordsMap,
183
- ttl: options.ttl
180
+ ttl: sessionConfig.ttl
184
181
  }) : {};
185
182
  Object.defineProperties(session, {
183
+ updateConfig: {
184
+ value: function updateConfig(newSessionOptions) {
185
+ sessionConfig = getSessionConfig(newSessionOptions);
186
+ }
187
+ },
186
188
  save: {
187
- value: async function save(saveOptions) {
189
+ value: async function save() {
188
190
  if ("headersSent" in res && res.headersSent) {
189
191
  throw new Error(
190
192
  "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()"
191
193
  );
192
194
  }
193
- const mergedOptions = mergeOptions(sessionOptions, saveOptions);
194
195
  const seal = await sealData2(session, {
195
196
  password: passwordsMap,
196
- ttl: mergedOptions.ttl
197
+ ttl: sessionConfig.ttl
197
198
  });
198
199
  const cookieValue = cookie.serialize(
199
- mergedOptions.cookieName,
200
+ sessionConfig.cookieName,
200
201
  seal,
201
- mergedOptions.cookieOptions
202
+ sessionConfig.cookieOptions
202
203
  );
203
204
  if (cookieValue.length > 4096) {
204
205
  throw new Error(
@@ -209,13 +210,12 @@ function createGetIronSession(sealData2, unsealData2) {
209
210
  }
210
211
  },
211
212
  destroy: {
212
- value: async function destroy(destroyOptions) {
213
+ value: function destroy() {
213
214
  Object.keys(session).forEach((key) => {
214
215
  delete session[key];
215
216
  });
216
- const mergedOptions = mergeOptions(sessionOptions, destroyOptions);
217
- const cookieValue = cookie.serialize(mergedOptions.cookieName, "", {
218
- ...mergedOptions.cookieOptions,
217
+ const cookieValue = cookie.serialize(sessionConfig.cookieName, "", {
218
+ ...sessionConfig.cookieOptions,
219
219
  maxAge: 0
220
220
  });
221
221
  setCookie(res, cookieValue);
@@ -226,9 +226,6 @@ function createGetIronSession(sealData2, unsealData2) {
226
226
  }
227
227
  }
228
228
  async function getIronSessionFromCookieStore(cookieStore, sessionOptions, sealData2, unsealData2) {
229
- if (!sessionOptions) {
230
- throw new Error("iron-session: Bad usage. Missing options.");
231
- }
232
229
  if (!sessionOptions.cookieName) {
233
230
  throw new Error("iron-session: Bad usage. Missing cookie name.");
234
231
  }
@@ -241,44 +238,42 @@ async function getIronSessionFromCookieStore(cookieStore, sessionOptions, sealDa
241
238
  "iron-session: Bad usage. Password must be at least 32 characters long."
242
239
  );
243
240
  }
244
- const options = mergeOptions(sessionOptions);
241
+ const sessionConfig = getSessionConfig(sessionOptions);
245
242
  const sealFromCookies = getServerActionCookie(
246
- options.cookieName,
243
+ sessionConfig.cookieName,
247
244
  cookieStore
248
245
  );
249
246
  const session = sealFromCookies ? await unsealData2(sealFromCookies, {
250
247
  password: passwordsMap,
251
- ttl: options.ttl
248
+ ttl: sessionConfig.ttl
252
249
  }) : {};
253
250
  Object.defineProperties(session, {
254
251
  save: {
255
- value: async function save(saveOptions) {
256
- const mergedOptions = mergeOptions(sessionOptions, saveOptions);
252
+ value: async function save() {
257
253
  const seal = await sealData2(session, {
258
254
  password: passwordsMap,
259
- ttl: mergedOptions.ttl
255
+ ttl: sessionConfig.ttl
260
256
  });
261
- const cookieLength = mergedOptions.cookieName.length + seal.length + JSON.stringify(mergedOptions.cookieOptions).length;
257
+ const cookieLength = sessionConfig.cookieName.length + seal.length + JSON.stringify(sessionConfig.cookieOptions).length;
262
258
  if (cookieLength > 4096) {
263
259
  throw new Error(
264
260
  `iron-session: Cookie length is too big (${cookieLength} bytes), browsers will refuse it. Try to remove some data.`
265
261
  );
266
262
  }
267
263
  cookieStore.set(
268
- mergedOptions.cookieName,
264
+ sessionConfig.cookieName,
269
265
  seal,
270
- mergedOptions.cookieOptions
266
+ sessionConfig.cookieOptions
271
267
  );
272
268
  }
273
269
  },
274
270
  destroy: {
275
- value: async function destroy(destroyOptions) {
271
+ value: function destroy() {
276
272
  Object.keys(session).forEach((key) => {
277
273
  delete session[key];
278
274
  });
279
- const mergedOptions = mergeOptions(sessionOptions, destroyOptions);
280
- const cookieOptions = { ...mergedOptions.cookieOptions, maxAge: 0 };
281
- cookieStore.set(mergedOptions.cookieName, "", cookieOptions);
275
+ const cookieOptions = { ...sessionConfig.cookieOptions, maxAge: 0 };
276
+ cookieStore.set(sessionConfig.cookieName, "", cookieOptions);
282
277
  }
283
278
  }
284
279
  });
@@ -288,9 +283,6 @@ var sealData = createSealData(crypto__namespace);
288
283
  var unsealData = createUnsealData(crypto__namespace);
289
284
  var getIronSession = createGetIronSession(sealData, unsealData);
290
285
 
291
- exports.createGetIronSession = createGetIronSession;
292
- exports.createSealData = createSealData;
293
- exports.createUnsealData = createUnsealData;
294
286
  exports.getIronSession = getIronSession;
295
287
  exports.sealData = sealData;
296
288
  exports.unsealData = unsealData;
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/core.ts","../src/index.ts"],"names":["sealData","unsealData","getIronSession"],"mappings":"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","sourcesContent":["import type { IncomingMessage, ServerResponse } from \"http\";\nimport { parse, serialize, type CookieSerializeOptions } from \"cookie\";\nimport {\n defaults as ironDefaults,\n seal as ironSeal,\n unseal as ironUnseal,\n} 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\n extends Pick<\n CookieSerializeOptions,\n \"domain\" | \"path\" | \"sameSite\" | \"secure\"\n > {\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?: CookieSerializeOptions[\"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<CookieSerializeOptions, \"httpOnly\" | \"maxAge\" | \"priority\">;\n\n/**\n * The high-level type definition of the .get() and .set() methods\n * of { cookies() } from \"next/headers\"\n */\nexport interface CookieStore {\n get: (name: string) => { name: string; value: string } | undefined;\n set: {\n (name: string, value: string, cookie?: Partial<ResponseCookie>): void;\n (options: ResponseCookie): void;\n };\n}\n\n/**\n * Set-Cookie Attributes do not include `encode`. We omit this from our `cookieOptions` type.\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<CookieSerializeOptions, \"encode\">;\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.\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\ntype OverridableOptions = Pick<SessionOptions, \"cookieOptions\" | \"ttl\">;\n\nexport type IronSession<T> = T & {\n /**\n * Destroys the session data and removes the cookie.\n */\n readonly destroy: (destroyOptions?: OverridableOptions) => Promise<void>;\n\n /**\n * Encrypts the session data and sets the cookie.\n */\n readonly save: (saveOptions?: OverridableOptions) => Promise<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<OverridableOptions> = {\n ttl: fourteenDaysInSeconds,\n cookieOptions: { httpOnly: true, secure: true, sameSite: \"lax\", path: \"/\" },\n};\n\nfunction normalizeStringPasswordToMap(password: Password): PasswordsMap {\n return typeof password === \"string\" ? { 1: password } : password;\n}\n\nfunction parseSeal(seal: string): {\n sealWithoutVersion: string;\n tokenVersion: number | null;\n} {\n const [sealWithoutVersion, tokenVersionAsString] =\n seal.split(versionDelimiter);\n const tokenVersion =\n tokenVersionAsString == null ? null : parseInt(tokenVersionAsString, 10);\n\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n return { sealWithoutVersion: sealWithoutVersion!, tokenVersion };\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 // The next line makes sure browser will expire cookies before seals are considered expired by the server.\n // It also allows for clock difference of 60 seconds between server and clients.\n return ttl - timestampSkewSec;\n}\n\nfunction getCookie(req: RequestType, cookieName: string): string {\n return (\n parse(\n (\"headers\" in req && typeof req.headers.get === \"function\"\n ? req.headers.get(\"cookie\")\n : (req as IncomingMessage).headers.cookie) ?? \"\",\n )[cookieName] ?? \"\"\n );\n}\n\nfunction getServerActionCookie(\n cookieName: string,\n cookieHandler: CookieStore,\n): string {\n const cookieObject = cookieHandler.get(cookieName);\n const cookie = cookieObject?.value;\n if (typeof cookie === \"string\") {\n return cookie;\n }\n return \"\";\n}\n\nfunction setCookie(res: ResponseType, cookieValue: string): void {\n if (\"headers\" in res && typeof res.headers.append === \"function\") {\n res.headers.append(\"set-cookie\", cookieValue);\n return;\n }\n let existingSetCookie = (res as ServerResponse).getHeader(\"set-cookie\") ?? [];\n if (!Array.isArray(existingSetCookie)) {\n existingSetCookie = [existingSetCookie.toString()];\n }\n (res as ServerResponse).setHeader(\"set-cookie\", [\n ...existingSetCookie,\n cookieValue,\n ]);\n}\n\nexport function createSealData(_crypto: Crypto) {\n return async function sealData(\n data: unknown,\n {\n password,\n ttl = fourteenDaysInSeconds,\n }: { password: Password; ttl?: number },\n ): Promise<string> {\n const passwordsMap = normalizeStringPasswordToMap(password);\n\n const mostRecentPasswordId = Math.max(\n ...Object.keys(passwordsMap).map(Number),\n );\n const passwordForSeal = {\n id: mostRecentPasswordId.toString(),\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n secret: passwordsMap[mostRecentPasswordId]!,\n };\n\n const seal = await ironSeal(_crypto, data, passwordForSeal, {\n ...ironDefaults,\n ttl: ttl * 1000,\n });\n\n return `${seal}${versionDelimiter}${currentMajorVersion}`;\n };\n}\n\nexport function createUnsealData(_crypto: Crypto) {\n return async function unsealData<T>(\n seal: string,\n {\n password,\n ttl = fourteenDaysInSeconds,\n }: { password: Password; ttl?: number },\n ): Promise<T> {\n const passwordsMap = normalizeStringPasswordToMap(password);\n const { sealWithoutVersion, tokenVersion } = parseSeal(seal);\n\n try {\n const data =\n (await ironUnseal(_crypto, sealWithoutVersion, passwordsMap, {\n ...ironDefaults,\n ttl: ttl * 1000,\n })) /* c8 ignore next */ ?? {};\n\n if (tokenVersion === 2) {\n return data as T;\n }\n\n // @ts-expect-error `persistent` does not exist on newer tokens\n return { ...data.persistent } as T;\n } catch (error) {\n if (\n error instanceof Error &&\n /^(Expired seal|Bad hmac value|Cannot find password|Incorrect number of sealed components)/.test(\n error.message,\n )\n ) {\n // if seal expired or\n // if seal is not valid (encrypted using a different password, when passwords are badly rotated) or\n // if we can't find back the password in the seal\n // then we just start a new session over\n return {} as T;\n }\n\n /* c8 ignore next 2 */\n throw error;\n }\n };\n}\n\nfunction mergeOptions(\n sessionOptions: SessionOptions,\n overrides?: OverridableOptions,\n): Required<SessionOptions> {\n const options: Required<SessionOptions> = {\n ...defaultOptions,\n ...sessionOptions,\n ...overrides,\n cookieOptions: {\n ...defaultOptions.cookieOptions,\n ...sessionOptions.cookieOptions,\n ...overrides?.cookieOptions,\n },\n };\n\n if (\n sessionOptions.cookieOptions &&\n \"maxAge\" in sessionOptions.cookieOptions\n ) {\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\nconst badUsageMessage =\n \"iron-session: Bad usage: use getIronSession(req, res, options) or getIronSession(cookies, options).\";\n\nexport function createGetIronSession(\n sealData: ReturnType<typeof createSealData>,\n unsealData: ReturnType<typeof createUnsealData>,\n) {\n return getIronSession;\n\n async function getIronSession<T extends object>(\n cookies: CookieStore,\n sessionOptions: SessionOptions,\n ): Promise<IronSession<T>>;\n async function getIronSession<T extends object>(\n req: RequestType,\n res: ResponseType,\n sessionOptions: SessionOptions,\n ): Promise<IronSession<T>>;\n async function getIronSession<T extends object>(\n reqOrCookieStore: RequestType | CookieStore,\n resOrsessionOptions: ResponseType | SessionOptions,\n sessionOptions?: SessionOptions,\n ): Promise<IronSession<T>> {\n if (!reqOrCookieStore) {\n throw new Error(badUsageMessage);\n }\n\n if (!resOrsessionOptions) {\n throw new Error(badUsageMessage);\n }\n\n if (!sessionOptions) {\n return getIronSessionFromCookieStore<T>(\n reqOrCookieStore as CookieStore,\n resOrsessionOptions as SessionOptions,\n sealData,\n unsealData,\n );\n }\n\n const req = reqOrCookieStore as RequestType;\n const res = resOrsessionOptions as ResponseType;\n\n if (!sessionOptions) {\n throw new Error(\"iron-session: Bad usage. Missing options.\");\n }\n\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.values(passwordsMap).some((password) => password.length < 32)) {\n throw new Error(\n \"iron-session: Bad usage. Password must be at least 32 characters long.\",\n );\n }\n\n const options = mergeOptions(sessionOptions);\n const sealFromCookies = getCookie(req, options.cookieName);\n const session = sealFromCookies\n ? await unsealData<T>(sealFromCookies, {\n password: passwordsMap,\n ttl: options.ttl,\n })\n : ({} as T);\n\n Object.defineProperties(session, {\n save: {\n value: async function save(saveOptions?: OverridableOptions) {\n if (\"headersSent\" in res && 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 const mergedOptions = mergeOptions(sessionOptions, saveOptions);\n const seal = await sealData(session, {\n password: passwordsMap,\n ttl: mergedOptions.ttl,\n });\n const cookieValue = serialize(\n mergedOptions.cookieName,\n seal,\n mergedOptions.cookieOptions,\n );\n\n if (cookieValue.length > 4096) {\n throw new Error(\n `iron-session: Cookie length is too big (${cookieValue.length} bytes), browsers will refuse it. Try to remove some data.`,\n );\n }\n\n setCookie(res, cookieValue);\n },\n },\n\n destroy: {\n value: async function destroy(destroyOptions?: OverridableOptions) {\n Object.keys(session).forEach((key) => {\n delete (session as Record<string, unknown>)[key];\n });\n\n const mergedOptions = mergeOptions(sessionOptions, destroyOptions);\n const cookieValue = serialize(mergedOptions.cookieName, \"\", {\n ...mergedOptions.cookieOptions,\n maxAge: 0,\n });\n\n setCookie(res, cookieValue);\n },\n },\n });\n\n return session as IronSession<T>;\n }\n}\n\nasync function getIronSessionFromCookieStore<T extends object>(\n cookieStore: CookieStore,\n sessionOptions: SessionOptions,\n sealData: ReturnType<typeof createSealData>,\n unsealData: ReturnType<typeof createUnsealData>,\n): Promise<IronSession<T>> {\n if (!sessionOptions) {\n throw new Error(\"iron-session: Bad usage. Missing options.\");\n }\n\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.values(passwordsMap).some((password) => password.length < 32)) {\n throw new Error(\n \"iron-session: Bad usage. Password must be at least 32 characters long.\",\n );\n }\n\n const options = mergeOptions(sessionOptions);\n const sealFromCookies = getServerActionCookie(\n options.cookieName,\n cookieStore,\n );\n const session = sealFromCookies\n ? await unsealData<T>(sealFromCookies, {\n password: passwordsMap,\n ttl: options.ttl,\n })\n : ({} as T);\n\n Object.defineProperties(session, {\n save: {\n value: async function save(saveOptions?: OverridableOptions) {\n const mergedOptions = mergeOptions(sessionOptions, saveOptions);\n const seal = await sealData(session, {\n password: passwordsMap,\n ttl: mergedOptions.ttl,\n });\n\n const cookieLength =\n mergedOptions.cookieName.length +\n seal.length +\n JSON.stringify(mergedOptions.cookieOptions).length;\n\n if (cookieLength > 4096) {\n throw new Error(\n `iron-session: Cookie length is too big (${cookieLength} bytes), browsers will refuse it. Try to remove some data.`,\n );\n }\n\n cookieStore.set(\n mergedOptions.cookieName,\n seal,\n mergedOptions.cookieOptions,\n );\n },\n },\n\n destroy: {\n value: async function destroy(destroyOptions: OverridableOptions) {\n Object.keys(session).forEach((key) => {\n delete (session as Record<string, unknown>)[key];\n });\n const mergedOptions = mergeOptions(sessionOptions, destroyOptions);\n const cookieOptions = { ...mergedOptions.cookieOptions, maxAge: 0 };\n\n cookieStore.set(mergedOptions.cookieName, \"\", cookieOptions);\n },\n },\n });\n\n return session as IronSession<T>;\n}\n","import {\n createGetIronSession,\n createSealData,\n createUnsealData,\n} from \"./core.js\";\n\nimport * as crypto from \"uncrypto\";\n\nexport * from \"./core.js\";\nexport const sealData = createSealData(crypto);\nexport const unsealData = createUnsealData(crypto);\nexport const getIronSession = createGetIronSession(sealData, unsealData);\n// export const getServerActionIronSession = createGetServerActionIronSession(\n// sealData,\n// unsealData,\n// );\n"]}
1
+ {"version":3,"sources":["../src/core.ts","../src/index.ts"],"names":["sealData","unsealData","getIronSession"],"mappings":"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","sourcesContent":["import type { IncomingMessage, ServerResponse } from \"http\";\nimport { parse, serialize, type CookieSerializeOptions } from \"cookie\";\nimport {\n defaults as ironDefaults,\n seal as ironSeal,\n unseal as ironUnseal,\n} 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\n extends Pick<\n CookieSerializeOptions,\n \"domain\" | \"path\" | \"sameSite\" | \"secure\"\n > {\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?: CookieSerializeOptions[\"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<CookieSerializeOptions, \"httpOnly\" | \"maxAge\" | \"priority\">;\n\n/**\n * The high-level type definition of the .get() and .set() methods\n * of { cookies() } from \"next/headers\"\n */\nexport interface CookieStore {\n get: (name: string) => { name: string; value: string } | undefined;\n set: {\n (name: string, value: string, cookie?: Partial<ResponseCookie>): void;\n (options: ResponseCookie): void;\n };\n}\n\n/**\n * Set-Cookie Attributes do not include `encode`. We omit this from our `cookieOptions` type.\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<CookieSerializeOptions, \"encode\">;\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.\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\nexport type IronSession<T> = 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: () => Promise<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\">> =\n {\n ttl: fourteenDaysInSeconds,\n cookieOptions: { httpOnly: true, secure: true, sameSite: \"lax\", path: \"/\" },\n };\n\nfunction normalizeStringPasswordToMap(password: Password): PasswordsMap {\n return typeof password === \"string\" ? { 1: password } : password;\n}\n\nfunction parseSeal(seal: string): {\n sealWithoutVersion: string;\n tokenVersion: number | null;\n} {\n const [sealWithoutVersion, tokenVersionAsString] =\n seal.split(versionDelimiter);\n const tokenVersion =\n tokenVersionAsString == null ? null : parseInt(tokenVersionAsString, 10);\n\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n return { sealWithoutVersion: sealWithoutVersion!, tokenVersion };\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 // The next line makes sure browser will expire cookies before seals are considered expired by the server.\n // It also allows for clock difference of 60 seconds between server and clients.\n return ttl - timestampSkewSec;\n}\n\nfunction getCookie(req: RequestType, cookieName: string): string {\n return (\n parse(\n (\"headers\" in req && typeof req.headers.get === \"function\"\n ? req.headers.get(\"cookie\")\n : (req as IncomingMessage).headers.cookie) ?? \"\",\n )[cookieName] ?? \"\"\n );\n}\n\nfunction getServerActionCookie(\n cookieName: string,\n cookieHandler: CookieStore,\n): string {\n const cookieObject = cookieHandler.get(cookieName);\n const cookie = cookieObject?.value;\n if (typeof cookie === \"string\") {\n return cookie;\n }\n return \"\";\n}\n\nfunction setCookie(res: ResponseType, cookieValue: string): void {\n if (\"headers\" in res && typeof res.headers.append === \"function\") {\n res.headers.append(\"set-cookie\", cookieValue);\n return;\n }\n let existingSetCookie = (res as ServerResponse).getHeader(\"set-cookie\") ?? [];\n if (!Array.isArray(existingSetCookie)) {\n existingSetCookie = [existingSetCookie.toString()];\n }\n (res as ServerResponse).setHeader(\"set-cookie\", [\n ...existingSetCookie,\n cookieValue,\n ]);\n}\n\nexport function createSealData(_crypto: Crypto) {\n return async function sealData(\n data: unknown,\n {\n password,\n ttl = fourteenDaysInSeconds,\n }: { password: Password; ttl?: number },\n ): Promise<string> {\n const passwordsMap = normalizeStringPasswordToMap(password);\n\n const mostRecentPasswordId = Math.max(\n ...Object.keys(passwordsMap).map(Number),\n );\n const passwordForSeal = {\n id: mostRecentPasswordId.toString(),\n secret: passwordsMap[mostRecentPasswordId]!,\n };\n\n const seal = await ironSeal(_crypto, data, passwordForSeal, {\n ...ironDefaults,\n ttl: ttl * 1000,\n });\n\n return `${seal}${versionDelimiter}${currentMajorVersion}`;\n };\n}\n\nexport function createUnsealData(_crypto: Crypto) {\n return async function unsealData<T>(\n seal: string,\n {\n password,\n ttl = fourteenDaysInSeconds,\n }: { password: Password; ttl?: number },\n ): Promise<T> {\n const passwordsMap = normalizeStringPasswordToMap(password);\n const { sealWithoutVersion, tokenVersion } = parseSeal(seal);\n\n try {\n const data =\n (await ironUnseal(_crypto, sealWithoutVersion, passwordsMap, {\n ...ironDefaults,\n ttl: ttl * 1000,\n })) ?? {};\n\n if (tokenVersion === 2) {\n return data as T;\n }\n\n // @ts-expect-error `persistent` does not exist on newer tokens\n return { ...data.persistent } as T;\n } catch (error) {\n if (\n error instanceof Error &&\n /^(Expired seal|Bad hmac value|Cannot find password|Incorrect number of sealed components)/.test(\n error.message,\n )\n ) {\n // if seal expired or\n // if seal is not valid (encrypted using a different password, when passwords are badly rotated) or\n // if we can't find back the password in the seal\n // then we just start a new session over\n return {} as T;\n }\n\n throw error;\n }\n };\n}\n\nfunction getSessionConfig(\n sessionOptions: SessionOptions,\n): Required<SessionOptions> {\n const options = {\n ...defaultOptions,\n ...sessionOptions,\n cookieOptions: {\n ...defaultOptions.cookieOptions,\n ...(sessionOptions.cookieOptions || {}),\n },\n };\n\n if (\n sessionOptions.cookieOptions &&\n \"maxAge\" in sessionOptions.cookieOptions\n ) {\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\nconst badUsageMessage =\n \"iron-session: Bad usage: use getIronSession(req, res, options) or getIronSession(cookieStore, options).\";\n\nexport function createGetIronSession(\n sealData: ReturnType<typeof createSealData>,\n unsealData: ReturnType<typeof createUnsealData>,\n) {\n return getIronSession;\n\n async function getIronSession<T extends object>(\n cookies: CookieStore,\n sessionOptions: SessionOptions,\n ): Promise<IronSession<T>>;\n async function getIronSession<T extends object>(\n req: RequestType,\n res: ResponseType,\n sessionOptions: SessionOptions,\n ): Promise<IronSession<T>>;\n async function getIronSession<T extends object>(\n reqOrCookieStore: RequestType | CookieStore,\n resOrsessionOptions: ResponseType | SessionOptions,\n sessionOptions?: SessionOptions,\n ): Promise<IronSession<T>> {\n if (!reqOrCookieStore) {\n throw new Error(badUsageMessage);\n }\n\n if (!resOrsessionOptions) {\n throw new Error(badUsageMessage);\n }\n\n if (!sessionOptions) {\n return getIronSessionFromCookieStore<T>(\n reqOrCookieStore as CookieStore,\n resOrsessionOptions as SessionOptions,\n sealData,\n unsealData,\n );\n }\n\n const req = reqOrCookieStore as RequestType;\n const res = resOrsessionOptions as ResponseType;\n\n if (!sessionOptions) {\n throw new Error(badUsageMessage);\n }\n\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.values(passwordsMap).some((password) => password.length < 32)) {\n throw new Error(\n \"iron-session: Bad usage. Password must be at least 32 characters long.\",\n );\n }\n\n let sessionConfig = getSessionConfig(sessionOptions);\n\n const sealFromCookies = getCookie(req, sessionConfig.cookieName);\n const session = sealFromCookies\n ? await unsealData<T>(sealFromCookies, {\n password: passwordsMap,\n ttl: sessionConfig.ttl,\n })\n : ({} as T);\n\n Object.defineProperties(session, {\n updateConfig: {\n value: function updateConfig(newSessionOptions: SessionOptions) {\n sessionConfig = getSessionConfig(newSessionOptions);\n },\n },\n save: {\n value: async function save() {\n if (\"headersSent\" in res && 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 const seal = await sealData(session, {\n password: passwordsMap,\n ttl: sessionConfig.ttl,\n });\n const cookieValue = serialize(\n sessionConfig.cookieName,\n seal,\n sessionConfig.cookieOptions,\n );\n\n if (cookieValue.length > 4096) {\n throw new Error(\n `iron-session: Cookie length is too big (${cookieValue.length} bytes), browsers will refuse it. Try to remove some data.`,\n );\n }\n\n setCookie(res, cookieValue);\n },\n },\n\n destroy: {\n value: function destroy() {\n Object.keys(session).forEach((key) => {\n delete (session as Record<string, unknown>)[key];\n });\n const cookieValue = serialize(sessionConfig.cookieName, \"\", {\n ...sessionConfig.cookieOptions,\n maxAge: 0,\n });\n\n setCookie(res, cookieValue);\n },\n },\n });\n\n return session as IronSession<T>;\n }\n}\n\nasync function getIronSessionFromCookieStore<T extends object>(\n cookieStore: CookieStore,\n sessionOptions: SessionOptions,\n sealData: ReturnType<typeof createSealData>,\n unsealData: ReturnType<typeof createUnsealData>,\n): Promise<IronSession<T>> {\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.values(passwordsMap).some((password) => password.length < 32)) {\n throw new Error(\n \"iron-session: Bad usage. Password must be at least 32 characters long.\",\n );\n }\n\n const sessionConfig = getSessionConfig(sessionOptions);\n const sealFromCookies = getServerActionCookie(\n sessionConfig.cookieName,\n cookieStore,\n );\n const session = sealFromCookies\n ? await unsealData<T>(sealFromCookies, {\n password: passwordsMap,\n ttl: sessionConfig.ttl,\n })\n : ({} as T);\n\n Object.defineProperties(session, {\n save: {\n value: async function save() {\n const seal = await sealData(session, {\n password: passwordsMap,\n ttl: sessionConfig.ttl,\n });\n\n const cookieLength =\n sessionConfig.cookieName.length +\n seal.length +\n JSON.stringify(sessionConfig.cookieOptions).length;\n\n if (cookieLength > 4096) {\n throw new Error(\n `iron-session: Cookie length is too big (${cookieLength} bytes), browsers will refuse it. Try to remove some data.`,\n );\n }\n\n cookieStore.set(\n sessionConfig.cookieName,\n seal,\n sessionConfig.cookieOptions,\n );\n },\n },\n\n destroy: {\n value: function destroy() {\n Object.keys(session).forEach((key) => {\n delete (session as Record<string, unknown>)[key];\n });\n\n const cookieOptions = { ...sessionConfig.cookieOptions, maxAge: 0 };\n cookieStore.set(sessionConfig.cookieName, \"\", cookieOptions);\n },\n },\n });\n\n return session as IronSession<T>;\n}\n","import {\n createGetIronSession,\n createSealData,\n createUnsealData,\n} from \"./core.js\";\n\nimport * as crypto from \"uncrypto\";\n\nexport type { IronSession, SessionOptions } from \"./core.js\";\nexport const sealData = createSealData(crypto);\nexport const unsealData = createUnsealData(crypto);\nexport const getIronSession = createGetIronSession(sealData, unsealData);\n"]}
package/dist/index.d.cts CHANGED
@@ -1,11 +1,8 @@
1
1
  import * as http from 'http';
2
- import { IncomingMessage, ServerResponse } from 'http';
3
2
  import { CookieSerializeOptions } from 'cookie';
4
3
 
5
4
  type PasswordsMap = Record<string, string>;
6
5
  type Password = PasswordsMap | string;
7
- type RequestType = IncomingMessage | Request;
8
- type ResponseType = Response | ServerResponse;
9
6
  /**
10
7
  * {@link https://wicg.github.io/cookie-store/#dictdef-cookielistitem CookieListItem}
11
8
  * as specified by W3C.
@@ -83,28 +80,19 @@ interface SessionOptions {
83
80
  */
84
81
  cookieOptions?: CookieOptions;
85
82
  }
86
- type OverridableOptions = Pick<SessionOptions, "cookieOptions" | "ttl">;
87
83
  type IronSession<T> = T & {
84
+ /**
85
+ * Encrypts the session data and sets the cookie.
86
+ */
87
+ readonly save: () => Promise<void>;
88
88
  /**
89
89
  * Destroys the session data and removes the cookie.
90
90
  */
91
- readonly destroy: (destroyOptions?: OverridableOptions) => Promise<void>;
91
+ readonly destroy: () => Promise<void>;
92
92
  /**
93
- * Encrypts the session data and sets the cookie.
93
+ * Update the session configuration. You still need to call save() to send the new cookie.
94
94
  */
95
- readonly save: (saveOptions?: OverridableOptions) => Promise<void>;
96
- };
97
- declare function createSealData(_crypto: Crypto): (data: unknown, { password, ttl, }: {
98
- password: Password;
99
- ttl?: number;
100
- }) => Promise<string>;
101
- declare function createUnsealData(_crypto: Crypto): <T>(seal: string, { password, ttl, }: {
102
- password: Password;
103
- ttl?: number;
104
- }) => Promise<T>;
105
- declare function createGetIronSession(sealData: ReturnType<typeof createSealData>, unsealData: ReturnType<typeof createUnsealData>): {
106
- <T extends object>(cookies: CookieStore, sessionOptions: SessionOptions): Promise<IronSession<T>>;
107
- <T_1 extends object>(req: RequestType, res: ResponseType, sessionOptions: SessionOptions): Promise<IronSession<T_1>>;
95
+ readonly updateConfig: (newSessionOptions: SessionOptions) => void;
108
96
  };
109
97
 
110
98
  declare const sealData: (data: unknown, { password, ttl, }: {
@@ -124,4 +112,4 @@ declare const getIronSession: {
124
112
  <T_1 extends object>(req: http.IncomingMessage | Request, res: Response | http.ServerResponse<http.IncomingMessage>, sessionOptions: SessionOptions): Promise<IronSession<T_1>>;
125
113
  };
126
114
 
127
- export { CookieStore, IronSession, SessionOptions, createGetIronSession, createSealData, createUnsealData, getIronSession, sealData, unsealData };
115
+ export { type IronSession, type SessionOptions, getIronSession, sealData, unsealData };
package/dist/index.d.ts CHANGED
@@ -1,11 +1,8 @@
1
1
  import * as http from 'http';
2
- import { IncomingMessage, ServerResponse } from 'http';
3
2
  import { CookieSerializeOptions } from 'cookie';
4
3
 
5
4
  type PasswordsMap = Record<string, string>;
6
5
  type Password = PasswordsMap | string;
7
- type RequestType = IncomingMessage | Request;
8
- type ResponseType = Response | ServerResponse;
9
6
  /**
10
7
  * {@link https://wicg.github.io/cookie-store/#dictdef-cookielistitem CookieListItem}
11
8
  * as specified by W3C.
@@ -83,28 +80,19 @@ interface SessionOptions {
83
80
  */
84
81
  cookieOptions?: CookieOptions;
85
82
  }
86
- type OverridableOptions = Pick<SessionOptions, "cookieOptions" | "ttl">;
87
83
  type IronSession<T> = T & {
84
+ /**
85
+ * Encrypts the session data and sets the cookie.
86
+ */
87
+ readonly save: () => Promise<void>;
88
88
  /**
89
89
  * Destroys the session data and removes the cookie.
90
90
  */
91
- readonly destroy: (destroyOptions?: OverridableOptions) => Promise<void>;
91
+ readonly destroy: () => Promise<void>;
92
92
  /**
93
- * Encrypts the session data and sets the cookie.
93
+ * Update the session configuration. You still need to call save() to send the new cookie.
94
94
  */
95
- readonly save: (saveOptions?: OverridableOptions) => Promise<void>;
96
- };
97
- declare function createSealData(_crypto: Crypto): (data: unknown, { password, ttl, }: {
98
- password: Password;
99
- ttl?: number;
100
- }) => Promise<string>;
101
- declare function createUnsealData(_crypto: Crypto): <T>(seal: string, { password, ttl, }: {
102
- password: Password;
103
- ttl?: number;
104
- }) => Promise<T>;
105
- declare function createGetIronSession(sealData: ReturnType<typeof createSealData>, unsealData: ReturnType<typeof createUnsealData>): {
106
- <T extends object>(cookies: CookieStore, sessionOptions: SessionOptions): Promise<IronSession<T>>;
107
- <T_1 extends object>(req: RequestType, res: ResponseType, sessionOptions: SessionOptions): Promise<IronSession<T_1>>;
95
+ readonly updateConfig: (newSessionOptions: SessionOptions) => void;
108
96
  };
109
97
 
110
98
  declare const sealData: (data: unknown, { password, ttl, }: {
@@ -124,4 +112,4 @@ declare const getIronSession: {
124
112
  <T_1 extends object>(req: http.IncomingMessage | Request, res: Response | http.ServerResponse<http.IncomingMessage>, sessionOptions: SessionOptions): Promise<IronSession<T_1>>;
125
113
  };
126
114
 
127
- export { CookieStore, IronSession, SessionOptions, createGetIronSession, createSealData, createUnsealData, getIronSession, sealData, unsealData };
115
+ export { type IronSession, type SessionOptions, getIronSession, sealData, unsealData };
package/dist/index.js CHANGED
@@ -63,7 +63,6 @@ function createSealData(_crypto) {
63
63
  );
64
64
  const passwordForSeal = {
65
65
  id: mostRecentPasswordId.toString(),
66
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
67
66
  secret: passwordsMap[mostRecentPasswordId]
68
67
  };
69
68
  const seal$1 = await seal(_crypto, data, passwordForSeal, {
@@ -99,15 +98,13 @@ function createUnsealData(_crypto) {
99
98
  }
100
99
  };
101
100
  }
102
- function mergeOptions(sessionOptions, overrides) {
101
+ function getSessionConfig(sessionOptions) {
103
102
  const options = {
104
103
  ...defaultOptions,
105
104
  ...sessionOptions,
106
- ...overrides,
107
105
  cookieOptions: {
108
106
  ...defaultOptions.cookieOptions,
109
- ...sessionOptions.cookieOptions,
110
- ...overrides?.cookieOptions
107
+ ...sessionOptions.cookieOptions || {}
111
108
  }
112
109
  };
113
110
  if (sessionOptions.cookieOptions && "maxAge" in sessionOptions.cookieOptions) {
@@ -119,7 +116,7 @@ function mergeOptions(sessionOptions, overrides) {
119
116
  }
120
117
  return options;
121
118
  }
122
- var badUsageMessage = "iron-session: Bad usage: use getIronSession(req, res, options) or getIronSession(cookies, options).";
119
+ var badUsageMessage = "iron-session: Bad usage: use getIronSession(req, res, options) or getIronSession(cookieStore, options).";
123
120
  function createGetIronSession(sealData2, unsealData2) {
124
121
  return getIronSession2;
125
122
  async function getIronSession2(reqOrCookieStore, resOrsessionOptions, sessionOptions) {
@@ -140,7 +137,7 @@ function createGetIronSession(sealData2, unsealData2) {
140
137
  const req = reqOrCookieStore;
141
138
  const res = resOrsessionOptions;
142
139
  if (!sessionOptions) {
143
- throw new Error("iron-session: Bad usage. Missing options.");
140
+ throw new Error(badUsageMessage);
144
141
  }
145
142
  if (!sessionOptions.cookieName) {
146
143
  throw new Error("iron-session: Bad usage. Missing cookie name.");
@@ -154,29 +151,33 @@ function createGetIronSession(sealData2, unsealData2) {
154
151
  "iron-session: Bad usage. Password must be at least 32 characters long."
155
152
  );
156
153
  }
157
- const options = mergeOptions(sessionOptions);
158
- const sealFromCookies = getCookie(req, options.cookieName);
154
+ let sessionConfig = getSessionConfig(sessionOptions);
155
+ const sealFromCookies = getCookie(req, sessionConfig.cookieName);
159
156
  const session = sealFromCookies ? await unsealData2(sealFromCookies, {
160
157
  password: passwordsMap,
161
- ttl: options.ttl
158
+ ttl: sessionConfig.ttl
162
159
  }) : {};
163
160
  Object.defineProperties(session, {
161
+ updateConfig: {
162
+ value: function updateConfig(newSessionOptions) {
163
+ sessionConfig = getSessionConfig(newSessionOptions);
164
+ }
165
+ },
164
166
  save: {
165
- value: async function save(saveOptions) {
167
+ value: async function save() {
166
168
  if ("headersSent" in res && res.headersSent) {
167
169
  throw new Error(
168
170
  "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()"
169
171
  );
170
172
  }
171
- const mergedOptions = mergeOptions(sessionOptions, saveOptions);
172
173
  const seal = await sealData2(session, {
173
174
  password: passwordsMap,
174
- ttl: mergedOptions.ttl
175
+ ttl: sessionConfig.ttl
175
176
  });
176
177
  const cookieValue = serialize(
177
- mergedOptions.cookieName,
178
+ sessionConfig.cookieName,
178
179
  seal,
179
- mergedOptions.cookieOptions
180
+ sessionConfig.cookieOptions
180
181
  );
181
182
  if (cookieValue.length > 4096) {
182
183
  throw new Error(
@@ -187,13 +188,12 @@ function createGetIronSession(sealData2, unsealData2) {
187
188
  }
188
189
  },
189
190
  destroy: {
190
- value: async function destroy(destroyOptions) {
191
+ value: function destroy() {
191
192
  Object.keys(session).forEach((key) => {
192
193
  delete session[key];
193
194
  });
194
- const mergedOptions = mergeOptions(sessionOptions, destroyOptions);
195
- const cookieValue = serialize(mergedOptions.cookieName, "", {
196
- ...mergedOptions.cookieOptions,
195
+ const cookieValue = serialize(sessionConfig.cookieName, "", {
196
+ ...sessionConfig.cookieOptions,
197
197
  maxAge: 0
198
198
  });
199
199
  setCookie(res, cookieValue);
@@ -204,9 +204,6 @@ function createGetIronSession(sealData2, unsealData2) {
204
204
  }
205
205
  }
206
206
  async function getIronSessionFromCookieStore(cookieStore, sessionOptions, sealData2, unsealData2) {
207
- if (!sessionOptions) {
208
- throw new Error("iron-session: Bad usage. Missing options.");
209
- }
210
207
  if (!sessionOptions.cookieName) {
211
208
  throw new Error("iron-session: Bad usage. Missing cookie name.");
212
209
  }
@@ -219,44 +216,42 @@ async function getIronSessionFromCookieStore(cookieStore, sessionOptions, sealDa
219
216
  "iron-session: Bad usage. Password must be at least 32 characters long."
220
217
  );
221
218
  }
222
- const options = mergeOptions(sessionOptions);
219
+ const sessionConfig = getSessionConfig(sessionOptions);
223
220
  const sealFromCookies = getServerActionCookie(
224
- options.cookieName,
221
+ sessionConfig.cookieName,
225
222
  cookieStore
226
223
  );
227
224
  const session = sealFromCookies ? await unsealData2(sealFromCookies, {
228
225
  password: passwordsMap,
229
- ttl: options.ttl
226
+ ttl: sessionConfig.ttl
230
227
  }) : {};
231
228
  Object.defineProperties(session, {
232
229
  save: {
233
- value: async function save(saveOptions) {
234
- const mergedOptions = mergeOptions(sessionOptions, saveOptions);
230
+ value: async function save() {
235
231
  const seal = await sealData2(session, {
236
232
  password: passwordsMap,
237
- ttl: mergedOptions.ttl
233
+ ttl: sessionConfig.ttl
238
234
  });
239
- const cookieLength = mergedOptions.cookieName.length + seal.length + JSON.stringify(mergedOptions.cookieOptions).length;
235
+ const cookieLength = sessionConfig.cookieName.length + seal.length + JSON.stringify(sessionConfig.cookieOptions).length;
240
236
  if (cookieLength > 4096) {
241
237
  throw new Error(
242
238
  `iron-session: Cookie length is too big (${cookieLength} bytes), browsers will refuse it. Try to remove some data.`
243
239
  );
244
240
  }
245
241
  cookieStore.set(
246
- mergedOptions.cookieName,
242
+ sessionConfig.cookieName,
247
243
  seal,
248
- mergedOptions.cookieOptions
244
+ sessionConfig.cookieOptions
249
245
  );
250
246
  }
251
247
  },
252
248
  destroy: {
253
- value: async function destroy(destroyOptions) {
249
+ value: function destroy() {
254
250
  Object.keys(session).forEach((key) => {
255
251
  delete session[key];
256
252
  });
257
- const mergedOptions = mergeOptions(sessionOptions, destroyOptions);
258
- const cookieOptions = { ...mergedOptions.cookieOptions, maxAge: 0 };
259
- cookieStore.set(mergedOptions.cookieName, "", cookieOptions);
253
+ const cookieOptions = { ...sessionConfig.cookieOptions, maxAge: 0 };
254
+ cookieStore.set(sessionConfig.cookieName, "", cookieOptions);
260
255
  }
261
256
  }
262
257
  });
@@ -266,6 +261,6 @@ var sealData = createSealData(crypto);
266
261
  var unsealData = createUnsealData(crypto);
267
262
  var getIronSession = createGetIronSession(sealData, unsealData);
268
263
 
269
- export { createGetIronSession, createSealData, createUnsealData, getIronSession, sealData, unsealData };
264
+ export { getIronSession, sealData, unsealData };
270
265
  //# sourceMappingURL=out.js.map
271
266
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/core.ts","../src/index.ts"],"names":["sealData","unsealData","getIronSession"],"mappings":";AACA,SAAS,OAAO,iBAA8C;AAC9D;AAAA,EACE,YAAY;AAAA,EACZ,QAAQ;AAAA,EACR,UAAU;AAAA,OACL;AA8GP,IAAM,mBAAmB;AACzB,IAAM,wBAAwB,KAAK,KAAK;AAIxC,IAAM,sBAAsB;AAC5B,IAAM,mBAAmB;AAEzB,IAAM,iBAA+C;AAAA,EACnD,KAAK;AAAA,EACL,eAAe,EAAE,UAAU,MAAM,QAAQ,MAAM,UAAU,OAAO,MAAM,IAAI;AAC5E;AAEA,SAAS,6BAA6B,UAAkC;AACtE,SAAO,OAAO,aAAa,WAAW,EAAE,GAAG,SAAS,IAAI;AAC1D;AAEA,SAAS,UAAU,MAGjB;AACA,QAAM,CAAC,oBAAoB,oBAAoB,IAC7C,KAAK,MAAM,gBAAgB;AAC7B,QAAM,eACJ,wBAAwB,OAAO,OAAO,SAAS,sBAAsB,EAAE;AAGzE,SAAO,EAAE,oBAAyC,aAAa;AACjE;AAEA,SAAS,oBAAoB,KAAqB;AAChD,MAAI,QAAQ,GAAG;AAKb,WAAO;AAAA,EACT;AAIA,SAAO,MAAM;AACf;AAEA,SAAS,UAAU,KAAkB,YAA4B;AAC/D,SACE;AAAA,KACG,aAAa,OAAO,OAAO,IAAI,QAAQ,QAAQ,aAC5C,IAAI,QAAQ,IAAI,QAAQ,IACvB,IAAwB,QAAQ,WAAW;AAAA,EAClD,EAAE,UAAU,KAAK;AAErB;AAEA,SAAS,sBACP,YACA,eACQ;AACR,QAAM,eAAe,cAAc,IAAI,UAAU;AACjD,QAAM,SAAS,cAAc;AAC7B,MAAI,OAAO,WAAW,UAAU;AAC9B,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAEA,SAAS,UAAU,KAAmB,aAA2B;AAC/D,MAAI,aAAa,OAAO,OAAO,IAAI,QAAQ,WAAW,YAAY;AAChE,QAAI,QAAQ,OAAO,cAAc,WAAW;AAC5C;AAAA,EACF;AACA,MAAI,oBAAqB,IAAuB,UAAU,YAAY,KAAK,CAAC;AAC5E,MAAI,CAAC,MAAM,QAAQ,iBAAiB,GAAG;AACrC,wBAAoB,CAAC,kBAAkB,SAAS,CAAC;AAAA,EACnD;AACA,EAAC,IAAuB,UAAU,cAAc;AAAA,IAC9C,GAAG;AAAA,IACH;AAAA,EACF,CAAC;AACH;AAEO,SAAS,eAAe,SAAiB;AAC9C,SAAO,eAAeA,UACpB,MACA;AAAA,IACE;AAAA,IACA,MAAM;AAAA,EACR,GACiB;AACjB,UAAM,eAAe,6BAA6B,QAAQ;AAE1D,UAAM,uBAAuB,KAAK;AAAA,MAChC,GAAG,OAAO,KAAK,YAAY,EAAE,IAAI,MAAM;AAAA,IACzC;AACA,UAAM,kBAAkB;AAAA,MACtB,IAAI,qBAAqB,SAAS;AAAA;AAAA,MAElC,QAAQ,aAAa,oBAAoB;AAAA,IAC3C;AAEA,UAAM,OAAO,MAAM,SAAS,SAAS,MAAM,iBAAiB;AAAA,MAC1D,GAAG;AAAA,MACH,KAAK,MAAM;AAAA,IACb,CAAC;AAED,WAAO,GAAG,IAAI,GAAG,gBAAgB,GAAG,mBAAmB;AAAA,EACzD;AACF;AAEO,SAAS,iBAAiB,SAAiB;AAChD,SAAO,eAAeC,YACpB,MACA;AAAA,IACE;AAAA,IACA,MAAM;AAAA,EACR,GACY;AACZ,UAAM,eAAe,6BAA6B,QAAQ;AAC1D,UAAM,EAAE,oBAAoB,aAAa,IAAI,UAAU,IAAI;AAE3D,QAAI;AACF,YAAM,OACH,MAAM,WAAW,SAAS,oBAAoB,cAAc;AAAA,QAC3D,GAAG;AAAA,QACH,KAAK,MAAM;AAAA,MACb,CAAC,KAA2B,CAAC;AAE/B,UAAI,iBAAiB,GAAG;AACtB,eAAO;AAAA,MACT;AAGA,aAAO,EAAE,GAAG,KAAK,WAAW;AAAA,IAC9B,SAAS,OAAO;AACd,UACE,iBAAiB,SACjB,4FAA4F;AAAA,QAC1F,MAAM;AAAA,MACR,GACA;AAKA,eAAO,CAAC;AAAA,MACV;AAGA,YAAM;AAAA,IACR;AAAA,EACF;AACF;AAEA,SAAS,aACP,gBACA,WAC0B;AAC1B,QAAM,UAAoC;AAAA,IACxC,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,eAAe;AAAA,MACb,GAAG,eAAe;AAAA,MAClB,GAAG,eAAe;AAAA,MAClB,GAAG,WAAW;AAAA,IAChB;AAAA,EACF;AAEA,MACE,eAAe,iBACf,YAAY,eAAe,eAC3B;AACA,QAAI,eAAe,cAAc,WAAW,QAAW;AAErD,cAAQ,MAAM;AAAA,IAChB;AAAA,EACF,OAAO;AACL,YAAQ,cAAc,SAAS,oBAAoB,QAAQ,GAAG;AAAA,EAChE;AAEA,SAAO;AACT;AAEA,IAAM,kBACJ;AAEK,SAAS,qBACdD,WACAC,aACA;AACA,SAAOC;AAWP,iBAAeA,gBACb,kBACA,qBACA,gBACyB;AACzB,QAAI,CAAC,kBAAkB;AACrB,YAAM,IAAI,MAAM,eAAe;AAAA,IACjC;AAEA,QAAI,CAAC,qBAAqB;AACxB,YAAM,IAAI,MAAM,eAAe;AAAA,IACjC;AAEA,QAAI,CAAC,gBAAgB;AACnB,aAAO;AAAA,QACL;AAAA,QACA;AAAA,QACAF;AAAA,QACAC;AAAA,MACF;AAAA,IACF;AAEA,UAAM,MAAM;AACZ,UAAM,MAAM;AAEZ,QAAI,CAAC,gBAAgB;AACnB,YAAM,IAAI,MAAM,2CAA2C;AAAA,IAC7D;AAEA,QAAI,CAAC,eAAe,YAAY;AAC9B,YAAM,IAAI,MAAM,+CAA+C;AAAA,IACjE;AAEA,QAAI,CAAC,eAAe,UAAU;AAC5B,YAAM,IAAI,MAAM,4CAA4C;AAAA,IAC9D;AAEA,UAAM,eAAe,6BAA6B,eAAe,QAAQ;AAEzE,QAAI,OAAO,OAAO,YAAY,EAAE,KAAK,CAAC,aAAa,SAAS,SAAS,EAAE,GAAG;AACxE,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAEA,UAAM,UAAU,aAAa,cAAc;AAC3C,UAAM,kBAAkB,UAAU,KAAK,QAAQ,UAAU;AACzD,UAAM,UAAU,kBACZ,MAAMA,YAAc,iBAAiB;AAAA,MACnC,UAAU;AAAA,MACV,KAAK,QAAQ;AAAA,IACf,CAAC,IACA,CAAC;AAEN,WAAO,iBAAiB,SAAS;AAAA,MAC/B,MAAM;AAAA,QACJ,OAAO,eAAe,KAAK,aAAkC;AAC3D,cAAI,iBAAiB,OAAO,IAAI,aAAa;AAC3C,kBAAM,IAAI;AAAA,cACR;AAAA,YACF;AAAA,UACF;AAEA,gBAAM,gBAAgB,aAAa,gBAAgB,WAAW;AAC9D,gBAAM,OAAO,MAAMD,UAAS,SAAS;AAAA,YACnC,UAAU;AAAA,YACV,KAAK,cAAc;AAAA,UACrB,CAAC;AACD,gBAAM,cAAc;AAAA,YAClB,cAAc;AAAA,YACd;AAAA,YACA,cAAc;AAAA,UAChB;AAEA,cAAI,YAAY,SAAS,MAAM;AAC7B,kBAAM,IAAI;AAAA,cACR,2CAA2C,YAAY,MAAM;AAAA,YAC/D;AAAA,UACF;AAEA,oBAAU,KAAK,WAAW;AAAA,QAC5B;AAAA,MACF;AAAA,MAEA,SAAS;AAAA,QACP,OAAO,eAAe,QAAQ,gBAAqC;AACjE,iBAAO,KAAK,OAAO,EAAE,QAAQ,CAAC,QAAQ;AACpC,mBAAQ,QAAoC,GAAG;AAAA,UACjD,CAAC;AAED,gBAAM,gBAAgB,aAAa,gBAAgB,cAAc;AACjE,gBAAM,cAAc,UAAU,cAAc,YAAY,IAAI;AAAA,YAC1D,GAAG,cAAc;AAAA,YACjB,QAAQ;AAAA,UACV,CAAC;AAED,oBAAU,KAAK,WAAW;AAAA,QAC5B;AAAA,MACF;AAAA,IACF,CAAC;AAED,WAAO;AAAA,EACT;AACF;AAEA,eAAe,8BACb,aACA,gBACAA,WACAC,aACyB;AACzB,MAAI,CAAC,gBAAgB;AACnB,UAAM,IAAI,MAAM,2CAA2C;AAAA,EAC7D;AAEA,MAAI,CAAC,eAAe,YAAY;AAC9B,UAAM,IAAI,MAAM,+CAA+C;AAAA,EACjE;AAEA,MAAI,CAAC,eAAe,UAAU;AAC5B,UAAM,IAAI,MAAM,4CAA4C;AAAA,EAC9D;AAEA,QAAM,eAAe,6BAA6B,eAAe,QAAQ;AAEzE,MAAI,OAAO,OAAO,YAAY,EAAE,KAAK,CAAC,aAAa,SAAS,SAAS,EAAE,GAAG;AACxE,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAEA,QAAM,UAAU,aAAa,cAAc;AAC3C,QAAM,kBAAkB;AAAA,IACtB,QAAQ;AAAA,IACR;AAAA,EACF;AACA,QAAM,UAAU,kBACZ,MAAMA,YAAc,iBAAiB;AAAA,IACnC,UAAU;AAAA,IACV,KAAK,QAAQ;AAAA,EACf,CAAC,IACA,CAAC;AAEN,SAAO,iBAAiB,SAAS;AAAA,IAC/B,MAAM;AAAA,MACJ,OAAO,eAAe,KAAK,aAAkC;AAC3D,cAAM,gBAAgB,aAAa,gBAAgB,WAAW;AAC9D,cAAM,OAAO,MAAMD,UAAS,SAAS;AAAA,UACnC,UAAU;AAAA,UACV,KAAK,cAAc;AAAA,QACrB,CAAC;AAED,cAAM,eACJ,cAAc,WAAW,SACzB,KAAK,SACL,KAAK,UAAU,cAAc,aAAa,EAAE;AAE9C,YAAI,eAAe,MAAM;AACvB,gBAAM,IAAI;AAAA,YACR,2CAA2C,YAAY;AAAA,UACzD;AAAA,QACF;AAEA,oBAAY;AAAA,UACV,cAAc;AAAA,UACd;AAAA,UACA,cAAc;AAAA,QAChB;AAAA,MACF;AAAA,IACF;AAAA,IAEA,SAAS;AAAA,MACP,OAAO,eAAe,QAAQ,gBAAoC;AAChE,eAAO,KAAK,OAAO,EAAE,QAAQ,CAAC,QAAQ;AACpC,iBAAQ,QAAoC,GAAG;AAAA,QACjD,CAAC;AACD,cAAM,gBAAgB,aAAa,gBAAgB,cAAc;AACjE,cAAM,gBAAgB,EAAE,GAAG,cAAc,eAAe,QAAQ,EAAE;AAElE,oBAAY,IAAI,cAAc,YAAY,IAAI,aAAa;AAAA,MAC7D;AAAA,IACF;AAAA,EACF,CAAC;AAED,SAAO;AACT;;;AChfA,YAAY,YAAY;AAGjB,IAAM,WAAW,eAAe,MAAM;AACtC,IAAM,aAAa,iBAAiB,MAAM;AAC1C,IAAM,iBAAiB,qBAAqB,UAAU,UAAU","sourcesContent":["import type { IncomingMessage, ServerResponse } from \"http\";\nimport { parse, serialize, type CookieSerializeOptions } from \"cookie\";\nimport {\n defaults as ironDefaults,\n seal as ironSeal,\n unseal as ironUnseal,\n} 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\n extends Pick<\n CookieSerializeOptions,\n \"domain\" | \"path\" | \"sameSite\" | \"secure\"\n > {\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?: CookieSerializeOptions[\"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<CookieSerializeOptions, \"httpOnly\" | \"maxAge\" | \"priority\">;\n\n/**\n * The high-level type definition of the .get() and .set() methods\n * of { cookies() } from \"next/headers\"\n */\nexport interface CookieStore {\n get: (name: string) => { name: string; value: string } | undefined;\n set: {\n (name: string, value: string, cookie?: Partial<ResponseCookie>): void;\n (options: ResponseCookie): void;\n };\n}\n\n/**\n * Set-Cookie Attributes do not include `encode`. We omit this from our `cookieOptions` type.\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<CookieSerializeOptions, \"encode\">;\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.\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\ntype OverridableOptions = Pick<SessionOptions, \"cookieOptions\" | \"ttl\">;\n\nexport type IronSession<T> = T & {\n /**\n * Destroys the session data and removes the cookie.\n */\n readonly destroy: (destroyOptions?: OverridableOptions) => Promise<void>;\n\n /**\n * Encrypts the session data and sets the cookie.\n */\n readonly save: (saveOptions?: OverridableOptions) => Promise<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<OverridableOptions> = {\n ttl: fourteenDaysInSeconds,\n cookieOptions: { httpOnly: true, secure: true, sameSite: \"lax\", path: \"/\" },\n};\n\nfunction normalizeStringPasswordToMap(password: Password): PasswordsMap {\n return typeof password === \"string\" ? { 1: password } : password;\n}\n\nfunction parseSeal(seal: string): {\n sealWithoutVersion: string;\n tokenVersion: number | null;\n} {\n const [sealWithoutVersion, tokenVersionAsString] =\n seal.split(versionDelimiter);\n const tokenVersion =\n tokenVersionAsString == null ? null : parseInt(tokenVersionAsString, 10);\n\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n return { sealWithoutVersion: sealWithoutVersion!, tokenVersion };\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 // The next line makes sure browser will expire cookies before seals are considered expired by the server.\n // It also allows for clock difference of 60 seconds between server and clients.\n return ttl - timestampSkewSec;\n}\n\nfunction getCookie(req: RequestType, cookieName: string): string {\n return (\n parse(\n (\"headers\" in req && typeof req.headers.get === \"function\"\n ? req.headers.get(\"cookie\")\n : (req as IncomingMessage).headers.cookie) ?? \"\",\n )[cookieName] ?? \"\"\n );\n}\n\nfunction getServerActionCookie(\n cookieName: string,\n cookieHandler: CookieStore,\n): string {\n const cookieObject = cookieHandler.get(cookieName);\n const cookie = cookieObject?.value;\n if (typeof cookie === \"string\") {\n return cookie;\n }\n return \"\";\n}\n\nfunction setCookie(res: ResponseType, cookieValue: string): void {\n if (\"headers\" in res && typeof res.headers.append === \"function\") {\n res.headers.append(\"set-cookie\", cookieValue);\n return;\n }\n let existingSetCookie = (res as ServerResponse).getHeader(\"set-cookie\") ?? [];\n if (!Array.isArray(existingSetCookie)) {\n existingSetCookie = [existingSetCookie.toString()];\n }\n (res as ServerResponse).setHeader(\"set-cookie\", [\n ...existingSetCookie,\n cookieValue,\n ]);\n}\n\nexport function createSealData(_crypto: Crypto) {\n return async function sealData(\n data: unknown,\n {\n password,\n ttl = fourteenDaysInSeconds,\n }: { password: Password; ttl?: number },\n ): Promise<string> {\n const passwordsMap = normalizeStringPasswordToMap(password);\n\n const mostRecentPasswordId = Math.max(\n ...Object.keys(passwordsMap).map(Number),\n );\n const passwordForSeal = {\n id: mostRecentPasswordId.toString(),\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n secret: passwordsMap[mostRecentPasswordId]!,\n };\n\n const seal = await ironSeal(_crypto, data, passwordForSeal, {\n ...ironDefaults,\n ttl: ttl * 1000,\n });\n\n return `${seal}${versionDelimiter}${currentMajorVersion}`;\n };\n}\n\nexport function createUnsealData(_crypto: Crypto) {\n return async function unsealData<T>(\n seal: string,\n {\n password,\n ttl = fourteenDaysInSeconds,\n }: { password: Password; ttl?: number },\n ): Promise<T> {\n const passwordsMap = normalizeStringPasswordToMap(password);\n const { sealWithoutVersion, tokenVersion } = parseSeal(seal);\n\n try {\n const data =\n (await ironUnseal(_crypto, sealWithoutVersion, passwordsMap, {\n ...ironDefaults,\n ttl: ttl * 1000,\n })) /* c8 ignore next */ ?? {};\n\n if (tokenVersion === 2) {\n return data as T;\n }\n\n // @ts-expect-error `persistent` does not exist on newer tokens\n return { ...data.persistent } as T;\n } catch (error) {\n if (\n error instanceof Error &&\n /^(Expired seal|Bad hmac value|Cannot find password|Incorrect number of sealed components)/.test(\n error.message,\n )\n ) {\n // if seal expired or\n // if seal is not valid (encrypted using a different password, when passwords are badly rotated) or\n // if we can't find back the password in the seal\n // then we just start a new session over\n return {} as T;\n }\n\n /* c8 ignore next 2 */\n throw error;\n }\n };\n}\n\nfunction mergeOptions(\n sessionOptions: SessionOptions,\n overrides?: OverridableOptions,\n): Required<SessionOptions> {\n const options: Required<SessionOptions> = {\n ...defaultOptions,\n ...sessionOptions,\n ...overrides,\n cookieOptions: {\n ...defaultOptions.cookieOptions,\n ...sessionOptions.cookieOptions,\n ...overrides?.cookieOptions,\n },\n };\n\n if (\n sessionOptions.cookieOptions &&\n \"maxAge\" in sessionOptions.cookieOptions\n ) {\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\nconst badUsageMessage =\n \"iron-session: Bad usage: use getIronSession(req, res, options) or getIronSession(cookies, options).\";\n\nexport function createGetIronSession(\n sealData: ReturnType<typeof createSealData>,\n unsealData: ReturnType<typeof createUnsealData>,\n) {\n return getIronSession;\n\n async function getIronSession<T extends object>(\n cookies: CookieStore,\n sessionOptions: SessionOptions,\n ): Promise<IronSession<T>>;\n async function getIronSession<T extends object>(\n req: RequestType,\n res: ResponseType,\n sessionOptions: SessionOptions,\n ): Promise<IronSession<T>>;\n async function getIronSession<T extends object>(\n reqOrCookieStore: RequestType | CookieStore,\n resOrsessionOptions: ResponseType | SessionOptions,\n sessionOptions?: SessionOptions,\n ): Promise<IronSession<T>> {\n if (!reqOrCookieStore) {\n throw new Error(badUsageMessage);\n }\n\n if (!resOrsessionOptions) {\n throw new Error(badUsageMessage);\n }\n\n if (!sessionOptions) {\n return getIronSessionFromCookieStore<T>(\n reqOrCookieStore as CookieStore,\n resOrsessionOptions as SessionOptions,\n sealData,\n unsealData,\n );\n }\n\n const req = reqOrCookieStore as RequestType;\n const res = resOrsessionOptions as ResponseType;\n\n if (!sessionOptions) {\n throw new Error(\"iron-session: Bad usage. Missing options.\");\n }\n\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.values(passwordsMap).some((password) => password.length < 32)) {\n throw new Error(\n \"iron-session: Bad usage. Password must be at least 32 characters long.\",\n );\n }\n\n const options = mergeOptions(sessionOptions);\n const sealFromCookies = getCookie(req, options.cookieName);\n const session = sealFromCookies\n ? await unsealData<T>(sealFromCookies, {\n password: passwordsMap,\n ttl: options.ttl,\n })\n : ({} as T);\n\n Object.defineProperties(session, {\n save: {\n value: async function save(saveOptions?: OverridableOptions) {\n if (\"headersSent\" in res && 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 const mergedOptions = mergeOptions(sessionOptions, saveOptions);\n const seal = await sealData(session, {\n password: passwordsMap,\n ttl: mergedOptions.ttl,\n });\n const cookieValue = serialize(\n mergedOptions.cookieName,\n seal,\n mergedOptions.cookieOptions,\n );\n\n if (cookieValue.length > 4096) {\n throw new Error(\n `iron-session: Cookie length is too big (${cookieValue.length} bytes), browsers will refuse it. Try to remove some data.`,\n );\n }\n\n setCookie(res, cookieValue);\n },\n },\n\n destroy: {\n value: async function destroy(destroyOptions?: OverridableOptions) {\n Object.keys(session).forEach((key) => {\n delete (session as Record<string, unknown>)[key];\n });\n\n const mergedOptions = mergeOptions(sessionOptions, destroyOptions);\n const cookieValue = serialize(mergedOptions.cookieName, \"\", {\n ...mergedOptions.cookieOptions,\n maxAge: 0,\n });\n\n setCookie(res, cookieValue);\n },\n },\n });\n\n return session as IronSession<T>;\n }\n}\n\nasync function getIronSessionFromCookieStore<T extends object>(\n cookieStore: CookieStore,\n sessionOptions: SessionOptions,\n sealData: ReturnType<typeof createSealData>,\n unsealData: ReturnType<typeof createUnsealData>,\n): Promise<IronSession<T>> {\n if (!sessionOptions) {\n throw new Error(\"iron-session: Bad usage. Missing options.\");\n }\n\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.values(passwordsMap).some((password) => password.length < 32)) {\n throw new Error(\n \"iron-session: Bad usage. Password must be at least 32 characters long.\",\n );\n }\n\n const options = mergeOptions(sessionOptions);\n const sealFromCookies = getServerActionCookie(\n options.cookieName,\n cookieStore,\n );\n const session = sealFromCookies\n ? await unsealData<T>(sealFromCookies, {\n password: passwordsMap,\n ttl: options.ttl,\n })\n : ({} as T);\n\n Object.defineProperties(session, {\n save: {\n value: async function save(saveOptions?: OverridableOptions) {\n const mergedOptions = mergeOptions(sessionOptions, saveOptions);\n const seal = await sealData(session, {\n password: passwordsMap,\n ttl: mergedOptions.ttl,\n });\n\n const cookieLength =\n mergedOptions.cookieName.length +\n seal.length +\n JSON.stringify(mergedOptions.cookieOptions).length;\n\n if (cookieLength > 4096) {\n throw new Error(\n `iron-session: Cookie length is too big (${cookieLength} bytes), browsers will refuse it. Try to remove some data.`,\n );\n }\n\n cookieStore.set(\n mergedOptions.cookieName,\n seal,\n mergedOptions.cookieOptions,\n );\n },\n },\n\n destroy: {\n value: async function destroy(destroyOptions: OverridableOptions) {\n Object.keys(session).forEach((key) => {\n delete (session as Record<string, unknown>)[key];\n });\n const mergedOptions = mergeOptions(sessionOptions, destroyOptions);\n const cookieOptions = { ...mergedOptions.cookieOptions, maxAge: 0 };\n\n cookieStore.set(mergedOptions.cookieName, \"\", cookieOptions);\n },\n },\n });\n\n return session as IronSession<T>;\n}\n","import {\n createGetIronSession,\n createSealData,\n createUnsealData,\n} from \"./core.js\";\n\nimport * as crypto from \"uncrypto\";\n\nexport * from \"./core.js\";\nexport const sealData = createSealData(crypto);\nexport const unsealData = createUnsealData(crypto);\nexport const getIronSession = createGetIronSession(sealData, unsealData);\n// export const getServerActionIronSession = createGetServerActionIronSession(\n// sealData,\n// unsealData,\n// );\n"]}
1
+ {"version":3,"sources":["../src/core.ts","../src/index.ts"],"names":["sealData","unsealData","getIronSession"],"mappings":"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","sourcesContent":["import type { IncomingMessage, ServerResponse } from \"http\";\nimport { parse, serialize, type CookieSerializeOptions } from \"cookie\";\nimport {\n defaults as ironDefaults,\n seal as ironSeal,\n unseal as ironUnseal,\n} 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\n extends Pick<\n CookieSerializeOptions,\n \"domain\" | \"path\" | \"sameSite\" | \"secure\"\n > {\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?: CookieSerializeOptions[\"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<CookieSerializeOptions, \"httpOnly\" | \"maxAge\" | \"priority\">;\n\n/**\n * The high-level type definition of the .get() and .set() methods\n * of { cookies() } from \"next/headers\"\n */\nexport interface CookieStore {\n get: (name: string) => { name: string; value: string } | undefined;\n set: {\n (name: string, value: string, cookie?: Partial<ResponseCookie>): void;\n (options: ResponseCookie): void;\n };\n}\n\n/**\n * Set-Cookie Attributes do not include `encode`. We omit this from our `cookieOptions` type.\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<CookieSerializeOptions, \"encode\">;\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.\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\nexport type IronSession<T> = 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: () => Promise<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\">> =\n {\n ttl: fourteenDaysInSeconds,\n cookieOptions: { httpOnly: true, secure: true, sameSite: \"lax\", path: \"/\" },\n };\n\nfunction normalizeStringPasswordToMap(password: Password): PasswordsMap {\n return typeof password === \"string\" ? { 1: password } : password;\n}\n\nfunction parseSeal(seal: string): {\n sealWithoutVersion: string;\n tokenVersion: number | null;\n} {\n const [sealWithoutVersion, tokenVersionAsString] =\n seal.split(versionDelimiter);\n const tokenVersion =\n tokenVersionAsString == null ? null : parseInt(tokenVersionAsString, 10);\n\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n return { sealWithoutVersion: sealWithoutVersion!, tokenVersion };\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 // The next line makes sure browser will expire cookies before seals are considered expired by the server.\n // It also allows for clock difference of 60 seconds between server and clients.\n return ttl - timestampSkewSec;\n}\n\nfunction getCookie(req: RequestType, cookieName: string): string {\n return (\n parse(\n (\"headers\" in req && typeof req.headers.get === \"function\"\n ? req.headers.get(\"cookie\")\n : (req as IncomingMessage).headers.cookie) ?? \"\",\n )[cookieName] ?? \"\"\n );\n}\n\nfunction getServerActionCookie(\n cookieName: string,\n cookieHandler: CookieStore,\n): string {\n const cookieObject = cookieHandler.get(cookieName);\n const cookie = cookieObject?.value;\n if (typeof cookie === \"string\") {\n return cookie;\n }\n return \"\";\n}\n\nfunction setCookie(res: ResponseType, cookieValue: string): void {\n if (\"headers\" in res && typeof res.headers.append === \"function\") {\n res.headers.append(\"set-cookie\", cookieValue);\n return;\n }\n let existingSetCookie = (res as ServerResponse).getHeader(\"set-cookie\") ?? [];\n if (!Array.isArray(existingSetCookie)) {\n existingSetCookie = [existingSetCookie.toString()];\n }\n (res as ServerResponse).setHeader(\"set-cookie\", [\n ...existingSetCookie,\n cookieValue,\n ]);\n}\n\nexport function createSealData(_crypto: Crypto) {\n return async function sealData(\n data: unknown,\n {\n password,\n ttl = fourteenDaysInSeconds,\n }: { password: Password; ttl?: number },\n ): Promise<string> {\n const passwordsMap = normalizeStringPasswordToMap(password);\n\n const mostRecentPasswordId = Math.max(\n ...Object.keys(passwordsMap).map(Number),\n );\n const passwordForSeal = {\n id: mostRecentPasswordId.toString(),\n secret: passwordsMap[mostRecentPasswordId]!,\n };\n\n const seal = await ironSeal(_crypto, data, passwordForSeal, {\n ...ironDefaults,\n ttl: ttl * 1000,\n });\n\n return `${seal}${versionDelimiter}${currentMajorVersion}`;\n };\n}\n\nexport function createUnsealData(_crypto: Crypto) {\n return async function unsealData<T>(\n seal: string,\n {\n password,\n ttl = fourteenDaysInSeconds,\n }: { password: Password; ttl?: number },\n ): Promise<T> {\n const passwordsMap = normalizeStringPasswordToMap(password);\n const { sealWithoutVersion, tokenVersion } = parseSeal(seal);\n\n try {\n const data =\n (await ironUnseal(_crypto, sealWithoutVersion, passwordsMap, {\n ...ironDefaults,\n ttl: ttl * 1000,\n })) ?? {};\n\n if (tokenVersion === 2) {\n return data as T;\n }\n\n // @ts-expect-error `persistent` does not exist on newer tokens\n return { ...data.persistent } as T;\n } catch (error) {\n if (\n error instanceof Error &&\n /^(Expired seal|Bad hmac value|Cannot find password|Incorrect number of sealed components)/.test(\n error.message,\n )\n ) {\n // if seal expired or\n // if seal is not valid (encrypted using a different password, when passwords are badly rotated) or\n // if we can't find back the password in the seal\n // then we just start a new session over\n return {} as T;\n }\n\n throw error;\n }\n };\n}\n\nfunction getSessionConfig(\n sessionOptions: SessionOptions,\n): Required<SessionOptions> {\n const options = {\n ...defaultOptions,\n ...sessionOptions,\n cookieOptions: {\n ...defaultOptions.cookieOptions,\n ...(sessionOptions.cookieOptions || {}),\n },\n };\n\n if (\n sessionOptions.cookieOptions &&\n \"maxAge\" in sessionOptions.cookieOptions\n ) {\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\nconst badUsageMessage =\n \"iron-session: Bad usage: use getIronSession(req, res, options) or getIronSession(cookieStore, options).\";\n\nexport function createGetIronSession(\n sealData: ReturnType<typeof createSealData>,\n unsealData: ReturnType<typeof createUnsealData>,\n) {\n return getIronSession;\n\n async function getIronSession<T extends object>(\n cookies: CookieStore,\n sessionOptions: SessionOptions,\n ): Promise<IronSession<T>>;\n async function getIronSession<T extends object>(\n req: RequestType,\n res: ResponseType,\n sessionOptions: SessionOptions,\n ): Promise<IronSession<T>>;\n async function getIronSession<T extends object>(\n reqOrCookieStore: RequestType | CookieStore,\n resOrsessionOptions: ResponseType | SessionOptions,\n sessionOptions?: SessionOptions,\n ): Promise<IronSession<T>> {\n if (!reqOrCookieStore) {\n throw new Error(badUsageMessage);\n }\n\n if (!resOrsessionOptions) {\n throw new Error(badUsageMessage);\n }\n\n if (!sessionOptions) {\n return getIronSessionFromCookieStore<T>(\n reqOrCookieStore as CookieStore,\n resOrsessionOptions as SessionOptions,\n sealData,\n unsealData,\n );\n }\n\n const req = reqOrCookieStore as RequestType;\n const res = resOrsessionOptions as ResponseType;\n\n if (!sessionOptions) {\n throw new Error(badUsageMessage);\n }\n\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.values(passwordsMap).some((password) => password.length < 32)) {\n throw new Error(\n \"iron-session: Bad usage. Password must be at least 32 characters long.\",\n );\n }\n\n let sessionConfig = getSessionConfig(sessionOptions);\n\n const sealFromCookies = getCookie(req, sessionConfig.cookieName);\n const session = sealFromCookies\n ? await unsealData<T>(sealFromCookies, {\n password: passwordsMap,\n ttl: sessionConfig.ttl,\n })\n : ({} as T);\n\n Object.defineProperties(session, {\n updateConfig: {\n value: function updateConfig(newSessionOptions: SessionOptions) {\n sessionConfig = getSessionConfig(newSessionOptions);\n },\n },\n save: {\n value: async function save() {\n if (\"headersSent\" in res && 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 const seal = await sealData(session, {\n password: passwordsMap,\n ttl: sessionConfig.ttl,\n });\n const cookieValue = serialize(\n sessionConfig.cookieName,\n seal,\n sessionConfig.cookieOptions,\n );\n\n if (cookieValue.length > 4096) {\n throw new Error(\n `iron-session: Cookie length is too big (${cookieValue.length} bytes), browsers will refuse it. Try to remove some data.`,\n );\n }\n\n setCookie(res, cookieValue);\n },\n },\n\n destroy: {\n value: function destroy() {\n Object.keys(session).forEach((key) => {\n delete (session as Record<string, unknown>)[key];\n });\n const cookieValue = serialize(sessionConfig.cookieName, \"\", {\n ...sessionConfig.cookieOptions,\n maxAge: 0,\n });\n\n setCookie(res, cookieValue);\n },\n },\n });\n\n return session as IronSession<T>;\n }\n}\n\nasync function getIronSessionFromCookieStore<T extends object>(\n cookieStore: CookieStore,\n sessionOptions: SessionOptions,\n sealData: ReturnType<typeof createSealData>,\n unsealData: ReturnType<typeof createUnsealData>,\n): Promise<IronSession<T>> {\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.values(passwordsMap).some((password) => password.length < 32)) {\n throw new Error(\n \"iron-session: Bad usage. Password must be at least 32 characters long.\",\n );\n }\n\n const sessionConfig = getSessionConfig(sessionOptions);\n const sealFromCookies = getServerActionCookie(\n sessionConfig.cookieName,\n cookieStore,\n );\n const session = sealFromCookies\n ? await unsealData<T>(sealFromCookies, {\n password: passwordsMap,\n ttl: sessionConfig.ttl,\n })\n : ({} as T);\n\n Object.defineProperties(session, {\n save: {\n value: async function save() {\n const seal = await sealData(session, {\n password: passwordsMap,\n ttl: sessionConfig.ttl,\n });\n\n const cookieLength =\n sessionConfig.cookieName.length +\n seal.length +\n JSON.stringify(sessionConfig.cookieOptions).length;\n\n if (cookieLength > 4096) {\n throw new Error(\n `iron-session: Cookie length is too big (${cookieLength} bytes), browsers will refuse it. Try to remove some data.`,\n );\n }\n\n cookieStore.set(\n sessionConfig.cookieName,\n seal,\n sessionConfig.cookieOptions,\n );\n },\n },\n\n destroy: {\n value: function destroy() {\n Object.keys(session).forEach((key) => {\n delete (session as Record<string, unknown>)[key];\n });\n\n const cookieOptions = { ...sessionConfig.cookieOptions, maxAge: 0 };\n cookieStore.set(sessionConfig.cookieName, \"\", cookieOptions);\n },\n },\n });\n\n return session as IronSession<T>;\n}\n","import {\n createGetIronSession,\n createSealData,\n createUnsealData,\n} from \"./core.js\";\n\nimport * as crypto from \"uncrypto\";\n\nexport type { IronSession, SessionOptions } from \"./core.js\";\nexport const sealData = createSealData(crypto);\nexport const unsealData = createUnsealData(crypto);\nexport const getIronSession = createGetIronSession(sealData, unsealData);\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "iron-session",
3
- "version": "8.0.0-beta.6",
3
+ "version": "8.0.0",
4
4
  "description": "Secure, stateless, and cookie-based session library for JavaScript",
5
5
  "keywords": [
6
6
  "session",
@@ -42,14 +42,13 @@
42
42
  "uncrypto": "0.1.3"
43
43
  },
44
44
  "devDependencies": {
45
- "@release-it/conventional-changelog": "8.0.1",
46
45
  "@types/cookie": "0.5.4",
47
- "@types/node": "20.9.0",
46
+ "@types/node": "18.18.10",
48
47
  "@typescript-eslint/eslint-plugin": "6.11.0",
49
48
  "@typescript-eslint/parser": "6.11.0",
50
49
  "c8": "8.0.1",
51
50
  "concurrently": "8.2.2",
52
- "eslint": "8.53.0",
51
+ "eslint": "8.54.0",
53
52
  "eslint-config-prettier": "9.0.0",
54
53
  "eslint-import-resolver-node": "0.3.9",
55
54
  "eslint-import-resolver-typescript": "3.6.1",
@@ -58,12 +57,11 @@
58
57
  "prettier": "3.1.0",
59
58
  "prettier-plugin-packagejson": "2.4.6",
60
59
  "publint": "0.2.5",
61
- "release-it": "17.0.0",
62
- "tsup": "7.2.0",
63
- "tsx": "4.1.2",
60
+ "tsup": "8.0.0",
61
+ "tsx": "4.1.4",
64
62
  "typescript": "5.2.2"
65
63
  },
66
- "packageManager": "pnpm@8.10.2",
64
+ "packageManager": "pnpm@8.10.5",
67
65
  "publishConfig": {
68
66
  "access": "public",
69
67
  "registry": "https://registry.npmjs.org"
@@ -72,7 +70,6 @@
72
70
  "build": "tsup",
73
71
  "dev": "pnpm build && concurrently \"pnpm build --watch\" \"pnpm --filter=next-example dev\" ",
74
72
  "lint": "tsc --noEmit && tsc --noEmit -p examples/next/tsconfig.json && pnpm eslint . && publint",
75
- "release": "pnpm lint",
76
73
  "test": "c8 -r text -r lcov node --loader tsx --test src/*.test.ts && pnpm build",
77
74
  "test:watch": "node --loader tsx --test --watch src/*.test.ts"
78
75
  }