iron-session 6.2.0-beta.1 → 6.2.1

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,7 @@
2
2
 
3
3
  <p align="center"><b>⭐️ Featured in the <a href="https://nextjs.org/docs/authentication">Next.js documentation</a></b></p>
4
4
 
5
- _🛠 Node.js stateless session utility using signed and encrypted cookies to store data. Works with Next.js, Express, NestJs, Fastify, and any Node.js HTTP framework._
5
+ _🛠 Node.js stateless session utility using signed and encrypted cookies to store data. Works with Next.js, Express, and Node.js HTTP servers._
6
6
 
7
7
  The session data is stored in encrypted cookies ("seals"). And only your server can decode the session data. There are no session ids, making iron sessions "stateless" from the server point of view.
8
8
 
@@ -48,7 +48,8 @@ _Table of contents:_
48
48
  ## Installation
49
49
 
50
50
  ```bash
51
- npm add iron-session
51
+ npm install iron-session
52
+ yarn add iron-session
52
53
  ```
53
54
 
54
55
  ## Next.js usage
@@ -177,7 +178,7 @@ As of version 6.2.0, this library is compatible with [Next.js middlewares](https
177
178
  Since there's no pre-available `res` object in Next.js's middlewares, you need to use iron-session this way:
178
179
 
179
180
  ```ts
180
- // pages/middleware.ts
181
+ // /middleware.ts
181
182
  import { NextResponse } from "next/server";
182
183
  import type { NextRequest } from "next/server";
183
184
  import { getIronSession } from "iron-session/edge";
@@ -210,7 +211,8 @@ export const middleware = async (req: NextRequest) => {
210
211
 
211
212
  // demo:
212
213
  if (user?.admin !== "true") {
213
- return new NextResponse(null, { status: 403 }); // unauthorized to see pages inside admin/
214
+ // unauthorized to see pages inside admin/
215
+ return NextResponse.redirect(new URL('/unauthorized', req.url)) // redirect to /unauthorized page
214
216
  }
215
217
 
216
218
  return res;
@@ -379,9 +381,12 @@ export const getServerSideProps = withSessionSsr(
379
381
 
380
382
  `req.session` is automatically populated with the right types so .save() and .destroy() can be called on it.
381
383
 
382
- But you might want to go further and type your session data also. To do so, use [module augmentation](https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation):
384
+ But you might want to go further and type your session data also. So you can get autocompletion on `session.user` for example. To do so, use [module augmentation](https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation):
383
385
 
384
386
  ```ts
387
+ // You may need the next line in some situations
388
+ // import * as IronSession from "iron-session";
389
+
385
390
  declare module "iron-session" {
386
391
  interface IronSessionData {
387
392
  user?: {
@@ -390,16 +395,6 @@ declare module "iron-session" {
390
395
  };
391
396
  }
392
397
  }
393
-
394
- // If you're also using the iron-session/edge endpoint, you need this too:
395
- // declare module "iron-session/edge" {
396
- // interface IronSessionData {
397
- // user?: {
398
- // id: number;
399
- // admin?: boolean;
400
- // };
401
- // }
402
- // }
403
398
  ```
404
399
 
405
400
  You can put this code anywhere in your project, as long as it is in a file that will be required at some point. For example it could be inside your `lib/withSession.ts` wrapper or inside an [`additional.d.ts`](https://nextjs.org/docs/basic-features/typescript) if you're using Next.js.
@@ -818,6 +813,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
818
813
  <tr>
819
814
  <td align="center"><a href="http://www.afterecon.com/"><img src="https://avatars.githubusercontent.com/u/5559355?v=4?s=100" width="100px;" alt=""/><br /><sub><b>John Vandivier</b></sub></a><br /><a href="https://github.com/vvo/iron-session/commits?author=Vandivier" title="Code">💻</a> <a href="#ideas-Vandivier" title="Ideas, Planning, & Feedback">🤔</a> <a href="#example-Vandivier" title="Examples">💡</a></td>
820
815
  <td align="center"><a href="https://searchableguy.com"><img src="https://avatars.githubusercontent.com/u/73341821?v=4?s=100" width="100px;" alt=""/><br /><sub><b>searchableguy</b></sub></a><br /><a href="https://github.com/vvo/iron-session/commits?author=searchableguy" title="Tests">⚠️</a> <a href="https://github.com/vvo/iron-session/commits?author=searchableguy" title="Documentation">📖</a> <a href="https://github.com/vvo/iron-session/commits?author=searchableguy" title="Code">💻</a></td>
816
+ <td align="center"><a href="http://brc-dd.me"><img src="https://avatars.githubusercontent.com/u/40380293?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Divyansh Singh</b></sub></a><br /><a href="https://github.com/vvo/iron-session/commits?author=brc-dd" title="Code">💻</a> <a href="https://github.com/vvo/iron-session/commits?author=brc-dd" title="Documentation">📖</a> <a href="#ideas-brc-dd" title="Ideas, Planning, & Feedback">🤔</a></td>
821
817
  </tr>
822
818
  </table>
823
819
 
package/dist/index.js CHANGED
@@ -163,12 +163,10 @@ function createUnsealData(_crypto2) {
163
163
  const passwordsAsMap = normalizeStringPasswordToMap(password);
164
164
  const { sealWithoutVersion, tokenVersion } = parseSeal(seal2);
165
165
  try {
166
- const data = await Iron.unseal(
167
- _crypto2,
168
- sealWithoutVersion,
169
- passwordsAsMap,
170
- { ...Iron.defaults, ttl: ttl * 1e3 }
171
- );
166
+ const data = await Iron.unseal(_crypto2, sealWithoutVersion, passwordsAsMap, {
167
+ ...Iron.defaults,
168
+ ttl: ttl * 1e3
169
+ }) || {};
172
170
  if (tokenVersion === 2) {
173
171
  return data;
174
172
  }
@@ -177,7 +175,7 @@ function createUnsealData(_crypto2) {
177
175
  };
178
176
  } catch (error) {
179
177
  if (error instanceof Error) {
180
- if (error.message === "Expired seal" || error.message === "Bad hmac value" || error.message === "Cannot find password: " || error.message === "Incorrect number of sealed components") {
178
+ if (error.message === "Expired seal" || error.message === "Bad hmac value" || error.message.startsWith("Cannot find password: ") || error.message === "Incorrect number of sealed components") {
181
179
  return {};
182
180
  }
183
181
  }
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/core.ts"],"sourcesContent":["import { createGetIronSession, createSealData, createUnsealData } from \"./core\";\nimport { Crypto } from \"@peculiar/webcrypto\";\n\nconst _crypto = new Crypto();\n\nexport * from \"./core\";\nexport const unsealData = createUnsealData(_crypto);\nexport const sealData = createSealData(_crypto);\nexport const getIronSession = createGetIronSession(\n _crypto,\n unsealData,\n sealData,\n);\n","import * as Iron from \"iron-webcrypto\";\nimport type { CookieSerializeOptions } from \"cookie\";\nimport cookie from \"cookie\";\nimport type { IncomingMessage, ServerResponse } from \"http\";\n\n// default time allowed to check for iron seal validity when ttl passed\n// see https://hapi.dev/family/iron/api/?v=6.0.0#options\nconst timestampSkewSec = 60;\n\ntype passwordsMap = { [id: string]: string };\ntype password = string | passwordsMap;\n\nconst fourteenDaysInSeconds = 15 * 24 * 3600;\n\n// We store a token major version to handle data format changes when any. So that when you upgrade the cookies\n// can be kept alive between upgrades, no need to disconnect everyone.\nconst currentMajorVersion = 2;\nconst versionDelimiter = \"~\";\n\nconst defaultOptions: {\n ttl: number;\n cookieOptions: CookieSerializeOptions;\n} = {\n ttl: fourteenDaysInSeconds,\n cookieOptions: {\n httpOnly: true,\n secure: true,\n sameSite: \"lax\",\n path: \"/\",\n },\n};\n\nexport interface IronSessionOptions {\n /**\n * This is the cookie name that will be used inside the browser. You should make sure it's unique given\n * your application. Example: vercel-session\n */\n cookieName: string;\n\n /**\n * This is the password(s) that will be used to encrypt the cookie. It can be either a string or an object\n * like {1: \"password\", 2: password}.\n *\n * When you provide multiple passwords then all of them will be used to decrypt the cookie and only the most\n * recent (= highest key, 2 in this example) password will be used to encrypt the cookie. This allow you\n * to use password rotation (security)\n */\n password: password;\n\n /**\n * This is the time in seconds that the session will be valid for. This also set the max-age attribute of\n * the cookie automatically (minus 60 seconds so that the cookie always expire before the session).\n */\n ttl?: number;\n\n /**\n * This is the options that will be passed to the cookie library.\n * You can see all of them here: https://github.com/jshttp/cookie#options-1.\n *\n * If you want to use \"session cookies\" (cookies that are deleted when the browser is closed) then you need\n * to pass cookieOptions: { maxAge: undefined }.\n */\n cookieOptions?: CookieSerializeOptions;\n}\n\n// eslint-disable-next-line @typescript-eslint/no-empty-interface\nexport interface IronSessionData {\n // If we allow for any keys, then there's no more type check on unknown properties\n // which is not good\n // If we allow for any keys, the later delete will work but I prefer to disable the\n // check at this stage and\n // provide good type checking instead\n // [key: string]: unknown;\n}\n\nexport type IronSession = IronSessionData & {\n /**\n * Destroys the session data and removes the cookie.\n */\n destroy: () => void;\n\n /**\n * Encrypts the session data and sets the cookie.\n */\n save: () => Promise<void>;\n};\n\ndeclare module \"http\" {\n interface IncomingMessage {\n session: IronSession;\n }\n}\n\ntype RequestType = IncomingMessage | Request;\ntype ResponseType = ServerResponse | Response;\n\nexport function createGetIronSession(\n _crypto: Crypto,\n unsealData: ReturnType<typeof createUnsealData>,\n sealData: ReturnType<typeof createSealData>,\n) {\n return async (\n req: RequestType,\n res: ResponseType,\n userSessionOptions: IronSessionOptions,\n ): Promise<IronSession> => {\n if (\n !req ||\n !res ||\n !userSessionOptions ||\n !userSessionOptions.cookieName ||\n !userSessionOptions.password\n ) {\n throw new Error(\n `iron-session: Bad usage. Minimum usage is const session = await getIronSession(req, res, { cookieName: \"...\", password: \"...\". Check the usage here: https://github.com/vvo/iron-session`,\n );\n }\n\n const passwordsAsMap = normalizeStringPasswordToMap(\n userSessionOptions.password,\n );\n\n Object.values(\n normalizeStringPasswordToMap(userSessionOptions.password),\n ).forEach((password) => {\n if (password.length < 32) {\n throw new Error(\n `iron-session: Bad usage. Password must be at least 32 characters long.`,\n );\n }\n });\n\n const options: Required<IronSessionOptions> = {\n ...defaultOptions,\n ...userSessionOptions,\n cookieOptions: {\n ...defaultOptions.cookieOptions,\n ...(userSessionOptions.cookieOptions || {}),\n },\n };\n\n if (options.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 options.ttl = 2147483647;\n }\n\n if (\n userSessionOptions.cookieOptions &&\n \"maxAge\" in userSessionOptions.cookieOptions\n ) {\n // session cookie, do not set maxAge, consider token as infinite\n if (userSessionOptions.cookieOptions.maxAge === undefined) {\n options.ttl = 0;\n } else {\n options.cookieOptions.maxAge = computeCookieMaxAge(\n userSessionOptions.cookieOptions.maxAge,\n );\n }\n } else {\n options.cookieOptions.maxAge = computeCookieMaxAge(options.ttl);\n }\n\n const sealFromCookies = cookie.parse(\n \"credentials\" in req\n ? req.headers.get(\"cookie\") || \"\"\n : req.headers.cookie || \"\",\n )[options.cookieName];\n\n const session =\n sealFromCookies === undefined\n ? {}\n : await unsealData<IronSessionData>(sealFromCookies, {\n password: passwordsAsMap,\n ttl: options.ttl,\n });\n\n Object.defineProperties(session, {\n save: {\n value: async function save() {\n if (\"headersSent\" in res && res.headersSent === true) {\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 const seal = await sealData(session, {\n password: passwordsAsMap,\n ttl: options.ttl,\n });\n const cookieValue = cookie.serialize(\n options.cookieName,\n seal,\n options.cookieOptions,\n );\n\n if (cookieValue.length > 4096) {\n throw new Error(\n `iron-session: Cookie length is too big ${cookieValue.length}, browsers will refuse it. Try to remove some data.`,\n );\n }\n\n addToCookies(cookieValue, res);\n },\n },\n destroy: {\n value: function destroy() {\n Object.keys(session).forEach((key) => {\n // @ts-ignore See comment on the IronSessionData interface\n delete session[key];\n });\n\n const cookieValue = cookie.serialize(options.cookieName, \"\", {\n ...options.cookieOptions,\n maxAge: 0,\n });\n addToCookies(cookieValue, res);\n },\n },\n });\n\n return session as IronSession;\n };\n}\n\nfunction addToCookies(cookieValue: string, res: ResponseType) {\n if (\"headers\" in res) {\n res.headers.append(\"set-cookie\", cookieValue);\n return;\n }\n\n let existingSetCookie =\n (res.getHeader(\"set-cookie\") as string[] | string) ?? [];\n if (typeof existingSetCookie === \"string\") {\n existingSetCookie = [existingSetCookie];\n }\n res.setHeader(\"set-cookie\", [...existingSetCookie, cookieValue]);\n}\n\nfunction computeCookieMaxAge(ttl: number) {\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 maximum between server and clients.\n // It also makes sure to expire the cookie immediately when value is 0\n return ttl - timestampSkewSec;\n}\n\nexport function createUnsealData(_crypto: Crypto) {\n return async <T = Record<string, unknown>>(\n seal: string,\n {\n password,\n ttl = fourteenDaysInSeconds,\n }: { password: password; ttl?: number },\n ): Promise<T> => {\n const passwordsAsMap = normalizeStringPasswordToMap(password);\n const { sealWithoutVersion, tokenVersion } = parseSeal(seal);\n\n try {\n const data = await Iron.unseal(\n _crypto,\n sealWithoutVersion,\n passwordsAsMap,\n { ...Iron.defaults, ttl: ttl * 1000 },\n );\n\n if (tokenVersion === 2) {\n return data as T;\n }\n\n return {\n // @ts-expect-error `persistent` does not exist on newer tokens\n ...data.persistent,\n };\n } catch (error) {\n if (error instanceof Error) {\n if (\n error.message === \"Expired seal\" ||\n error.message === \"Bad hmac value\" ||\n error.message === \"Cannot find password: \" ||\n error.message === \"Incorrect number of sealed components\"\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\n throw error;\n }\n };\n}\n\nfunction parseSeal(seal: string): {\n sealWithoutVersion: string;\n tokenVersion: number | null;\n} {\n if (seal[seal.length - 2] === versionDelimiter) {\n const [sealWithoutVersion, tokenVersionAsString] =\n seal.split(versionDelimiter);\n return {\n sealWithoutVersion,\n tokenVersion: parseInt(tokenVersionAsString, 10),\n };\n }\n\n return { sealWithoutVersion: seal, tokenVersion: null };\n}\n\nexport function createSealData(_crypto: Crypto) {\n return async (\n data: unknown,\n {\n password,\n ttl = fourteenDaysInSeconds,\n }: { password: password; ttl?: number },\n ) => {\n const passwordsAsMap = normalizeStringPasswordToMap(password);\n\n const mostRecentPasswordId = Math.max(\n ...Object.keys(passwordsAsMap).map((id) => parseInt(id, 10)),\n );\n\n const passwordForSeal = {\n id: mostRecentPasswordId.toString(),\n secret: passwordsAsMap[mostRecentPasswordId],\n };\n\n const seal = await Iron.seal(_crypto, data, passwordForSeal, {\n ...Iron.defaults,\n ttl: ttl * 1000,\n });\n\n return `${seal}${versionDelimiter}${currentMajorVersion}`;\n };\n}\n\nfunction normalizeStringPasswordToMap(password: password) {\n return typeof password === \"string\" ? { 1: password } : password;\n}\n"],"mappings":"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","names":["_crypto","unsealData","sealData","cookie","seal"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/core.ts"],"sourcesContent":["import { createGetIronSession, createSealData, createUnsealData } from \"./core\";\nimport { Crypto } from \"@peculiar/webcrypto\";\n\nconst _crypto = new Crypto();\n\nexport * from \"./core\";\nexport const unsealData = createUnsealData(_crypto);\nexport const sealData = createSealData(_crypto);\nexport const getIronSession = createGetIronSession(\n _crypto,\n unsealData,\n sealData,\n);\n","import * as Iron from \"iron-webcrypto\";\nimport type { CookieSerializeOptions } from \"cookie\";\nimport cookie from \"cookie\";\nimport type { IncomingMessage, ServerResponse } from \"http\";\n\n// default time allowed to check for iron seal validity when ttl passed\n// see https://hapi.dev/family/iron/api/?v=6.0.0#options\nconst timestampSkewSec = 60;\n\ntype passwordsMap = { [id: string]: string };\ntype password = string | passwordsMap;\n\nconst fourteenDaysInSeconds = 15 * 24 * 3600;\n\n// We store a token major version to handle data format changes when any. So that when you upgrade the cookies\n// can be kept alive between upgrades, no need to disconnect everyone.\nconst currentMajorVersion = 2;\nconst versionDelimiter = \"~\";\n\nconst defaultOptions: {\n ttl: number;\n cookieOptions: CookieSerializeOptions;\n} = {\n ttl: fourteenDaysInSeconds,\n cookieOptions: {\n httpOnly: true,\n secure: true,\n sameSite: \"lax\",\n path: \"/\",\n },\n};\n\nexport interface IronSessionOptions {\n /**\n * This is the cookie name that will be used inside the browser. You should make sure it's unique given\n * your application. Example: vercel-session\n */\n cookieName: string;\n\n /**\n * This is the password(s) that will be used to encrypt the cookie. It can be either a string or an object\n * like {1: \"password\", 2: password}.\n *\n * When you provide multiple passwords then all of them will be used to decrypt the cookie and only the most\n * recent (= highest key, 2 in this example) password will be used to encrypt the cookie. This allow you\n * to use password rotation (security)\n */\n password: password;\n\n /**\n * This is the time in seconds that the session will be valid for. This also set the max-age attribute of\n * the cookie automatically (minus 60 seconds so that the cookie always expire before the session).\n */\n ttl?: number;\n\n /**\n * This is the options that will be passed to the cookie library.\n * You can see all of them here: https://github.com/jshttp/cookie#options-1.\n *\n * If you want to use \"session cookies\" (cookies that are deleted when the browser is closed) then you need\n * to pass cookieOptions: { maxAge: undefined }.\n */\n cookieOptions?: CookieSerializeOptions;\n}\n\n// eslint-disable-next-line @typescript-eslint/no-empty-interface\nexport interface IronSessionData {\n // If we allow for any keys, then there's no more type check on unknown properties\n // which is not good\n // If we allow for any keys, the later delete will work but I prefer to disable the\n // check at this stage and\n // provide good type checking instead\n // [key: string]: unknown;\n}\n\nexport type IronSession = IronSessionData & {\n /**\n * Destroys the session data and removes the cookie.\n */\n destroy: () => void;\n\n /**\n * Encrypts the session data and sets the cookie.\n */\n save: () => Promise<void>;\n};\n\ndeclare module \"http\" {\n interface IncomingMessage {\n session: IronSession;\n }\n}\n\ntype RequestType = IncomingMessage | Request;\ntype ResponseType = ServerResponse | Response;\n\nexport function createGetIronSession(\n _crypto: Crypto,\n unsealData: ReturnType<typeof createUnsealData>,\n sealData: ReturnType<typeof createSealData>,\n) {\n return async (\n req: RequestType,\n res: ResponseType,\n userSessionOptions: IronSessionOptions,\n ): Promise<IronSession> => {\n if (\n !req ||\n !res ||\n !userSessionOptions ||\n !userSessionOptions.cookieName ||\n !userSessionOptions.password\n ) {\n throw new Error(\n `iron-session: Bad usage. Minimum usage is const session = await getIronSession(req, res, { cookieName: \"...\", password: \"...\". Check the usage here: https://github.com/vvo/iron-session`,\n );\n }\n\n const passwordsAsMap = normalizeStringPasswordToMap(\n userSessionOptions.password,\n );\n\n Object.values(\n normalizeStringPasswordToMap(userSessionOptions.password),\n ).forEach((password) => {\n if (password.length < 32) {\n throw new Error(\n `iron-session: Bad usage. Password must be at least 32 characters long.`,\n );\n }\n });\n\n const options: Required<IronSessionOptions> = {\n ...defaultOptions,\n ...userSessionOptions,\n cookieOptions: {\n ...defaultOptions.cookieOptions,\n ...(userSessionOptions.cookieOptions || {}),\n },\n };\n\n if (options.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 options.ttl = 2147483647;\n }\n\n if (\n userSessionOptions.cookieOptions &&\n \"maxAge\" in userSessionOptions.cookieOptions\n ) {\n // session cookie, do not set maxAge, consider token as infinite\n if (userSessionOptions.cookieOptions.maxAge === undefined) {\n options.ttl = 0;\n } else {\n options.cookieOptions.maxAge = computeCookieMaxAge(\n userSessionOptions.cookieOptions.maxAge,\n );\n }\n } else {\n options.cookieOptions.maxAge = computeCookieMaxAge(options.ttl);\n }\n\n const sealFromCookies = cookie.parse(\n \"credentials\" in req\n ? req.headers.get(\"cookie\") || \"\"\n : req.headers.cookie || \"\",\n )[options.cookieName];\n\n const session =\n sealFromCookies === undefined\n ? {}\n : await unsealData<IronSessionData>(sealFromCookies, {\n password: passwordsAsMap,\n ttl: options.ttl,\n });\n\n Object.defineProperties(session, {\n save: {\n value: async function save() {\n if (\"headersSent\" in res && res.headersSent === true) {\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 const seal = await sealData(session, {\n password: passwordsAsMap,\n ttl: options.ttl,\n });\n const cookieValue = cookie.serialize(\n options.cookieName,\n seal,\n options.cookieOptions,\n );\n\n if (cookieValue.length > 4096) {\n throw new Error(\n `iron-session: Cookie length is too big ${cookieValue.length}, browsers will refuse it. Try to remove some data.`,\n );\n }\n\n addToCookies(cookieValue, res);\n },\n },\n destroy: {\n value: function destroy() {\n Object.keys(session).forEach((key) => {\n // @ts-ignore See comment on the IronSessionData interface\n delete session[key];\n });\n\n const cookieValue = cookie.serialize(options.cookieName, \"\", {\n ...options.cookieOptions,\n maxAge: 0,\n });\n addToCookies(cookieValue, res);\n },\n },\n });\n\n return session as IronSession;\n };\n}\n\nfunction addToCookies(cookieValue: string, res: ResponseType) {\n if (\"headers\" in res) {\n res.headers.append(\"set-cookie\", cookieValue);\n return;\n }\n\n let existingSetCookie =\n (res.getHeader(\"set-cookie\") as string[] | string) ?? [];\n if (typeof existingSetCookie === \"string\") {\n existingSetCookie = [existingSetCookie];\n }\n res.setHeader(\"set-cookie\", [...existingSetCookie, cookieValue]);\n}\n\nfunction computeCookieMaxAge(ttl: number) {\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 maximum between server and clients.\n // It also makes sure to expire the cookie immediately when value is 0\n return ttl - timestampSkewSec;\n}\n\nexport function createUnsealData(_crypto: Crypto) {\n return async <T = Record<string, unknown>>(\n seal: string,\n {\n password,\n ttl = fourteenDaysInSeconds,\n }: { password: password; ttl?: number },\n ): Promise<T> => {\n const passwordsAsMap = normalizeStringPasswordToMap(password);\n const { sealWithoutVersion, tokenVersion } = parseSeal(seal);\n\n try {\n const data =\n (await Iron.unseal(_crypto, sealWithoutVersion, passwordsAsMap, {\n ...Iron.defaults,\n ttl: ttl * 1000,\n })) || {};\n\n if (tokenVersion === 2) {\n return data as T;\n }\n\n return {\n // @ts-expect-error `persistent` does not exist on newer tokens\n ...data.persistent,\n };\n } catch (error) {\n if (error instanceof Error) {\n if (\n error.message === \"Expired seal\" ||\n error.message === \"Bad hmac value\" ||\n error.message.startsWith(\"Cannot find password: \") ||\n error.message === \"Incorrect number of sealed components\"\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\n throw error;\n }\n };\n}\n\nfunction parseSeal(seal: string): {\n sealWithoutVersion: string;\n tokenVersion: number | null;\n} {\n if (seal[seal.length - 2] === versionDelimiter) {\n const [sealWithoutVersion, tokenVersionAsString] =\n seal.split(versionDelimiter);\n return {\n sealWithoutVersion,\n tokenVersion: parseInt(tokenVersionAsString, 10),\n };\n }\n\n return { sealWithoutVersion: seal, tokenVersion: null };\n}\n\nexport function createSealData(_crypto: Crypto) {\n return async (\n data: unknown,\n {\n password,\n ttl = fourteenDaysInSeconds,\n }: { password: password; ttl?: number },\n ) => {\n const passwordsAsMap = normalizeStringPasswordToMap(password);\n\n const mostRecentPasswordId = Math.max(\n ...Object.keys(passwordsAsMap).map((id) => parseInt(id, 10)),\n );\n\n const passwordForSeal = {\n id: mostRecentPasswordId.toString(),\n secret: passwordsAsMap[mostRecentPasswordId],\n };\n\n const seal = await Iron.seal(_crypto, data, passwordForSeal, {\n ...Iron.defaults,\n ttl: ttl * 1000,\n });\n\n return `${seal}${versionDelimiter}${currentMajorVersion}`;\n };\n}\n\nfunction normalizeStringPasswordToMap(password: password) {\n return typeof password === \"string\" ? { 1: password } : password;\n}\n"],"mappings":"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","names":["_crypto","unsealData","sealData","cookie","seal"]}
package/dist/index.mjs CHANGED
@@ -126,12 +126,10 @@ function createUnsealData(_crypto2) {
126
126
  const passwordsAsMap = normalizeStringPasswordToMap(password);
127
127
  const { sealWithoutVersion, tokenVersion } = parseSeal(seal2);
128
128
  try {
129
- const data = await Iron.unseal(
130
- _crypto2,
131
- sealWithoutVersion,
132
- passwordsAsMap,
133
- { ...Iron.defaults, ttl: ttl * 1e3 }
134
- );
129
+ const data = await Iron.unseal(_crypto2, sealWithoutVersion, passwordsAsMap, {
130
+ ...Iron.defaults,
131
+ ttl: ttl * 1e3
132
+ }) || {};
135
133
  if (tokenVersion === 2) {
136
134
  return data;
137
135
  }
@@ -140,7 +138,7 @@ function createUnsealData(_crypto2) {
140
138
  };
141
139
  } catch (error) {
142
140
  if (error instanceof Error) {
143
- if (error.message === "Expired seal" || error.message === "Bad hmac value" || error.message === "Cannot find password: " || error.message === "Incorrect number of sealed components") {
141
+ if (error.message === "Expired seal" || error.message === "Bad hmac value" || error.message.startsWith("Cannot find password: ") || error.message === "Incorrect number of sealed components") {
144
142
  return {};
145
143
  }
146
144
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/core.ts","../src/index.ts"],"sourcesContent":["import * as Iron from \"iron-webcrypto\";\nimport type { CookieSerializeOptions } from \"cookie\";\nimport cookie from \"cookie\";\nimport type { IncomingMessage, ServerResponse } from \"http\";\n\n// default time allowed to check for iron seal validity when ttl passed\n// see https://hapi.dev/family/iron/api/?v=6.0.0#options\nconst timestampSkewSec = 60;\n\ntype passwordsMap = { [id: string]: string };\ntype password = string | passwordsMap;\n\nconst fourteenDaysInSeconds = 15 * 24 * 3600;\n\n// We store a token major version to handle data format changes when any. So that when you upgrade the cookies\n// can be kept alive between upgrades, no need to disconnect everyone.\nconst currentMajorVersion = 2;\nconst versionDelimiter = \"~\";\n\nconst defaultOptions: {\n ttl: number;\n cookieOptions: CookieSerializeOptions;\n} = {\n ttl: fourteenDaysInSeconds,\n cookieOptions: {\n httpOnly: true,\n secure: true,\n sameSite: \"lax\",\n path: \"/\",\n },\n};\n\nexport interface IronSessionOptions {\n /**\n * This is the cookie name that will be used inside the browser. You should make sure it's unique given\n * your application. Example: vercel-session\n */\n cookieName: string;\n\n /**\n * This is the password(s) that will be used to encrypt the cookie. It can be either a string or an object\n * like {1: \"password\", 2: password}.\n *\n * When you provide multiple passwords then all of them will be used to decrypt the cookie and only the most\n * recent (= highest key, 2 in this example) password will be used to encrypt the cookie. This allow you\n * to use password rotation (security)\n */\n password: password;\n\n /**\n * This is the time in seconds that the session will be valid for. This also set the max-age attribute of\n * the cookie automatically (minus 60 seconds so that the cookie always expire before the session).\n */\n ttl?: number;\n\n /**\n * This is the options that will be passed to the cookie library.\n * You can see all of them here: https://github.com/jshttp/cookie#options-1.\n *\n * If you want to use \"session cookies\" (cookies that are deleted when the browser is closed) then you need\n * to pass cookieOptions: { maxAge: undefined }.\n */\n cookieOptions?: CookieSerializeOptions;\n}\n\n// eslint-disable-next-line @typescript-eslint/no-empty-interface\nexport interface IronSessionData {\n // If we allow for any keys, then there's no more type check on unknown properties\n // which is not good\n // If we allow for any keys, the later delete will work but I prefer to disable the\n // check at this stage and\n // provide good type checking instead\n // [key: string]: unknown;\n}\n\nexport type IronSession = IronSessionData & {\n /**\n * Destroys the session data and removes the cookie.\n */\n destroy: () => void;\n\n /**\n * Encrypts the session data and sets the cookie.\n */\n save: () => Promise<void>;\n};\n\ndeclare module \"http\" {\n interface IncomingMessage {\n session: IronSession;\n }\n}\n\ntype RequestType = IncomingMessage | Request;\ntype ResponseType = ServerResponse | Response;\n\nexport function createGetIronSession(\n _crypto: Crypto,\n unsealData: ReturnType<typeof createUnsealData>,\n sealData: ReturnType<typeof createSealData>,\n) {\n return async (\n req: RequestType,\n res: ResponseType,\n userSessionOptions: IronSessionOptions,\n ): Promise<IronSession> => {\n if (\n !req ||\n !res ||\n !userSessionOptions ||\n !userSessionOptions.cookieName ||\n !userSessionOptions.password\n ) {\n throw new Error(\n `iron-session: Bad usage. Minimum usage is const session = await getIronSession(req, res, { cookieName: \"...\", password: \"...\". Check the usage here: https://github.com/vvo/iron-session`,\n );\n }\n\n const passwordsAsMap = normalizeStringPasswordToMap(\n userSessionOptions.password,\n );\n\n Object.values(\n normalizeStringPasswordToMap(userSessionOptions.password),\n ).forEach((password) => {\n if (password.length < 32) {\n throw new Error(\n `iron-session: Bad usage. Password must be at least 32 characters long.`,\n );\n }\n });\n\n const options: Required<IronSessionOptions> = {\n ...defaultOptions,\n ...userSessionOptions,\n cookieOptions: {\n ...defaultOptions.cookieOptions,\n ...(userSessionOptions.cookieOptions || {}),\n },\n };\n\n if (options.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 options.ttl = 2147483647;\n }\n\n if (\n userSessionOptions.cookieOptions &&\n \"maxAge\" in userSessionOptions.cookieOptions\n ) {\n // session cookie, do not set maxAge, consider token as infinite\n if (userSessionOptions.cookieOptions.maxAge === undefined) {\n options.ttl = 0;\n } else {\n options.cookieOptions.maxAge = computeCookieMaxAge(\n userSessionOptions.cookieOptions.maxAge,\n );\n }\n } else {\n options.cookieOptions.maxAge = computeCookieMaxAge(options.ttl);\n }\n\n const sealFromCookies = cookie.parse(\n \"credentials\" in req\n ? req.headers.get(\"cookie\") || \"\"\n : req.headers.cookie || \"\",\n )[options.cookieName];\n\n const session =\n sealFromCookies === undefined\n ? {}\n : await unsealData<IronSessionData>(sealFromCookies, {\n password: passwordsAsMap,\n ttl: options.ttl,\n });\n\n Object.defineProperties(session, {\n save: {\n value: async function save() {\n if (\"headersSent\" in res && res.headersSent === true) {\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 const seal = await sealData(session, {\n password: passwordsAsMap,\n ttl: options.ttl,\n });\n const cookieValue = cookie.serialize(\n options.cookieName,\n seal,\n options.cookieOptions,\n );\n\n if (cookieValue.length > 4096) {\n throw new Error(\n `iron-session: Cookie length is too big ${cookieValue.length}, browsers will refuse it. Try to remove some data.`,\n );\n }\n\n addToCookies(cookieValue, res);\n },\n },\n destroy: {\n value: function destroy() {\n Object.keys(session).forEach((key) => {\n // @ts-ignore See comment on the IronSessionData interface\n delete session[key];\n });\n\n const cookieValue = cookie.serialize(options.cookieName, \"\", {\n ...options.cookieOptions,\n maxAge: 0,\n });\n addToCookies(cookieValue, res);\n },\n },\n });\n\n return session as IronSession;\n };\n}\n\nfunction addToCookies(cookieValue: string, res: ResponseType) {\n if (\"headers\" in res) {\n res.headers.append(\"set-cookie\", cookieValue);\n return;\n }\n\n let existingSetCookie =\n (res.getHeader(\"set-cookie\") as string[] | string) ?? [];\n if (typeof existingSetCookie === \"string\") {\n existingSetCookie = [existingSetCookie];\n }\n res.setHeader(\"set-cookie\", [...existingSetCookie, cookieValue]);\n}\n\nfunction computeCookieMaxAge(ttl: number) {\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 maximum between server and clients.\n // It also makes sure to expire the cookie immediately when value is 0\n return ttl - timestampSkewSec;\n}\n\nexport function createUnsealData(_crypto: Crypto) {\n return async <T = Record<string, unknown>>(\n seal: string,\n {\n password,\n ttl = fourteenDaysInSeconds,\n }: { password: password; ttl?: number },\n ): Promise<T> => {\n const passwordsAsMap = normalizeStringPasswordToMap(password);\n const { sealWithoutVersion, tokenVersion } = parseSeal(seal);\n\n try {\n const data = await Iron.unseal(\n _crypto,\n sealWithoutVersion,\n passwordsAsMap,\n { ...Iron.defaults, ttl: ttl * 1000 },\n );\n\n if (tokenVersion === 2) {\n return data as T;\n }\n\n return {\n // @ts-expect-error `persistent` does not exist on newer tokens\n ...data.persistent,\n };\n } catch (error) {\n if (error instanceof Error) {\n if (\n error.message === \"Expired seal\" ||\n error.message === \"Bad hmac value\" ||\n error.message === \"Cannot find password: \" ||\n error.message === \"Incorrect number of sealed components\"\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\n throw error;\n }\n };\n}\n\nfunction parseSeal(seal: string): {\n sealWithoutVersion: string;\n tokenVersion: number | null;\n} {\n if (seal[seal.length - 2] === versionDelimiter) {\n const [sealWithoutVersion, tokenVersionAsString] =\n seal.split(versionDelimiter);\n return {\n sealWithoutVersion,\n tokenVersion: parseInt(tokenVersionAsString, 10),\n };\n }\n\n return { sealWithoutVersion: seal, tokenVersion: null };\n}\n\nexport function createSealData(_crypto: Crypto) {\n return async (\n data: unknown,\n {\n password,\n ttl = fourteenDaysInSeconds,\n }: { password: password; ttl?: number },\n ) => {\n const passwordsAsMap = normalizeStringPasswordToMap(password);\n\n const mostRecentPasswordId = Math.max(\n ...Object.keys(passwordsAsMap).map((id) => parseInt(id, 10)),\n );\n\n const passwordForSeal = {\n id: mostRecentPasswordId.toString(),\n secret: passwordsAsMap[mostRecentPasswordId],\n };\n\n const seal = await Iron.seal(_crypto, data, passwordForSeal, {\n ...Iron.defaults,\n ttl: ttl * 1000,\n });\n\n return `${seal}${versionDelimiter}${currentMajorVersion}`;\n };\n}\n\nfunction normalizeStringPasswordToMap(password: password) {\n return typeof password === \"string\" ? { 1: password } : password;\n}\n","import { createGetIronSession, createSealData, createUnsealData } from \"./core\";\nimport { Crypto } from \"@peculiar/webcrypto\";\n\nconst _crypto = new Crypto();\n\nexport * from \"./core\";\nexport const unsealData = createUnsealData(_crypto);\nexport const sealData = createSealData(_crypto);\nexport const getIronSession = createGetIronSession(\n _crypto,\n unsealData,\n sealData,\n);\n"],"mappings":"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","names":["_crypto","unsealData","sealData","seal"]}
1
+ {"version":3,"sources":["../src/core.ts","../src/index.ts"],"sourcesContent":["import * as Iron from \"iron-webcrypto\";\nimport type { CookieSerializeOptions } from \"cookie\";\nimport cookie from \"cookie\";\nimport type { IncomingMessage, ServerResponse } from \"http\";\n\n// default time allowed to check for iron seal validity when ttl passed\n// see https://hapi.dev/family/iron/api/?v=6.0.0#options\nconst timestampSkewSec = 60;\n\ntype passwordsMap = { [id: string]: string };\ntype password = string | passwordsMap;\n\nconst fourteenDaysInSeconds = 15 * 24 * 3600;\n\n// We store a token major version to handle data format changes when any. So that when you upgrade the cookies\n// can be kept alive between upgrades, no need to disconnect everyone.\nconst currentMajorVersion = 2;\nconst versionDelimiter = \"~\";\n\nconst defaultOptions: {\n ttl: number;\n cookieOptions: CookieSerializeOptions;\n} = {\n ttl: fourteenDaysInSeconds,\n cookieOptions: {\n httpOnly: true,\n secure: true,\n sameSite: \"lax\",\n path: \"/\",\n },\n};\n\nexport interface IronSessionOptions {\n /**\n * This is the cookie name that will be used inside the browser. You should make sure it's unique given\n * your application. Example: vercel-session\n */\n cookieName: string;\n\n /**\n * This is the password(s) that will be used to encrypt the cookie. It can be either a string or an object\n * like {1: \"password\", 2: password}.\n *\n * When you provide multiple passwords then all of them will be used to decrypt the cookie and only the most\n * recent (= highest key, 2 in this example) password will be used to encrypt the cookie. This allow you\n * to use password rotation (security)\n */\n password: password;\n\n /**\n * This is the time in seconds that the session will be valid for. This also set the max-age attribute of\n * the cookie automatically (minus 60 seconds so that the cookie always expire before the session).\n */\n ttl?: number;\n\n /**\n * This is the options that will be passed to the cookie library.\n * You can see all of them here: https://github.com/jshttp/cookie#options-1.\n *\n * If you want to use \"session cookies\" (cookies that are deleted when the browser is closed) then you need\n * to pass cookieOptions: { maxAge: undefined }.\n */\n cookieOptions?: CookieSerializeOptions;\n}\n\n// eslint-disable-next-line @typescript-eslint/no-empty-interface\nexport interface IronSessionData {\n // If we allow for any keys, then there's no more type check on unknown properties\n // which is not good\n // If we allow for any keys, the later delete will work but I prefer to disable the\n // check at this stage and\n // provide good type checking instead\n // [key: string]: unknown;\n}\n\nexport type IronSession = IronSessionData & {\n /**\n * Destroys the session data and removes the cookie.\n */\n destroy: () => void;\n\n /**\n * Encrypts the session data and sets the cookie.\n */\n save: () => Promise<void>;\n};\n\ndeclare module \"http\" {\n interface IncomingMessage {\n session: IronSession;\n }\n}\n\ntype RequestType = IncomingMessage | Request;\ntype ResponseType = ServerResponse | Response;\n\nexport function createGetIronSession(\n _crypto: Crypto,\n unsealData: ReturnType<typeof createUnsealData>,\n sealData: ReturnType<typeof createSealData>,\n) {\n return async (\n req: RequestType,\n res: ResponseType,\n userSessionOptions: IronSessionOptions,\n ): Promise<IronSession> => {\n if (\n !req ||\n !res ||\n !userSessionOptions ||\n !userSessionOptions.cookieName ||\n !userSessionOptions.password\n ) {\n throw new Error(\n `iron-session: Bad usage. Minimum usage is const session = await getIronSession(req, res, { cookieName: \"...\", password: \"...\". Check the usage here: https://github.com/vvo/iron-session`,\n );\n }\n\n const passwordsAsMap = normalizeStringPasswordToMap(\n userSessionOptions.password,\n );\n\n Object.values(\n normalizeStringPasswordToMap(userSessionOptions.password),\n ).forEach((password) => {\n if (password.length < 32) {\n throw new Error(\n `iron-session: Bad usage. Password must be at least 32 characters long.`,\n );\n }\n });\n\n const options: Required<IronSessionOptions> = {\n ...defaultOptions,\n ...userSessionOptions,\n cookieOptions: {\n ...defaultOptions.cookieOptions,\n ...(userSessionOptions.cookieOptions || {}),\n },\n };\n\n if (options.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 options.ttl = 2147483647;\n }\n\n if (\n userSessionOptions.cookieOptions &&\n \"maxAge\" in userSessionOptions.cookieOptions\n ) {\n // session cookie, do not set maxAge, consider token as infinite\n if (userSessionOptions.cookieOptions.maxAge === undefined) {\n options.ttl = 0;\n } else {\n options.cookieOptions.maxAge = computeCookieMaxAge(\n userSessionOptions.cookieOptions.maxAge,\n );\n }\n } else {\n options.cookieOptions.maxAge = computeCookieMaxAge(options.ttl);\n }\n\n const sealFromCookies = cookie.parse(\n \"credentials\" in req\n ? req.headers.get(\"cookie\") || \"\"\n : req.headers.cookie || \"\",\n )[options.cookieName];\n\n const session =\n sealFromCookies === undefined\n ? {}\n : await unsealData<IronSessionData>(sealFromCookies, {\n password: passwordsAsMap,\n ttl: options.ttl,\n });\n\n Object.defineProperties(session, {\n save: {\n value: async function save() {\n if (\"headersSent\" in res && res.headersSent === true) {\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 const seal = await sealData(session, {\n password: passwordsAsMap,\n ttl: options.ttl,\n });\n const cookieValue = cookie.serialize(\n options.cookieName,\n seal,\n options.cookieOptions,\n );\n\n if (cookieValue.length > 4096) {\n throw new Error(\n `iron-session: Cookie length is too big ${cookieValue.length}, browsers will refuse it. Try to remove some data.`,\n );\n }\n\n addToCookies(cookieValue, res);\n },\n },\n destroy: {\n value: function destroy() {\n Object.keys(session).forEach((key) => {\n // @ts-ignore See comment on the IronSessionData interface\n delete session[key];\n });\n\n const cookieValue = cookie.serialize(options.cookieName, \"\", {\n ...options.cookieOptions,\n maxAge: 0,\n });\n addToCookies(cookieValue, res);\n },\n },\n });\n\n return session as IronSession;\n };\n}\n\nfunction addToCookies(cookieValue: string, res: ResponseType) {\n if (\"headers\" in res) {\n res.headers.append(\"set-cookie\", cookieValue);\n return;\n }\n\n let existingSetCookie =\n (res.getHeader(\"set-cookie\") as string[] | string) ?? [];\n if (typeof existingSetCookie === \"string\") {\n existingSetCookie = [existingSetCookie];\n }\n res.setHeader(\"set-cookie\", [...existingSetCookie, cookieValue]);\n}\n\nfunction computeCookieMaxAge(ttl: number) {\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 maximum between server and clients.\n // It also makes sure to expire the cookie immediately when value is 0\n return ttl - timestampSkewSec;\n}\n\nexport function createUnsealData(_crypto: Crypto) {\n return async <T = Record<string, unknown>>(\n seal: string,\n {\n password,\n ttl = fourteenDaysInSeconds,\n }: { password: password; ttl?: number },\n ): Promise<T> => {\n const passwordsAsMap = normalizeStringPasswordToMap(password);\n const { sealWithoutVersion, tokenVersion } = parseSeal(seal);\n\n try {\n const data =\n (await Iron.unseal(_crypto, sealWithoutVersion, passwordsAsMap, {\n ...Iron.defaults,\n ttl: ttl * 1000,\n })) || {};\n\n if (tokenVersion === 2) {\n return data as T;\n }\n\n return {\n // @ts-expect-error `persistent` does not exist on newer tokens\n ...data.persistent,\n };\n } catch (error) {\n if (error instanceof Error) {\n if (\n error.message === \"Expired seal\" ||\n error.message === \"Bad hmac value\" ||\n error.message.startsWith(\"Cannot find password: \") ||\n error.message === \"Incorrect number of sealed components\"\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\n throw error;\n }\n };\n}\n\nfunction parseSeal(seal: string): {\n sealWithoutVersion: string;\n tokenVersion: number | null;\n} {\n if (seal[seal.length - 2] === versionDelimiter) {\n const [sealWithoutVersion, tokenVersionAsString] =\n seal.split(versionDelimiter);\n return {\n sealWithoutVersion,\n tokenVersion: parseInt(tokenVersionAsString, 10),\n };\n }\n\n return { sealWithoutVersion: seal, tokenVersion: null };\n}\n\nexport function createSealData(_crypto: Crypto) {\n return async (\n data: unknown,\n {\n password,\n ttl = fourteenDaysInSeconds,\n }: { password: password; ttl?: number },\n ) => {\n const passwordsAsMap = normalizeStringPasswordToMap(password);\n\n const mostRecentPasswordId = Math.max(\n ...Object.keys(passwordsAsMap).map((id) => parseInt(id, 10)),\n );\n\n const passwordForSeal = {\n id: mostRecentPasswordId.toString(),\n secret: passwordsAsMap[mostRecentPasswordId],\n };\n\n const seal = await Iron.seal(_crypto, data, passwordForSeal, {\n ...Iron.defaults,\n ttl: ttl * 1000,\n });\n\n return `${seal}${versionDelimiter}${currentMajorVersion}`;\n };\n}\n\nfunction normalizeStringPasswordToMap(password: password) {\n return typeof password === \"string\" ? { 1: password } : password;\n}\n","import { createGetIronSession, createSealData, createUnsealData } from \"./core\";\nimport { Crypto } from \"@peculiar/webcrypto\";\n\nconst _crypto = new Crypto();\n\nexport * from \"./core\";\nexport const unsealData = createUnsealData(_crypto);\nexport const sealData = createSealData(_crypto);\nexport const getIronSession = createGetIronSession(\n _crypto,\n unsealData,\n sealData,\n);\n"],"mappings":"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","names":["_crypto","unsealData","sealData","seal"]}
@@ -1,68 +1,6 @@
1
- import { CookieSerializeOptions } from 'cookie';
1
+ import * as iron_session from 'iron-session';
2
+ export { IronSessionData, IronSessionOptions } from 'iron-session';
2
3
  import * as http from 'http';
3
- import { IncomingMessage, ServerResponse } from 'http';
4
-
5
- declare type passwordsMap = {
6
- [id: string]: string;
7
- };
8
- declare type password = string | passwordsMap;
9
- interface IronSessionOptions {
10
- /**
11
- * This is the cookie name that will be used inside the browser. You should make sure it's unique given
12
- * your application. Example: vercel-session
13
- */
14
- cookieName: string;
15
- /**
16
- * This is the password(s) that will be used to encrypt the cookie. It can be either a string or an object
17
- * like {1: "password", 2: password}.
18
- *
19
- * When you provide multiple passwords then all of them will be used to decrypt the cookie and only the most
20
- * recent (= highest key, 2 in this example) password will be used to encrypt the cookie. This allow you
21
- * to use password rotation (security)
22
- */
23
- password: password;
24
- /**
25
- * This is the time in seconds that the session will be valid for. This also set the max-age attribute of
26
- * the cookie automatically (minus 60 seconds so that the cookie always expire before the session).
27
- */
28
- ttl?: number;
29
- /**
30
- * This is the options that will be passed to the cookie library.
31
- * You can see all of them here: https://github.com/jshttp/cookie#options-1.
32
- *
33
- * If you want to use "session cookies" (cookies that are deleted when the browser is closed) then you need
34
- * to pass cookieOptions: { maxAge: undefined }.
35
- */
36
- cookieOptions?: CookieSerializeOptions;
37
- }
38
- interface IronSessionData {
39
- }
40
- declare type IronSession = IronSessionData & {
41
- /**
42
- * Destroys the session data and removes the cookie.
43
- */
44
- destroy: () => void;
45
- /**
46
- * Encrypts the session data and sets the cookie.
47
- */
48
- save: () => Promise<void>;
49
- };
50
- declare module "http" {
51
- interface IncomingMessage {
52
- session: IronSession;
53
- }
54
- }
55
- declare type RequestType = IncomingMessage | Request;
56
- declare type ResponseType = ServerResponse | Response;
57
- declare function createGetIronSession(_crypto: Crypto, unsealData: ReturnType<typeof createUnsealData>, sealData: ReturnType<typeof createSealData>): (req: RequestType, res: ResponseType, userSessionOptions: IronSessionOptions) => Promise<IronSession>;
58
- declare function createUnsealData(_crypto: Crypto): <T = Record<string, unknown>>(seal: string, { password, ttl, }: {
59
- password: password;
60
- ttl?: number | undefined;
61
- }) => Promise<T>;
62
- declare function createSealData(_crypto: Crypto): (data: unknown, { password, ttl, }: {
63
- password: password;
64
- ttl?: number | undefined;
65
- }) => Promise<string>;
66
4
 
67
5
  declare const unsealData: <T = Record<string, unknown>>(seal: string, { password, ttl, }: {
68
6
  password: string | {
@@ -76,6 +14,6 @@ declare const sealData: (data: unknown, { password, ttl, }: {
76
14
  };
77
15
  ttl?: number | undefined;
78
16
  }) => Promise<string>;
79
- declare const getIronSession: (req: http.IncomingMessage | Request, res: http.ServerResponse | Response, userSessionOptions: IronSessionOptions) => Promise<IronSession>;
17
+ declare const getIronSession: (req: http.IncomingMessage | Request, res: http.ServerResponse | Response, userSessionOptions: iron_session.IronSessionOptions) => Promise<iron_session.IronSession>;
80
18
 
81
- export { IronSession, IronSessionData, IronSessionOptions, createGetIronSession, createSealData, createUnsealData, getIronSession, sealData, unsealData };
19
+ export { getIronSession, sealData, unsealData };
@@ -26,9 +26,6 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
26
26
  // edge/index.ts
27
27
  var edge_exports = {};
28
28
  __export(edge_exports, {
29
- createGetIronSession: () => createGetIronSession,
30
- createSealData: () => createSealData,
31
- createUnsealData: () => createUnsealData,
32
29
  getIronSession: () => getIronSession,
33
30
  sealData: () => sealData,
34
31
  unsealData: () => unsealData
@@ -163,12 +160,10 @@ function createUnsealData(_crypto2) {
163
160
  const passwordsAsMap = normalizeStringPasswordToMap(password);
164
161
  const { sealWithoutVersion, tokenVersion } = parseSeal(seal2);
165
162
  try {
166
- const data = await Iron.unseal(
167
- _crypto2,
168
- sealWithoutVersion,
169
- passwordsAsMap,
170
- { ...Iron.defaults, ttl: ttl * 1e3 }
171
- );
163
+ const data = await Iron.unseal(_crypto2, sealWithoutVersion, passwordsAsMap, {
164
+ ...Iron.defaults,
165
+ ttl: ttl * 1e3
166
+ }) || {};
172
167
  if (tokenVersion === 2) {
173
168
  return data;
174
169
  }
@@ -177,7 +172,7 @@ function createUnsealData(_crypto2) {
177
172
  };
178
173
  } catch (error) {
179
174
  if (error instanceof Error) {
180
- if (error.message === "Expired seal" || error.message === "Bad hmac value" || error.message === "Cannot find password: " || error.message === "Incorrect number of sealed components") {
175
+ if (error.message === "Expired seal" || error.message === "Bad hmac value" || error.message.startsWith("Cannot find password: ") || error.message === "Incorrect number of sealed components") {
181
176
  return {};
182
177
  }
183
178
  }
@@ -240,9 +235,6 @@ var getIronSession = createGetIronSession(
240
235
  );
241
236
  // Annotate the CommonJS export names for ESM import in node:
242
237
  0 && (module.exports = {
243
- createGetIronSession,
244
- createSealData,
245
- createUnsealData,
246
238
  getIronSession,
247
239
  sealData,
248
240
  unsealData
@@ -1 +1 @@
1
- {"version":3,"sources":["../index.ts","../../src/core.ts"],"sourcesContent":["import {\n createGetIronSession,\n createSealData,\n createUnsealData,\n} from \"../src/core\";\n\nconst getCrypto = (): Crypto => {\n if (typeof globalThis.crypto?.subtle === \"object\") return globalThis.crypto;\n // @ts-ignore crypto.webcrypto is not available in dom, but is there in newer node versions\n if (typeof globalThis.crypto?.webcrypto?.subtle === \"object\")\n // @ts-ignore same as above\n return globalThis.crypto.webcrypto;\n throw new Error(\n \"no native implementation of WebCrypto is available in current context\",\n );\n};\n\nconst _crypto = getCrypto();\n\nexport * from \"../src/core\";\nexport const unsealData = createUnsealData(_crypto);\nexport const sealData = createSealData(_crypto);\nexport const getIronSession = createGetIronSession(\n _crypto,\n unsealData,\n sealData,\n);\n","import * as Iron from \"iron-webcrypto\";\nimport type { CookieSerializeOptions } from \"cookie\";\nimport cookie from \"cookie\";\nimport type { IncomingMessage, ServerResponse } from \"http\";\n\n// default time allowed to check for iron seal validity when ttl passed\n// see https://hapi.dev/family/iron/api/?v=6.0.0#options\nconst timestampSkewSec = 60;\n\ntype passwordsMap = { [id: string]: string };\ntype password = string | passwordsMap;\n\nconst fourteenDaysInSeconds = 15 * 24 * 3600;\n\n// We store a token major version to handle data format changes when any. So that when you upgrade the cookies\n// can be kept alive between upgrades, no need to disconnect everyone.\nconst currentMajorVersion = 2;\nconst versionDelimiter = \"~\";\n\nconst defaultOptions: {\n ttl: number;\n cookieOptions: CookieSerializeOptions;\n} = {\n ttl: fourteenDaysInSeconds,\n cookieOptions: {\n httpOnly: true,\n secure: true,\n sameSite: \"lax\",\n path: \"/\",\n },\n};\n\nexport interface IronSessionOptions {\n /**\n * This is the cookie name that will be used inside the browser. You should make sure it's unique given\n * your application. Example: vercel-session\n */\n cookieName: string;\n\n /**\n * This is the password(s) that will be used to encrypt the cookie. It can be either a string or an object\n * like {1: \"password\", 2: password}.\n *\n * When you provide multiple passwords then all of them will be used to decrypt the cookie and only the most\n * recent (= highest key, 2 in this example) password will be used to encrypt the cookie. This allow you\n * to use password rotation (security)\n */\n password: password;\n\n /**\n * This is the time in seconds that the session will be valid for. This also set the max-age attribute of\n * the cookie automatically (minus 60 seconds so that the cookie always expire before the session).\n */\n ttl?: number;\n\n /**\n * This is the options that will be passed to the cookie library.\n * You can see all of them here: https://github.com/jshttp/cookie#options-1.\n *\n * If you want to use \"session cookies\" (cookies that are deleted when the browser is closed) then you need\n * to pass cookieOptions: { maxAge: undefined }.\n */\n cookieOptions?: CookieSerializeOptions;\n}\n\n// eslint-disable-next-line @typescript-eslint/no-empty-interface\nexport interface IronSessionData {\n // If we allow for any keys, then there's no more type check on unknown properties\n // which is not good\n // If we allow for any keys, the later delete will work but I prefer to disable the\n // check at this stage and\n // provide good type checking instead\n // [key: string]: unknown;\n}\n\nexport type IronSession = IronSessionData & {\n /**\n * Destroys the session data and removes the cookie.\n */\n destroy: () => void;\n\n /**\n * Encrypts the session data and sets the cookie.\n */\n save: () => Promise<void>;\n};\n\ndeclare module \"http\" {\n interface IncomingMessage {\n session: IronSession;\n }\n}\n\ntype RequestType = IncomingMessage | Request;\ntype ResponseType = ServerResponse | Response;\n\nexport function createGetIronSession(\n _crypto: Crypto,\n unsealData: ReturnType<typeof createUnsealData>,\n sealData: ReturnType<typeof createSealData>,\n) {\n return async (\n req: RequestType,\n res: ResponseType,\n userSessionOptions: IronSessionOptions,\n ): Promise<IronSession> => {\n if (\n !req ||\n !res ||\n !userSessionOptions ||\n !userSessionOptions.cookieName ||\n !userSessionOptions.password\n ) {\n throw new Error(\n `iron-session: Bad usage. Minimum usage is const session = await getIronSession(req, res, { cookieName: \"...\", password: \"...\". Check the usage here: https://github.com/vvo/iron-session`,\n );\n }\n\n const passwordsAsMap = normalizeStringPasswordToMap(\n userSessionOptions.password,\n );\n\n Object.values(\n normalizeStringPasswordToMap(userSessionOptions.password),\n ).forEach((password) => {\n if (password.length < 32) {\n throw new Error(\n `iron-session: Bad usage. Password must be at least 32 characters long.`,\n );\n }\n });\n\n const options: Required<IronSessionOptions> = {\n ...defaultOptions,\n ...userSessionOptions,\n cookieOptions: {\n ...defaultOptions.cookieOptions,\n ...(userSessionOptions.cookieOptions || {}),\n },\n };\n\n if (options.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 options.ttl = 2147483647;\n }\n\n if (\n userSessionOptions.cookieOptions &&\n \"maxAge\" in userSessionOptions.cookieOptions\n ) {\n // session cookie, do not set maxAge, consider token as infinite\n if (userSessionOptions.cookieOptions.maxAge === undefined) {\n options.ttl = 0;\n } else {\n options.cookieOptions.maxAge = computeCookieMaxAge(\n userSessionOptions.cookieOptions.maxAge,\n );\n }\n } else {\n options.cookieOptions.maxAge = computeCookieMaxAge(options.ttl);\n }\n\n const sealFromCookies = cookie.parse(\n \"credentials\" in req\n ? req.headers.get(\"cookie\") || \"\"\n : req.headers.cookie || \"\",\n )[options.cookieName];\n\n const session =\n sealFromCookies === undefined\n ? {}\n : await unsealData<IronSessionData>(sealFromCookies, {\n password: passwordsAsMap,\n ttl: options.ttl,\n });\n\n Object.defineProperties(session, {\n save: {\n value: async function save() {\n if (\"headersSent\" in res && res.headersSent === true) {\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 const seal = await sealData(session, {\n password: passwordsAsMap,\n ttl: options.ttl,\n });\n const cookieValue = cookie.serialize(\n options.cookieName,\n seal,\n options.cookieOptions,\n );\n\n if (cookieValue.length > 4096) {\n throw new Error(\n `iron-session: Cookie length is too big ${cookieValue.length}, browsers will refuse it. Try to remove some data.`,\n );\n }\n\n addToCookies(cookieValue, res);\n },\n },\n destroy: {\n value: function destroy() {\n Object.keys(session).forEach((key) => {\n // @ts-ignore See comment on the IronSessionData interface\n delete session[key];\n });\n\n const cookieValue = cookie.serialize(options.cookieName, \"\", {\n ...options.cookieOptions,\n maxAge: 0,\n });\n addToCookies(cookieValue, res);\n },\n },\n });\n\n return session as IronSession;\n };\n}\n\nfunction addToCookies(cookieValue: string, res: ResponseType) {\n if (\"headers\" in res) {\n res.headers.append(\"set-cookie\", cookieValue);\n return;\n }\n\n let existingSetCookie =\n (res.getHeader(\"set-cookie\") as string[] | string) ?? [];\n if (typeof existingSetCookie === \"string\") {\n existingSetCookie = [existingSetCookie];\n }\n res.setHeader(\"set-cookie\", [...existingSetCookie, cookieValue]);\n}\n\nfunction computeCookieMaxAge(ttl: number) {\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 maximum between server and clients.\n // It also makes sure to expire the cookie immediately when value is 0\n return ttl - timestampSkewSec;\n}\n\nexport function createUnsealData(_crypto: Crypto) {\n return async <T = Record<string, unknown>>(\n seal: string,\n {\n password,\n ttl = fourteenDaysInSeconds,\n }: { password: password; ttl?: number },\n ): Promise<T> => {\n const passwordsAsMap = normalizeStringPasswordToMap(password);\n const { sealWithoutVersion, tokenVersion } = parseSeal(seal);\n\n try {\n const data = await Iron.unseal(\n _crypto,\n sealWithoutVersion,\n passwordsAsMap,\n { ...Iron.defaults, ttl: ttl * 1000 },\n );\n\n if (tokenVersion === 2) {\n return data as T;\n }\n\n return {\n // @ts-expect-error `persistent` does not exist on newer tokens\n ...data.persistent,\n };\n } catch (error) {\n if (error instanceof Error) {\n if (\n error.message === \"Expired seal\" ||\n error.message === \"Bad hmac value\" ||\n error.message === \"Cannot find password: \" ||\n error.message === \"Incorrect number of sealed components\"\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\n throw error;\n }\n };\n}\n\nfunction parseSeal(seal: string): {\n sealWithoutVersion: string;\n tokenVersion: number | null;\n} {\n if (seal[seal.length - 2] === versionDelimiter) {\n const [sealWithoutVersion, tokenVersionAsString] =\n seal.split(versionDelimiter);\n return {\n sealWithoutVersion,\n tokenVersion: parseInt(tokenVersionAsString, 10),\n };\n }\n\n return { sealWithoutVersion: seal, tokenVersion: null };\n}\n\nexport function createSealData(_crypto: Crypto) {\n return async (\n data: unknown,\n {\n password,\n ttl = fourteenDaysInSeconds,\n }: { password: password; ttl?: number },\n ) => {\n const passwordsAsMap = normalizeStringPasswordToMap(password);\n\n const mostRecentPasswordId = Math.max(\n ...Object.keys(passwordsAsMap).map((id) => parseInt(id, 10)),\n );\n\n const passwordForSeal = {\n id: mostRecentPasswordId.toString(),\n secret: passwordsAsMap[mostRecentPasswordId],\n };\n\n const seal = await Iron.seal(_crypto, data, passwordForSeal, {\n ...Iron.defaults,\n ttl: ttl * 1000,\n });\n\n return `${seal}${versionDelimiter}${currentMajorVersion}`;\n };\n}\n\nfunction normalizeStringPasswordToMap(password: password) {\n return typeof password === \"string\" ? { 1: password } : password;\n}\n"],"mappings":"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","names":["_crypto","unsealData","sealData","cookie","seal"]}
1
+ {"version":3,"sources":["../index.ts","../../src/core.ts"],"sourcesContent":["// This allows the types to be linked from the main module instead of duplicated\nexport type { IronSessionOptions, IronSessionData } from \"iron-session\";\n\nimport {\n createGetIronSession,\n createSealData,\n createUnsealData,\n} from \"../src/core\";\n\nconst getCrypto = (): Crypto => {\n if (typeof globalThis.crypto?.subtle === \"object\") return globalThis.crypto;\n // @ts-ignore crypto.webcrypto is not available in dom, but is there in newer node versions\n if (typeof globalThis.crypto?.webcrypto?.subtle === \"object\")\n // @ts-ignore same as above\n return globalThis.crypto.webcrypto;\n throw new Error(\n \"no native implementation of WebCrypto is available in current context\",\n );\n};\n\nconst _crypto = getCrypto();\n\nexport const unsealData = createUnsealData(_crypto);\nexport const sealData = createSealData(_crypto);\nexport const getIronSession = createGetIronSession(\n _crypto,\n unsealData,\n sealData,\n);\n","import * as Iron from \"iron-webcrypto\";\nimport type { CookieSerializeOptions } from \"cookie\";\nimport cookie from \"cookie\";\nimport type { IncomingMessage, ServerResponse } from \"http\";\n\n// default time allowed to check for iron seal validity when ttl passed\n// see https://hapi.dev/family/iron/api/?v=6.0.0#options\nconst timestampSkewSec = 60;\n\ntype passwordsMap = { [id: string]: string };\ntype password = string | passwordsMap;\n\nconst fourteenDaysInSeconds = 15 * 24 * 3600;\n\n// We store a token major version to handle data format changes when any. So that when you upgrade the cookies\n// can be kept alive between upgrades, no need to disconnect everyone.\nconst currentMajorVersion = 2;\nconst versionDelimiter = \"~\";\n\nconst defaultOptions: {\n ttl: number;\n cookieOptions: CookieSerializeOptions;\n} = {\n ttl: fourteenDaysInSeconds,\n cookieOptions: {\n httpOnly: true,\n secure: true,\n sameSite: \"lax\",\n path: \"/\",\n },\n};\n\nexport interface IronSessionOptions {\n /**\n * This is the cookie name that will be used inside the browser. You should make sure it's unique given\n * your application. Example: vercel-session\n */\n cookieName: string;\n\n /**\n * This is the password(s) that will be used to encrypt the cookie. It can be either a string or an object\n * like {1: \"password\", 2: password}.\n *\n * When you provide multiple passwords then all of them will be used to decrypt the cookie and only the most\n * recent (= highest key, 2 in this example) password will be used to encrypt the cookie. This allow you\n * to use password rotation (security)\n */\n password: password;\n\n /**\n * This is the time in seconds that the session will be valid for. This also set the max-age attribute of\n * the cookie automatically (minus 60 seconds so that the cookie always expire before the session).\n */\n ttl?: number;\n\n /**\n * This is the options that will be passed to the cookie library.\n * You can see all of them here: https://github.com/jshttp/cookie#options-1.\n *\n * If you want to use \"session cookies\" (cookies that are deleted when the browser is closed) then you need\n * to pass cookieOptions: { maxAge: undefined }.\n */\n cookieOptions?: CookieSerializeOptions;\n}\n\n// eslint-disable-next-line @typescript-eslint/no-empty-interface\nexport interface IronSessionData {\n // If we allow for any keys, then there's no more type check on unknown properties\n // which is not good\n // If we allow for any keys, the later delete will work but I prefer to disable the\n // check at this stage and\n // provide good type checking instead\n // [key: string]: unknown;\n}\n\nexport type IronSession = IronSessionData & {\n /**\n * Destroys the session data and removes the cookie.\n */\n destroy: () => void;\n\n /**\n * Encrypts the session data and sets the cookie.\n */\n save: () => Promise<void>;\n};\n\ndeclare module \"http\" {\n interface IncomingMessage {\n session: IronSession;\n }\n}\n\ntype RequestType = IncomingMessage | Request;\ntype ResponseType = ServerResponse | Response;\n\nexport function createGetIronSession(\n _crypto: Crypto,\n unsealData: ReturnType<typeof createUnsealData>,\n sealData: ReturnType<typeof createSealData>,\n) {\n return async (\n req: RequestType,\n res: ResponseType,\n userSessionOptions: IronSessionOptions,\n ): Promise<IronSession> => {\n if (\n !req ||\n !res ||\n !userSessionOptions ||\n !userSessionOptions.cookieName ||\n !userSessionOptions.password\n ) {\n throw new Error(\n `iron-session: Bad usage. Minimum usage is const session = await getIronSession(req, res, { cookieName: \"...\", password: \"...\". Check the usage here: https://github.com/vvo/iron-session`,\n );\n }\n\n const passwordsAsMap = normalizeStringPasswordToMap(\n userSessionOptions.password,\n );\n\n Object.values(\n normalizeStringPasswordToMap(userSessionOptions.password),\n ).forEach((password) => {\n if (password.length < 32) {\n throw new Error(\n `iron-session: Bad usage. Password must be at least 32 characters long.`,\n );\n }\n });\n\n const options: Required<IronSessionOptions> = {\n ...defaultOptions,\n ...userSessionOptions,\n cookieOptions: {\n ...defaultOptions.cookieOptions,\n ...(userSessionOptions.cookieOptions || {}),\n },\n };\n\n if (options.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 options.ttl = 2147483647;\n }\n\n if (\n userSessionOptions.cookieOptions &&\n \"maxAge\" in userSessionOptions.cookieOptions\n ) {\n // session cookie, do not set maxAge, consider token as infinite\n if (userSessionOptions.cookieOptions.maxAge === undefined) {\n options.ttl = 0;\n } else {\n options.cookieOptions.maxAge = computeCookieMaxAge(\n userSessionOptions.cookieOptions.maxAge,\n );\n }\n } else {\n options.cookieOptions.maxAge = computeCookieMaxAge(options.ttl);\n }\n\n const sealFromCookies = cookie.parse(\n \"credentials\" in req\n ? req.headers.get(\"cookie\") || \"\"\n : req.headers.cookie || \"\",\n )[options.cookieName];\n\n const session =\n sealFromCookies === undefined\n ? {}\n : await unsealData<IronSessionData>(sealFromCookies, {\n password: passwordsAsMap,\n ttl: options.ttl,\n });\n\n Object.defineProperties(session, {\n save: {\n value: async function save() {\n if (\"headersSent\" in res && res.headersSent === true) {\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 const seal = await sealData(session, {\n password: passwordsAsMap,\n ttl: options.ttl,\n });\n const cookieValue = cookie.serialize(\n options.cookieName,\n seal,\n options.cookieOptions,\n );\n\n if (cookieValue.length > 4096) {\n throw new Error(\n `iron-session: Cookie length is too big ${cookieValue.length}, browsers will refuse it. Try to remove some data.`,\n );\n }\n\n addToCookies(cookieValue, res);\n },\n },\n destroy: {\n value: function destroy() {\n Object.keys(session).forEach((key) => {\n // @ts-ignore See comment on the IronSessionData interface\n delete session[key];\n });\n\n const cookieValue = cookie.serialize(options.cookieName, \"\", {\n ...options.cookieOptions,\n maxAge: 0,\n });\n addToCookies(cookieValue, res);\n },\n },\n });\n\n return session as IronSession;\n };\n}\n\nfunction addToCookies(cookieValue: string, res: ResponseType) {\n if (\"headers\" in res) {\n res.headers.append(\"set-cookie\", cookieValue);\n return;\n }\n\n let existingSetCookie =\n (res.getHeader(\"set-cookie\") as string[] | string) ?? [];\n if (typeof existingSetCookie === \"string\") {\n existingSetCookie = [existingSetCookie];\n }\n res.setHeader(\"set-cookie\", [...existingSetCookie, cookieValue]);\n}\n\nfunction computeCookieMaxAge(ttl: number) {\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 maximum between server and clients.\n // It also makes sure to expire the cookie immediately when value is 0\n return ttl - timestampSkewSec;\n}\n\nexport function createUnsealData(_crypto: Crypto) {\n return async <T = Record<string, unknown>>(\n seal: string,\n {\n password,\n ttl = fourteenDaysInSeconds,\n }: { password: password; ttl?: number },\n ): Promise<T> => {\n const passwordsAsMap = normalizeStringPasswordToMap(password);\n const { sealWithoutVersion, tokenVersion } = parseSeal(seal);\n\n try {\n const data =\n (await Iron.unseal(_crypto, sealWithoutVersion, passwordsAsMap, {\n ...Iron.defaults,\n ttl: ttl * 1000,\n })) || {};\n\n if (tokenVersion === 2) {\n return data as T;\n }\n\n return {\n // @ts-expect-error `persistent` does not exist on newer tokens\n ...data.persistent,\n };\n } catch (error) {\n if (error instanceof Error) {\n if (\n error.message === \"Expired seal\" ||\n error.message === \"Bad hmac value\" ||\n error.message.startsWith(\"Cannot find password: \") ||\n error.message === \"Incorrect number of sealed components\"\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\n throw error;\n }\n };\n}\n\nfunction parseSeal(seal: string): {\n sealWithoutVersion: string;\n tokenVersion: number | null;\n} {\n if (seal[seal.length - 2] === versionDelimiter) {\n const [sealWithoutVersion, tokenVersionAsString] =\n seal.split(versionDelimiter);\n return {\n sealWithoutVersion,\n tokenVersion: parseInt(tokenVersionAsString, 10),\n };\n }\n\n return { sealWithoutVersion: seal, tokenVersion: null };\n}\n\nexport function createSealData(_crypto: Crypto) {\n return async (\n data: unknown,\n {\n password,\n ttl = fourteenDaysInSeconds,\n }: { password: password; ttl?: number },\n ) => {\n const passwordsAsMap = normalizeStringPasswordToMap(password);\n\n const mostRecentPasswordId = Math.max(\n ...Object.keys(passwordsAsMap).map((id) => parseInt(id, 10)),\n );\n\n const passwordForSeal = {\n id: mostRecentPasswordId.toString(),\n secret: passwordsAsMap[mostRecentPasswordId],\n };\n\n const seal = await Iron.seal(_crypto, data, passwordForSeal, {\n ...Iron.defaults,\n ttl: ttl * 1000,\n });\n\n return `${seal}${versionDelimiter}${currentMajorVersion}`;\n };\n}\n\nfunction normalizeStringPasswordToMap(password: password) {\n return typeof password === \"string\" ? { 1: password } : password;\n}\n"],"mappings":"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","names":["_crypto","unsealData","sealData","cookie","seal"]}
@@ -126,12 +126,10 @@ function createUnsealData(_crypto2) {
126
126
  const passwordsAsMap = normalizeStringPasswordToMap(password);
127
127
  const { sealWithoutVersion, tokenVersion } = parseSeal(seal2);
128
128
  try {
129
- const data = await Iron.unseal(
130
- _crypto2,
131
- sealWithoutVersion,
132
- passwordsAsMap,
133
- { ...Iron.defaults, ttl: ttl * 1e3 }
134
- );
129
+ const data = await Iron.unseal(_crypto2, sealWithoutVersion, passwordsAsMap, {
130
+ ...Iron.defaults,
131
+ ttl: ttl * 1e3
132
+ }) || {};
135
133
  if (tokenVersion === 2) {
136
134
  return data;
137
135
  }
@@ -140,7 +138,7 @@ function createUnsealData(_crypto2) {
140
138
  };
141
139
  } catch (error) {
142
140
  if (error instanceof Error) {
143
- if (error.message === "Expired seal" || error.message === "Bad hmac value" || error.message === "Cannot find password: " || error.message === "Incorrect number of sealed components") {
141
+ if (error.message === "Expired seal" || error.message === "Bad hmac value" || error.message.startsWith("Cannot find password: ") || error.message === "Incorrect number of sealed components") {
144
142
  return {};
145
143
  }
146
144
  }
@@ -202,9 +200,6 @@ var getIronSession = createGetIronSession(
202
200
  sealData
203
201
  );
204
202
  export {
205
- createGetIronSession,
206
- createSealData,
207
- createUnsealData,
208
203
  getIronSession,
209
204
  sealData,
210
205
  unsealData
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/core.ts","../index.ts"],"sourcesContent":["import * as Iron from \"iron-webcrypto\";\nimport type { CookieSerializeOptions } from \"cookie\";\nimport cookie from \"cookie\";\nimport type { IncomingMessage, ServerResponse } from \"http\";\n\n// default time allowed to check for iron seal validity when ttl passed\n// see https://hapi.dev/family/iron/api/?v=6.0.0#options\nconst timestampSkewSec = 60;\n\ntype passwordsMap = { [id: string]: string };\ntype password = string | passwordsMap;\n\nconst fourteenDaysInSeconds = 15 * 24 * 3600;\n\n// We store a token major version to handle data format changes when any. So that when you upgrade the cookies\n// can be kept alive between upgrades, no need to disconnect everyone.\nconst currentMajorVersion = 2;\nconst versionDelimiter = \"~\";\n\nconst defaultOptions: {\n ttl: number;\n cookieOptions: CookieSerializeOptions;\n} = {\n ttl: fourteenDaysInSeconds,\n cookieOptions: {\n httpOnly: true,\n secure: true,\n sameSite: \"lax\",\n path: \"/\",\n },\n};\n\nexport interface IronSessionOptions {\n /**\n * This is the cookie name that will be used inside the browser. You should make sure it's unique given\n * your application. Example: vercel-session\n */\n cookieName: string;\n\n /**\n * This is the password(s) that will be used to encrypt the cookie. It can be either a string or an object\n * like {1: \"password\", 2: password}.\n *\n * When you provide multiple passwords then all of them will be used to decrypt the cookie and only the most\n * recent (= highest key, 2 in this example) password will be used to encrypt the cookie. This allow you\n * to use password rotation (security)\n */\n password: password;\n\n /**\n * This is the time in seconds that the session will be valid for. This also set the max-age attribute of\n * the cookie automatically (minus 60 seconds so that the cookie always expire before the session).\n */\n ttl?: number;\n\n /**\n * This is the options that will be passed to the cookie library.\n * You can see all of them here: https://github.com/jshttp/cookie#options-1.\n *\n * If you want to use \"session cookies\" (cookies that are deleted when the browser is closed) then you need\n * to pass cookieOptions: { maxAge: undefined }.\n */\n cookieOptions?: CookieSerializeOptions;\n}\n\n// eslint-disable-next-line @typescript-eslint/no-empty-interface\nexport interface IronSessionData {\n // If we allow for any keys, then there's no more type check on unknown properties\n // which is not good\n // If we allow for any keys, the later delete will work but I prefer to disable the\n // check at this stage and\n // provide good type checking instead\n // [key: string]: unknown;\n}\n\nexport type IronSession = IronSessionData & {\n /**\n * Destroys the session data and removes the cookie.\n */\n destroy: () => void;\n\n /**\n * Encrypts the session data and sets the cookie.\n */\n save: () => Promise<void>;\n};\n\ndeclare module \"http\" {\n interface IncomingMessage {\n session: IronSession;\n }\n}\n\ntype RequestType = IncomingMessage | Request;\ntype ResponseType = ServerResponse | Response;\n\nexport function createGetIronSession(\n _crypto: Crypto,\n unsealData: ReturnType<typeof createUnsealData>,\n sealData: ReturnType<typeof createSealData>,\n) {\n return async (\n req: RequestType,\n res: ResponseType,\n userSessionOptions: IronSessionOptions,\n ): Promise<IronSession> => {\n if (\n !req ||\n !res ||\n !userSessionOptions ||\n !userSessionOptions.cookieName ||\n !userSessionOptions.password\n ) {\n throw new Error(\n `iron-session: Bad usage. Minimum usage is const session = await getIronSession(req, res, { cookieName: \"...\", password: \"...\". Check the usage here: https://github.com/vvo/iron-session`,\n );\n }\n\n const passwordsAsMap = normalizeStringPasswordToMap(\n userSessionOptions.password,\n );\n\n Object.values(\n normalizeStringPasswordToMap(userSessionOptions.password),\n ).forEach((password) => {\n if (password.length < 32) {\n throw new Error(\n `iron-session: Bad usage. Password must be at least 32 characters long.`,\n );\n }\n });\n\n const options: Required<IronSessionOptions> = {\n ...defaultOptions,\n ...userSessionOptions,\n cookieOptions: {\n ...defaultOptions.cookieOptions,\n ...(userSessionOptions.cookieOptions || {}),\n },\n };\n\n if (options.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 options.ttl = 2147483647;\n }\n\n if (\n userSessionOptions.cookieOptions &&\n \"maxAge\" in userSessionOptions.cookieOptions\n ) {\n // session cookie, do not set maxAge, consider token as infinite\n if (userSessionOptions.cookieOptions.maxAge === undefined) {\n options.ttl = 0;\n } else {\n options.cookieOptions.maxAge = computeCookieMaxAge(\n userSessionOptions.cookieOptions.maxAge,\n );\n }\n } else {\n options.cookieOptions.maxAge = computeCookieMaxAge(options.ttl);\n }\n\n const sealFromCookies = cookie.parse(\n \"credentials\" in req\n ? req.headers.get(\"cookie\") || \"\"\n : req.headers.cookie || \"\",\n )[options.cookieName];\n\n const session =\n sealFromCookies === undefined\n ? {}\n : await unsealData<IronSessionData>(sealFromCookies, {\n password: passwordsAsMap,\n ttl: options.ttl,\n });\n\n Object.defineProperties(session, {\n save: {\n value: async function save() {\n if (\"headersSent\" in res && res.headersSent === true) {\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 const seal = await sealData(session, {\n password: passwordsAsMap,\n ttl: options.ttl,\n });\n const cookieValue = cookie.serialize(\n options.cookieName,\n seal,\n options.cookieOptions,\n );\n\n if (cookieValue.length > 4096) {\n throw new Error(\n `iron-session: Cookie length is too big ${cookieValue.length}, browsers will refuse it. Try to remove some data.`,\n );\n }\n\n addToCookies(cookieValue, res);\n },\n },\n destroy: {\n value: function destroy() {\n Object.keys(session).forEach((key) => {\n // @ts-ignore See comment on the IronSessionData interface\n delete session[key];\n });\n\n const cookieValue = cookie.serialize(options.cookieName, \"\", {\n ...options.cookieOptions,\n maxAge: 0,\n });\n addToCookies(cookieValue, res);\n },\n },\n });\n\n return session as IronSession;\n };\n}\n\nfunction addToCookies(cookieValue: string, res: ResponseType) {\n if (\"headers\" in res) {\n res.headers.append(\"set-cookie\", cookieValue);\n return;\n }\n\n let existingSetCookie =\n (res.getHeader(\"set-cookie\") as string[] | string) ?? [];\n if (typeof existingSetCookie === \"string\") {\n existingSetCookie = [existingSetCookie];\n }\n res.setHeader(\"set-cookie\", [...existingSetCookie, cookieValue]);\n}\n\nfunction computeCookieMaxAge(ttl: number) {\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 maximum between server and clients.\n // It also makes sure to expire the cookie immediately when value is 0\n return ttl - timestampSkewSec;\n}\n\nexport function createUnsealData(_crypto: Crypto) {\n return async <T = Record<string, unknown>>(\n seal: string,\n {\n password,\n ttl = fourteenDaysInSeconds,\n }: { password: password; ttl?: number },\n ): Promise<T> => {\n const passwordsAsMap = normalizeStringPasswordToMap(password);\n const { sealWithoutVersion, tokenVersion } = parseSeal(seal);\n\n try {\n const data = await Iron.unseal(\n _crypto,\n sealWithoutVersion,\n passwordsAsMap,\n { ...Iron.defaults, ttl: ttl * 1000 },\n );\n\n if (tokenVersion === 2) {\n return data as T;\n }\n\n return {\n // @ts-expect-error `persistent` does not exist on newer tokens\n ...data.persistent,\n };\n } catch (error) {\n if (error instanceof Error) {\n if (\n error.message === \"Expired seal\" ||\n error.message === \"Bad hmac value\" ||\n error.message === \"Cannot find password: \" ||\n error.message === \"Incorrect number of sealed components\"\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\n throw error;\n }\n };\n}\n\nfunction parseSeal(seal: string): {\n sealWithoutVersion: string;\n tokenVersion: number | null;\n} {\n if (seal[seal.length - 2] === versionDelimiter) {\n const [sealWithoutVersion, tokenVersionAsString] =\n seal.split(versionDelimiter);\n return {\n sealWithoutVersion,\n tokenVersion: parseInt(tokenVersionAsString, 10),\n };\n }\n\n return { sealWithoutVersion: seal, tokenVersion: null };\n}\n\nexport function createSealData(_crypto: Crypto) {\n return async (\n data: unknown,\n {\n password,\n ttl = fourteenDaysInSeconds,\n }: { password: password; ttl?: number },\n ) => {\n const passwordsAsMap = normalizeStringPasswordToMap(password);\n\n const mostRecentPasswordId = Math.max(\n ...Object.keys(passwordsAsMap).map((id) => parseInt(id, 10)),\n );\n\n const passwordForSeal = {\n id: mostRecentPasswordId.toString(),\n secret: passwordsAsMap[mostRecentPasswordId],\n };\n\n const seal = await Iron.seal(_crypto, data, passwordForSeal, {\n ...Iron.defaults,\n ttl: ttl * 1000,\n });\n\n return `${seal}${versionDelimiter}${currentMajorVersion}`;\n };\n}\n\nfunction normalizeStringPasswordToMap(password: password) {\n return typeof password === \"string\" ? { 1: password } : password;\n}\n","import {\n createGetIronSession,\n createSealData,\n createUnsealData,\n} from \"../src/core\";\n\nconst getCrypto = (): Crypto => {\n if (typeof globalThis.crypto?.subtle === \"object\") return globalThis.crypto;\n // @ts-ignore crypto.webcrypto is not available in dom, but is there in newer node versions\n if (typeof globalThis.crypto?.webcrypto?.subtle === \"object\")\n // @ts-ignore same as above\n return globalThis.crypto.webcrypto;\n throw new Error(\n \"no native implementation of WebCrypto is available in current context\",\n );\n};\n\nconst _crypto = getCrypto();\n\nexport * from \"../src/core\";\nexport const unsealData = createUnsealData(_crypto);\nexport const sealData = createSealData(_crypto);\nexport const getIronSession = createGetIronSession(\n _crypto,\n unsealData,\n sealData,\n);\n"],"mappings":"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","names":["_crypto","unsealData","sealData","seal"]}
1
+ {"version":3,"sources":["../../src/core.ts","../index.ts"],"sourcesContent":["import * as Iron from \"iron-webcrypto\";\nimport type { CookieSerializeOptions } from \"cookie\";\nimport cookie from \"cookie\";\nimport type { IncomingMessage, ServerResponse } from \"http\";\n\n// default time allowed to check for iron seal validity when ttl passed\n// see https://hapi.dev/family/iron/api/?v=6.0.0#options\nconst timestampSkewSec = 60;\n\ntype passwordsMap = { [id: string]: string };\ntype password = string | passwordsMap;\n\nconst fourteenDaysInSeconds = 15 * 24 * 3600;\n\n// We store a token major version to handle data format changes when any. So that when you upgrade the cookies\n// can be kept alive between upgrades, no need to disconnect everyone.\nconst currentMajorVersion = 2;\nconst versionDelimiter = \"~\";\n\nconst defaultOptions: {\n ttl: number;\n cookieOptions: CookieSerializeOptions;\n} = {\n ttl: fourteenDaysInSeconds,\n cookieOptions: {\n httpOnly: true,\n secure: true,\n sameSite: \"lax\",\n path: \"/\",\n },\n};\n\nexport interface IronSessionOptions {\n /**\n * This is the cookie name that will be used inside the browser. You should make sure it's unique given\n * your application. Example: vercel-session\n */\n cookieName: string;\n\n /**\n * This is the password(s) that will be used to encrypt the cookie. It can be either a string or an object\n * like {1: \"password\", 2: password}.\n *\n * When you provide multiple passwords then all of them will be used to decrypt the cookie and only the most\n * recent (= highest key, 2 in this example) password will be used to encrypt the cookie. This allow you\n * to use password rotation (security)\n */\n password: password;\n\n /**\n * This is the time in seconds that the session will be valid for. This also set the max-age attribute of\n * the cookie automatically (minus 60 seconds so that the cookie always expire before the session).\n */\n ttl?: number;\n\n /**\n * This is the options that will be passed to the cookie library.\n * You can see all of them here: https://github.com/jshttp/cookie#options-1.\n *\n * If you want to use \"session cookies\" (cookies that are deleted when the browser is closed) then you need\n * to pass cookieOptions: { maxAge: undefined }.\n */\n cookieOptions?: CookieSerializeOptions;\n}\n\n// eslint-disable-next-line @typescript-eslint/no-empty-interface\nexport interface IronSessionData {\n // If we allow for any keys, then there's no more type check on unknown properties\n // which is not good\n // If we allow for any keys, the later delete will work but I prefer to disable the\n // check at this stage and\n // provide good type checking instead\n // [key: string]: unknown;\n}\n\nexport type IronSession = IronSessionData & {\n /**\n * Destroys the session data and removes the cookie.\n */\n destroy: () => void;\n\n /**\n * Encrypts the session data and sets the cookie.\n */\n save: () => Promise<void>;\n};\n\ndeclare module \"http\" {\n interface IncomingMessage {\n session: IronSession;\n }\n}\n\ntype RequestType = IncomingMessage | Request;\ntype ResponseType = ServerResponse | Response;\n\nexport function createGetIronSession(\n _crypto: Crypto,\n unsealData: ReturnType<typeof createUnsealData>,\n sealData: ReturnType<typeof createSealData>,\n) {\n return async (\n req: RequestType,\n res: ResponseType,\n userSessionOptions: IronSessionOptions,\n ): Promise<IronSession> => {\n if (\n !req ||\n !res ||\n !userSessionOptions ||\n !userSessionOptions.cookieName ||\n !userSessionOptions.password\n ) {\n throw new Error(\n `iron-session: Bad usage. Minimum usage is const session = await getIronSession(req, res, { cookieName: \"...\", password: \"...\". Check the usage here: https://github.com/vvo/iron-session`,\n );\n }\n\n const passwordsAsMap = normalizeStringPasswordToMap(\n userSessionOptions.password,\n );\n\n Object.values(\n normalizeStringPasswordToMap(userSessionOptions.password),\n ).forEach((password) => {\n if (password.length < 32) {\n throw new Error(\n `iron-session: Bad usage. Password must be at least 32 characters long.`,\n );\n }\n });\n\n const options: Required<IronSessionOptions> = {\n ...defaultOptions,\n ...userSessionOptions,\n cookieOptions: {\n ...defaultOptions.cookieOptions,\n ...(userSessionOptions.cookieOptions || {}),\n },\n };\n\n if (options.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 options.ttl = 2147483647;\n }\n\n if (\n userSessionOptions.cookieOptions &&\n \"maxAge\" in userSessionOptions.cookieOptions\n ) {\n // session cookie, do not set maxAge, consider token as infinite\n if (userSessionOptions.cookieOptions.maxAge === undefined) {\n options.ttl = 0;\n } else {\n options.cookieOptions.maxAge = computeCookieMaxAge(\n userSessionOptions.cookieOptions.maxAge,\n );\n }\n } else {\n options.cookieOptions.maxAge = computeCookieMaxAge(options.ttl);\n }\n\n const sealFromCookies = cookie.parse(\n \"credentials\" in req\n ? req.headers.get(\"cookie\") || \"\"\n : req.headers.cookie || \"\",\n )[options.cookieName];\n\n const session =\n sealFromCookies === undefined\n ? {}\n : await unsealData<IronSessionData>(sealFromCookies, {\n password: passwordsAsMap,\n ttl: options.ttl,\n });\n\n Object.defineProperties(session, {\n save: {\n value: async function save() {\n if (\"headersSent\" in res && res.headersSent === true) {\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 const seal = await sealData(session, {\n password: passwordsAsMap,\n ttl: options.ttl,\n });\n const cookieValue = cookie.serialize(\n options.cookieName,\n seal,\n options.cookieOptions,\n );\n\n if (cookieValue.length > 4096) {\n throw new Error(\n `iron-session: Cookie length is too big ${cookieValue.length}, browsers will refuse it. Try to remove some data.`,\n );\n }\n\n addToCookies(cookieValue, res);\n },\n },\n destroy: {\n value: function destroy() {\n Object.keys(session).forEach((key) => {\n // @ts-ignore See comment on the IronSessionData interface\n delete session[key];\n });\n\n const cookieValue = cookie.serialize(options.cookieName, \"\", {\n ...options.cookieOptions,\n maxAge: 0,\n });\n addToCookies(cookieValue, res);\n },\n },\n });\n\n return session as IronSession;\n };\n}\n\nfunction addToCookies(cookieValue: string, res: ResponseType) {\n if (\"headers\" in res) {\n res.headers.append(\"set-cookie\", cookieValue);\n return;\n }\n\n let existingSetCookie =\n (res.getHeader(\"set-cookie\") as string[] | string) ?? [];\n if (typeof existingSetCookie === \"string\") {\n existingSetCookie = [existingSetCookie];\n }\n res.setHeader(\"set-cookie\", [...existingSetCookie, cookieValue]);\n}\n\nfunction computeCookieMaxAge(ttl: number) {\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 maximum between server and clients.\n // It also makes sure to expire the cookie immediately when value is 0\n return ttl - timestampSkewSec;\n}\n\nexport function createUnsealData(_crypto: Crypto) {\n return async <T = Record<string, unknown>>(\n seal: string,\n {\n password,\n ttl = fourteenDaysInSeconds,\n }: { password: password; ttl?: number },\n ): Promise<T> => {\n const passwordsAsMap = normalizeStringPasswordToMap(password);\n const { sealWithoutVersion, tokenVersion } = parseSeal(seal);\n\n try {\n const data =\n (await Iron.unseal(_crypto, sealWithoutVersion, passwordsAsMap, {\n ...Iron.defaults,\n ttl: ttl * 1000,\n })) || {};\n\n if (tokenVersion === 2) {\n return data as T;\n }\n\n return {\n // @ts-expect-error `persistent` does not exist on newer tokens\n ...data.persistent,\n };\n } catch (error) {\n if (error instanceof Error) {\n if (\n error.message === \"Expired seal\" ||\n error.message === \"Bad hmac value\" ||\n error.message.startsWith(\"Cannot find password: \") ||\n error.message === \"Incorrect number of sealed components\"\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\n throw error;\n }\n };\n}\n\nfunction parseSeal(seal: string): {\n sealWithoutVersion: string;\n tokenVersion: number | null;\n} {\n if (seal[seal.length - 2] === versionDelimiter) {\n const [sealWithoutVersion, tokenVersionAsString] =\n seal.split(versionDelimiter);\n return {\n sealWithoutVersion,\n tokenVersion: parseInt(tokenVersionAsString, 10),\n };\n }\n\n return { sealWithoutVersion: seal, tokenVersion: null };\n}\n\nexport function createSealData(_crypto: Crypto) {\n return async (\n data: unknown,\n {\n password,\n ttl = fourteenDaysInSeconds,\n }: { password: password; ttl?: number },\n ) => {\n const passwordsAsMap = normalizeStringPasswordToMap(password);\n\n const mostRecentPasswordId = Math.max(\n ...Object.keys(passwordsAsMap).map((id) => parseInt(id, 10)),\n );\n\n const passwordForSeal = {\n id: mostRecentPasswordId.toString(),\n secret: passwordsAsMap[mostRecentPasswordId],\n };\n\n const seal = await Iron.seal(_crypto, data, passwordForSeal, {\n ...Iron.defaults,\n ttl: ttl * 1000,\n });\n\n return `${seal}${versionDelimiter}${currentMajorVersion}`;\n };\n}\n\nfunction normalizeStringPasswordToMap(password: password) {\n return typeof password === \"string\" ? { 1: password } : password;\n}\n","// This allows the types to be linked from the main module instead of duplicated\nexport type { IronSessionOptions, IronSessionData } from \"iron-session\";\n\nimport {\n createGetIronSession,\n createSealData,\n createUnsealData,\n} from \"../src/core\";\n\nconst getCrypto = (): Crypto => {\n if (typeof globalThis.crypto?.subtle === \"object\") return globalThis.crypto;\n // @ts-ignore crypto.webcrypto is not available in dom, but is there in newer node versions\n if (typeof globalThis.crypto?.webcrypto?.subtle === \"object\")\n // @ts-ignore same as above\n return globalThis.crypto.webcrypto;\n throw new Error(\n \"no native implementation of WebCrypto is available in current context\",\n );\n};\n\nconst _crypto = getCrypto();\n\nexport const unsealData = createUnsealData(_crypto);\nexport const sealData = createSealData(_crypto);\nexport const getIronSession = createGetIronSession(\n _crypto,\n unsealData,\n sealData,\n);\n"],"mappings":"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","names":["_crypto","unsealData","sealData","seal"]}
package/edge/index.d.ts CHANGED
@@ -1,68 +1,6 @@
1
- import { CookieSerializeOptions } from 'cookie';
1
+ import * as iron_session from 'iron-session';
2
+ export { IronSessionData, IronSessionOptions } from 'iron-session';
2
3
  import * as http from 'http';
3
- import { IncomingMessage, ServerResponse } from 'http';
4
-
5
- declare type passwordsMap = {
6
- [id: string]: string;
7
- };
8
- declare type password = string | passwordsMap;
9
- interface IronSessionOptions {
10
- /**
11
- * This is the cookie name that will be used inside the browser. You should make sure it's unique given
12
- * your application. Example: vercel-session
13
- */
14
- cookieName: string;
15
- /**
16
- * This is the password(s) that will be used to encrypt the cookie. It can be either a string or an object
17
- * like {1: "password", 2: password}.
18
- *
19
- * When you provide multiple passwords then all of them will be used to decrypt the cookie and only the most
20
- * recent (= highest key, 2 in this example) password will be used to encrypt the cookie. This allow you
21
- * to use password rotation (security)
22
- */
23
- password: password;
24
- /**
25
- * This is the time in seconds that the session will be valid for. This also set the max-age attribute of
26
- * the cookie automatically (minus 60 seconds so that the cookie always expire before the session).
27
- */
28
- ttl?: number;
29
- /**
30
- * This is the options that will be passed to the cookie library.
31
- * You can see all of them here: https://github.com/jshttp/cookie#options-1.
32
- *
33
- * If you want to use "session cookies" (cookies that are deleted when the browser is closed) then you need
34
- * to pass cookieOptions: { maxAge: undefined }.
35
- */
36
- cookieOptions?: CookieSerializeOptions;
37
- }
38
- interface IronSessionData {
39
- }
40
- declare type IronSession = IronSessionData & {
41
- /**
42
- * Destroys the session data and removes the cookie.
43
- */
44
- destroy: () => void;
45
- /**
46
- * Encrypts the session data and sets the cookie.
47
- */
48
- save: () => Promise<void>;
49
- };
50
- declare module "http" {
51
- interface IncomingMessage {
52
- session: IronSession;
53
- }
54
- }
55
- declare type RequestType = IncomingMessage | Request;
56
- declare type ResponseType = ServerResponse | Response;
57
- declare function createGetIronSession(_crypto: Crypto, unsealData: ReturnType<typeof createUnsealData>, sealData: ReturnType<typeof createSealData>): (req: RequestType, res: ResponseType, userSessionOptions: IronSessionOptions) => Promise<IronSession>;
58
- declare function createUnsealData(_crypto: Crypto): <T = Record<string, unknown>>(seal: string, { password, ttl, }: {
59
- password: password;
60
- ttl?: number | undefined;
61
- }) => Promise<T>;
62
- declare function createSealData(_crypto: Crypto): (data: unknown, { password, ttl, }: {
63
- password: password;
64
- ttl?: number | undefined;
65
- }) => Promise<string>;
66
4
 
67
5
  declare const unsealData: <T = Record<string, unknown>>(seal: string, { password, ttl, }: {
68
6
  password: string | {
@@ -76,6 +14,6 @@ declare const sealData: (data: unknown, { password, ttl, }: {
76
14
  };
77
15
  ttl?: number | undefined;
78
16
  }) => Promise<string>;
79
- declare const getIronSession: (req: http.IncomingMessage | Request, res: http.ServerResponse | Response, userSessionOptions: IronSessionOptions) => Promise<IronSession>;
17
+ declare const getIronSession: (req: http.IncomingMessage | Request, res: http.ServerResponse | Response, userSessionOptions: iron_session.IronSessionOptions) => Promise<iron_session.IronSession>;
80
18
 
81
- export { IronSession, IronSessionData, IronSessionOptions, createGetIronSession, createSealData, createUnsealData, getIronSession, sealData, unsealData };
19
+ export { getIronSession, sealData, unsealData };
package/edge/index.js CHANGED
@@ -26,9 +26,6 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
26
26
  // edge/index.ts
27
27
  var edge_exports = {};
28
28
  __export(edge_exports, {
29
- createGetIronSession: () => createGetIronSession,
30
- createSealData: () => createSealData,
31
- createUnsealData: () => createUnsealData,
32
29
  getIronSession: () => getIronSession,
33
30
  sealData: () => sealData,
34
31
  unsealData: () => unsealData
@@ -163,12 +160,10 @@ function createUnsealData(_crypto2) {
163
160
  const passwordsAsMap = normalizeStringPasswordToMap(password);
164
161
  const { sealWithoutVersion, tokenVersion } = parseSeal(seal2);
165
162
  try {
166
- const data = await Iron.unseal(
167
- _crypto2,
168
- sealWithoutVersion,
169
- passwordsAsMap,
170
- { ...Iron.defaults, ttl: ttl * 1e3 }
171
- );
163
+ const data = await Iron.unseal(_crypto2, sealWithoutVersion, passwordsAsMap, {
164
+ ...Iron.defaults,
165
+ ttl: ttl * 1e3
166
+ }) || {};
172
167
  if (tokenVersion === 2) {
173
168
  return data;
174
169
  }
@@ -177,7 +172,7 @@ function createUnsealData(_crypto2) {
177
172
  };
178
173
  } catch (error) {
179
174
  if (error instanceof Error) {
180
- if (error.message === "Expired seal" || error.message === "Bad hmac value" || error.message === "Cannot find password: " || error.message === "Incorrect number of sealed components") {
175
+ if (error.message === "Expired seal" || error.message === "Bad hmac value" || error.message.startsWith("Cannot find password: ") || error.message === "Incorrect number of sealed components") {
181
176
  return {};
182
177
  }
183
178
  }
@@ -240,9 +235,6 @@ var getIronSession = createGetIronSession(
240
235
  );
241
236
  // Annotate the CommonJS export names for ESM import in node:
242
237
  0 && (module.exports = {
243
- createGetIronSession,
244
- createSealData,
245
- createUnsealData,
246
238
  getIronSession,
247
239
  sealData,
248
240
  unsealData
package/edge/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../index.ts","../../src/core.ts"],"sourcesContent":["import {\n createGetIronSession,\n createSealData,\n createUnsealData,\n} from \"../src/core\";\n\nconst getCrypto = (): Crypto => {\n if (typeof globalThis.crypto?.subtle === \"object\") return globalThis.crypto;\n // @ts-ignore crypto.webcrypto is not available in dom, but is there in newer node versions\n if (typeof globalThis.crypto?.webcrypto?.subtle === \"object\")\n // @ts-ignore same as above\n return globalThis.crypto.webcrypto;\n throw new Error(\n \"no native implementation of WebCrypto is available in current context\",\n );\n};\n\nconst _crypto = getCrypto();\n\nexport * from \"../src/core\";\nexport const unsealData = createUnsealData(_crypto);\nexport const sealData = createSealData(_crypto);\nexport const getIronSession = createGetIronSession(\n _crypto,\n unsealData,\n sealData,\n);\n","import * as Iron from \"iron-webcrypto\";\nimport type { CookieSerializeOptions } from \"cookie\";\nimport cookie from \"cookie\";\nimport type { IncomingMessage, ServerResponse } from \"http\";\n\n// default time allowed to check for iron seal validity when ttl passed\n// see https://hapi.dev/family/iron/api/?v=6.0.0#options\nconst timestampSkewSec = 60;\n\ntype passwordsMap = { [id: string]: string };\ntype password = string | passwordsMap;\n\nconst fourteenDaysInSeconds = 15 * 24 * 3600;\n\n// We store a token major version to handle data format changes when any. So that when you upgrade the cookies\n// can be kept alive between upgrades, no need to disconnect everyone.\nconst currentMajorVersion = 2;\nconst versionDelimiter = \"~\";\n\nconst defaultOptions: {\n ttl: number;\n cookieOptions: CookieSerializeOptions;\n} = {\n ttl: fourteenDaysInSeconds,\n cookieOptions: {\n httpOnly: true,\n secure: true,\n sameSite: \"lax\",\n path: \"/\",\n },\n};\n\nexport interface IronSessionOptions {\n /**\n * This is the cookie name that will be used inside the browser. You should make sure it's unique given\n * your application. Example: vercel-session\n */\n cookieName: string;\n\n /**\n * This is the password(s) that will be used to encrypt the cookie. It can be either a string or an object\n * like {1: \"password\", 2: password}.\n *\n * When you provide multiple passwords then all of them will be used to decrypt the cookie and only the most\n * recent (= highest key, 2 in this example) password will be used to encrypt the cookie. This allow you\n * to use password rotation (security)\n */\n password: password;\n\n /**\n * This is the time in seconds that the session will be valid for. This also set the max-age attribute of\n * the cookie automatically (minus 60 seconds so that the cookie always expire before the session).\n */\n ttl?: number;\n\n /**\n * This is the options that will be passed to the cookie library.\n * You can see all of them here: https://github.com/jshttp/cookie#options-1.\n *\n * If you want to use \"session cookies\" (cookies that are deleted when the browser is closed) then you need\n * to pass cookieOptions: { maxAge: undefined }.\n */\n cookieOptions?: CookieSerializeOptions;\n}\n\n// eslint-disable-next-line @typescript-eslint/no-empty-interface\nexport interface IronSessionData {\n // If we allow for any keys, then there's no more type check on unknown properties\n // which is not good\n // If we allow for any keys, the later delete will work but I prefer to disable the\n // check at this stage and\n // provide good type checking instead\n // [key: string]: unknown;\n}\n\nexport type IronSession = IronSessionData & {\n /**\n * Destroys the session data and removes the cookie.\n */\n destroy: () => void;\n\n /**\n * Encrypts the session data and sets the cookie.\n */\n save: () => Promise<void>;\n};\n\ndeclare module \"http\" {\n interface IncomingMessage {\n session: IronSession;\n }\n}\n\ntype RequestType = IncomingMessage | Request;\ntype ResponseType = ServerResponse | Response;\n\nexport function createGetIronSession(\n _crypto: Crypto,\n unsealData: ReturnType<typeof createUnsealData>,\n sealData: ReturnType<typeof createSealData>,\n) {\n return async (\n req: RequestType,\n res: ResponseType,\n userSessionOptions: IronSessionOptions,\n ): Promise<IronSession> => {\n if (\n !req ||\n !res ||\n !userSessionOptions ||\n !userSessionOptions.cookieName ||\n !userSessionOptions.password\n ) {\n throw new Error(\n `iron-session: Bad usage. Minimum usage is const session = await getIronSession(req, res, { cookieName: \"...\", password: \"...\". Check the usage here: https://github.com/vvo/iron-session`,\n );\n }\n\n const passwordsAsMap = normalizeStringPasswordToMap(\n userSessionOptions.password,\n );\n\n Object.values(\n normalizeStringPasswordToMap(userSessionOptions.password),\n ).forEach((password) => {\n if (password.length < 32) {\n throw new Error(\n `iron-session: Bad usage. Password must be at least 32 characters long.`,\n );\n }\n });\n\n const options: Required<IronSessionOptions> = {\n ...defaultOptions,\n ...userSessionOptions,\n cookieOptions: {\n ...defaultOptions.cookieOptions,\n ...(userSessionOptions.cookieOptions || {}),\n },\n };\n\n if (options.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 options.ttl = 2147483647;\n }\n\n if (\n userSessionOptions.cookieOptions &&\n \"maxAge\" in userSessionOptions.cookieOptions\n ) {\n // session cookie, do not set maxAge, consider token as infinite\n if (userSessionOptions.cookieOptions.maxAge === undefined) {\n options.ttl = 0;\n } else {\n options.cookieOptions.maxAge = computeCookieMaxAge(\n userSessionOptions.cookieOptions.maxAge,\n );\n }\n } else {\n options.cookieOptions.maxAge = computeCookieMaxAge(options.ttl);\n }\n\n const sealFromCookies = cookie.parse(\n \"credentials\" in req\n ? req.headers.get(\"cookie\") || \"\"\n : req.headers.cookie || \"\",\n )[options.cookieName];\n\n const session =\n sealFromCookies === undefined\n ? {}\n : await unsealData<IronSessionData>(sealFromCookies, {\n password: passwordsAsMap,\n ttl: options.ttl,\n });\n\n Object.defineProperties(session, {\n save: {\n value: async function save() {\n if (\"headersSent\" in res && res.headersSent === true) {\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 const seal = await sealData(session, {\n password: passwordsAsMap,\n ttl: options.ttl,\n });\n const cookieValue = cookie.serialize(\n options.cookieName,\n seal,\n options.cookieOptions,\n );\n\n if (cookieValue.length > 4096) {\n throw new Error(\n `iron-session: Cookie length is too big ${cookieValue.length}, browsers will refuse it. Try to remove some data.`,\n );\n }\n\n addToCookies(cookieValue, res);\n },\n },\n destroy: {\n value: function destroy() {\n Object.keys(session).forEach((key) => {\n // @ts-ignore See comment on the IronSessionData interface\n delete session[key];\n });\n\n const cookieValue = cookie.serialize(options.cookieName, \"\", {\n ...options.cookieOptions,\n maxAge: 0,\n });\n addToCookies(cookieValue, res);\n },\n },\n });\n\n return session as IronSession;\n };\n}\n\nfunction addToCookies(cookieValue: string, res: ResponseType) {\n if (\"headers\" in res) {\n res.headers.append(\"set-cookie\", cookieValue);\n return;\n }\n\n let existingSetCookie =\n (res.getHeader(\"set-cookie\") as string[] | string) ?? [];\n if (typeof existingSetCookie === \"string\") {\n existingSetCookie = [existingSetCookie];\n }\n res.setHeader(\"set-cookie\", [...existingSetCookie, cookieValue]);\n}\n\nfunction computeCookieMaxAge(ttl: number) {\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 maximum between server and clients.\n // It also makes sure to expire the cookie immediately when value is 0\n return ttl - timestampSkewSec;\n}\n\nexport function createUnsealData(_crypto: Crypto) {\n return async <T = Record<string, unknown>>(\n seal: string,\n {\n password,\n ttl = fourteenDaysInSeconds,\n }: { password: password; ttl?: number },\n ): Promise<T> => {\n const passwordsAsMap = normalizeStringPasswordToMap(password);\n const { sealWithoutVersion, tokenVersion } = parseSeal(seal);\n\n try {\n const data = await Iron.unseal(\n _crypto,\n sealWithoutVersion,\n passwordsAsMap,\n { ...Iron.defaults, ttl: ttl * 1000 },\n );\n\n if (tokenVersion === 2) {\n return data as T;\n }\n\n return {\n // @ts-expect-error `persistent` does not exist on newer tokens\n ...data.persistent,\n };\n } catch (error) {\n if (error instanceof Error) {\n if (\n error.message === \"Expired seal\" ||\n error.message === \"Bad hmac value\" ||\n error.message === \"Cannot find password: \" ||\n error.message === \"Incorrect number of sealed components\"\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\n throw error;\n }\n };\n}\n\nfunction parseSeal(seal: string): {\n sealWithoutVersion: string;\n tokenVersion: number | null;\n} {\n if (seal[seal.length - 2] === versionDelimiter) {\n const [sealWithoutVersion, tokenVersionAsString] =\n seal.split(versionDelimiter);\n return {\n sealWithoutVersion,\n tokenVersion: parseInt(tokenVersionAsString, 10),\n };\n }\n\n return { sealWithoutVersion: seal, tokenVersion: null };\n}\n\nexport function createSealData(_crypto: Crypto) {\n return async (\n data: unknown,\n {\n password,\n ttl = fourteenDaysInSeconds,\n }: { password: password; ttl?: number },\n ) => {\n const passwordsAsMap = normalizeStringPasswordToMap(password);\n\n const mostRecentPasswordId = Math.max(\n ...Object.keys(passwordsAsMap).map((id) => parseInt(id, 10)),\n );\n\n const passwordForSeal = {\n id: mostRecentPasswordId.toString(),\n secret: passwordsAsMap[mostRecentPasswordId],\n };\n\n const seal = await Iron.seal(_crypto, data, passwordForSeal, {\n ...Iron.defaults,\n ttl: ttl * 1000,\n });\n\n return `${seal}${versionDelimiter}${currentMajorVersion}`;\n };\n}\n\nfunction normalizeStringPasswordToMap(password: password) {\n return typeof password === \"string\" ? { 1: password } : password;\n}\n"],"mappings":"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","names":["_crypto","unsealData","sealData","cookie","seal"]}
1
+ {"version":3,"sources":["../index.ts","../../src/core.ts"],"sourcesContent":["// This allows the types to be linked from the main module instead of duplicated\nexport type { IronSessionOptions, IronSessionData } from \"iron-session\";\n\nimport {\n createGetIronSession,\n createSealData,\n createUnsealData,\n} from \"../src/core\";\n\nconst getCrypto = (): Crypto => {\n if (typeof globalThis.crypto?.subtle === \"object\") return globalThis.crypto;\n // @ts-ignore crypto.webcrypto is not available in dom, but is there in newer node versions\n if (typeof globalThis.crypto?.webcrypto?.subtle === \"object\")\n // @ts-ignore same as above\n return globalThis.crypto.webcrypto;\n throw new Error(\n \"no native implementation of WebCrypto is available in current context\",\n );\n};\n\nconst _crypto = getCrypto();\n\nexport const unsealData = createUnsealData(_crypto);\nexport const sealData = createSealData(_crypto);\nexport const getIronSession = createGetIronSession(\n _crypto,\n unsealData,\n sealData,\n);\n","import * as Iron from \"iron-webcrypto\";\nimport type { CookieSerializeOptions } from \"cookie\";\nimport cookie from \"cookie\";\nimport type { IncomingMessage, ServerResponse } from \"http\";\n\n// default time allowed to check for iron seal validity when ttl passed\n// see https://hapi.dev/family/iron/api/?v=6.0.0#options\nconst timestampSkewSec = 60;\n\ntype passwordsMap = { [id: string]: string };\ntype password = string | passwordsMap;\n\nconst fourteenDaysInSeconds = 15 * 24 * 3600;\n\n// We store a token major version to handle data format changes when any. So that when you upgrade the cookies\n// can be kept alive between upgrades, no need to disconnect everyone.\nconst currentMajorVersion = 2;\nconst versionDelimiter = \"~\";\n\nconst defaultOptions: {\n ttl: number;\n cookieOptions: CookieSerializeOptions;\n} = {\n ttl: fourteenDaysInSeconds,\n cookieOptions: {\n httpOnly: true,\n secure: true,\n sameSite: \"lax\",\n path: \"/\",\n },\n};\n\nexport interface IronSessionOptions {\n /**\n * This is the cookie name that will be used inside the browser. You should make sure it's unique given\n * your application. Example: vercel-session\n */\n cookieName: string;\n\n /**\n * This is the password(s) that will be used to encrypt the cookie. It can be either a string or an object\n * like {1: \"password\", 2: password}.\n *\n * When you provide multiple passwords then all of them will be used to decrypt the cookie and only the most\n * recent (= highest key, 2 in this example) password will be used to encrypt the cookie. This allow you\n * to use password rotation (security)\n */\n password: password;\n\n /**\n * This is the time in seconds that the session will be valid for. This also set the max-age attribute of\n * the cookie automatically (minus 60 seconds so that the cookie always expire before the session).\n */\n ttl?: number;\n\n /**\n * This is the options that will be passed to the cookie library.\n * You can see all of them here: https://github.com/jshttp/cookie#options-1.\n *\n * If you want to use \"session cookies\" (cookies that are deleted when the browser is closed) then you need\n * to pass cookieOptions: { maxAge: undefined }.\n */\n cookieOptions?: CookieSerializeOptions;\n}\n\n// eslint-disable-next-line @typescript-eslint/no-empty-interface\nexport interface IronSessionData {\n // If we allow for any keys, then there's no more type check on unknown properties\n // which is not good\n // If we allow for any keys, the later delete will work but I prefer to disable the\n // check at this stage and\n // provide good type checking instead\n // [key: string]: unknown;\n}\n\nexport type IronSession = IronSessionData & {\n /**\n * Destroys the session data and removes the cookie.\n */\n destroy: () => void;\n\n /**\n * Encrypts the session data and sets the cookie.\n */\n save: () => Promise<void>;\n};\n\ndeclare module \"http\" {\n interface IncomingMessage {\n session: IronSession;\n }\n}\n\ntype RequestType = IncomingMessage | Request;\ntype ResponseType = ServerResponse | Response;\n\nexport function createGetIronSession(\n _crypto: Crypto,\n unsealData: ReturnType<typeof createUnsealData>,\n sealData: ReturnType<typeof createSealData>,\n) {\n return async (\n req: RequestType,\n res: ResponseType,\n userSessionOptions: IronSessionOptions,\n ): Promise<IronSession> => {\n if (\n !req ||\n !res ||\n !userSessionOptions ||\n !userSessionOptions.cookieName ||\n !userSessionOptions.password\n ) {\n throw new Error(\n `iron-session: Bad usage. Minimum usage is const session = await getIronSession(req, res, { cookieName: \"...\", password: \"...\". Check the usage here: https://github.com/vvo/iron-session`,\n );\n }\n\n const passwordsAsMap = normalizeStringPasswordToMap(\n userSessionOptions.password,\n );\n\n Object.values(\n normalizeStringPasswordToMap(userSessionOptions.password),\n ).forEach((password) => {\n if (password.length < 32) {\n throw new Error(\n `iron-session: Bad usage. Password must be at least 32 characters long.`,\n );\n }\n });\n\n const options: Required<IronSessionOptions> = {\n ...defaultOptions,\n ...userSessionOptions,\n cookieOptions: {\n ...defaultOptions.cookieOptions,\n ...(userSessionOptions.cookieOptions || {}),\n },\n };\n\n if (options.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 options.ttl = 2147483647;\n }\n\n if (\n userSessionOptions.cookieOptions &&\n \"maxAge\" in userSessionOptions.cookieOptions\n ) {\n // session cookie, do not set maxAge, consider token as infinite\n if (userSessionOptions.cookieOptions.maxAge === undefined) {\n options.ttl = 0;\n } else {\n options.cookieOptions.maxAge = computeCookieMaxAge(\n userSessionOptions.cookieOptions.maxAge,\n );\n }\n } else {\n options.cookieOptions.maxAge = computeCookieMaxAge(options.ttl);\n }\n\n const sealFromCookies = cookie.parse(\n \"credentials\" in req\n ? req.headers.get(\"cookie\") || \"\"\n : req.headers.cookie || \"\",\n )[options.cookieName];\n\n const session =\n sealFromCookies === undefined\n ? {}\n : await unsealData<IronSessionData>(sealFromCookies, {\n password: passwordsAsMap,\n ttl: options.ttl,\n });\n\n Object.defineProperties(session, {\n save: {\n value: async function save() {\n if (\"headersSent\" in res && res.headersSent === true) {\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 const seal = await sealData(session, {\n password: passwordsAsMap,\n ttl: options.ttl,\n });\n const cookieValue = cookie.serialize(\n options.cookieName,\n seal,\n options.cookieOptions,\n );\n\n if (cookieValue.length > 4096) {\n throw new Error(\n `iron-session: Cookie length is too big ${cookieValue.length}, browsers will refuse it. Try to remove some data.`,\n );\n }\n\n addToCookies(cookieValue, res);\n },\n },\n destroy: {\n value: function destroy() {\n Object.keys(session).forEach((key) => {\n // @ts-ignore See comment on the IronSessionData interface\n delete session[key];\n });\n\n const cookieValue = cookie.serialize(options.cookieName, \"\", {\n ...options.cookieOptions,\n maxAge: 0,\n });\n addToCookies(cookieValue, res);\n },\n },\n });\n\n return session as IronSession;\n };\n}\n\nfunction addToCookies(cookieValue: string, res: ResponseType) {\n if (\"headers\" in res) {\n res.headers.append(\"set-cookie\", cookieValue);\n return;\n }\n\n let existingSetCookie =\n (res.getHeader(\"set-cookie\") as string[] | string) ?? [];\n if (typeof existingSetCookie === \"string\") {\n existingSetCookie = [existingSetCookie];\n }\n res.setHeader(\"set-cookie\", [...existingSetCookie, cookieValue]);\n}\n\nfunction computeCookieMaxAge(ttl: number) {\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 maximum between server and clients.\n // It also makes sure to expire the cookie immediately when value is 0\n return ttl - timestampSkewSec;\n}\n\nexport function createUnsealData(_crypto: Crypto) {\n return async <T = Record<string, unknown>>(\n seal: string,\n {\n password,\n ttl = fourteenDaysInSeconds,\n }: { password: password; ttl?: number },\n ): Promise<T> => {\n const passwordsAsMap = normalizeStringPasswordToMap(password);\n const { sealWithoutVersion, tokenVersion } = parseSeal(seal);\n\n try {\n const data =\n (await Iron.unseal(_crypto, sealWithoutVersion, passwordsAsMap, {\n ...Iron.defaults,\n ttl: ttl * 1000,\n })) || {};\n\n if (tokenVersion === 2) {\n return data as T;\n }\n\n return {\n // @ts-expect-error `persistent` does not exist on newer tokens\n ...data.persistent,\n };\n } catch (error) {\n if (error instanceof Error) {\n if (\n error.message === \"Expired seal\" ||\n error.message === \"Bad hmac value\" ||\n error.message.startsWith(\"Cannot find password: \") ||\n error.message === \"Incorrect number of sealed components\"\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\n throw error;\n }\n };\n}\n\nfunction parseSeal(seal: string): {\n sealWithoutVersion: string;\n tokenVersion: number | null;\n} {\n if (seal[seal.length - 2] === versionDelimiter) {\n const [sealWithoutVersion, tokenVersionAsString] =\n seal.split(versionDelimiter);\n return {\n sealWithoutVersion,\n tokenVersion: parseInt(tokenVersionAsString, 10),\n };\n }\n\n return { sealWithoutVersion: seal, tokenVersion: null };\n}\n\nexport function createSealData(_crypto: Crypto) {\n return async (\n data: unknown,\n {\n password,\n ttl = fourteenDaysInSeconds,\n }: { password: password; ttl?: number },\n ) => {\n const passwordsAsMap = normalizeStringPasswordToMap(password);\n\n const mostRecentPasswordId = Math.max(\n ...Object.keys(passwordsAsMap).map((id) => parseInt(id, 10)),\n );\n\n const passwordForSeal = {\n id: mostRecentPasswordId.toString(),\n secret: passwordsAsMap[mostRecentPasswordId],\n };\n\n const seal = await Iron.seal(_crypto, data, passwordForSeal, {\n ...Iron.defaults,\n ttl: ttl * 1000,\n });\n\n return `${seal}${versionDelimiter}${currentMajorVersion}`;\n };\n}\n\nfunction normalizeStringPasswordToMap(password: password) {\n return typeof password === \"string\" ? { 1: password } : password;\n}\n"],"mappings":"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","names":["_crypto","unsealData","sealData","cookie","seal"]}
package/edge/index.mjs CHANGED
@@ -126,12 +126,10 @@ function createUnsealData(_crypto2) {
126
126
  const passwordsAsMap = normalizeStringPasswordToMap(password);
127
127
  const { sealWithoutVersion, tokenVersion } = parseSeal(seal2);
128
128
  try {
129
- const data = await Iron.unseal(
130
- _crypto2,
131
- sealWithoutVersion,
132
- passwordsAsMap,
133
- { ...Iron.defaults, ttl: ttl * 1e3 }
134
- );
129
+ const data = await Iron.unseal(_crypto2, sealWithoutVersion, passwordsAsMap, {
130
+ ...Iron.defaults,
131
+ ttl: ttl * 1e3
132
+ }) || {};
135
133
  if (tokenVersion === 2) {
136
134
  return data;
137
135
  }
@@ -140,7 +138,7 @@ function createUnsealData(_crypto2) {
140
138
  };
141
139
  } catch (error) {
142
140
  if (error instanceof Error) {
143
- if (error.message === "Expired seal" || error.message === "Bad hmac value" || error.message === "Cannot find password: " || error.message === "Incorrect number of sealed components") {
141
+ if (error.message === "Expired seal" || error.message === "Bad hmac value" || error.message.startsWith("Cannot find password: ") || error.message === "Incorrect number of sealed components") {
144
142
  return {};
145
143
  }
146
144
  }
@@ -202,9 +200,6 @@ var getIronSession = createGetIronSession(
202
200
  sealData
203
201
  );
204
202
  export {
205
- createGetIronSession,
206
- createSealData,
207
- createUnsealData,
208
203
  getIronSession,
209
204
  sealData,
210
205
  unsealData
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/core.ts","../index.ts"],"sourcesContent":["import * as Iron from \"iron-webcrypto\";\nimport type { CookieSerializeOptions } from \"cookie\";\nimport cookie from \"cookie\";\nimport type { IncomingMessage, ServerResponse } from \"http\";\n\n// default time allowed to check for iron seal validity when ttl passed\n// see https://hapi.dev/family/iron/api/?v=6.0.0#options\nconst timestampSkewSec = 60;\n\ntype passwordsMap = { [id: string]: string };\ntype password = string | passwordsMap;\n\nconst fourteenDaysInSeconds = 15 * 24 * 3600;\n\n// We store a token major version to handle data format changes when any. So that when you upgrade the cookies\n// can be kept alive between upgrades, no need to disconnect everyone.\nconst currentMajorVersion = 2;\nconst versionDelimiter = \"~\";\n\nconst defaultOptions: {\n ttl: number;\n cookieOptions: CookieSerializeOptions;\n} = {\n ttl: fourteenDaysInSeconds,\n cookieOptions: {\n httpOnly: true,\n secure: true,\n sameSite: \"lax\",\n path: \"/\",\n },\n};\n\nexport interface IronSessionOptions {\n /**\n * This is the cookie name that will be used inside the browser. You should make sure it's unique given\n * your application. Example: vercel-session\n */\n cookieName: string;\n\n /**\n * This is the password(s) that will be used to encrypt the cookie. It can be either a string or an object\n * like {1: \"password\", 2: password}.\n *\n * When you provide multiple passwords then all of them will be used to decrypt the cookie and only the most\n * recent (= highest key, 2 in this example) password will be used to encrypt the cookie. This allow you\n * to use password rotation (security)\n */\n password: password;\n\n /**\n * This is the time in seconds that the session will be valid for. This also set the max-age attribute of\n * the cookie automatically (minus 60 seconds so that the cookie always expire before the session).\n */\n ttl?: number;\n\n /**\n * This is the options that will be passed to the cookie library.\n * You can see all of them here: https://github.com/jshttp/cookie#options-1.\n *\n * If you want to use \"session cookies\" (cookies that are deleted when the browser is closed) then you need\n * to pass cookieOptions: { maxAge: undefined }.\n */\n cookieOptions?: CookieSerializeOptions;\n}\n\n// eslint-disable-next-line @typescript-eslint/no-empty-interface\nexport interface IronSessionData {\n // If we allow for any keys, then there's no more type check on unknown properties\n // which is not good\n // If we allow for any keys, the later delete will work but I prefer to disable the\n // check at this stage and\n // provide good type checking instead\n // [key: string]: unknown;\n}\n\nexport type IronSession = IronSessionData & {\n /**\n * Destroys the session data and removes the cookie.\n */\n destroy: () => void;\n\n /**\n * Encrypts the session data and sets the cookie.\n */\n save: () => Promise<void>;\n};\n\ndeclare module \"http\" {\n interface IncomingMessage {\n session: IronSession;\n }\n}\n\ntype RequestType = IncomingMessage | Request;\ntype ResponseType = ServerResponse | Response;\n\nexport function createGetIronSession(\n _crypto: Crypto,\n unsealData: ReturnType<typeof createUnsealData>,\n sealData: ReturnType<typeof createSealData>,\n) {\n return async (\n req: RequestType,\n res: ResponseType,\n userSessionOptions: IronSessionOptions,\n ): Promise<IronSession> => {\n if (\n !req ||\n !res ||\n !userSessionOptions ||\n !userSessionOptions.cookieName ||\n !userSessionOptions.password\n ) {\n throw new Error(\n `iron-session: Bad usage. Minimum usage is const session = await getIronSession(req, res, { cookieName: \"...\", password: \"...\". Check the usage here: https://github.com/vvo/iron-session`,\n );\n }\n\n const passwordsAsMap = normalizeStringPasswordToMap(\n userSessionOptions.password,\n );\n\n Object.values(\n normalizeStringPasswordToMap(userSessionOptions.password),\n ).forEach((password) => {\n if (password.length < 32) {\n throw new Error(\n `iron-session: Bad usage. Password must be at least 32 characters long.`,\n );\n }\n });\n\n const options: Required<IronSessionOptions> = {\n ...defaultOptions,\n ...userSessionOptions,\n cookieOptions: {\n ...defaultOptions.cookieOptions,\n ...(userSessionOptions.cookieOptions || {}),\n },\n };\n\n if (options.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 options.ttl = 2147483647;\n }\n\n if (\n userSessionOptions.cookieOptions &&\n \"maxAge\" in userSessionOptions.cookieOptions\n ) {\n // session cookie, do not set maxAge, consider token as infinite\n if (userSessionOptions.cookieOptions.maxAge === undefined) {\n options.ttl = 0;\n } else {\n options.cookieOptions.maxAge = computeCookieMaxAge(\n userSessionOptions.cookieOptions.maxAge,\n );\n }\n } else {\n options.cookieOptions.maxAge = computeCookieMaxAge(options.ttl);\n }\n\n const sealFromCookies = cookie.parse(\n \"credentials\" in req\n ? req.headers.get(\"cookie\") || \"\"\n : req.headers.cookie || \"\",\n )[options.cookieName];\n\n const session =\n sealFromCookies === undefined\n ? {}\n : await unsealData<IronSessionData>(sealFromCookies, {\n password: passwordsAsMap,\n ttl: options.ttl,\n });\n\n Object.defineProperties(session, {\n save: {\n value: async function save() {\n if (\"headersSent\" in res && res.headersSent === true) {\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 const seal = await sealData(session, {\n password: passwordsAsMap,\n ttl: options.ttl,\n });\n const cookieValue = cookie.serialize(\n options.cookieName,\n seal,\n options.cookieOptions,\n );\n\n if (cookieValue.length > 4096) {\n throw new Error(\n `iron-session: Cookie length is too big ${cookieValue.length}, browsers will refuse it. Try to remove some data.`,\n );\n }\n\n addToCookies(cookieValue, res);\n },\n },\n destroy: {\n value: function destroy() {\n Object.keys(session).forEach((key) => {\n // @ts-ignore See comment on the IronSessionData interface\n delete session[key];\n });\n\n const cookieValue = cookie.serialize(options.cookieName, \"\", {\n ...options.cookieOptions,\n maxAge: 0,\n });\n addToCookies(cookieValue, res);\n },\n },\n });\n\n return session as IronSession;\n };\n}\n\nfunction addToCookies(cookieValue: string, res: ResponseType) {\n if (\"headers\" in res) {\n res.headers.append(\"set-cookie\", cookieValue);\n return;\n }\n\n let existingSetCookie =\n (res.getHeader(\"set-cookie\") as string[] | string) ?? [];\n if (typeof existingSetCookie === \"string\") {\n existingSetCookie = [existingSetCookie];\n }\n res.setHeader(\"set-cookie\", [...existingSetCookie, cookieValue]);\n}\n\nfunction computeCookieMaxAge(ttl: number) {\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 maximum between server and clients.\n // It also makes sure to expire the cookie immediately when value is 0\n return ttl - timestampSkewSec;\n}\n\nexport function createUnsealData(_crypto: Crypto) {\n return async <T = Record<string, unknown>>(\n seal: string,\n {\n password,\n ttl = fourteenDaysInSeconds,\n }: { password: password; ttl?: number },\n ): Promise<T> => {\n const passwordsAsMap = normalizeStringPasswordToMap(password);\n const { sealWithoutVersion, tokenVersion } = parseSeal(seal);\n\n try {\n const data = await Iron.unseal(\n _crypto,\n sealWithoutVersion,\n passwordsAsMap,\n { ...Iron.defaults, ttl: ttl * 1000 },\n );\n\n if (tokenVersion === 2) {\n return data as T;\n }\n\n return {\n // @ts-expect-error `persistent` does not exist on newer tokens\n ...data.persistent,\n };\n } catch (error) {\n if (error instanceof Error) {\n if (\n error.message === \"Expired seal\" ||\n error.message === \"Bad hmac value\" ||\n error.message === \"Cannot find password: \" ||\n error.message === \"Incorrect number of sealed components\"\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\n throw error;\n }\n };\n}\n\nfunction parseSeal(seal: string): {\n sealWithoutVersion: string;\n tokenVersion: number | null;\n} {\n if (seal[seal.length - 2] === versionDelimiter) {\n const [sealWithoutVersion, tokenVersionAsString] =\n seal.split(versionDelimiter);\n return {\n sealWithoutVersion,\n tokenVersion: parseInt(tokenVersionAsString, 10),\n };\n }\n\n return { sealWithoutVersion: seal, tokenVersion: null };\n}\n\nexport function createSealData(_crypto: Crypto) {\n return async (\n data: unknown,\n {\n password,\n ttl = fourteenDaysInSeconds,\n }: { password: password; ttl?: number },\n ) => {\n const passwordsAsMap = normalizeStringPasswordToMap(password);\n\n const mostRecentPasswordId = Math.max(\n ...Object.keys(passwordsAsMap).map((id) => parseInt(id, 10)),\n );\n\n const passwordForSeal = {\n id: mostRecentPasswordId.toString(),\n secret: passwordsAsMap[mostRecentPasswordId],\n };\n\n const seal = await Iron.seal(_crypto, data, passwordForSeal, {\n ...Iron.defaults,\n ttl: ttl * 1000,\n });\n\n return `${seal}${versionDelimiter}${currentMajorVersion}`;\n };\n}\n\nfunction normalizeStringPasswordToMap(password: password) {\n return typeof password === \"string\" ? { 1: password } : password;\n}\n","import {\n createGetIronSession,\n createSealData,\n createUnsealData,\n} from \"../src/core\";\n\nconst getCrypto = (): Crypto => {\n if (typeof globalThis.crypto?.subtle === \"object\") return globalThis.crypto;\n // @ts-ignore crypto.webcrypto is not available in dom, but is there in newer node versions\n if (typeof globalThis.crypto?.webcrypto?.subtle === \"object\")\n // @ts-ignore same as above\n return globalThis.crypto.webcrypto;\n throw new Error(\n \"no native implementation of WebCrypto is available in current context\",\n );\n};\n\nconst _crypto = getCrypto();\n\nexport * from \"../src/core\";\nexport const unsealData = createUnsealData(_crypto);\nexport const sealData = createSealData(_crypto);\nexport const getIronSession = createGetIronSession(\n _crypto,\n unsealData,\n sealData,\n);\n"],"mappings":"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","names":["_crypto","unsealData","sealData","seal"]}
1
+ {"version":3,"sources":["../../src/core.ts","../index.ts"],"sourcesContent":["import * as Iron from \"iron-webcrypto\";\nimport type { CookieSerializeOptions } from \"cookie\";\nimport cookie from \"cookie\";\nimport type { IncomingMessage, ServerResponse } from \"http\";\n\n// default time allowed to check for iron seal validity when ttl passed\n// see https://hapi.dev/family/iron/api/?v=6.0.0#options\nconst timestampSkewSec = 60;\n\ntype passwordsMap = { [id: string]: string };\ntype password = string | passwordsMap;\n\nconst fourteenDaysInSeconds = 15 * 24 * 3600;\n\n// We store a token major version to handle data format changes when any. So that when you upgrade the cookies\n// can be kept alive between upgrades, no need to disconnect everyone.\nconst currentMajorVersion = 2;\nconst versionDelimiter = \"~\";\n\nconst defaultOptions: {\n ttl: number;\n cookieOptions: CookieSerializeOptions;\n} = {\n ttl: fourteenDaysInSeconds,\n cookieOptions: {\n httpOnly: true,\n secure: true,\n sameSite: \"lax\",\n path: \"/\",\n },\n};\n\nexport interface IronSessionOptions {\n /**\n * This is the cookie name that will be used inside the browser. You should make sure it's unique given\n * your application. Example: vercel-session\n */\n cookieName: string;\n\n /**\n * This is the password(s) that will be used to encrypt the cookie. It can be either a string or an object\n * like {1: \"password\", 2: password}.\n *\n * When you provide multiple passwords then all of them will be used to decrypt the cookie and only the most\n * recent (= highest key, 2 in this example) password will be used to encrypt the cookie. This allow you\n * to use password rotation (security)\n */\n password: password;\n\n /**\n * This is the time in seconds that the session will be valid for. This also set the max-age attribute of\n * the cookie automatically (minus 60 seconds so that the cookie always expire before the session).\n */\n ttl?: number;\n\n /**\n * This is the options that will be passed to the cookie library.\n * You can see all of them here: https://github.com/jshttp/cookie#options-1.\n *\n * If you want to use \"session cookies\" (cookies that are deleted when the browser is closed) then you need\n * to pass cookieOptions: { maxAge: undefined }.\n */\n cookieOptions?: CookieSerializeOptions;\n}\n\n// eslint-disable-next-line @typescript-eslint/no-empty-interface\nexport interface IronSessionData {\n // If we allow for any keys, then there's no more type check on unknown properties\n // which is not good\n // If we allow for any keys, the later delete will work but I prefer to disable the\n // check at this stage and\n // provide good type checking instead\n // [key: string]: unknown;\n}\n\nexport type IronSession = IronSessionData & {\n /**\n * Destroys the session data and removes the cookie.\n */\n destroy: () => void;\n\n /**\n * Encrypts the session data and sets the cookie.\n */\n save: () => Promise<void>;\n};\n\ndeclare module \"http\" {\n interface IncomingMessage {\n session: IronSession;\n }\n}\n\ntype RequestType = IncomingMessage | Request;\ntype ResponseType = ServerResponse | Response;\n\nexport function createGetIronSession(\n _crypto: Crypto,\n unsealData: ReturnType<typeof createUnsealData>,\n sealData: ReturnType<typeof createSealData>,\n) {\n return async (\n req: RequestType,\n res: ResponseType,\n userSessionOptions: IronSessionOptions,\n ): Promise<IronSession> => {\n if (\n !req ||\n !res ||\n !userSessionOptions ||\n !userSessionOptions.cookieName ||\n !userSessionOptions.password\n ) {\n throw new Error(\n `iron-session: Bad usage. Minimum usage is const session = await getIronSession(req, res, { cookieName: \"...\", password: \"...\". Check the usage here: https://github.com/vvo/iron-session`,\n );\n }\n\n const passwordsAsMap = normalizeStringPasswordToMap(\n userSessionOptions.password,\n );\n\n Object.values(\n normalizeStringPasswordToMap(userSessionOptions.password),\n ).forEach((password) => {\n if (password.length < 32) {\n throw new Error(\n `iron-session: Bad usage. Password must be at least 32 characters long.`,\n );\n }\n });\n\n const options: Required<IronSessionOptions> = {\n ...defaultOptions,\n ...userSessionOptions,\n cookieOptions: {\n ...defaultOptions.cookieOptions,\n ...(userSessionOptions.cookieOptions || {}),\n },\n };\n\n if (options.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 options.ttl = 2147483647;\n }\n\n if (\n userSessionOptions.cookieOptions &&\n \"maxAge\" in userSessionOptions.cookieOptions\n ) {\n // session cookie, do not set maxAge, consider token as infinite\n if (userSessionOptions.cookieOptions.maxAge === undefined) {\n options.ttl = 0;\n } else {\n options.cookieOptions.maxAge = computeCookieMaxAge(\n userSessionOptions.cookieOptions.maxAge,\n );\n }\n } else {\n options.cookieOptions.maxAge = computeCookieMaxAge(options.ttl);\n }\n\n const sealFromCookies = cookie.parse(\n \"credentials\" in req\n ? req.headers.get(\"cookie\") || \"\"\n : req.headers.cookie || \"\",\n )[options.cookieName];\n\n const session =\n sealFromCookies === undefined\n ? {}\n : await unsealData<IronSessionData>(sealFromCookies, {\n password: passwordsAsMap,\n ttl: options.ttl,\n });\n\n Object.defineProperties(session, {\n save: {\n value: async function save() {\n if (\"headersSent\" in res && res.headersSent === true) {\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 const seal = await sealData(session, {\n password: passwordsAsMap,\n ttl: options.ttl,\n });\n const cookieValue = cookie.serialize(\n options.cookieName,\n seal,\n options.cookieOptions,\n );\n\n if (cookieValue.length > 4096) {\n throw new Error(\n `iron-session: Cookie length is too big ${cookieValue.length}, browsers will refuse it. Try to remove some data.`,\n );\n }\n\n addToCookies(cookieValue, res);\n },\n },\n destroy: {\n value: function destroy() {\n Object.keys(session).forEach((key) => {\n // @ts-ignore See comment on the IronSessionData interface\n delete session[key];\n });\n\n const cookieValue = cookie.serialize(options.cookieName, \"\", {\n ...options.cookieOptions,\n maxAge: 0,\n });\n addToCookies(cookieValue, res);\n },\n },\n });\n\n return session as IronSession;\n };\n}\n\nfunction addToCookies(cookieValue: string, res: ResponseType) {\n if (\"headers\" in res) {\n res.headers.append(\"set-cookie\", cookieValue);\n return;\n }\n\n let existingSetCookie =\n (res.getHeader(\"set-cookie\") as string[] | string) ?? [];\n if (typeof existingSetCookie === \"string\") {\n existingSetCookie = [existingSetCookie];\n }\n res.setHeader(\"set-cookie\", [...existingSetCookie, cookieValue]);\n}\n\nfunction computeCookieMaxAge(ttl: number) {\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 maximum between server and clients.\n // It also makes sure to expire the cookie immediately when value is 0\n return ttl - timestampSkewSec;\n}\n\nexport function createUnsealData(_crypto: Crypto) {\n return async <T = Record<string, unknown>>(\n seal: string,\n {\n password,\n ttl = fourteenDaysInSeconds,\n }: { password: password; ttl?: number },\n ): Promise<T> => {\n const passwordsAsMap = normalizeStringPasswordToMap(password);\n const { sealWithoutVersion, tokenVersion } = parseSeal(seal);\n\n try {\n const data =\n (await Iron.unseal(_crypto, sealWithoutVersion, passwordsAsMap, {\n ...Iron.defaults,\n ttl: ttl * 1000,\n })) || {};\n\n if (tokenVersion === 2) {\n return data as T;\n }\n\n return {\n // @ts-expect-error `persistent` does not exist on newer tokens\n ...data.persistent,\n };\n } catch (error) {\n if (error instanceof Error) {\n if (\n error.message === \"Expired seal\" ||\n error.message === \"Bad hmac value\" ||\n error.message.startsWith(\"Cannot find password: \") ||\n error.message === \"Incorrect number of sealed components\"\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\n throw error;\n }\n };\n}\n\nfunction parseSeal(seal: string): {\n sealWithoutVersion: string;\n tokenVersion: number | null;\n} {\n if (seal[seal.length - 2] === versionDelimiter) {\n const [sealWithoutVersion, tokenVersionAsString] =\n seal.split(versionDelimiter);\n return {\n sealWithoutVersion,\n tokenVersion: parseInt(tokenVersionAsString, 10),\n };\n }\n\n return { sealWithoutVersion: seal, tokenVersion: null };\n}\n\nexport function createSealData(_crypto: Crypto) {\n return async (\n data: unknown,\n {\n password,\n ttl = fourteenDaysInSeconds,\n }: { password: password; ttl?: number },\n ) => {\n const passwordsAsMap = normalizeStringPasswordToMap(password);\n\n const mostRecentPasswordId = Math.max(\n ...Object.keys(passwordsAsMap).map((id) => parseInt(id, 10)),\n );\n\n const passwordForSeal = {\n id: mostRecentPasswordId.toString(),\n secret: passwordsAsMap[mostRecentPasswordId],\n };\n\n const seal = await Iron.seal(_crypto, data, passwordForSeal, {\n ...Iron.defaults,\n ttl: ttl * 1000,\n });\n\n return `${seal}${versionDelimiter}${currentMajorVersion}`;\n };\n}\n\nfunction normalizeStringPasswordToMap(password: password) {\n return typeof password === \"string\" ? { 1: password } : password;\n}\n","// This allows the types to be linked from the main module instead of duplicated\nexport type { IronSessionOptions, IronSessionData } from \"iron-session\";\n\nimport {\n createGetIronSession,\n createSealData,\n createUnsealData,\n} from \"../src/core\";\n\nconst getCrypto = (): Crypto => {\n if (typeof globalThis.crypto?.subtle === \"object\") return globalThis.crypto;\n // @ts-ignore crypto.webcrypto is not available in dom, but is there in newer node versions\n if (typeof globalThis.crypto?.webcrypto?.subtle === \"object\")\n // @ts-ignore same as above\n return globalThis.crypto.webcrypto;\n throw new Error(\n \"no native implementation of WebCrypto is available in current context\",\n );\n};\n\nconst _crypto = getCrypto();\n\nexport const unsealData = createUnsealData(_crypto);\nexport const sealData = createSealData(_crypto);\nexport const getIronSession = createGetIronSession(\n _crypto,\n unsealData,\n sealData,\n);\n"],"mappings":";AAAA,YAAY,UAAU;AAEtB,OAAO,YAAY;AAKnB,IAAM,mBAAmB;AAKzB,IAAM,wBAAwB,KAAK,KAAK;AAIxC,IAAM,sBAAsB;AAC5B,IAAM,mBAAmB;AAEzB,IAAM,iBAGF;AAAA,EACF,KAAK;AAAA,EACL,eAAe;AAAA,IACb,UAAU;AAAA,IACV,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,MAAM;AAAA,EACR;AACF;AAkEO,SAAS,qBACdA,UACAC,aACAC,WACA;AACA,SAAO,OACL,KACA,KACA,uBACyB;AACzB,QACE,CAAC,OACD,CAAC,OACD,CAAC,sBACD,CAAC,mBAAmB,cACpB,CAAC,mBAAmB,UACpB;AACA,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAEA,UAAM,iBAAiB;AAAA,MACrB,mBAAmB;AAAA,IACrB;AAEA,WAAO;AAAA,MACL,6BAA6B,mBAAmB,QAAQ;AAAA,IAC1D,EAAE,QAAQ,CAAC,aAAa;AACtB,UAAI,SAAS,SAAS,IAAI;AACxB,cAAM,IAAI;AAAA,UACR;AAAA,QACF;AAAA,MACF;AAAA,IACF,CAAC;AAED,UAAM,UAAwC;AAAA,MAC5C,GAAG;AAAA,MACH,GAAG;AAAA,MACH,eAAe;AAAA,QACb,GAAG,eAAe;AAAA,QAClB,GAAI,mBAAmB,iBAAiB,CAAC;AAAA,MAC3C;AAAA,IACF;AAEA,QAAI,QAAQ,QAAQ,GAAG;AAKrB,cAAQ,MAAM;AAAA,IAChB;AAEA,QACE,mBAAmB,iBACnB,YAAY,mBAAmB,eAC/B;AAEA,UAAI,mBAAmB,cAAc,WAAW,QAAW;AACzD,gBAAQ,MAAM;AAAA,MAChB,OAAO;AACL,gBAAQ,cAAc,SAAS;AAAA,UAC7B,mBAAmB,cAAc;AAAA,QACnC;AAAA,MACF;AAAA,IACF,OAAO;AACL,cAAQ,cAAc,SAAS,oBAAoB,QAAQ,GAAG;AAAA,IAChE;AAEA,UAAM,kBAAkB,OAAO;AAAA,MAC7B,iBAAiB,MACb,IAAI,QAAQ,IAAI,QAAQ,KAAK,KAC7B,IAAI,QAAQ,UAAU;AAAA,IAC5B,EAAE,QAAQ;AAEV,UAAM,UACJ,oBAAoB,SAChB,CAAC,IACD,MAAMD,YAA4B,iBAAiB;AAAA,MACjD,UAAU;AAAA,MACV,KAAK,QAAQ;AAAA,IACf,CAAC;AAEP,WAAO,iBAAiB,SAAS;AAAA,MAC/B,MAAM;AAAA,QACJ,OAAO,eAAe,OAAO;AAC3B,cAAI,iBAAiB,OAAO,IAAI,gBAAgB,MAAM;AACpD,kBAAM,IAAI;AAAA,cACR;AAAA,YACF;AAAA,UACF;AACA,gBAAME,QAAO,MAAMD,UAAS,SAAS;AAAA,YACnC,UAAU;AAAA,YACV,KAAK,QAAQ;AAAA,UACf,CAAC;AACD,gBAAM,cAAc,OAAO;AAAA,YACzB,QAAQ;AAAA,YACRC;AAAA,YACA,QAAQ;AAAA,UACV;AAEA,cAAI,YAAY,SAAS,MAAM;AAC7B,kBAAM,IAAI;AAAA,cACR,0CAA0C,YAAY;AAAA,YACxD;AAAA,UACF;AAEA,uBAAa,aAAa,GAAG;AAAA,QAC/B;AAAA,MACF;AAAA,MACA,SAAS;AAAA,QACP,OAAO,SAAS,UAAU;AACxB,iBAAO,KAAK,OAAO,EAAE,QAAQ,CAAC,QAAQ;AAEpC,mBAAO,QAAQ;AAAA,UACjB,CAAC;AAED,gBAAM,cAAc,OAAO,UAAU,QAAQ,YAAY,IAAI;AAAA,YAC3D,GAAG,QAAQ;AAAA,YACX,QAAQ;AAAA,UACV,CAAC;AACD,uBAAa,aAAa,GAAG;AAAA,QAC/B;AAAA,MACF;AAAA,IACF,CAAC;AAED,WAAO;AAAA,EACT;AACF;AAEA,SAAS,aAAa,aAAqB,KAAmB;AAlO9D;AAmOE,MAAI,aAAa,KAAK;AACpB,QAAI,QAAQ,OAAO,cAAc,WAAW;AAC5C;AAAA,EACF;AAEA,MAAI,qBACD,SAAI,UAAU,YAAY,MAA1B,YAAqD,CAAC;AACzD,MAAI,OAAO,sBAAsB,UAAU;AACzC,wBAAoB,CAAC,iBAAiB;AAAA,EACxC;AACA,MAAI,UAAU,cAAc,CAAC,GAAG,mBAAmB,WAAW,CAAC;AACjE;AAEA,SAAS,oBAAoB,KAAa;AAIxC,SAAO,MAAM;AACf;AAEO,SAAS,iBAAiBH,UAAiB;AAChD,SAAO,OACLG,OACA;AAAA,IACE;AAAA,IACA,MAAM;AAAA,EACR,MACe;AACf,UAAM,iBAAiB,6BAA6B,QAAQ;AAC5D,UAAM,EAAE,oBAAoB,aAAa,IAAI,UAAUA,KAAI;AAE3D,QAAI;AACF,YAAM,OACH,MAAW,YAAOH,UAAS,oBAAoB,gBAAgB;AAAA,QAC9D,GAAQ;AAAA,QACR,KAAK,MAAM;AAAA,MACb,CAAC,KAAM,CAAC;AAEV,UAAI,iBAAiB,GAAG;AACtB,eAAO;AAAA,MACT;AAEA,aAAO;AAAA,QAEL,GAAG,KAAK;AAAA,MACV;AAAA,IACF,SAAS,OAAP;AACA,UAAI,iBAAiB,OAAO;AAC1B,YACE,MAAM,YAAY,kBAClB,MAAM,YAAY,oBAClB,MAAM,QAAQ,WAAW,wBAAwB,KACjD,MAAM,YAAY,yCAClB;AAKA,iBAAO,CAAC;AAAA,QACV;AAAA,MACF;AAEA,YAAM;AAAA,IACR;AAAA,EACF;AACF;AAEA,SAAS,UAAUG,OAGjB;AACA,MAAIA,MAAKA,MAAK,SAAS,OAAO,kBAAkB;AAC9C,UAAM,CAAC,oBAAoB,oBAAoB,IAC7CA,MAAK,MAAM,gBAAgB;AAC7B,WAAO;AAAA,MACL;AAAA,MACA,cAAc,SAAS,sBAAsB,EAAE;AAAA,IACjD;AAAA,EACF;AAEA,SAAO,EAAE,oBAAoBA,OAAM,cAAc,KAAK;AACxD;AAEO,SAAS,eAAeH,UAAiB;AAC9C,SAAO,OACL,MACA;AAAA,IACE;AAAA,IACA,MAAM;AAAA,EACR,MACG;AACH,UAAM,iBAAiB,6BAA6B,QAAQ;AAE5D,UAAM,uBAAuB,KAAK;AAAA,MAChC,GAAG,OAAO,KAAK,cAAc,EAAE,IAAI,CAAC,OAAO,SAAS,IAAI,EAAE,CAAC;AAAA,IAC7D;AAEA,UAAM,kBAAkB;AAAA,MACtB,IAAI,qBAAqB,SAAS;AAAA,MAClC,QAAQ,eAAe;AAAA,IACzB;AAEA,UAAMG,QAAO,MAAW,UAAKH,UAAS,MAAM,iBAAiB;AAAA,MAC3D,GAAQ;AAAA,MACR,KAAK,MAAM;AAAA,IACb,CAAC;AAED,WAAO,GAAGG,QAAO,mBAAmB;AAAA,EACtC;AACF;AAEA,SAAS,6BAA6B,UAAoB;AACxD,SAAO,OAAO,aAAa,WAAW,EAAE,GAAG,SAAS,IAAI;AAC1D;;;AC3UA,IAAM,YAAY,MAAc;AAThC;AAUE,MAAI,SAAO,gBAAW,WAAX,mBAAmB,YAAW;AAAU,WAAO,WAAW;AAErE,MAAI,SAAO,sBAAW,WAAX,mBAAmB,cAAnB,mBAA8B,YAAW;AAElD,WAAO,WAAW,OAAO;AAC3B,QAAM,IAAI;AAAA,IACR;AAAA,EACF;AACF;AAEA,IAAM,UAAU,UAAU;AAEnB,IAAM,aAAa,iBAAiB,OAAO;AAC3C,IAAM,WAAW,eAAe,OAAO;AACvC,IAAM,iBAAiB;AAAA,EAC5B;AAAA,EACA;AAAA,EACA;AACF;","names":["_crypto","unsealData","sealData","seal"]}
package/edge/index.ts CHANGED
@@ -1,3 +1,6 @@
1
+ // This allows the types to be linked from the main module instead of duplicated
2
+ export type { IronSessionOptions, IronSessionData } from "iron-session";
3
+
1
4
  import {
2
5
  createGetIronSession,
3
6
  createSealData,
@@ -17,7 +20,6 @@ const getCrypto = (): Crypto => {
17
20
 
18
21
  const _crypto = getCrypto();
19
22
 
20
- export * from "../src/core";
21
23
  export const unsealData = createUnsealData(_crypto);
22
24
  export const sealData = createSealData(_crypto);
23
25
  export const getIronSession = createGetIronSession(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "iron-session",
3
- "version": "6.2.0-beta.1",
3
+ "version": "6.2.1",
4
4
  "description": "Node.js stateless session utility using signed and encrypted cookies to store data. Works with Next.js, Express, NestJs, Fastify, and any Node.js HTTP framework.",
5
5
  "keywords": [
6
6
  "Next.js",
@@ -8,8 +8,8 @@
8
8
  "cookies",
9
9
  "login",
10
10
  "auth",
11
+ "authentication",
11
12
  "Express",
12
- "NestJS",
13
13
  "edge",
14
14
  "cloudflare workers"
15
15
  ],
@@ -38,7 +38,7 @@
38
38
  "./edge": {
39
39
  "import": "./edge/dist/index.mjs",
40
40
  "require": "./edge/dist/index.js",
41
- "types": "./next/dist/index.d.ts"
41
+ "types": "./edge/dist/index.d.ts"
42
42
  }
43
43
  },
44
44
  "main": "./dist/index.js",
@@ -136,15 +136,15 @@
136
136
  "@types/express": "^4.17.13",
137
137
  "@types/node": "^17.0.41",
138
138
  "cookie": "^0.5.0",
139
- "iron-webcrypto": "^0.1.0"
139
+ "iron-webcrypto": "^0.2.5"
140
140
  },
141
141
  "devDependencies": {
142
- "@swc/core": "1.2.233",
142
+ "@swc/core": "1.2.242",
143
143
  "@swc/jest": "0.2.22",
144
144
  "@tsconfig/node12": "1.0.11",
145
- "@types/jest": "28.1.6",
146
- "@typescript-eslint/eslint-plugin": "5.33.0",
147
- "@typescript-eslint/parser": "5.33.0",
145
+ "@types/jest": "28.1.8",
146
+ "@typescript-eslint/eslint-plugin": "^5.35.1",
147
+ "@typescript-eslint/parser": "^5.35.1",
148
148
  "concurrently": "7.3.0",
149
149
  "eslint": "8.22.0",
150
150
  "jest": "28.1.3",