iron-session 6.1.3 → 6.2.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
- // src/index.ts
2
- import Iron from "@hapi/iron";
1
+ // src/core.ts
2
+ import * as Iron from "iron-webcrypto";
3
3
  import cookie from "cookie";
4
4
  var timestampSkewSec = 60;
5
5
  var fourteenDaysInSeconds = 15 * 24 * 3600;
@@ -14,75 +14,81 @@ var defaultOptions = {
14
14
  path: "/"
15
15
  }
16
16
  };
17
- async function getIronSession(req, res, userSessionOptions) {
18
- if (!req || !res || !userSessionOptions || !userSessionOptions.cookieName || !userSessionOptions.password) {
19
- 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`);
20
- }
21
- const passwordsAsMap = normalizeStringPasswordToMap(userSessionOptions.password);
22
- Object.values(normalizeStringPasswordToMap(userSessionOptions.password)).forEach((password) => {
23
- if (password.length < 32) {
24
- throw new Error(`iron-session: Bad usage. Password must be at least 32 characters long.`);
17
+ function createGetIronSession(_crypto2, unsealData2, sealData2) {
18
+ return async (req, res, userSessionOptions) => {
19
+ if (!req || !res || !userSessionOptions || !userSessionOptions.cookieName || !userSessionOptions.password) {
20
+ 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`);
25
21
  }
26
- });
27
- const options = {
28
- ...defaultOptions,
29
- ...userSessionOptions,
30
- cookieOptions: {
31
- ...defaultOptions.cookieOptions,
32
- ...userSessionOptions.cookieOptions || {}
22
+ const passwordsAsMap = normalizeStringPasswordToMap(userSessionOptions.password);
23
+ Object.values(normalizeStringPasswordToMap(userSessionOptions.password)).forEach((password) => {
24
+ if (password.length < 32) {
25
+ throw new Error(`iron-session: Bad usage. Password must be at least 32 characters long.`);
26
+ }
27
+ });
28
+ const options = {
29
+ ...defaultOptions,
30
+ ...userSessionOptions,
31
+ cookieOptions: {
32
+ ...defaultOptions.cookieOptions,
33
+ ...userSessionOptions.cookieOptions || {}
34
+ }
35
+ };
36
+ if (options.ttl === 0) {
37
+ options.ttl = 2147483647;
33
38
  }
34
- };
35
- if (options.ttl === 0) {
36
- options.ttl = 2147483647;
37
- }
38
- if (userSessionOptions.cookieOptions && "maxAge" in userSessionOptions.cookieOptions) {
39
- if (userSessionOptions.cookieOptions.maxAge === void 0) {
40
- options.ttl = 0;
39
+ if (userSessionOptions.cookieOptions && "maxAge" in userSessionOptions.cookieOptions) {
40
+ if (userSessionOptions.cookieOptions.maxAge === void 0) {
41
+ options.ttl = 0;
42
+ } else {
43
+ options.cookieOptions.maxAge = computeCookieMaxAge(userSessionOptions.cookieOptions.maxAge);
44
+ }
41
45
  } else {
42
- options.cookieOptions.maxAge = computeCookieMaxAge(userSessionOptions.cookieOptions.maxAge);
46
+ options.cookieOptions.maxAge = computeCookieMaxAge(options.ttl);
43
47
  }
44
- } else {
45
- options.cookieOptions.maxAge = computeCookieMaxAge(options.ttl);
46
- }
47
- const sealFromCookies = cookie.parse(req.headers.cookie || "")[options.cookieName];
48
- const session = sealFromCookies === void 0 ? {} : await unsealData(sealFromCookies, {
49
- password: passwordsAsMap,
50
- ttl: options.ttl
51
- });
52
- Object.defineProperties(session, {
53
- save: {
54
- value: async function save() {
55
- if (res.headersSent === true) {
56
- 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()`);
48
+ const sealFromCookies = cookie.parse("credentials" in req ? req.headers.get("cookie") || "" : req.headers.cookie || "")[options.cookieName];
49
+ const session = sealFromCookies === void 0 ? {} : await unsealData2(sealFromCookies, {
50
+ password: passwordsAsMap,
51
+ ttl: options.ttl
52
+ });
53
+ Object.defineProperties(session, {
54
+ save: {
55
+ value: async function save() {
56
+ if ("headersSent" in res && res.headersSent === true) {
57
+ 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()`);
58
+ }
59
+ const seal2 = await sealData2(session, {
60
+ password: passwordsAsMap,
61
+ ttl: options.ttl
62
+ });
63
+ const cookieValue = cookie.serialize(options.cookieName, seal2, options.cookieOptions);
64
+ if (cookieValue.length > 4096) {
65
+ throw new Error(`iron-session: Cookie length is too big ${cookieValue.length}, browsers will refuse it. Try to remove some data.`);
66
+ }
67
+ addToCookies(cookieValue, res);
57
68
  }
58
- const seal = await sealData(session, {
59
- password: passwordsAsMap,
60
- ttl: options.ttl
61
- });
62
- const cookieValue = cookie.serialize(options.cookieName, seal, options.cookieOptions);
63
- if (cookieValue.length > 4096) {
64
- throw new Error(`iron-session: Cookie length is too big ${cookieValue.length}, browsers will refuse it. Try to remove some data.`);
69
+ },
70
+ destroy: {
71
+ value: function destroy() {
72
+ Object.keys(session).forEach((key) => {
73
+ delete session[key];
74
+ });
75
+ const cookieValue = cookie.serialize(options.cookieName, "", {
76
+ ...options.cookieOptions,
77
+ maxAge: 0
78
+ });
79
+ addToCookies(cookieValue, res);
65
80
  }
66
- addToCookies(cookieValue, res);
67
- }
68
- },
69
- destroy: {
70
- value: function destroy() {
71
- Object.keys(session).forEach((key) => {
72
- delete session[key];
73
- });
74
- const cookieValue = cookie.serialize(options.cookieName, "", {
75
- ...options.cookieOptions,
76
- maxAge: 0
77
- });
78
- addToCookies(cookieValue, res);
79
81
  }
80
- }
81
- });
82
- return session;
82
+ });
83
+ return session;
84
+ };
83
85
  }
