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/MIGRATION.md +123 -0
- package/README.md +135 -38
- package/dist/index.d.ts +271 -92
- package/dist/index.js +358 -254
- package/dist/index.js.map +1 -1
- package/package.json +75 -53
- package/dist/index.cjs +0 -295
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.cts +0 -115
package/dist/index.d.ts
CHANGED
|
@@ -1,115 +1,294 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
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<
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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<
|
|
24
|
+
type ResponseCookie = CookieListItem & Pick<SetCookie, "httpOnly" | "priority"> & {
|
|
25
|
+
maxAge?: number | undefined;
|
|
26
|
+
};
|
|
23
27
|
/**
|
|
24
|
-
*
|
|
25
|
-
* of
|
|
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
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
|
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<
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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
|
-
|
|
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
|