@prairielearn/session 1.0.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/.turbo/turbo-build.log +0 -0
- package/README.md +39 -0
- package/dist/before-end.d.ts +25 -0
- package/dist/before-end.js +81 -0
- package/dist/before-end.js.map +1 -0
- package/dist/before-end.test.d.ts +1 -0
- package/dist/before-end.test.js +33 -0
- package/dist/before-end.test.js.map +1 -0
- package/dist/cookie.d.ts +4 -0
- package/dist/cookie.js +34 -0
- package/dist/cookie.js.map +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.js +89 -0
- package/dist/index.js.map +1 -0
- package/dist/index.test.d.ts +1 -0
- package/dist/index.test.js +506 -0
- package/dist/index.test.js.map +1 -0
- package/dist/memory-store.d.ts +7 -0
- package/dist/memory-store.js +28 -0
- package/dist/memory-store.js.map +1 -0
- package/dist/session.d.ts +14 -0
- package/dist/session.js +78 -0
- package/dist/session.js.map +1 -0
- package/dist/session.test.d.ts +1 -0
- package/dist/session.test.js +92 -0
- package/dist/session.test.js.map +1 -0
- package/dist/store.d.ts +9 -0
- package/dist/store.js +3 -0
- package/dist/store.js.map +1 -0
- package/dist/test-utils.d.ts +10 -0
- package/dist/test-utils.js +32 -0
- package/dist/test-utils.js.map +1 -0
- package/package.json +44 -0
- package/src/before-end.test.ts +34 -0
- package/src/before-end.ts +96 -0
- package/src/cookie.ts +38 -0
- package/src/index.test.ts +628 -0
- package/src/index.ts +132 -0
- package/src/memory-store.ts +25 -0
- package/src/session.test.ts +122 -0
- package/src/session.ts +106 -0
- package/src/store.ts +10 -0
- package/src/test-utils.ts +42 -0
- package/tsconfig.json +11 -0
|
@@ -0,0 +1,628 @@
|
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import { assert } from 'chai';
|
|
3
|
+
import fetch from 'node-fetch';
|
|
4
|
+
import fetchCookie from 'fetch-cookie';
|
|
5
|
+
import { parse as parseSetCookie } from 'set-cookie-parser';
|
|
6
|
+
import asyncHandler from 'express-async-handler';
|
|
7
|
+
|
|
8
|
+
import { createSessionMiddleware } from './index';
|
|
9
|
+
import { MemoryStore } from './memory-store';
|
|
10
|
+
import { withServer } from './test-utils';
|
|
11
|
+
|
|
12
|
+
const TEST_SECRET = 'test-secret';
|
|
13
|
+
|
|
14
|
+
describe('session middleware', () => {
|
|
15
|
+
it('sets a session cookie', async () => {
|
|
16
|
+
const app = express();
|
|
17
|
+
app.use(createSessionMiddleware({ secret: TEST_SECRET, store: new MemoryStore() }));
|
|
18
|
+
app.get('/', (_req, res) => res.sendStatus(200));
|
|
19
|
+
|
|
20
|
+
await withServer(app, async ({ url }) => {
|
|
21
|
+
const res = await fetch(url);
|
|
22
|
+
assert.equal(res.status, 200);
|
|
23
|
+
|
|
24
|
+
const header = res.headers.get('set-cookie');
|
|
25
|
+
const cookies = parseSetCookie(header ?? '');
|
|
26
|
+
assert.equal(cookies.length, 1);
|
|
27
|
+
assert.equal(cookies[0].name, 'session');
|
|
28
|
+
assert.equal(cookies[0].path, '/');
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('sets a session cookie with a custom name', async () => {
|
|
33
|
+
const app = express();
|
|
34
|
+
app.use(
|
|
35
|
+
createSessionMiddleware({
|
|
36
|
+
secret: TEST_SECRET,
|
|
37
|
+
store: new MemoryStore(),
|
|
38
|
+
cookie: {
|
|
39
|
+
name: 'prairielearn_session',
|
|
40
|
+
},
|
|
41
|
+
}),
|
|
42
|
+
);
|
|
43
|
+
app.get('/', (_req, res) => res.sendStatus(200));
|
|
44
|
+
|
|
45
|
+
await withServer(app, async ({ url }) => {
|
|
46
|
+
const res = await fetch(url);
|
|
47
|
+
assert.equal(res.status, 200);
|
|
48
|
+
|
|
49
|
+
const header = res.headers.get('set-cookie');
|
|
50
|
+
const cookies = parseSetCookie(header ?? '');
|
|
51
|
+
assert.equal(cookies.length, 1);
|
|
52
|
+
assert.equal(cookies[0].name, 'prairielearn_session');
|
|
53
|
+
assert.equal(cookies[0].path, '/');
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('sets a secure cookie for proxied HTTPS request', async () => {
|
|
58
|
+
const app = express();
|
|
59
|
+
app.enable('trust proxy');
|
|
60
|
+
app.use(
|
|
61
|
+
createSessionMiddleware({
|
|
62
|
+
secret: TEST_SECRET,
|
|
63
|
+
store: new MemoryStore(),
|
|
64
|
+
cookie: {
|
|
65
|
+
secure: true,
|
|
66
|
+
},
|
|
67
|
+
}),
|
|
68
|
+
);
|
|
69
|
+
app.get('/', (_req, res) => res.sendStatus(200));
|
|
70
|
+
|
|
71
|
+
await withServer(app, async ({ url }) => {
|
|
72
|
+
const res = await fetch(url, {
|
|
73
|
+
headers: {
|
|
74
|
+
'X-Forwarded-Proto': 'https',
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
assert.equal(res.status, 200);
|
|
78
|
+
|
|
79
|
+
const header = res.headers.get('set-cookie');
|
|
80
|
+
const cookies = parseSetCookie(header ?? '');
|
|
81
|
+
assert.equal(cookies.length, 1);
|
|
82
|
+
assert.equal(cookies[0].name, 'session');
|
|
83
|
+
assert.equal(cookies[0].path, '/');
|
|
84
|
+
assert.isTrue(cookies[0].secure);
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('does not set a secure cookie for proxied HTTP request', async () => {
|
|
89
|
+
const app = express();
|
|
90
|
+
app.enable('trust proxy');
|
|
91
|
+
app.use(
|
|
92
|
+
createSessionMiddleware({
|
|
93
|
+
secret: TEST_SECRET,
|
|
94
|
+
store: new MemoryStore(),
|
|
95
|
+
cookie: {
|
|
96
|
+
secure: true,
|
|
97
|
+
},
|
|
98
|
+
}),
|
|
99
|
+
);
|
|
100
|
+
app.get('/', (_req, res) => res.sendStatus(200));
|
|
101
|
+
|
|
102
|
+
await withServer(app, async ({ url }) => {
|
|
103
|
+
const res = await fetch(url, {
|
|
104
|
+
headers: {
|
|
105
|
+
'X-Forwarded-Proto': 'http',
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
assert.equal(res.status, 200);
|
|
109
|
+
|
|
110
|
+
const header = res.headers.get('set-cookie');
|
|
111
|
+
assert.isNull(header);
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('automatically sets secure for proxied HTTPS request', async () => {
|
|
116
|
+
const app = express();
|
|
117
|
+
app.enable('trust proxy');
|
|
118
|
+
app.use(
|
|
119
|
+
createSessionMiddleware({
|
|
120
|
+
secret: TEST_SECRET,
|
|
121
|
+
store: new MemoryStore(),
|
|
122
|
+
cookie: {
|
|
123
|
+
secure: 'auto',
|
|
124
|
+
},
|
|
125
|
+
}),
|
|
126
|
+
);
|
|
127
|
+
app.get('/', (_req, res) => res.sendStatus(200));
|
|
128
|
+
|
|
129
|
+
await withServer(app, async ({ url }) => {
|
|
130
|
+
const res = await fetch(url, {
|
|
131
|
+
headers: {
|
|
132
|
+
'X-Forwarded-Proto': 'https',
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
assert.equal(res.status, 200);
|
|
136
|
+
|
|
137
|
+
const header = res.headers.get('set-cookie');
|
|
138
|
+
const cookies = parseSetCookie(header ?? '');
|
|
139
|
+
assert.equal(cookies.length, 1);
|
|
140
|
+
assert.equal(cookies[0].name, 'session');
|
|
141
|
+
assert.equal(cookies[0].path, '/');
|
|
142
|
+
assert.isTrue(cookies[0].secure);
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('automatically sets secure for proxied HTTP request', async () => {
|
|
147
|
+
const app = express();
|
|
148
|
+
app.enable('trust proxy');
|
|
149
|
+
app.use(
|
|
150
|
+
createSessionMiddleware({
|
|
151
|
+
secret: TEST_SECRET,
|
|
152
|
+
store: new MemoryStore(),
|
|
153
|
+
cookie: {
|
|
154
|
+
secure: 'auto',
|
|
155
|
+
},
|
|
156
|
+
}),
|
|
157
|
+
);
|
|
158
|
+
app.get('/', (_req, res) => res.sendStatus(200));
|
|
159
|
+
|
|
160
|
+
await withServer(app, async ({ url }) => {
|
|
161
|
+
const res = await fetch(url, {
|
|
162
|
+
headers: {
|
|
163
|
+
'X-Forwarded-Proto': 'http',
|
|
164
|
+
},
|
|
165
|
+
});
|
|
166
|
+
assert.equal(res.status, 200);
|
|
167
|
+
|
|
168
|
+
const header = res.headers.get('set-cookie');
|
|
169
|
+
const cookies = parseSetCookie(header ?? '');
|
|
170
|
+
assert.equal(cookies.length, 1);
|
|
171
|
+
assert.equal(cookies[0].name, 'session');
|
|
172
|
+
assert.equal(cookies[0].path, '/');
|
|
173
|
+
assert.isUndefined(cookies[0].secure);
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it('sets secure cookie based on a function', async () => {
|
|
178
|
+
const app = express();
|
|
179
|
+
app.enable('trust proxy');
|
|
180
|
+
app.use(
|
|
181
|
+
createSessionMiddleware({
|
|
182
|
+
secret: TEST_SECRET,
|
|
183
|
+
store: new MemoryStore(),
|
|
184
|
+
cookie: {
|
|
185
|
+
secure: (req) => req.hostname === 'example.com',
|
|
186
|
+
},
|
|
187
|
+
}),
|
|
188
|
+
);
|
|
189
|
+
app.get('/', (_req, res) => res.sendStatus(200));
|
|
190
|
+
|
|
191
|
+
await withServer(app, async ({ url }) => {
|
|
192
|
+
const insecureRes = await fetch(url, {
|
|
193
|
+
headers: {
|
|
194
|
+
'X-Forwarded-Host': 'subdomain.example.com',
|
|
195
|
+
'X-Forwarded-Proto': 'http',
|
|
196
|
+
},
|
|
197
|
+
});
|
|
198
|
+
assert.equal(insecureRes.status, 200);
|
|
199
|
+
|
|
200
|
+
const insecureHeader = insecureRes.headers.get('set-cookie');
|
|
201
|
+
const insecureCookie = parseSetCookie(insecureHeader ?? '');
|
|
202
|
+
assert.equal(insecureCookie.length, 1);
|
|
203
|
+
assert.equal(insecureCookie[0].name, 'session');
|
|
204
|
+
assert.equal(insecureCookie[0].path, '/');
|
|
205
|
+
assert.isUndefined(insecureCookie[0].secure);
|
|
206
|
+
|
|
207
|
+
const secureRes = await fetch(url, {
|
|
208
|
+
headers: {
|
|
209
|
+
'X-Forwarded-Host': 'example.com',
|
|
210
|
+
'X-Forwarded-Proto': 'https',
|
|
211
|
+
},
|
|
212
|
+
});
|
|
213
|
+
assert.equal(secureRes.status, 200);
|
|
214
|
+
|
|
215
|
+
const secureHeader = secureRes.headers.get('set-cookie');
|
|
216
|
+
const secureCookies = parseSetCookie(secureHeader ?? '');
|
|
217
|
+
assert.equal(secureCookies.length, 1);
|
|
218
|
+
assert.equal(secureCookies[0].name, 'session');
|
|
219
|
+
assert.equal(secureCookies[0].path, '/');
|
|
220
|
+
assert.isTrue(secureCookies[0].secure);
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it('persists session data across requests', async () => {
|
|
225
|
+
const app = express();
|
|
226
|
+
app.use(
|
|
227
|
+
createSessionMiddleware({
|
|
228
|
+
store: new MemoryStore(),
|
|
229
|
+
secret: TEST_SECRET,
|
|
230
|
+
}),
|
|
231
|
+
);
|
|
232
|
+
app.get('/', (req, res) => {
|
|
233
|
+
req.session.count ??= 0;
|
|
234
|
+
req.session.count += 1;
|
|
235
|
+
res.send(req.session.count.toString());
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
await withServer(app, async ({ url }) => {
|
|
239
|
+
const fetchWithCookies = fetchCookie(fetch);
|
|
240
|
+
|
|
241
|
+
let res = await fetchWithCookies(url);
|
|
242
|
+
assert.equal(res.status, 200);
|
|
243
|
+
assert.equal(await res.text(), '1');
|
|
244
|
+
|
|
245
|
+
res = await fetchWithCookies(url);
|
|
246
|
+
assert.equal(res.status, 200);
|
|
247
|
+
assert.equal(await res.text(), '2');
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
it('commits the session before sending a redirect', async () => {
|
|
252
|
+
const app = express();
|
|
253
|
+
app.use(
|
|
254
|
+
createSessionMiddleware({
|
|
255
|
+
store: new MemoryStore(),
|
|
256
|
+
secret: TEST_SECRET,
|
|
257
|
+
}),
|
|
258
|
+
);
|
|
259
|
+
app.post('/', (req, res) => {
|
|
260
|
+
req.session.test = 'test';
|
|
261
|
+
res.redirect(req.originalUrl);
|
|
262
|
+
});
|
|
263
|
+
app.get('/', (req, res) => {
|
|
264
|
+
res.send(req.session.test ?? 'NO VALUE');
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
await withServer(app, async ({ url }) => {
|
|
268
|
+
const res = await fetchCookie(fetch)(url, {
|
|
269
|
+
method: 'POST',
|
|
270
|
+
});
|
|
271
|
+
assert.equal(res.status, 200);
|
|
272
|
+
|
|
273
|
+
const body = await res.text();
|
|
274
|
+
assert.equal(body, 'test');
|
|
275
|
+
});
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
it('destroys session', async () => {
|
|
279
|
+
const store = new MemoryStore();
|
|
280
|
+
|
|
281
|
+
const app = express();
|
|
282
|
+
app.use(
|
|
283
|
+
createSessionMiddleware({
|
|
284
|
+
store,
|
|
285
|
+
secret: TEST_SECRET,
|
|
286
|
+
}),
|
|
287
|
+
);
|
|
288
|
+
app.get('/', (_req, res) => res.sendStatus(200));
|
|
289
|
+
app.use(
|
|
290
|
+
'/destroy',
|
|
291
|
+
asyncHandler(async (req, res) => {
|
|
292
|
+
await req.session.destroy();
|
|
293
|
+
res.sendStatus(200);
|
|
294
|
+
}),
|
|
295
|
+
);
|
|
296
|
+
|
|
297
|
+
await withServer(app, async ({ url }) => {
|
|
298
|
+
const fetchWithCookies = fetchCookie(fetch);
|
|
299
|
+
|
|
300
|
+
// Generate a new session.
|
|
301
|
+
await fetchWithCookies(url);
|
|
302
|
+
|
|
303
|
+
// Destroy the session.
|
|
304
|
+
const destroyRes = await fetchWithCookies(`${url}/destroy`);
|
|
305
|
+
assert.equal(destroyRes.status, 200);
|
|
306
|
+
|
|
307
|
+
// Ensure the session cookie was cleared in the response.
|
|
308
|
+
const header = destroyRes.headers.get('set-cookie');
|
|
309
|
+
const cookies = parseSetCookie(header ?? '');
|
|
310
|
+
assert.equal(cookies.length, 1);
|
|
311
|
+
assert.equal(cookies[0].name, 'session');
|
|
312
|
+
assert.equal(cookies[0].path, '/');
|
|
313
|
+
assert.equal(cookies[0].expires?.getTime(), 0);
|
|
314
|
+
|
|
315
|
+
// Ensure the session was destroyed in the session store.
|
|
316
|
+
const sessionId = cookies[0].value.split('.')[0];
|
|
317
|
+
assert.isNull(await store.get(sessionId));
|
|
318
|
+
});
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
it('regenerates session', async () => {
|
|
322
|
+
const store = new MemoryStore();
|
|
323
|
+
|
|
324
|
+
const app = express();
|
|
325
|
+
app.use(
|
|
326
|
+
createSessionMiddleware({
|
|
327
|
+
store,
|
|
328
|
+
secret: TEST_SECRET,
|
|
329
|
+
}),
|
|
330
|
+
);
|
|
331
|
+
app.get('/', (req, res) => {
|
|
332
|
+
res.send(req.session.regenerated ? 'true' : 'false');
|
|
333
|
+
});
|
|
334
|
+
app.get(
|
|
335
|
+
'/regenerate',
|
|
336
|
+
asyncHandler(async (req, res) => {
|
|
337
|
+
await req.session.regenerate();
|
|
338
|
+
req.session.regenerated = true;
|
|
339
|
+
res.sendStatus(200);
|
|
340
|
+
}),
|
|
341
|
+
);
|
|
342
|
+
|
|
343
|
+
await withServer(app, async ({ url }) => {
|
|
344
|
+
const fetchWithCookies = fetchCookie(fetch);
|
|
345
|
+
|
|
346
|
+
// Generate a new session.
|
|
347
|
+
let res = await fetchWithCookies(url);
|
|
348
|
+
assert.equal(res.status, 200);
|
|
349
|
+
assert.equal(await res.text(), 'false');
|
|
350
|
+
|
|
351
|
+
// Extract the original cookie value.
|
|
352
|
+
let header = res.headers.get('set-cookie');
|
|
353
|
+
let cookies = parseSetCookie(header ?? '');
|
|
354
|
+
assert.equal(cookies.length, 1);
|
|
355
|
+
const originalCookieValue = cookies[0].value;
|
|
356
|
+
|
|
357
|
+
// Regenerate the session.
|
|
358
|
+
res = await fetchWithCookies(`${url}/regenerate`);
|
|
359
|
+
assert.equal(res.status, 200);
|
|
360
|
+
|
|
361
|
+
// Ensure that the session cookie was changed.
|
|
362
|
+
header = res.headers.get('set-cookie');
|
|
363
|
+
cookies = parseSetCookie(header ?? '');
|
|
364
|
+
assert.equal(cookies.length, 1);
|
|
365
|
+
const newCookieValue = cookies[0].value;
|
|
366
|
+
assert.notEqual(newCookieValue, originalCookieValue);
|
|
367
|
+
|
|
368
|
+
// Ensure the original session is no longer present in the session store.
|
|
369
|
+
const originalSessionId = originalCookieValue.split('.')[0];
|
|
370
|
+
assert.isNull(await store.get(originalSessionId));
|
|
371
|
+
|
|
372
|
+
// Ensure that the regenerated session data was persisted.
|
|
373
|
+
res = await fetchWithCookies(url);
|
|
374
|
+
assert.equal(res.status, 200);
|
|
375
|
+
assert.equal(await res.text(), 'true');
|
|
376
|
+
});
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
it('creates a new session if signature checks fail', async () => {
|
|
380
|
+
const store = new MemoryStore();
|
|
381
|
+
|
|
382
|
+
const app = express();
|
|
383
|
+
app.use(
|
|
384
|
+
createSessionMiddleware({
|
|
385
|
+
store,
|
|
386
|
+
secret: TEST_SECRET,
|
|
387
|
+
}),
|
|
388
|
+
);
|
|
389
|
+
app.get('/', (req, res) => res.send(req.session.id));
|
|
390
|
+
|
|
391
|
+
await withServer(app, async ({ url }) => {
|
|
392
|
+
const cookieJar = new fetchCookie.toughCookie.CookieJar();
|
|
393
|
+
const fetchWithCookies = fetchCookie(fetch, cookieJar);
|
|
394
|
+
|
|
395
|
+
// Generate a new session.
|
|
396
|
+
let res = await fetchWithCookies(url);
|
|
397
|
+
assert.equal(res.status, 200);
|
|
398
|
+
const originalSessionId = await res.text();
|
|
399
|
+
|
|
400
|
+
// Tamper with the session cookie.
|
|
401
|
+
const cookie = cookieJar.getCookiesSync(url)[0];
|
|
402
|
+
cookie.value = 'tampered';
|
|
403
|
+
cookieJar.setCookieSync(cookie, url);
|
|
404
|
+
|
|
405
|
+
// Make sure we get a new session.
|
|
406
|
+
res = await fetchWithCookies(url);
|
|
407
|
+
assert.equal(res.status, 200);
|
|
408
|
+
const newSessionId = await res.text();
|
|
409
|
+
assert.notEqual(newSessionId, originalSessionId);
|
|
410
|
+
|
|
411
|
+
// Make sure the existing session is still present in the store. We don't
|
|
412
|
+
// want someone to be able to evict other sessions by submitting invalid
|
|
413
|
+
// cookies.
|
|
414
|
+
assert.isNotNull(await store.get(originalSessionId));
|
|
415
|
+
});
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
it('does not re-set the cookie on subsequent requests', async () => {
|
|
419
|
+
const app = express();
|
|
420
|
+
app.use(
|
|
421
|
+
createSessionMiddleware({
|
|
422
|
+
store: new MemoryStore(),
|
|
423
|
+
secret: TEST_SECRET,
|
|
424
|
+
}),
|
|
425
|
+
);
|
|
426
|
+
app.get('/', (_req, res) => res.sendStatus(200));
|
|
427
|
+
|
|
428
|
+
await withServer(app, async ({ url }) => {
|
|
429
|
+
const fetchWithCookies = fetchCookie(fetch);
|
|
430
|
+
|
|
431
|
+
// Generate a new session.
|
|
432
|
+
let res = await fetchWithCookies(url);
|
|
433
|
+
assert.equal(res.status, 200);
|
|
434
|
+
|
|
435
|
+
// Make another request with the same session.
|
|
436
|
+
res = await fetchWithCookies(url);
|
|
437
|
+
assert.equal(res.status, 200);
|
|
438
|
+
|
|
439
|
+
// Ensure that the cookie wasn't set again.
|
|
440
|
+
const header = res.headers.get('set-cookie');
|
|
441
|
+
assert.isNull(header);
|
|
442
|
+
});
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
it('does not set the cookie if it is not supposed to', async () => {
|
|
446
|
+
const store = new MemoryStore();
|
|
447
|
+
|
|
448
|
+
const app = express();
|
|
449
|
+
app.enable('trust proxy');
|
|
450
|
+
app.use(
|
|
451
|
+
createSessionMiddleware({
|
|
452
|
+
store,
|
|
453
|
+
secret: TEST_SECRET,
|
|
454
|
+
canSetCookie: (req) => req.hostname === 'example.com',
|
|
455
|
+
}),
|
|
456
|
+
);
|
|
457
|
+
app.get('/', (req, res) => {
|
|
458
|
+
req.session.count ??= 0;
|
|
459
|
+
req.session.count += 1;
|
|
460
|
+
|
|
461
|
+
res.send(req.session.id);
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
await withServer(app, async ({ url }) => {
|
|
465
|
+
const fetchWithCookies = fetchCookie(fetch);
|
|
466
|
+
|
|
467
|
+
// Fetch from a subdomain.
|
|
468
|
+
let res = await fetchWithCookies(url, {
|
|
469
|
+
headers: {
|
|
470
|
+
'X-Forwarded-Host': 'subdomain.example.com',
|
|
471
|
+
},
|
|
472
|
+
});
|
|
473
|
+
assert.equal(res.status, 200);
|
|
474
|
+
|
|
475
|
+
// There should not be a cookie.
|
|
476
|
+
assert.isNull(res.headers.get('set-cookie'));
|
|
477
|
+
|
|
478
|
+
// Since the cookie wasn't set, the session should not have been persisted
|
|
479
|
+
// to the store.
|
|
480
|
+
const originalSessionId = await res.text();
|
|
481
|
+
assert.isNull(await store.get(originalSessionId));
|
|
482
|
+
|
|
483
|
+
// Now fetch from the correct domain.
|
|
484
|
+
res = await fetchWithCookies(url, {
|
|
485
|
+
headers: {
|
|
486
|
+
'X-Forwarded-Host': 'example.com',
|
|
487
|
+
},
|
|
488
|
+
});
|
|
489
|
+
assert.equal(res.status, 200);
|
|
490
|
+
|
|
491
|
+
// There should be a cookie for this request.
|
|
492
|
+
const header = res.headers.get('set-cookie');
|
|
493
|
+
assert.isNotNull(header);
|
|
494
|
+
const cookies = parseSetCookie(header ?? '');
|
|
495
|
+
assert.equal(cookies.length, 1);
|
|
496
|
+
assert.equal(cookies[0].name, 'session');
|
|
497
|
+
|
|
498
|
+
// The session should have been persisted.
|
|
499
|
+
const newSessionId = await res.text();
|
|
500
|
+
let session = await store.get(newSessionId);
|
|
501
|
+
assert.isNotNull(session);
|
|
502
|
+
assert.equal(session?.data.count, 1);
|
|
503
|
+
|
|
504
|
+
// Now, fetch from the subdomain again. This time, the session should
|
|
505
|
+
// work as normal.
|
|
506
|
+
res = await fetchWithCookies(url, {
|
|
507
|
+
headers: {
|
|
508
|
+
'X-Forwarded-Host': 'subdomain.example.com',
|
|
509
|
+
},
|
|
510
|
+
});
|
|
511
|
+
assert.equal(res.status, 200);
|
|
512
|
+
assert.equal(await res.text(), newSessionId);
|
|
513
|
+
|
|
514
|
+
// Ensure that there was still no Set-Cookie header.
|
|
515
|
+
assert.isNull(res.headers.get('set-cookie'));
|
|
516
|
+
|
|
517
|
+
// Ensure that the session was persisted.
|
|
518
|
+
session = await store.get(newSessionId);
|
|
519
|
+
assert.isNotNull(session);
|
|
520
|
+
assert.equal(session?.data.count, 2);
|
|
521
|
+
});
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
it('extends the expiration date of the cookie', async () => {
|
|
525
|
+
const store = new MemoryStore();
|
|
526
|
+
|
|
527
|
+
const app = express();
|
|
528
|
+
app.use(
|
|
529
|
+
createSessionMiddleware({
|
|
530
|
+
store,
|
|
531
|
+
secret: TEST_SECRET,
|
|
532
|
+
cookie: {
|
|
533
|
+
maxAge: 1000,
|
|
534
|
+
},
|
|
535
|
+
}),
|
|
536
|
+
);
|
|
537
|
+
app.get('/', (_req, res) => res.sendStatus(200));
|
|
538
|
+
app.get('/extend', (req, res) => {
|
|
539
|
+
req.session.setExpiration(Date.now() + 10000);
|
|
540
|
+
res.sendStatus(200);
|
|
541
|
+
});
|
|
542
|
+
|
|
543
|
+
await withServer(app, async ({ url }) => {
|
|
544
|
+
const fetchWithCookies = fetchCookie(fetch);
|
|
545
|
+
|
|
546
|
+
// Generate a new session.
|
|
547
|
+
let res = await fetchWithCookies(url);
|
|
548
|
+
assert.equal(res.status, 200);
|
|
549
|
+
|
|
550
|
+
// Grab the original expiration date.
|
|
551
|
+
let header = res.headers.get('set-cookie');
|
|
552
|
+
let cookies = parseSetCookie(header ?? '');
|
|
553
|
+
assert.equal(cookies.length, 1);
|
|
554
|
+
assert.isUndefined(cookies[0].maxAge);
|
|
555
|
+
const originalExpirationDate = cookies[0].expires;
|
|
556
|
+
assert(originalExpirationDate);
|
|
557
|
+
|
|
558
|
+
// Also grab the expiration date from the store.
|
|
559
|
+
const sessionId = cookies[0].value.split('.')[0];
|
|
560
|
+
const session = await store.get(sessionId);
|
|
561
|
+
assert(session);
|
|
562
|
+
const originalStoreExpirationDate = session.expiresAt;
|
|
563
|
+
|
|
564
|
+
// Ensure that the expiration dates are consistent.
|
|
565
|
+
assert.equal(originalExpirationDate.getTime(), originalStoreExpirationDate.getTime());
|
|
566
|
+
|
|
567
|
+
// Make another request with the same session.
|
|
568
|
+
res = await fetchWithCookies(`${url}/extend`);
|
|
569
|
+
assert.equal(res.status, 200);
|
|
570
|
+
|
|
571
|
+
// Ensure that the cookie was set again.
|
|
572
|
+
header = res.headers.get('set-cookie');
|
|
573
|
+
cookies = parseSetCookie(header ?? '');
|
|
574
|
+
assert.equal(cookies.length, 1);
|
|
575
|
+
assert.isUndefined(cookies[0].maxAge);
|
|
576
|
+
const newExpirationDate = cookies[0].expires;
|
|
577
|
+
assert(newExpirationDate);
|
|
578
|
+
|
|
579
|
+
// Ensure that the expiration date was extended.
|
|
580
|
+
assert.notEqual(newExpirationDate.getTime(), originalExpirationDate.getTime());
|
|
581
|
+
|
|
582
|
+
// Also grab the new expiration date from the store.
|
|
583
|
+
const newSession = await store.get(sessionId);
|
|
584
|
+
assert(newSession);
|
|
585
|
+
const newStoreExpirationDate = newSession.expiresAt;
|
|
586
|
+
|
|
587
|
+
// Ensure that the expiration dates are consistent.
|
|
588
|
+
assert.equal(newExpirationDate.getTime(), newStoreExpirationDate.getTime());
|
|
589
|
+
});
|
|
590
|
+
});
|
|
591
|
+
|
|
592
|
+
it('does not persist session data if the session did not change', async () => {
|
|
593
|
+
const store = new MemoryStore();
|
|
594
|
+
|
|
595
|
+
let setCount = 0;
|
|
596
|
+
const originalStoreSet = store.set.bind(store);
|
|
597
|
+
store.set = async (...args) => {
|
|
598
|
+
setCount += 1;
|
|
599
|
+
await originalStoreSet(...args);
|
|
600
|
+
};
|
|
601
|
+
|
|
602
|
+
const app = express();
|
|
603
|
+
app.use(
|
|
604
|
+
createSessionMiddleware({
|
|
605
|
+
store,
|
|
606
|
+
secret: TEST_SECRET,
|
|
607
|
+
}),
|
|
608
|
+
);
|
|
609
|
+
app.get('/', (_req, res) => res.sendStatus(200));
|
|
610
|
+
|
|
611
|
+
await withServer(app, async ({ url }) => {
|
|
612
|
+
const fetchWithCookies = fetchCookie(fetch);
|
|
613
|
+
// Generate a new session.
|
|
614
|
+
let res = await fetchWithCookies(url);
|
|
615
|
+
assert.equal(res.status, 200);
|
|
616
|
+
|
|
617
|
+
// Ensure the session was persisted.
|
|
618
|
+
assert.equal(setCount, 1);
|
|
619
|
+
|
|
620
|
+
// Make another request with the same session.
|
|
621
|
+
res = await fetchWithCookies(url);
|
|
622
|
+
assert.equal(res.status, 200);
|
|
623
|
+
|
|
624
|
+
// Ensure the session was not persisted.
|
|
625
|
+
assert.equal(setCount, 1);
|
|
626
|
+
});
|
|
627
|
+
});
|
|
628
|
+
});
|