84
86
  function addToCookies(cookieValue, res) {
85
87
  var _a;
88
+ if ("headers" in res) {
89
+ res.headers.append("set-cookie", cookieValue);
90
+ return;
91
+ }
86
92
  let existingSetCookie = (_a = res.getHeader("set-cookie")) != null ? _a : [];
87
93
  if (typeof existingSetCookie === "string") {
88
94
  existingSetCookie = [existingSetCookie];
@@ -92,64 +98,75 @@ function addToCookies(cookieValue, res) {
92
98
  function computeCookieMaxAge(ttl) {
93
99
  return ttl - timestampSkewSec;
94
100
  }
95
- async function unsealData(seal, {
96
- password,
97
- ttl = fourteenDaysInSeconds
98
- }) {
99
- const passwordsAsMap = normalizeStringPasswordToMap(password);
100
- const { sealWithoutVersion, tokenVersion } = parseSeal(seal);
101
- try {
102
- const data = await Iron.unseal(sealWithoutVersion, passwordsAsMap, {
103
- ...Iron.defaults,
104
- ttl: ttl * 1e3
105
- });
106
- if (tokenVersion === 2) {
107
- return data;
108
- }
109
- return {
110
- ...data.persistent
111
- };
112
- } catch (error) {
113
- if (error instanceof Error) {
114
- if (error.message === "Expired seal" || error.message === "Bad hmac value" || error.message === "Cannot find password: " || error.message === "Incorrect number of sealed components") {
115
- return {};
101
+ function createUnsealData(_crypto2) {
102
+ return async (seal2, {
103
+ password,
104
+ ttl = fourteenDaysInSeconds
105
+ }) => {
106
+ const passwordsAsMap = normalizeStringPasswordToMap(password);
107
+ const { sealWithoutVersion, tokenVersion } = parseSeal(seal2);
108
+ try {
109
+ const data = await Iron.unseal(_crypto2, sealWithoutVersion, passwordsAsMap, { ...Iron.defaults, ttl: ttl * 1e3 });
110
+ if (tokenVersion === 2) {
111
+ return data;
112
+ }
113
+ return {
114
+ ...data.persistent
115
+ };
116
+ } catch (error) {
117
+ if (error instanceof Error) {
118
+ if (error.message === "Expired seal" || error.message === "Bad hmac value" || error.message === "Cannot find password: " || error.message === "Incorrect number of sealed components") {
119
+ return {};
120
+ }
116
121
  }
122
+ throw error;
117
123
  }
118
- throw error;
119
- }
124
+ };
120
125
  }
121
- function parseSeal(seal) {
122
- if (seal[seal.length - 2] === versionDelimiter) {
123
- const [sealWithoutVersion, tokenVersionAsString] = seal.split(versionDelimiter);
126
+ function parseSeal(seal2) {
127
+ if (seal2[seal2.length - 2] === versionDelimiter) {
128
+ const [sealWithoutVersion, tokenVersionAsString] = seal2.split(versionDelimiter);
124
129
  return {
125
130
  sealWithoutVersion,
126
131
  tokenVersion: parseInt(tokenVersionAsString, 10)
127
132
  };
128
133
  }
129
- return { sealWithoutVersion: seal, tokenVersion: null };
134
+ return { sealWithoutVersion: seal2, tokenVersion: null };
130
135
  }
131
- async function sealData(data, {
132
- password,
133
- ttl = fourteenDaysInSeconds
134
- }) {
135
- const passwordsAsMap = normalizeStringPasswordToMap(password);
136
- const mostRecentPasswordId = Math.max(...Object.keys(passwordsAsMap).map((id) => parseInt(id, 10)));
137
- const passwordForSeal = {
138
- id: mostRecentPasswordId.toString(),
139
- secret: passwordsAsMap[mostRecentPasswordId]
136
+ function createSealData(_crypto2) {
137
+ return async (data, {
138
+ password,
139
+ ttl = fourteenDaysInSeconds
140
+ }) => {
141
+ const passwordsAsMap = normalizeStringPasswordToMap(password);
142
+ const mostRecentPasswordId = Math.max(...Object.keys(passwordsAsMap).map((id) => parseInt(id, 10)));
143
+ const passwordForSeal = {
144
+ id: mostRecentPasswordId.toString(),
145
+ secret: passwordsAsMap[mostRecentPasswordId]
146
+ };
147
+ const seal2 = await Iron.seal(_crypto2, data, passwordForSeal, {
148
+ ...Iron.defaults,
149
+ ttl: ttl * 1e3
150
+ });
151
+ return `${seal2}${versionDelimiter}${currentMajorVersion}`;
140
152
  };
141
- const seal = await Iron.seal(data, passwordForSeal, {
142
- ...Iron.defaults,
143
- ttl: ttl * 1e3
144
- });
145
- return `${seal}${versionDelimiter}${currentMajorVersion}`;
146
153
  }
147
154
  function normalizeStringPasswordToMap(password) {
148
155
  return typeof password === "string" ? { 1: password } : password;
149
156
  }
157
+
158
+ // src/index.ts
159
+ import { Crypto } from "@peculiar/webcrypto";
160
+ var _crypto = new Crypto();
161
+ var unsealData = createUnsealData(_crypto);
162
+ var sealData = createSealData(_crypto);
163
+ var getIronSession = createGetIronSession(_crypto, unsealData, sealData);
150
164
  export {
165
+ createGetIronSession,
166
+ createSealData,
167
+ createUnsealData,
151
168
  getIronSession,
152
169
  sealData,
153
170
  unsealData
154
171
  };
155
- //# sourceMappingURL=index.mjs.map
172
+ //# sourceMappingURL=index.mjs.map
@@ -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/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":[]}
@@ -0,0 +1,81 @@
1
+ import { CookieSerializeOptions } from 'cookie';
2
+ 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
+
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>;
80
+
81
+ export { IronSession, IronSessionData, IronSessionOptions, createGetIronSession, createSealData, createUnsealData, getIronSession, sealData, unsealData };
@@ -0,0 +1,213 @@
1
+ var __create = Object.create;
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getProtoOf = Object.getPrototypeOf;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod));
20
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
21
+
22
+ // edge/index.ts
23
+ var edge_exports = {};
24
+ __export(edge_exports, {
25
+ createGetIronSession: () => createGetIronSession,
26
+ createSealData: () => createSealData,
27
+ createUnsealData: () => createUnsealData,
28
+ getIronSession: () => getIronSession,
29
+ sealData: () => sealData,
30
+ unsealData: () => unsealData
31
+ });
32
+ module.exports = __toCommonJS(edge_exports);
33
+
34
+ // src/core.ts
35
+ var Iron = __toESM(require("iron-webcrypto"));
36
+ var import_cookie = __toESM(require("cookie"));
37
+ var timestampSkewSec = 60;
38
+ var fourteenDaysInSeconds = 15 * 24 * 3600;
39
+ var currentMajorVersion = 2;
40
+ var versionDelimiter = "~";
41
+ var defaultOptions = {
42
+ ttl: fourteenDaysInSeconds,
43
+ cookieOptions: {
44
+ httpOnly: true,
45
+ secure: true,
46
+ sameSite: "lax",
47
+ path: "/"
48
+ }
49
+ };
50
+ function createGetIronSession(_crypto2, unsealData2, sealData2) {
51
+ return async (req, res, userSessionOptions) => {
52
+ if (!req || !res || !userSessionOptions || !userSessionOptions.cookieName || !userSessionOptions.password) {
53
+ 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`);
54
+ }
55
+ const passwordsAsMap = normalizeStringPasswordToMap(userSessionOptions.password);
56
+ Object.values(normalizeStringPasswordToMap(userSessionOptions.password)).forEach((password) => {
57
+ if (password.length < 32) {
58
+ throw new Error(`iron-session: Bad usage. Password must be at least 32 characters long.`);
59
+ }
60
+ });
61
+ const options = {
62
+ ...defaultOptions,
63
+ ...userSessionOptions,
64
+ cookieOptions: {
65
+ ...defaultOptions.cookieOptions,
66
+ ...userSessionOptions.cookieOptions || {}
67
+ }
68
+ };
69
+ if (options.ttl === 0) {
70
+ options.ttl = 2147483647;
71
+ }
72
+ if (userSessionOptions.cookieOptions && "maxAge" in userSessionOptions.cookieOptions) {
73
+ if (userSessionOptions.cookieOptions.maxAge === void 0) {
74
+ options.ttl = 0;
75
+ } else {
76
+ options.cookieOptions.maxAge = computeCookieMaxAge(userSessionOptions.cookieOptions.maxAge);
77
+ }
78
+ } else {
79
+ options.cookieOptions.maxAge = computeCookieMaxAge(options.ttl);
80
+ }
81
+ const sealFromCookies = import_cookie.default.parse("credentials" in req ? req.headers.get("cookie") || "" : req.headers.cookie || "")[options.cookieName];
82
+ const session = sealFromCookies === void 0 ? {} : await unsealData2(sealFromCookies, {
83
+ password: passwordsAsMap,
84
+ ttl: options.ttl
85
+ });
86
+ Object.defineProperties(session, {
87
+ save: {
88
+ value: async function save() {
89
+ if ("headersSent" in res && res.headersSent === true) {
90
+ 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()`);
91
+ }
92
+ const seal2 = await sealData2(session, {
93
+ password: passwordsAsMap,
94
+ ttl: options.ttl
95
+ });
96
+ const cookieValue = import_cookie.default.serialize(options.cookieName, seal2, options.cookieOptions);
97
+ if (cookieValue.length > 4096) {
98
+ throw new Error(`iron-session: Cookie length is too big ${cookieValue.length}, browsers will refuse it. Try to remove some data.`);
99
+ }
100
+ addToCookies(cookieValue, res);
101
+ }
102
+ },
103
+ destroy: {
104
+ value: function destroy() {
105
+ Object.keys(session).forEach((key) => {
106
+ delete session[key];
107
+ });
108
+ const cookieValue = import_cookie.default.serialize(options.cookieName, "", {
109
+ ...options.cookieOptions,
110
+ maxAge: 0
111
+ });
112
+ addToCookies(cookieValue, res);
113
+ }
114
+ }
115
+ });
116
+ return session;
117
+ };
118
+ }
119
+ function addToCookies(cookieValue, res) {
120
+ var _a;
121
+ if ("headers" in res) {
122
+ res.headers.append("set-cookie", cookieValue);
123
+ return;
124
+ }
125
+ let existingSetCookie = (_a = res.getHeader("set-cookie")) != null ? _a : [];
126
+ if (typeof existingSetCookie === "string") {
127
+ existingSetCookie = [existingSetCookie];
128
+ }
129
+ res.setHeader("set-cookie", [...existingSetCookie, cookieValue]);
130
+ }
131
+ function computeCookieMaxAge(ttl) {
132
+ return ttl - timestampSkewSec;
133
+ }
134
+ function createUnsealData(_crypto2) {
135
+ return async (seal2, {
136
+ password,
137
+ ttl = fourteenDaysInSeconds
138
+ }) => {
139
+ const passwordsAsMap = normalizeStringPasswordToMap(password);
140
+ const { sealWithoutVersion, tokenVersion } = parseSeal(seal2);
141
+ try {
142
+ const data = await Iron.unseal(_crypto2, sealWithoutVersion, passwordsAsMap, { ...Iron.defaults, ttl: ttl * 1e3 });
143
+ if (tokenVersion === 2) {
144
+ return data;
145
+ }
146
+ return {
147
+ ...data.persistent
148
+ };
149
+ } catch (error) {
150
+ if (error instanceof Error) {
151
+ if (error.message === "Expired seal" || error.message === "Bad hmac value" || error.message === "Cannot find password: " || error.message === "Incorrect number of sealed components") {
152
+ return {};
153
+ }
154
+ }
155
+ throw error;
156
+ }
157
+ };
158
+ }
159
+ function parseSeal(seal2) {
160
+ if (seal2[seal2.length - 2] === versionDelimiter) {
161
+ const [sealWithoutVersion, tokenVersionAsString] = seal2.split(versionDelimiter);
162
+ return {
163
+ sealWithoutVersion,
164
+ tokenVersion: parseInt(tokenVersionAsString, 10)
165
+ };
166
+ }
167
+ return { sealWithoutVersion: seal2, tokenVersion: null };
168
+ }
169
+ function createSealData(_crypto2) {
170
+ return async (data, {
171
+ password,
172
+ ttl = fourteenDaysInSeconds
173
+ }) => {
174
+ const passwordsAsMap = normalizeStringPasswordToMap(password);
175
+ const mostRecentPasswordId = Math.max(...Object.keys(passwordsAsMap).map((id) => parseInt(id, 10)));
176
+ const passwordForSeal = {
177
+ id: mostRecentPasswordId.toString(),
178
+ secret: passwordsAsMap[mostRecentPasswordId]
179
+ };
180
+ const seal2 = await Iron.seal(_crypto2, data, passwordForSeal, {
181
+ ...Iron.defaults,
182
+ ttl: ttl * 1e3
183
+ });
184
+ return `${seal2}${versionDelimiter}${currentMajorVersion}`;
185
+ };
186
+ }
187
+ function normalizeStringPasswordToMap(password) {
188
+ return typeof password === "string" ? { 1: password } : password;
189
+ }
190
+
191
+ // edge/index.ts
192
+ var getCrypto = () => {
193
+ var _a, _b, _c;
194
+ if (typeof ((_a = globalThis.crypto) == null ? void 0 : _a.subtle) === "object")
195
+ return globalThis.crypto;
196
+ if (typeof ((_c = (_b = globalThis.crypto) == null ? void 0 : _b.webcrypto) == null ? void 0 : _c.subtle) === "object")
197
+ return globalThis.crypto.webcrypto;
198
+ throw new Error("no native implementation of WebCrypto is available in current context");
199
+ };
200
+ var _crypto = getCrypto();
201
+ var unsealData = createUnsealData(_crypto);
202
+ var sealData = createSealData(_crypto);
203
+ var getIronSession = createGetIronSession(_crypto, unsealData, sealData);
204
+ // Annotate the CommonJS export names for ESM import in node:
205
+ 0 && (module.exports = {
206
+ createGetIronSession,
207
+ createSealData,
208
+ createUnsealData,
209
+ getIronSession,
210
+ sealData,
211
+ unsealData
212
+ });
213
+ //# sourceMappingURL=index.js.map