iron-session 8.0.3 → 9.0.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.d.ts CHANGED
@@ -1,115 +1,294 @@
1
- import * as http from 'http';
2
- import { CookieSerializeOptions } from 'cookie';
3
-
1
+ import { SetCookie } from "cookie";
2
+ import { IncomingMessage, ServerResponse } from "node:http";
3
+ //#region src/core.d.ts
4
4
  type PasswordsMap = Record<string, string>;
5
5
  type Password = PasswordsMap | string;
6
+ type RequestType = IncomingMessage | Request;
7
+ type ResponseType = Response | ServerResponse;
6
8
  /**
7
9
  * {@link https://wicg.github.io/cookie-store/#dictdef-cookielistitem CookieListItem}
8
10
  * as specified by W3C.
9
11
  */
10
- interface CookieListItem extends Pick<CookieSerializeOptions, "domain" | "path" | "sameSite" | "secure"> {
11
- /** A string with the name of a cookie. */
12
- name: string;
13
- /** A string containing the value of the cookie. */
14
- value: string;
15
- /** A number of milliseconds or Date interface containing the expires of the cookie. */
16
- expires?: CookieSerializeOptions["expires"] | number;
12
+ interface CookieListItem extends Pick<SetCookie, "domain" | "path" | "sameSite" | "secure"> {
13
+ /** A string with the name of a cookie. */
14
+ name: string;
15
+ /** A string containing the value of the cookie. */
16
+ value: string;
17
+ /** A number of milliseconds or Date interface containing the expires of the cookie. */
18
+ expires?: SetCookie["expires"] | number;
17
19
  }
18
20
  /**
19
21
  * Superset of {@link CookieListItem} extending it with
20
22
  * the `httpOnly`, `maxAge` and `priority` properties.
21
23
  */
22
- type ResponseCookie = CookieListItem & Pick<CookieSerializeOptions, "httpOnly" | "maxAge" | "priority">;
24
+ type ResponseCookie = CookieListItem & Pick<SetCookie, "httpOnly" | "priority"> & {
25
+ maxAge?: number | undefined;
26
+ };
23
27
  /**
24
- * The high-level type definition of the .get() and .set() methods
25
- * of { cookies() } from "next/headers"
28
+ * What iron-session needs from a cookie store. This is deliberately the shape
29
+ * of `await cookies()` from `next/headers`, so it can be passed in directly.
30
+ *
31
+ * `getAll` is optional: it is only used to find cookie chunks, and stores that
32
+ * cannot enumerate are probed by name instead.
26
33
  */
27
34
  interface CookieStore {
28
- get: (name: string) => {
29
- name: string;
30
- value: string;
31
- } | undefined;
32
- set: {
33
- (name: string, value: string, cookie?: Partial<ResponseCookie>): void;
34
- (options: ResponseCookie): void;
35
- };
35
+ get: (name: string) => {
36
+ name: string;
37
+ value: string;
38
+ } | undefined;
39
+ getAll?: () => {
40
+ name: string;
41
+ value: string;
42
+ }[];
43
+ /**
44
+ * One signature with a required third argument, returning `unknown`.
45
+ *
46
+ * Every part of that matters for `getIronSession(await cookies(), ...)` to
47
+ * typecheck against Next.js, which is what #840 was about:
48
+ *
49
+ * - not an overload pair. Next declares a single signature over a tuple
50
+ * union, and a single signature is not assignable to an overload pair.
51
+ * - the third argument is required, not optional. Next's tuple element is
52
+ * `cookie?: Partial<ResponseCookie>`, and under
53
+ * `exactOptionalPropertyTypes` our optional parameter widened to
54
+ * `Partial<ResponseCookie> | undefined`, which their tuple rejects. We
55
+ * always pass cookie options anyway.
56
+ * - `unknown` return. Next returns the cookie store itself, not void.
57
+ */
58
+ set: (name: string, value: string, cookie: Partial<ResponseCookie>) => unknown;
36
59
  }
37
60
  /**
38
- * Set-Cookie Attributes do not include `encode`. We omit this from our `cookieOptions` type.
61
+ * Set-Cookie attributes. `name` and `value` are owned by iron-session
62
+ * (`cookieName` and the seal), so they are not configurable here.
39
63
  *
40
64
  * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie
41
65
  * @see https://developer.chrome.com/docs/devtools/application/cookies/
42
66
  */
43
- type CookieOptions = Omit<CookieSerializeOptions, "encode">;
67
+ type CookieOptions = Omit<SetCookie, "name" | "value" | "maxAge"> & {
68
+ /**
69
+ * Explicitly allows `undefined` so that `cookieOptions: { maxAge: undefined }`
70
+ * keeps working under `exactOptionalPropertyTypes`. That is the documented way
71
+ * to ask for a "session cookie" that the browser drops when it closes.
72
+ */
73
+ maxAge?: number | undefined;
74
+ };
75
+ /**
76
+ * Why an existing cookie could not be read. Every reason results in a fresh,
77
+ * empty session: a session library cannot tell a tampered cookie from a
78
+ * badly rotated password, so the only safe outcome is to start over.
79
+ * Use `onUnsealError` to observe these, they are otherwise invisible.
80
+ */
81
+ type UnsealErrorReason =
82
+ /** The seal is past its expiration. Normal, this is how sessions end. */
83
+ "expired" |
84
+ /** Integrity check failed, or the value is not a seal at all. Possible tampering. */
85
+ "invalid" |
86
+ /** The seal references a password id that is not in the password map. Usually a rotation mistake. */
87
+ "unknown-password";
44
88
  interface SessionOptions {
45
- /**
46
- * The cookie name that will be used inside the browser. Make sure it's unique
47
- * given your application.
48
- *
49
- * @example 'vercel-session'
50
- */
51
- cookieName: string;
52
- /**
53
- * The password(s) that will be used to encrypt the cookie. Can either be a string
54
- * or an object.
55
- *
56
- * When you provide multiple passwords then all of them will be used to decrypt
57
- * the cookie. But only the most recent (`= highest key`, `2` in the example)
58
- * password will be used to encrypt the cookie. This allows password rotation.
59
- *
60
- * @example { 1: 'password-1', 2: 'password-2' }
61
- */
62
- password: Password;
63
- /**
64
- * The time (in seconds) that the session will be valid for. Also sets the
65
- * `max-age` attribute of the cookie automatically (`= ttl - 60s`, so that the
66
- * cookie always expire before the session).
67
- *
68
- * `ttl = 0` means no expiration.
69
- *
70
- * @default 1209600
71
- */
72
- ttl?: number;
73
- /**
74
- * The options that will be passed to the cookie library.
75
- *
76
- * If you want to use "session cookies" (cookies that are deleted when the browser
77
- * is closed) then you need to pass `cookieOptions: { maxAge: undefined }`
78
- *
79
- * @see https://github.com/jshttp/cookie#options-1
80
- */
81
- cookieOptions?: CookieOptions;
89
+ /**
90
+ * The cookie name that will be used inside the browser. Make sure it's unique
91
+ * given your application.
92
+ *
93
+ * @example 'vercel-session'
94
+ */
95
+ cookieName: string;
96
+ /**
97
+ * The password(s) that will be used to encrypt the cookie. Can either be a string
98
+ * or an object.
99
+ *
100
+ * When you provide multiple passwords then all of them will be used to decrypt
101
+ * the cookie. But only the most recent (`= highest key`, `2` in the example)
102
+ * password will be used to encrypt the cookie. This allows password rotation.
103
+ *
104
+ * @example { 1: 'password-1', 2: 'password-2' }
105
+ */
106
+ password: Password;
107
+ /**
108
+ * The time (in seconds) that the session will be valid for. Also sets the
109
+ * `max-age` attribute of the cookie automatically (`= ttl - 60s`, so that the
110
+ * cookie always expire before the session).
111
+ *
112
+ * `ttl = 0` means no expiration. Do not use it for authentication: the seal
113
+ * is then accepted forever and there is no way to revoke it.
114
+ *
115
+ * @default 1209600
116
+ */
117
+ ttl?: number;
118
+ /**
119
+ * The options that will be passed to the cookie library.
120
+ *
121
+ * If you want to use "session cookies" (cookies that are deleted when the browser
122
+ * is closed) then you need to pass `cookieOptions: { maxAge: undefined }`
123
+ *
124
+ * @see https://github.com/jshttp/cookie#options-1
125
+ */
126
+ cookieOptions?: CookieOptions;
127
+ /**
128
+ * Called when an existing cookie could not be read, right before the session
129
+ * is reset to an empty object.
130
+ *
131
+ * A stateless session library cannot tell a tampered cookie from a badly
132
+ * rotated password, so it always starts a new session rather than throwing.
133
+ * That is the safe default, but it means genuine problems are invisible:
134
+ * a `"unknown-password"` burst usually means a broken password rotation, and
135
+ * an `"invalid"` burst can mean someone is probing your cookies. Log them.
136
+ *
137
+ * This hook must not throw. It is not a place to deny access: the session is
138
+ * empty either way.
139
+ *
140
+ * @example
141
+ * onUnsealError: (reason, error) => {
142
+ * if (reason !== "expired") logger.warn({ reason, error }, "session cookie rejected");
143
+ * }
144
+ */
145
+ onUnsealError?: (reason: UnsealErrorReason, error: unknown) => void;
146
+ /**
147
+ * Split a session that does not fit in one cookie across several cookies,
148
+ * named `<cookieName>.0`, `<cookieName>.1` and so on.
149
+ *
150
+ * Off by default, and reading works either way: turning this on or off does
151
+ * not sign anyone out.
152
+ *
153
+ * Read the size limits before reaching for this. A browser caps one cookie at
154
+ * 4096 bytes, but the real constraint is the request side: every cookie is
155
+ * sent on every request, and proxies cap the whole `Cookie` header well below
156
+ * what four chunks can produce. nginx allows 8 KB by default, and a CDN or
157
+ * load balancer in front of it may allow less. Going over that returns 400 or
158
+ * 431 at the edge, before your app runs, which is much harder to debug than
159
+ * an error from us. iron-session refuses more than {@link MAX_CHUNKS} chunks
160
+ * for that reason.
161
+ *
162
+ * Chunking is an escape hatch for a session slightly over the limit. If you
163
+ * need a lot of room, store an id in the session and keep the data in your
164
+ * database.
165
+ *
166
+ * @default false
167
+ */
168
+ chunk?: boolean;
82
169
  }
83
- type IronSession<T> = T & {
84
- /**
85
- * Encrypts the session data and sets the cookie.
86
- */
87
- readonly save: () => Promise<void>;
88
- /**
89
- * Destroys the session data and removes the cookie.
90
- */
91
- readonly destroy: () => void;
92
- /**
93
- * Update the session configuration. You still need to call save() to send the new cookie.
94
- */
95
- readonly updateConfig: (newSessionOptions: SessionOptions) => void;
96
- };
97
-
98
- declare const sealData: (data: unknown, { password, ttl, }: {
99
- password: string | {
100
- [x: string]: string;
101
- };
102
- ttl?: number;
103
- }) => Promise<string>;
104
- declare const unsealData: <T>(seal: string, { password, ttl, }: {
105
- password: string | {
106
- [x: string]: string;
107
- };
108
- ttl?: number;
109
- }) => Promise<T>;
110
- declare const getIronSession: {
111
- <T extends object>(cookies: CookieStore, sessionOptions: SessionOptions): Promise<IronSession<T>>;
112
- <T extends object>(req: http.IncomingMessage | Request, res: Response | http.ServerResponse<http.IncomingMessage>, sessionOptions: SessionOptions): Promise<IronSession<T>>;
170
+ /**
171
+ * A session object: your data, plus `save`, `destroy` and `updateConfig`.
172
+ *
173
+ * `T` is wrapped in `Partial` because a session that does not exist yet reads as
174
+ * an empty object. Declaring `IronSession<{ user: User }>` used to promise that
175
+ * `session.user` was there, so `session.user.id` typechecked and then threw at
176
+ * runtime on the first visit, on an expired cookie, and after `destroy()`.
177
+ */
178
+ type IronSession<T> = Partial<T> & {
179
+ /**
180
+ * Encrypts the session data and sets the cookie.
181
+ */
182
+ readonly save: () => Promise<void>;
183
+ /**
184
+ * Destroys the session data and removes the cookie.
185
+ */
186
+ readonly destroy: () => void;
187
+ /**
188
+ * Update the session configuration. You still need to call save() to send the new cookie.
189
+ */
190
+ readonly updateConfig: (newSessionOptions: SessionOptions) => void;
113
191
  };
114
-
115
- export { type IronSession, type SessionOptions, getIronSession, sealData, unsealData };
192
+ declare function sealData(data: unknown, { password, ttl }: {
193
+ password: Password;
194
+ ttl?: number;
195
+ }): Promise<string>;
196
+ declare function unsealData<T>(seal: string, { password, ttl, onUnsealError }: {
197
+ password: Password;
198
+ ttl?: number;
199
+ onUnsealError?: (reason: UnsealErrorReason, error: unknown) => void;
200
+ }): Promise<T>;
201
+ /**
202
+ * The one thing iron-session needs from a runtime: read a cookie by name, and
203
+ * write a cookie back. Everything else (Node req/res, web Request/Response,
204
+ * Next's `cookies()`, Next's proxy) is an adapter that produces one of these.
205
+ *
206
+ * There used to be two copies of the read/save/destroy logic, one per calling
207
+ * convention, and they drifted: the cookie size limit was computed differently
208
+ * in each, and only one of them checked whether it was still possible to send a
209
+ * header. Everything now goes through a single implementation.
210
+ */
211
+ interface CookieJar {
212
+ read: (name: string) => string | undefined;
213
+ write: (name: string, value: string, options: CookieOptions) => void;
214
+ /**
215
+ * Names of the cookies present on the request, when the runtime can list
216
+ * them. Used to find cookie chunks.
217
+ */
218
+ names?: () => string[];
219
+ /**
220
+ * Throws if a cookie can no longer be sent, for runtimes where writing after
221
+ * a certain point is silently dropped. Losing a session cookie without an
222
+ * error is much worse than a loud failure.
223
+ */
224
+ assertWritable?: () => void;
225
+ }
226
+ /**
227
+ * Node's `http` server, which is also Express, Connect and Next.js API routes.
228
+ *
229
+ * @example
230
+ * const session = await getIronSession(nodeCookies(req, res), options);
231
+ */
232
+ declare function nodeCookies(req: IncomingMessage, res: ServerResponse): CookieJar;
233
+ /**
234
+ * Web-standard `Request` plus anything with appendable headers: a `Response`,
235
+ * a bare `Headers`, or Next's `NextResponse`.
236
+ *
237
+ * @example
238
+ * const headers = new Headers();
239
+ * const session = await getIronSession(webCookies(request, headers), options);
240
+ * return new Response(body, { headers });
241
+ */
242
+ declare function webCookies(request: Request, response: Response | Headers): CookieJar;
243
+ /**
244
+ * The parts of `NextRequest`/`NextResponse` we touch, structurally typed so
245
+ * `next` is not a dependency.
246
+ *
247
+ * Request and response cookies are not the same type in Next: request cookies
248
+ * take `(name, value)` only, because attributes are meaningless on an incoming
249
+ * cookie, while response cookies take `(name, value, options)`. Declaring one
250
+ * shared interface for both is what made `NextRequest` fail to assign.
251
+ */
252
+ interface NextRequestCookies {
253
+ get: (name: string) => {
254
+ name: string;
255
+ value: string;
256
+ } | undefined;
257
+ getAll?: () => {
258
+ name: string;
259
+ value: string;
260
+ }[];
261
+ set: (name: string, value: string) => unknown;
262
+ }
263
+ interface NextResponseCookies {
264
+ set: (name: string, value: string, options: Partial<ResponseCookie>) => unknown;
265
+ }
266
+ /**
267
+ * Next.js `proxy.ts` (called `middleware.ts` before Next 16).
268
+ *
269
+ * Writing a raw `set-cookie` header here does not work the way you would
270
+ * expect: Next only merges a cookie into the current render when it goes
271
+ * through `response.cookies.set()`, so `session.save()` in middleware appeared
272
+ * to succeed and then vanished. Writing to `request.cookies` as well makes the
273
+ * new value visible to code that reads the session later in the same request,
274
+ * which is what makes rotation work.
275
+ *
276
+ * @example
277
+ * export async function proxy(request: NextRequest) {
278
+ * const response = NextResponse.next();
279
+ * const session = await getIronSession(nextProxyCookies(request, response), options);
280
+ * session.lastSeen = Date.now();
281
+ * await session.save();
282
+ * return response;
283
+ * }
284
+ */
285
+ declare function nextProxyCookies(request: {
286
+ cookies: NextRequestCookies;
287
+ }, response: {
288
+ cookies: NextResponseCookies;
289
+ }): CookieJar;
290
+ declare function getIronSession<T extends object>(cookies: CookieStore | CookieJar, sessionOptions: SessionOptions): Promise<IronSession<T>>;
291
+ declare function getIronSession<T extends object>(req: RequestType, res: ResponseType, sessionOptions: SessionOptions): Promise<IronSession<T>>;
292
+ //#endregion
293
+ export { type CookieJar, type CookieStore, type IronSession, type SessionOptions, type UnsealErrorReason, getIronSession, nextProxyCookies, nodeCookies, sealData, unsealData, webCookies };
294
+ //# sourceMappingURL=index.d.ts.map