iron-session 6.1.2 → 6.2.0-beta.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,8 +2,6 @@
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
- **⚠️ Nov 2021 update**: The library was renamed to `iron-session` and fully rewritten in TypeScript, it includes lots of new features and fixes. Follow the migration guide here: https://github.com/vvo/iron-session/releases/tag/v6.0.0.
6
-
7
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._
8
6
 
9
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.
@@ -12,14 +10,15 @@ This strategy of storing session data is the same technique used by **frameworks
12
10
 
13
11
  The underlying cryptography library is [iron](https://hapi.dev/module/iron) which was [created by the lead developer of OAuth 2.0](https://hueniversedotcom.wordpress.com/2015/09/19/auth-to-see-the-wizard-or-i-wrote-an-oauth-replacement/).
14
12
 
15
- <p align="center"><b>Online demo at <a href="https://iron-session-example.vercel.app/">https://iron-session-example.vercel.app</a> 👀</b></p>
13
+ <p align="center"><b>Online demo: <a href="https://iron-session-example.vercel.app/">https://iron-session-example.vercel.app</a> 👀</b></p>
16
14
 
17
15
  ---
18
16
 
19
17
  _Table of contents:_
20
18
 
21
19
  - [Installation](#installation)
22
- - [Usage (Next.js)](#usage-nextjs)
20
+ - [Next.js usage](#nextjs-usage)
21
+ - [Next.js middlewares usage](#nextjs-middlewares-usage)
23
22
  - [Advanced usage](#advanced-usage)
24
23
  - [Coding best practices](#coding-best-practices)
25
24
  - [Session wrappers](#session-wrappers)
@@ -32,8 +31,8 @@ _Table of contents:_
32
31
  - [Firebase usage](#firebase-usage)
33
32
  - [API](#api)
34
33
  - [ironOptions](#ironoptions)
35
- - [Next.js: withIronSessionApiRoute(handler, ironOptions)](#nextjs-withironsessionapiroutehandler-ironoptions--req-nextapirequest-res-nextapiresponse--ironoptions--promiseironoptions)
36
- - [Next.js: withIronSessionSsr(handler, ironOptions)](#nextjs-withironsessionapiroutehandler-ironoptions--req-nextapirequest-res-nextapiresponse--ironoptions--promiseironoptions)
34
+ - [Next.js: withIronSessionApiRoute(handler, ironOptions | (req: NextApiRequest, res: NextApiResponse) => IronOptions | Promise\<IronOptions\>)](#nextjs-withironsessionapiroutehandler-ironoptions--req-nextapirequest-res-nextapiresponse--ironoptions--promiseironoptions)
35
+ - [Next.js: withIronSessionSsr(handler, ironOptions | (req: IncomingMessage, res: ServerResponse) => IronOptions | Promise\<IronOptions\>)](#nextjs-withironsessionssrhandler-ironoptions--req-incomingmessage-res-serverresponse--ironoptions--promiseironoptions)
37
36
  - [Express: ironSession(ironOptions)](#express-ironsessionironoptions)
38
37
  - [session.save()](#sessionsave)
39
38
  - [session.destroy()](#sessiondestroy)
@@ -52,7 +51,7 @@ _Table of contents:_
52
51
  npm add iron-session
53
52
  ```
54
53
 
55
- ## Usage (Next.js)
54
+ ## Next.js usage
56
55
 
57
56
  You can find full featured examples (Next.js, Express) in the [examples folder](examples).
58
57
 
@@ -171,6 +170,61 @@ export const getServerSideProps = withIronSessionSsr(
171
170
 
172
171
  Note: We encourage you to create a `withSession` utility so you do not have to repeat the password and cookie name in every route. You can see how to do that [in the example](./examples/next.js-typescript/lib/session.ts).
173
172
 
173
+ ## Next.js middlewares usage
174
+
175
+ As of version 6.2.0, this library is compatible with [Next.js middlewares](https://nextjs.org/docs/advanced-features/middleware) locally and when deployed on Vercel.
176
+
177
+ Since there's no pre-available `res` object in Next.js's middlewares, you need to use iron-session this way:
178
+
179
+ ```ts
180
+ // pages/middleware.ts
181
+ import { NextResponse } from "next/server";
182
+ import type { NextRequest } from "next/server";
183
+ import { getIronSession } from "iron-session/edge";
184
+
185
+ export const middleware = async (req: NextRequest) => {
186
+ const res = NextResponse.next();
187
+ const session = await getIronSession(req, res, {
188
+ cookieName: "myapp_cookiename",
189
+ password: "complex_password_at_least_32_characters_long",
190
+ // secure: true should be used in production (HTTPS) but can't be used in development (HTTP)
191
+ cookieOptions: {
192
+ secure: process.env.NODE_ENV === "production",
193
+ },
194
+ });
195
+
196
+ // do anything with session here:
197
+ const { user } = session;
198
+
199
+ // like mutate user:
200
+ // user.something = someOtherThing;
201
+ // or:
202
+ // session.user = someoneElse;
203
+
204
+ // uncomment next line to commit changes:
205
+ // await session.save();
206
+ // or maybe you want to destroy session:
207
+ // await session.destroy();
208
+
209
+ console.log("from middleware", { user });
210
+
211
+ // demo:
212
+ if (user?.admin !== "true") {
213
+ return new NextResponse(null, { status: 403 }); // unauthorized to see pages inside admin/
214
+ }
215
+
216
+ return res;
217
+ };
218
+
219
+ export const config = {
220
+ matcher: "/admin",
221
+ };
222
+ ```
223
+
224
+ Note: There's a good probability that you can also use iron-session in the context of [Cloudflare Workers](https://workers.cloudflare.com/), try it and let us know.
225
+
226
+ _Huge thanks to [Divyansh Singh](https://github.com/brc-dd) who ported [hapijs/iron](https://github.com/hapijs/iron) to the [Web Crypto API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API) and implemented the required changes in iron-session._
227
+
174
228
  ## Advanced usage
175
229
 
176
230
  ### Coding best practices
@@ -336,6 +390,16 @@ declare module "iron-session" {
336
390
  };
337
391
  }
338
392
  }
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
+ // }
339
403
  ```
340
404
 
341
405
  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.
@@ -568,7 +632,7 @@ Only two options are required: `password` and `cookieName`. Everything else is a
568
632
 
569
633
  - `password`, **required**: Private key used to encrypt the cookie. It has to be at least 32 characters long. Use https://1password.com/password-generator/ to generate strong passwords. `password` can be either a `string` or an `array` of objects like this: `[{id: 2, password: "..."}, {id: 1, password: "..."}]` to allow for password rotation.
570
634
  - `cookieName`, **required**: Name of the cookie to be stored
571
- - `ttl`, _optional_: In seconds, default to 14 days
635
+ - `ttl`, _optional_: In seconds. Default to the equivalent of 14 days. You can set this to `0` and iron-session will compute the maximum allowed value by cookies (~70 years).
572
636
  - [`cookieOptions`](https://github.com/jshttp/cookie#cookieserializename-value-options), _optional_: Any option available from [jshttp/cookie#serialize](https://github.com/jshttp/cookie#cookieserializename-value-options). Default to:
573
637
 
574
638
  ```js
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { CookieSerializeOptions } from 'cookie';
2
+ import * as http from 'http';
2
3
  import { IncomingMessage, ServerResponse } from 'http';
3
4
 
4
5
  declare type passwordsMap = {
@@ -51,14 +52,30 @@ declare module "http" {
51
52
  session: IronSession;
52
53
  }
53
54
  }
54
- declare function getIronSession(req: IncomingMessage, res: ServerResponse, userSessionOptions: IronSessionOptions): Promise<IronSession>;
55
- declare function unsealData<T = Record<string, unknown>>(seal: string, { password, ttl, }: {
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, }: {
56
59
  password: password;
57
- ttl?: number;
58
- }): Promise<T>;
59
- declare function sealData(data: unknown, { password, ttl, }: {
60
+ ttl?: number | undefined;
61
+ }) => Promise<T>;
62
+ declare function createSealData(_crypto: Crypto): (data: unknown, { password, ttl, }: {
60
63
  password: password;
61
- ttl?: number;
62
- }): Promise<string>;
64
+ ttl?: number | undefined;
65
+ }) => Promise<string>;
66
+
67
+ declare const unsealData: <T = Record<string, unknown>>(seal: string, { password, ttl, }: {
68
+ password: string | {
69
+ [id: string]: string;
70
+ };
71
+ ttl?: number | undefined;
72
+ }) => Promise<T>;
73
+ declare const sealData: (data: unknown, { password, ttl, }: {
74
+ password: string | {
75
+ [id: string]: string;
76
+ };
77
+ ttl?: number | undefined;
78
+ }) => Promise<string>;
79
+ declare const getIronSession: (req: http.IncomingMessage | Request, res: http.ServerResponse | Response, userSessionOptions: IronSessionOptions) => Promise<IronSession>;
63
80
 
64
- export { IronSession, IronSessionData, IronSessionOptions, getIronSession, sealData, unsealData };
81
+ export { IronSession, IronSessionData, IronSessionOptions, createGetIronSession, createSealData, createUnsealData, getIronSession, sealData, unsealData };
package/dist/index.js CHANGED
@@ -1,35 +1,43 @@
1
+ "use strict";
1
2
  var __create = Object.create;
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
5
6
  var __getProtoOf = Object.getPrototypeOf;
6
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
7
- var __markAsModule = (target) => __defProp(target, "__esModule", { value: true });
8
8
  var __export = (target, all) => {
9
- __markAsModule(target);
10
9
  for (var name in all)
11
10
  __defProp(target, name, { get: all[name], enumerable: true });
12
11
  };
13
- var __reExport = (target, module2, desc) => {
14
- if (module2 && typeof module2 === "object" || typeof module2 === "function") {
15
- for (let key of __getOwnPropNames(module2))
16
- if (!__hasOwnProp.call(target, key) && key !== "default")
17
- __defProp(target, key, { get: () => module2[key], enumerable: !(desc = __getOwnPropDesc(module2, key)) || desc.enumerable });
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
18
17
  }
19
- return target;
20
- };
21
- var __toModule = (module2) => {
22
- return __reExport(__markAsModule(__defProp(module2 != null ? __create(__getProtoOf(module2)) : {}, "default", module2 && module2.__esModule && "default" in module2 ? { get: () => module2.default, enumerable: true } : { value: module2, enumerable: true })), module2);
18
+ return to;
23
19
  };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
22
+ mod
23
+ ));
24
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
24
25
 
25
26
  // src/index.ts
26
- __export(exports, {
27
+ var src_exports = {};
28
+ __export(src_exports, {
29
+ createGetIronSession: () => createGetIronSession,
30
+ createSealData: () => createSealData,
31
+ createUnsealData: () => createUnsealData,
27
32
  getIronSession: () => getIronSession,
28
33
  sealData: () => sealData,
29
34
  unsealData: () => unsealData
30
35
  });
31
- var import_iron = __toModule(require("@hapi/iron"));
32
- var import_cookie = __toModule(require("cookie"));
36
+ module.exports = __toCommonJS(src_exports);
37
+
38
+ // src/core.ts
39
+ var Iron = __toESM(require("iron-webcrypto"));
40
+ var import_cookie = __toESM(require("cookie"));
33
41
  var timestampSkewSec = 60;
34
42
  var fourteenDaysInSeconds = 15 * 24 * 3600;
35
43
  var currentMajorVersion = 2;
@@ -43,75 +51,101 @@ var defaultOptions = {
43
51
  path: "/"
44
52
  }
45
53
  };
46
- async function getIronSession(req, res, userSessionOptions) {
47
- if (!req || !res || !userSessionOptions || !userSessionOptions.cookieName || !userSessionOptions.password) {
48
- throw new Error(`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`);
49
- }
50
- const passwordsAsMap = normalizeStringPasswordToMap(userSessionOptions.password);
51
- Object.values(normalizeStringPasswordToMap(userSessionOptions.password)).forEach((password) => {
52
- if (password.length < 32) {
53
- throw new Error(`iron-session: Bad usage. Password must be at least 32 characters long.`);
54
+ function createGetIronSession(_crypto2, unsealData2, sealData2) {
55
+ return async (req, res, userSessionOptions) => {
56
+ if (!req || !res || !userSessionOptions || !userSessionOptions.cookieName || !userSessionOptions.password) {
57
+ throw new Error(
58
+ `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`
59
+ );
54
60
  }
55
- });
56
- const options = {
57
- ...defaultOptions,
58
- ...userSessionOptions,
59
- cookieOptions: {
60
- ...defaultOptions.cookieOptions,
61
- ...userSessionOptions.cookieOptions || {}
61
+ const passwordsAsMap = normalizeStringPasswordToMap(
62
+ userSessionOptions.password
63
+ );
64
+ Object.values(
65
+ normalizeStringPasswordToMap(userSessionOptions.password)
66
+ ).forEach((password) => {
67
+ if (password.length < 32) {
68
+ throw new Error(
69
+ `iron-session: Bad usage. Password must be at least 32 characters long.`
70
+ );
71
+ }
72
+ });
73
+ const options = {
74
+ ...defaultOptions,
75
+ ...userSessionOptions,
76
+ cookieOptions: {
77
+ ...defaultOptions.cookieOptions,
78
+ ...userSessionOptions.cookieOptions || {}
79
+ }
80
+ };
81
+ if (options.ttl === 0) {
82
+ options.ttl = 2147483647;
62
83
  }
63
- };
64
- if (options.ttl === 0) {
65
- options.ttl = 2147483647;
66
- }
67
- if (userSessionOptions.cookieOptions && "maxAge" in userSessionOptions.cookieOptions) {
68
- if (userSessionOptions.cookieOptions.maxAge === void 0) {
69
- options.ttl = 0;
84
+ if (userSessionOptions.cookieOptions && "maxAge" in userSessionOptions.cookieOptions) {
85
+ if (userSessionOptions.cookieOptions.maxAge === void 0) {
86
+ options.ttl = 0;
87
+ } else {
88
+ options.cookieOptions.maxAge = computeCookieMaxAge(
89
+ userSessionOptions.cookieOptions.maxAge
90
+ );
91
+ }
70
92
  } else {
71
- options.cookieOptions.maxAge = computeCookieMaxAge(userSessionOptions.cookieOptions.maxAge);
93
+ options.cookieOptions.maxAge = computeCookieMaxAge(options.ttl);
72
94
  }
73
- } else {
74
- options.cookieOptions.maxAge = computeCookieMaxAge(options.ttl);
75
- }
76
- const sealFromCookies = import_cookie.default.parse(req.headers.cookie || "")[options.cookieName];
77
- const session = sealFromCookies === void 0 ? {} : await unsealData(sealFromCookies, {
78
- password: passwordsAsMap,
79
- ttl: options.ttl
80
- });
81
- Object.defineProperties(session, {
82
- save: {
83
- value: async function save() {
84
- if (res.headersSent === true) {
85
- throw new Error(`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()`);
95
+ const sealFromCookies = import_cookie.default.parse(
96
+ "credentials" in req ? req.headers.get("cookie") || "" : req.headers.cookie || ""
97
+ )[options.cookieName];
98
+ const session = sealFromCookies === void 0 ? {} : await unsealData2(sealFromCookies, {
99
+ password: passwordsAsMap,
100
+ ttl: options.ttl
101
+ });
102
+ Object.defineProperties(session, {
103
+ save: {
104
+ value: async function save() {
105
+ if ("headersSent" in res && res.headersSent === true) {
106
+ throw new Error(
107
+ `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()`
108
+ );
109
+ }
110
+ const seal2 = await sealData2(session, {
111
+ password: passwordsAsMap,
112
+ ttl: options.ttl
113
+ });
114
+ const cookieValue = import_cookie.default.serialize(
115
+ options.cookieName,
116
+ seal2,
117
+ options.cookieOptions
118
+ );
119
+ if (cookieValue.length > 4096) {
120
+ throw new Error(
121
+ `iron-session: Cookie length is too big ${cookieValue.length}, browsers will refuse it. Try to remove some data.`
122
+ );
123
+ }
124
+ addToCookies(cookieValue, res);
86
125
  }
87
- const seal = await sealData(session, {
88
- password: passwordsAsMap,
89
- ttl: options.ttl
90
- });
91
- const cookieValue = import_cookie.default.serialize(options.cookieName, seal, options.cookieOptions);
92
- if (cookieValue.length > 4096) {
93
- throw new Error(`iron-session: Cookie length is too big ${cookieValue.length}, browsers will refuse it. Try to remove some data.`);
126
+ },
127
+ destroy: {
128
+ value: function destroy() {
129
+ Object.keys(session).forEach((key) => {
130
+ delete session[key];
131
+ });
132
+ const cookieValue = import_cookie.default.serialize(options.cookieName, "", {
133
+ ...options.cookieOptions,
134
+ maxAge: 0
135
+ });
136
+ addToCookies(cookieValue, res);
94
137
  }
95
- addToCookies(cookieValue, res);
96
138
  }
97
- },
98
- destroy: {
99
- value: function destroy() {
100
- Object.keys(session).forEach((key) => {
101
- delete session[key];
102
- });
103
- const cookieValue = import_cookie.default.serialize(options.cookieName, "", {
104
- ...options.cookieOptions,
105
- maxAge: 0
106
- });
107
- addToCookies(cookieValue, res);
108
- }
109
- }
110
- });
111
- return session;
139
+ });
140
+ return session;
141
+ };
112
142
  }
113
143
  function addToCookies(cookieValue, res) {
114
144
  var _a;
145
+ if ("headers" in res) {
146
+ res.headers.append("set-cookie", cookieValue);
147
+ return;
148
+ }
115
149
  let existingSetCookie = (_a = res.getHeader("set-cookie")) != null ? _a : [];
116
150
  if (typeof existingSetCookie === "string") {
117
151
  existingSetCookie = [existingSetCookie];
@@ -121,65 +155,87 @@ function addToCookies(cookieValue, res) {
121
155
  function computeCookieMaxAge(ttl) {
122
156
  return ttl - timestampSkewSec;
123
157
  }
124
- async function unsealData(seal, {
125
- password,
126
- ttl = fourteenDaysInSeconds
127
- }) {
128
- const passwordsAsMap = normalizeStringPasswordToMap(password);
129
- const { sealWithoutVersion, tokenVersion } = parseSeal(seal);
130
- try {
131
- const data = await import_iron.default.unseal(sealWithoutVersion, passwordsAsMap, {
132
- ...import_iron.default.defaults,
133
- ttl: ttl * 1e3
134
- });
135
- if (tokenVersion === 2) {
136
- return data;
137
- }
138
- return {
139
- ...data.persistent
140
- };
141
- } catch (error) {
142
- 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") {
144
- return {};
158
+ function createUnsealData(_crypto2) {
159
+ return async (seal2, {
160
+ password,
161
+ ttl = fourteenDaysInSeconds
162
+ }) => {
163
+ const passwordsAsMap = normalizeStringPasswordToMap(password);
164
+ const { sealWithoutVersion, tokenVersion } = parseSeal(seal2);
165
+ try {
166
+ const data = await Iron.unseal(
167
+ _crypto2,
168
+ sealWithoutVersion,
169
+ passwordsAsMap,
170
+ { ...Iron.defaults, ttl: ttl * 1e3 }
171
+ );
172
+ if (tokenVersion === 2) {
173
+ return data;
174
+ }
175
+ return {
176
+ ...data.persistent
177
+ };
178
+ } catch (error) {
179
+ 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") {
181
+ return {};
182
+ }
145
183
  }
184
+ throw error;
146
185
  }
147
- throw error;
148
- }
186
+ };
149
187
  }
150
- function parseSeal(seal) {
151
- if (seal[seal.length - 2] === versionDelimiter) {
152
- const [sealWithoutVersion, tokenVersionAsString] = seal.split(versionDelimiter);
188
+ function parseSeal(seal2) {
189
+ if (seal2[seal2.length - 2] === versionDelimiter) {
190
+ const [sealWithoutVersion, tokenVersionAsString] = seal2.split(versionDelimiter);
153
191
  return {
154
192
  sealWithoutVersion,
155
193
  tokenVersion: parseInt(tokenVersionAsString, 10)
156
194
  };
157
195
  }
158
- return { sealWithoutVersion: seal, tokenVersion: null };
196
+ return { sealWithoutVersion: seal2, tokenVersion: null };
159
197
  }
160
- async function sealData(data, {
161
- password,
162
- ttl = fourteenDaysInSeconds
163
- }) {
164
- const passwordsAsMap = normalizeStringPasswordToMap(password);
165
- const mostRecentPasswordId = Math.max(...Object.keys(passwordsAsMap).map((id) => parseInt(id, 10)));
166
- const passwordForSeal = {
167
- id: mostRecentPasswordId.toString(),
168
- secret: passwordsAsMap[mostRecentPasswordId]
198
+ function createSealData(_crypto2) {
199
+ return async (data, {
200
+ password,
201
+ ttl = fourteenDaysInSeconds
202
+ }) => {
203
+ const passwordsAsMap = normalizeStringPasswordToMap(password);
204
+ const mostRecentPasswordId = Math.max(
205
+ ...Object.keys(passwordsAsMap).map((id) => parseInt(id, 10))
206
+ );
207
+ const passwordForSeal = {
208
+ id: mostRecentPasswordId.toString(),
209
+ secret: passwordsAsMap[mostRecentPasswordId]
210
+ };
211
+ const seal2 = await Iron.seal(_crypto2, data, passwordForSeal, {
212
+ ...Iron.defaults,
213
+ ttl: ttl * 1e3
214
+ });
215
+ return `${seal2}${versionDelimiter}${currentMajorVersion}`;
169
216
  };
170
- const seal = await import_iron.default.seal(data, passwordForSeal, {
171
- ...import_iron.default.defaults,
172
- ttl: ttl * 1e3
173
- });
174
- return `${seal}${versionDelimiter}${currentMajorVersion}`;
175
217
  }
176
218
  function normalizeStringPasswordToMap(password) {
177
219
  return typeof password === "string" ? { 1: password } : password;
178
220
  }
221
+
222
+ // src/index.ts
223
+ var import_webcrypto = require("@peculiar/webcrypto");
224
+ var _crypto = new import_webcrypto.Crypto();
225
+ var unsealData = createUnsealData(_crypto);
226
+ var sealData = createSealData(_crypto);
227
+ var getIronSession = createGetIronSession(
228
+ _crypto,
229
+ unsealData,
230
+ sealData
231
+ );
179
232
  // Annotate the CommonJS export names for ESM import in node:
180
233
  0 && (module.exports = {
234
+ createGetIronSession,
235
+ createSealData,
236
+ createUnsealData,
181
237
  getIronSession,
182
238
  sealData,
183
239
  unsealData
184
240
  });
185
- //# sourceMappingURL=index.js.map
241
+ //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1,7 +1 @@
1
- {
2
- "version": 3,
3
- "sources": ["../src/index.ts"],
4
- "sourcesContent": ["import Iron from \"@hapi/iron\";\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\nexport async function getIronSession(\n req: IncomingMessage,\n res: ServerResponse,\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(req.headers.cookie || \"\")[\n options.cookieName\n ];\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 (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\nfunction addToCookies(cookieValue: string, res: ServerResponse) {\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 async function unsealData<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(sealWithoutVersion, passwordsAsMap, {\n ...Iron.defaults,\n ttl: ttl * 1000,\n });\n\n if (tokenVersion === 2) {\n return data;\n }\n\n return {\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\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 async function sealData(\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(data, passwordForSeal, {\n ...Iron.defaults,\n ttl: ttl * 1000,\n });\n\n return `${seal}${versionDelimiter}${currentMajorVersion}`;\n}\n\nfunction normalizeStringPasswordToMap(password: password) {\n return typeof password === \"string\" ? { 1: password } : password;\n}\n"],
5
- "mappings": "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",
6
- "names": []
7
- }
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"]}