@prairielearn/session 3.0.24 → 3.0.26

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/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @prairielearn/session
2
2
 
3
+ ## 3.0.26
4
+
5
+ ### Patch Changes
6
+
7
+ - 0900843: Switch to the `tsgo` compiler
8
+
9
+ ## 3.0.25
10
+
11
+ ### Patch Changes
12
+
13
+ - 70a8029: Upgrade all JavaScript dependencies
14
+
3
15
  ## 3.0.24
4
16
 
5
17
  ### Patch Changes
@@ -1 +1 @@
1
- {"version":3,"file":"before-end.d.ts","sourceRoot":"","sources":["../src/before-end.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAE5C;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,YAAY,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,QAsE9E"}
1
+ {"version":3,"file":"before-end.d.ts","sourceRoot":"","sources":["../src/before-end.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAE5C;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,YAAY,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,QAsE9E","sourcesContent":["import type { NextFunction } from 'express';\n\n/**\n * The following function is based on code from `express-session`:\n *\n * https://github.com/expressjs/session/blob/1010fadc2f071ddf2add94235d72224cf65159c6/index.js#L246-L360\n *\n * This code is used to work around the fact that Express doesn't have a good\n * hook to allow us to perform some asynchronous operation before the response\n * is written to the client.\n *\n * Note that this is truly only necessary for Express. Other Node frameworks\n * like Fastify and Adonis have hooks that allow us to do this without any\n * hacks. It's also probably only useful in the context of Express, as it\n * seems to rely on the fact that Express and its ecosystem generally don't\n * call `end()` without an additional chunk of data. If it instead called\n * `write()` with the final data and then `end()` with no data, this code\n * wouldn't function as intended. It's possible that `stream.pipe(res)` does\n * in fact behave this way, so it's probably not completely safe to use this\n * code when streaming responses back to the client.\n *\n * One could probably make this safer by *also* hooking into `response.write()`\n * and buffering the data. My understanding of Node streams isn't good enough\n * to implement that, though.\n */\nexport function beforeEnd(res: any, next: NextFunction, fn: () => Promise<void>) {\n const _end = res.end as any;\n const _write = res.write as any;\n let ended = false;\n\n res.end = function end(chunk: any, encoding: any) {\n if (ended) {\n return false;\n }\n\n ended = true;\n\n let ret: any;\n let sync = true;\n\n function writeend() {\n if (sync) {\n ret = _end.call(res, chunk, encoding);\n sync = false;\n return;\n }\n\n _end.call(res);\n }\n\n function writetop() {\n if (!sync) {\n return ret;\n }\n\n if (!res._header) {\n res._implicitHeader();\n }\n\n if (chunk == null) {\n ret = true;\n return ret;\n }\n\n const contentLength = Number(res.getHeader('Content-Length'));\n\n if (!Number.isNaN(contentLength) && contentLength > 0) {\n chunk = !Buffer.isBuffer(chunk) ? Buffer.from(chunk, encoding) : chunk;\n encoding = undefined;\n\n if (chunk.length > 0) {\n ret = _write.call(res, chunk.slice(0, -1));\n chunk = chunk.slice(-1);\n return ret;\n }\n }\n\n ret = _write.call(res, chunk, encoding);\n sync = false;\n\n return ret;\n }\n\n fn().then(\n () => {\n writeend();\n },\n (err) => {\n setImmediate(next, err);\n writeend();\n },\n );\n\n return writetop();\n };\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"before-end.js","sourceRoot":"","sources":["../src/before-end.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,UAAU,SAAS,CAAC,GAAQ,EAAE,IAAkB,EAAE,EAAuB;IAC7E,MAAM,IAAI,GAAG,GAAG,CAAC,GAAU,CAAC;IAC5B,MAAM,MAAM,GAAG,GAAG,CAAC,KAAY,CAAC;IAChC,IAAI,KAAK,GAAG,KAAK,CAAC;IAElB,GAAG,CAAC,GAAG,GAAG,SAAS,GAAG,CAAC,KAAU,EAAE,QAAa;QAC9C,IAAI,KAAK,EAAE,CAAC;YACV,OAAO,KAAK,CAAC;QACf,CAAC;QAED,KAAK,GAAG,IAAI,CAAC;QAEb,IAAI,GAAQ,CAAC;QACb,IAAI,IAAI,GAAG,IAAI,CAAC;QAEhB,SAAS,QAAQ;YACf,IAAI,IAAI,EAAE,CAAC;gBACT,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,EAAE,QAAQ,CAAC,CAAC;gBACtC,IAAI,GAAG,KAAK,CAAC;gBACb,OAAO;YACT,CAAC;YAED,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACjB,CAAC;QAED,SAAS,QAAQ;YACf,IAAI,CAAC,IAAI,EAAE,CAAC;gBACV,OAAO,GAAG,CAAC;YACb,CAAC;YAED,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC;gBACjB,GAAG,CAAC,eAAe,EAAE,CAAC;YACxB,CAAC;YAED,IAAI,KAAK,IAAI,IAAI,EAAE,CAAC;gBAClB,GAAG,GAAG,IAAI,CAAC;gBACX,OAAO,GAAG,CAAC;YACb,CAAC;YAED,MAAM,aAAa,GAAG,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,gBAAgB,CAAC,CAAC,CAAC;YAE9D,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,aAAa,CAAC,IAAI,aAAa,GAAG,CAAC,EAAE,CAAC;gBACtD,KAAK,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;gBACvE,QAAQ,GAAG,SAAS,CAAC;gBAErB,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;oBACrB,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;oBAC3C,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;oBACxB,OAAO,GAAG,CAAC;gBACb,CAAC;YACH,CAAC;YAED,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,EAAE,QAAQ,CAAC,CAAC;YACxC,IAAI,GAAG,KAAK,CAAC;YAEb,OAAO,GAAG,CAAC;QACb,CAAC;QAED,EAAE,EAAE,CAAC,IAAI,CACP,GAAG,EAAE;YACH,QAAQ,EAAE,CAAC;QACb,CAAC,EACD,CAAC,GAAG,EAAE,EAAE;YACN,YAAY,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;YACxB,QAAQ,EAAE,CAAC;QACb,CAAC,CACF,CAAC;QAEF,OAAO,QAAQ,EAAE,CAAC;IACpB,CAAC,CAAC;AACJ,CAAC","sourcesContent":["import type { NextFunction } from 'express';\n\n/**\n * The following function is based on code from `express-session`:\n *\n * https://github.com/expressjs/session/blob/1010fadc2f071ddf2add94235d72224cf65159c6/index.js#L246-L360\n *\n * This code is used to work around the fact that Express doesn't have a good\n * hook to allow us to perform some asynchronous operation before the response\n * is written to the client.\n *\n * Note that this is truly only necessary for Express. Other Node frameworks\n * like Fastify and Adonis have hooks that allow us to do this without any\n * hacks. It's also probably only useful in the context of Express, as it\n * seems to rely on the fact that Express and its ecosystem generally don't\n * call `end()` without an additional chunk of data. If it instead called\n * `write()` with the final data and then `end()` with no data, this code\n * wouldn't function as intended. It's possible that `stream.pipe(res)` does\n * in fact behave this way, so it's probably not completely safe to use this\n * code when streaming responses back to the client.\n *\n * One could probably make this safer by *also* hooking into `response.write()`\n * and buffering the data. My understanding of Node streams isn't good enough\n * to implement that, though.\n */\nexport function beforeEnd(res: any, next: NextFunction, fn: () => Promise<void>) {\n const _end = res.end as any;\n const _write = res.write as any;\n let ended = false;\n\n res.end = function end(chunk: any, encoding: any) {\n if (ended) {\n return false;\n }\n\n ended = true;\n\n let ret: any;\n let sync = true;\n\n function writeend() {\n if (sync) {\n ret = _end.call(res, chunk, encoding);\n sync = false;\n return;\n }\n\n _end.call(res);\n }\n\n function writetop() {\n if (!sync) {\n return ret;\n }\n\n if (!res._header) {\n res._implicitHeader();\n }\n\n if (chunk == null) {\n ret = true;\n return ret;\n }\n\n const contentLength = Number(res.getHeader('Content-Length'));\n\n if (!Number.isNaN(contentLength) && contentLength > 0) {\n chunk = !Buffer.isBuffer(chunk) ? Buffer.from(chunk, encoding) : chunk;\n encoding = undefined;\n\n if (chunk.length > 0) {\n ret = _write.call(res, chunk.slice(0, -1));\n chunk = chunk.slice(-1);\n return ret;\n }\n }\n\n ret = _write.call(res, chunk, encoding);\n sync = false;\n\n return ret;\n }\n\n fn().then(\n () => {\n writeend();\n },\n (err) => {\n setImmediate(next, err);\n writeend();\n },\n );\n\n return writetop();\n };\n}\n"]}
1
+ {"version":3,"file":"before-end.js","sourceRoot":"","sources":["../src/before-end.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,UAAU,SAAS,CAAC,GAAQ,EAAE,IAAkB,EAAE,EAAuB,EAAE;IAC/E,MAAM,IAAI,GAAG,GAAG,CAAC,GAAU,CAAC;IAC5B,MAAM,MAAM,GAAG,GAAG,CAAC,KAAY,CAAC;IAChC,IAAI,KAAK,GAAG,KAAK,CAAC;IAElB,GAAG,CAAC,GAAG,GAAG,SAAS,GAAG,CAAC,KAAU,EAAE,QAAa,EAAE;QAChD,IAAI,KAAK,EAAE,CAAC;YACV,OAAO,KAAK,CAAC;QACf,CAAC;QAED,KAAK,GAAG,IAAI,CAAC;QAEb,IAAI,GAAQ,CAAC;QACb,IAAI,IAAI,GAAG,IAAI,CAAC;QAEhB,SAAS,QAAQ,GAAG;YAClB,IAAI,IAAI,EAAE,CAAC;gBACT,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,EAAE,QAAQ,CAAC,CAAC;gBACtC,IAAI,GAAG,KAAK,CAAC;gBACb,OAAO;YACT,CAAC;YAED,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAAA,CAChB;QAED,SAAS,QAAQ,GAAG;YAClB,IAAI,CAAC,IAAI,EAAE,CAAC;gBACV,OAAO,GAAG,CAAC;YACb,CAAC;YAED,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC;gBACjB,GAAG,CAAC,eAAe,EAAE,CAAC;YACxB,CAAC;YAED,IAAI,KAAK,IAAI,IAAI,EAAE,CAAC;gBAClB,GAAG,GAAG,IAAI,CAAC;gBACX,OAAO,GAAG,CAAC;YACb,CAAC;YAED,MAAM,aAAa,GAAG,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,gBAAgB,CAAC,CAAC,CAAC;YAE9D,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,aAAa,CAAC,IAAI,aAAa,GAAG,CAAC,EAAE,CAAC;gBACtD,KAAK,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;gBACvE,QAAQ,GAAG,SAAS,CAAC;gBAErB,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;oBACrB,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;oBAC3C,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;oBACxB,OAAO,GAAG,CAAC;gBACb,CAAC;YACH,CAAC;YAED,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,EAAE,QAAQ,CAAC,CAAC;YACxC,IAAI,GAAG,KAAK,CAAC;YAEb,OAAO,GAAG,CAAC;QAAA,CACZ;QAED,EAAE,EAAE,CAAC,IAAI,CACP,GAAG,EAAE,CAAC;YACJ,QAAQ,EAAE,CAAC;QAAA,CACZ,EACD,CAAC,GAAG,EAAE,EAAE,CAAC;YACP,YAAY,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;YACxB,QAAQ,EAAE,CAAC;QAAA,CACZ,CACF,CAAC;QAEF,OAAO,QAAQ,EAAE,CAAC;IAAA,CACnB,CAAC;AAAA,CACH","sourcesContent":["import type { NextFunction } from 'express';\n\n/**\n * The following function is based on code from `express-session`:\n *\n * https://github.com/expressjs/session/blob/1010fadc2f071ddf2add94235d72224cf65159c6/index.js#L246-L360\n *\n * This code is used to work around the fact that Express doesn't have a good\n * hook to allow us to perform some asynchronous operation before the response\n * is written to the client.\n *\n * Note that this is truly only necessary for Express. Other Node frameworks\n * like Fastify and Adonis have hooks that allow us to do this without any\n * hacks. It's also probably only useful in the context of Express, as it\n * seems to rely on the fact that Express and its ecosystem generally don't\n * call `end()` without an additional chunk of data. If it instead called\n * `write()` with the final data and then `end()` with no data, this code\n * wouldn't function as intended. It's possible that `stream.pipe(res)` does\n * in fact behave this way, so it's probably not completely safe to use this\n * code when streaming responses back to the client.\n *\n * One could probably make this safer by *also* hooking into `response.write()`\n * and buffering the data. My understanding of Node streams isn't good enough\n * to implement that, though.\n */\nexport function beforeEnd(res: any, next: NextFunction, fn: () => Promise<void>) {\n const _end = res.end as any;\n const _write = res.write as any;\n let ended = false;\n\n res.end = function end(chunk: any, encoding: any) {\n if (ended) {\n return false;\n }\n\n ended = true;\n\n let ret: any;\n let sync = true;\n\n function writeend() {\n if (sync) {\n ret = _end.call(res, chunk, encoding);\n sync = false;\n return;\n }\n\n _end.call(res);\n }\n\n function writetop() {\n if (!sync) {\n return ret;\n }\n\n if (!res._header) {\n res._implicitHeader();\n }\n\n if (chunk == null) {\n ret = true;\n return ret;\n }\n\n const contentLength = Number(res.getHeader('Content-Length'));\n\n if (!Number.isNaN(contentLength) && contentLength > 0) {\n chunk = !Buffer.isBuffer(chunk) ? Buffer.from(chunk, encoding) : chunk;\n encoding = undefined;\n\n if (chunk.length > 0) {\n ret = _write.call(res, chunk.slice(0, -1));\n chunk = chunk.slice(-1);\n return ret;\n }\n }\n\n ret = _write.call(res, chunk, encoding);\n sync = false;\n\n return ret;\n }\n\n fn().then(\n () => {\n writeend();\n },\n (err) => {\n setImmediate(next, err);\n writeend();\n },\n );\n\n return writetop();\n };\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"before-end.test.d.ts","sourceRoot":"","sources":["../src/before-end.test.ts"],"names":[],"mappings":""}
1
+ {"version":3,"file":"before-end.test.d.ts","sourceRoot":"","sources":["../src/before-end.test.ts"],"names":[],"mappings":"","sourcesContent":["import express, { type NextFunction, type Request, type Response } from 'express';\nimport fetch from 'node-fetch';\nimport { assert, describe, it } from 'vitest';\n\nimport { withServer } from '@prairielearn/express-test-utils';\n\nimport { beforeEnd } from './before-end.js';\n\ndescribe('beforeEnd', () => {\n it('handles errors correctly', async () => {\n const app = express();\n app.use((_req, res, next) => {\n beforeEnd(res, next, async () => {\n throw new Error('oops');\n });\n\n next();\n });\n\n app.get('/', (_req, res) => res.sendStatus(200));\n\n let error: Error | null = null;\n app.use((err: any, _req: Request, _res: Response, next: NextFunction) => {\n error = err;\n next();\n });\n\n await withServer(app, async ({ url }) => {\n const res = await fetch(url);\n\n assert.equal(res.status, 200);\n assert.equal(error?.message, 'oops');\n });\n });\n});\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"before-end.test.js","sourceRoot":"","sources":["../src/before-end.test.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,EAAE,EAAkD,MAAM,SAAS,CAAC;AAClF,OAAO,KAAK,MAAM,YAAY,CAAC;AAC/B,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAE9C,OAAO,EAAE,UAAU,EAAE,MAAM,kCAAkC,CAAC;AAE9D,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAE5C,QAAQ,CAAC,WAAW,EAAE,GAAG,EAAE;IACzB,EAAE,CAAC,0BAA0B,EAAE,KAAK,IAAI,EAAE;QACxC,MAAM,GAAG,GAAG,OAAO,EAAE,CAAC;QACtB,GAAG,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE;YAC1B,SAAS,CAAC,GAAG,EAAE,IAAI,EAAE,KAAK,IAAI,EAAE;gBAC9B,MAAM,IAAI,KAAK,CAAC,MAAM,CAAC,CAAC;YAC1B,CAAC,CAAC,CAAC;YAEH,IAAI,EAAE,CAAC;QACT,CAAC,CAAC,CAAC;QAEH,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC;QAEjD,IAAI,KAAK,GAAiB,IAAI,CAAC;QAC/B,GAAG,CAAC,GAAG,CAAC,CAAC,GAAQ,EAAE,IAAa,EAAE,IAAc,EAAE,IAAkB,EAAE,EAAE;YACtE,KAAK,GAAG,GAAG,CAAC;YACZ,IAAI,EAAE,CAAC;QACT,CAAC,CAAC,CAAC;QAEH,MAAM,UAAU,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE;YACtC,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,CAAC;YAE7B,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;YAC9B,MAAM,CAAC,KAAK,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;QACvC,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC","sourcesContent":["import express, { type NextFunction, type Request, type Response } from 'express';\nimport fetch from 'node-fetch';\nimport { assert, describe, it } from 'vitest';\n\nimport { withServer } from '@prairielearn/express-test-utils';\n\nimport { beforeEnd } from './before-end.js';\n\ndescribe('beforeEnd', () => {\n it('handles errors correctly', async () => {\n const app = express();\n app.use((_req, res, next) => {\n beforeEnd(res, next, async () => {\n throw new Error('oops');\n });\n\n next();\n });\n\n app.get('/', (_req, res) => res.sendStatus(200));\n\n let error: Error | null = null;\n app.use((err: any, _req: Request, _res: Response, next: NextFunction) => {\n error = err;\n next();\n });\n\n await withServer(app, async ({ url }) => {\n const res = await fetch(url);\n\n assert.equal(res.status, 200);\n assert.equal(error?.message, 'oops');\n });\n });\n});\n"]}
1
+ {"version":3,"file":"before-end.test.js","sourceRoot":"","sources":["../src/before-end.test.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,EAAE,EAAkD,MAAM,SAAS,CAAC;AAClF,OAAO,KAAK,MAAM,YAAY,CAAC;AAC/B,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAE9C,OAAO,EAAE,UAAU,EAAE,MAAM,kCAAkC,CAAC;AAE9D,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAE5C,QAAQ,CAAC,WAAW,EAAE,GAAG,EAAE,CAAC;IAC1B,EAAE,CAAC,0BAA0B,EAAE,KAAK,IAAI,EAAE,CAAC;QACzC,MAAM,GAAG,GAAG,OAAO,EAAE,CAAC;QACtB,GAAG,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE,CAAC;YAC3B,SAAS,CAAC,GAAG,EAAE,IAAI,EAAE,KAAK,IAAI,EAAE,CAAC;gBAC/B,MAAM,IAAI,KAAK,CAAC,MAAM,CAAC,CAAC;YAAA,CACzB,CAAC,CAAC;YAEH,IAAI,EAAE,CAAC;QAAA,CACR,CAAC,CAAC;QAEH,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC;QAEjD,IAAI,KAAK,GAAiB,IAAI,CAAC;QAC/B,GAAG,CAAC,GAAG,CAAC,CAAC,GAAQ,EAAE,IAAa,EAAE,IAAc,EAAE,IAAkB,EAAE,EAAE,CAAC;YACvE,KAAK,GAAG,GAAG,CAAC;YACZ,IAAI,EAAE,CAAC;QAAA,CACR,CAAC,CAAC;QAEH,MAAM,UAAU,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE,CAAC;YACvC,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,CAAC;YAE7B,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;YAC9B,MAAM,CAAC,KAAK,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;QAAA,CACtC,CAAC,CAAC;IAAA,CACJ,CAAC,CAAC;AAAA,CACJ,CAAC,CAAC","sourcesContent":["import express, { type NextFunction, type Request, type Response } from 'express';\nimport fetch from 'node-fetch';\nimport { assert, describe, it } from 'vitest';\n\nimport { withServer } from '@prairielearn/express-test-utils';\n\nimport { beforeEnd } from './before-end.js';\n\ndescribe('beforeEnd', () => {\n it('handles errors correctly', async () => {\n const app = express();\n app.use((_req, res, next) => {\n beforeEnd(res, next, async () => {\n throw new Error('oops');\n });\n\n next();\n });\n\n app.get('/', (_req, res) => res.sendStatus(200));\n\n let error: Error | null = null;\n app.use((err: any, _req: Request, _res: Response, next: NextFunction) => {\n error = err;\n next();\n });\n\n await withServer(app, async ({ url }) => {\n const res = await fetch(url);\n\n assert.equal(res.status, 200);\n assert.equal(error?.message, 'oops');\n });\n });\n});\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"cookie.d.ts","sourceRoot":"","sources":["../src/cookie.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAEvC,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,MAAM,GAAG,CAAC,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC,CAAC;AAE1E,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,YAAY,GAAG,OAAO,CAU9E;AAED,wBAAgB,sBAAsB,CACpC,aAAa,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EACxC,OAAO,EAAE,MAAM,EAAE,iBAalB"}
1
+ {"version":3,"file":"cookie.d.ts","sourceRoot":"","sources":["../src/cookie.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAEvC,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,MAAM,GAAG,CAAC,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC,CAAC;AAE1E,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,YAAY,GAAG,OAAO,CAU9E;AAED,wBAAgB,sBAAsB,CACpC,aAAa,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EACxC,OAAO,EAAE,MAAM,EAAE,iBAalB","sourcesContent":["import signature from 'cookie-signature';\nimport type { Request } from 'express';\n\nexport type CookieSecure = boolean | 'auto' | ((req: Request) => boolean);\n\nexport function shouldSecureCookie(req: Request, secure: CookieSecure): boolean {\n if (typeof secure === 'function') {\n return secure(req);\n }\n\n if (secure === 'auto') {\n return req.protocol === 'https';\n }\n\n return secure;\n}\n\nexport function getSessionIdFromCookie(\n sessionCookie: string | null | undefined,\n secrets: string[],\n) {\n // Try each secret until we find one that works.\n if (sessionCookie) {\n for (const secret of secrets) {\n const value = signature.unsign(sessionCookie, secret);\n if (value !== false) {\n return value;\n }\n }\n }\n\n return null;\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"cookie.js","sourceRoot":"","sources":["../src/cookie.ts"],"names":[],"mappings":"AAAA,OAAO,SAAS,MAAM,kBAAkB,CAAC;AAKzC,MAAM,UAAU,kBAAkB,CAAC,GAAY,EAAE,MAAoB;IACnE,IAAI,OAAO,MAAM,KAAK,UAAU,EAAE,CAAC;QACjC,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC;IACrB,CAAC;IAED,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC;QACtB,OAAO,GAAG,CAAC,QAAQ,KAAK,OAAO,CAAC;IAClC,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,UAAU,sBAAsB,CACpC,aAAwC,EACxC,OAAiB;IAEjB,gDAAgD;IAChD,IAAI,aAAa,EAAE,CAAC;QAClB,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;YAC7B,MAAM,KAAK,GAAG,SAAS,CAAC,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC;YACtD,IAAI,KAAK,KAAK,KAAK,EAAE,CAAC;gBACpB,OAAO,KAAK,CAAC;YACf,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC","sourcesContent":["import signature from 'cookie-signature';\nimport type { Request } from 'express';\n\nexport type CookieSecure = boolean | 'auto' | ((req: Request) => boolean);\n\nexport function shouldSecureCookie(req: Request, secure: CookieSecure): boolean {\n if (typeof secure === 'function') {\n return secure(req);\n }\n\n if (secure === 'auto') {\n return req.protocol === 'https';\n }\n\n return secure;\n}\n\nexport function getSessionIdFromCookie(\n sessionCookie: string | null | undefined,\n secrets: string[],\n) {\n // Try each secret until we find one that works.\n if (sessionCookie) {\n for (const secret of secrets) {\n const value = signature.unsign(sessionCookie, secret);\n if (value !== false) {\n return value;\n }\n }\n }\n\n return null;\n}\n"]}
1
+ {"version":3,"file":"cookie.js","sourceRoot":"","sources":["../src/cookie.ts"],"names":[],"mappings":"AAAA,OAAO,SAAS,MAAM,kBAAkB,CAAC;AAKzC,MAAM,UAAU,kBAAkB,CAAC,GAAY,EAAE,MAAoB,EAAW;IAC9E,IAAI,OAAO,MAAM,KAAK,UAAU,EAAE,CAAC;QACjC,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC;IACrB,CAAC;IAED,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC;QACtB,OAAO,GAAG,CAAC,QAAQ,KAAK,OAAO,CAAC;IAClC,CAAC;IAED,OAAO,MAAM,CAAC;AAAA,CACf;AAED,MAAM,UAAU,sBAAsB,CACpC,aAAwC,EACxC,OAAiB,EACjB;IACA,gDAAgD;IAChD,IAAI,aAAa,EAAE,CAAC;QAClB,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;YAC7B,MAAM,KAAK,GAAG,SAAS,CAAC,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC;YACtD,IAAI,KAAK,KAAK,KAAK,EAAE,CAAC;gBACpB,OAAO,KAAK,CAAC;YACf,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,IAAI,CAAC;AAAA,CACb","sourcesContent":["import signature from 'cookie-signature';\nimport type { Request } from 'express';\n\nexport type CookieSecure = boolean | 'auto' | ((req: Request) => boolean);\n\nexport function shouldSecureCookie(req: Request, secure: CookieSecure): boolean {\n if (typeof secure === 'function') {\n return secure(req);\n }\n\n if (secure === 'auto') {\n return req.protocol === 'https';\n }\n\n return secure;\n}\n\nexport function getSessionIdFromCookie(\n sessionCookie: string | null | undefined,\n secrets: string[],\n) {\n // Try each secret until we find one that works.\n if (sessionCookie) {\n for (const secret of secrets) {\n const value = signature.unsign(sessionCookie, secret);\n if (value !== false) {\n return value;\n }\n }\n }\n\n return null;\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,KAAK,YAAY,EAA8C,MAAM,aAAa,CAAC;AAC5F,OAAO,EACL,KAAK,OAAO,EAKb,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,YAAY,CAAC;AAE/C,OAAO,CAAC,MAAM,CAAC;IAEb,UAAU,OAAO,CAAC;QAChB,UAAU,OAAO;YACf,OAAO,EAAE,OAAO,CAAC;SAClB;KACF;CACF;AAED,UAAU,aAAa;IACrB,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,KAAK,GAAG,QAAQ,CAAC;IAC/C,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC1B,KAAK,EAAE,YAAY,CAAC;IACpB,MAAM,CAAC,EAAE,aAAa,GAAG;QACvB;;;;WAIG;QACH,IAAI,CAAC,EAAE,MAAM,CAAC;QACd;;;;WAIG;QACH,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;QACtB;;WAEG;QACH,cAAc,CAAC,EAAE,IAAI,CAAC,aAAa,EAAE,QAAQ,CAAC,EAAE,CAAC;KAClD,CAAC;CACH;AAED,YAAY,EAAE,YAAY,EAAE,CAAC;AAK7B,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,cAAc,gJA6I9D"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,KAAK,YAAY,EAA8C,MAAM,aAAa,CAAC;AAC5F,OAAO,EACL,KAAK,OAAO,EAKb,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,YAAY,CAAC;AAE/C,OAAO,CAAC,MAAM,CAAC,CAAC;IAEd,UAAU,OAAO,CAAC,CAAC;QACjB,UAAU,OAAO;YACf,OAAO,EAAE,OAAO,CAAC;SAClB;KACF;CACF;AAED,UAAU,aAAa;IACrB,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,KAAK,GAAG,QAAQ,CAAC;IAC/C,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC1B,KAAK,EAAE,YAAY,CAAC;IACpB,MAAM,CAAC,EAAE,aAAa,GAAG;QACvB;;;;WAIG;QACH,IAAI,CAAC,EAAE,MAAM,CAAC;QACd;;;;WAIG;QACH,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;QACtB;;WAEG;QACH,cAAc,CAAC,EAAE,IAAI,CAAC,aAAa,EAAE,QAAQ,CAAC,EAAE,CAAC;KAClD,CAAC;CACH;AAED,YAAY,EAAE,YAAY,EAAE,CAAC;AAK7B,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,cAAc,gJA6I9D","sourcesContent":["import cookie from 'cookie';\nimport signature from 'cookie-signature';\nimport type { NextFunction, Request, Response } from 'express';\nimport asyncHandler from 'express-async-handler';\nimport onHeaders from 'on-headers';\n\nimport { beforeEnd } from './before-end.js';\nimport { type CookieSecure, getSessionIdFromCookie, shouldSecureCookie } from './cookie.js';\nimport {\n type Session,\n generateSessionId,\n hashSession,\n loadSession,\n truncateExpirationDate,\n} from './session.js';\nimport { type SessionStore } from './store.js';\n\ndeclare global {\n // eslint-disable-next-line @typescript-eslint/no-namespace\n namespace Express {\n interface Request {\n session: Session;\n }\n }\n}\n\ninterface CookieOptions {\n secure?: CookieSecure;\n httpOnly?: boolean;\n domain?: string;\n sameSite?: boolean | 'none' | 'lax' | 'strict';\n maxAge?: number;\n}\n\nexport interface SessionOptions {\n secret: string | string[];\n store: SessionStore;\n cookie?: CookieOptions & {\n /**\n * The name of the session cookie. The session is always read from this\n * named cookie, but it may be written to multiple cookies if `writeNames`\n * is provided.\n */\n name?: string;\n /**\n * Multiple write names can be provided to allow for a session cookie to be\n * written to multiple names. This can be useful for a migration of a cookie\n * to an explicit subdomain, for example.\n */\n writeNames?: string[];\n /**\n * Used with `writeNames` to provide additional options for each written cookie.\n */\n writeOverrides?: Omit<CookieOptions, 'secure'>[];\n };\n}\n\nexport type { SessionStore };\n\nconst DEFAULT_COOKIE_NAME = 'session';\nconst DEFAULT_COOKIE_MAX_AGE = 86400000; // 1 day\n\nexport function createSessionMiddleware(options: SessionOptions) {\n const secrets = Array.isArray(options.secret) ? options.secret : [options.secret];\n const cookieName = options.cookie?.name ?? DEFAULT_COOKIE_NAME;\n const cookieMaxAge = options.cookie?.maxAge ?? DEFAULT_COOKIE_MAX_AGE;\n const store = options.store;\n\n // Ensure that the session cookie that we're reading from will be written to.\n const writeCookieNames = options.cookie?.writeNames ?? [cookieName];\n if (!writeCookieNames.includes(cookieName)) {\n throw new Error('cookie.name must be included in cookie.writeNames');\n }\n\n // Validate write overrides.\n if (options.cookie?.writeOverrides && !options.cookie.writeNames) {\n throw new Error('cookie.writeOverrides must be used with cookie.writeNames');\n }\n if (\n options.cookie?.writeOverrides &&\n options.cookie.writeOverrides.length !== writeCookieNames.length\n ) {\n throw new Error('cookie.writeOverrides must have the same length as cookie.writeNames');\n }\n\n return asyncHandler(async function sessionMiddleware(\n req: Request,\n res: Response,\n next: NextFunction,\n ) {\n const cookies = cookie.parse(req.headers.cookie ?? '');\n const sessionCookie = cookies[cookieName];\n const cookieSessionId = getSessionIdFromCookie(sessionCookie, secrets);\n const sessionId = cookieSessionId ?? (await generateSessionId());\n req.session = await loadSession(sessionId, req, store, cookieMaxAge);\n\n const originalHash = hashSession(req.session);\n const originalExpirationDate = req.session.getExpirationDate();\n\n onHeaders(res, () => {\n if (!req.session) {\n if (cookieSessionId) {\n // If the request arrived with a session cookie but the session was\n // destroyed, clear the cookie.\n //\n // To cover all our bases, we'll clear *all* known session cookies to\n // ensure that state sessions aren't left behind. We'll also send commands\n // to clear the cookies both on and off the explicit domain, to handle\n // the case where the application has moved from one domain to another.\n writeCookieNames.forEach((cookieName, i) => {\n res.clearCookie(cookieName);\n const domain = options.cookie?.writeOverrides?.[i]?.domain ?? options.cookie?.domain;\n if (domain) {\n res.clearCookie(cookieName, { domain: options.cookie?.domain });\n }\n });\n return;\n }\n\n // There is no session to do anything with.\n return;\n }\n\n const secureCookie = shouldSecureCookie(req, options.cookie?.secure ?? 'auto');\n if (secureCookie && req.protocol !== 'https') {\n // Avoid sending cookie over insecure connection.\n return;\n }\n\n // Ensure that all known session cookies are set to the same value.\n const hasAllCookies = writeCookieNames.every((cookieName) => !!cookies[cookieName]);\n const isNewSession = !cookieSessionId || cookieSessionId !== req.session.id;\n const didExpirationChange =\n originalExpirationDate.getTime() !== req.session.getExpirationDate().getTime();\n if (isNewSession || didExpirationChange || !hasAllCookies) {\n const signedSessionId = signSessionId(req.session.id, secrets[0]);\n writeCookieNames.forEach((cookieName, i) => {\n res.cookie(cookieName, signedSessionId, {\n secure: secureCookie,\n httpOnly: options.cookie?.httpOnly ?? true,\n domain: options.cookie?.domain,\n sameSite: options.cookie?.sameSite ?? false,\n expires: req.session.getExpirationDate(),\n ...options.cookie?.writeOverrides?.[i],\n });\n });\n }\n });\n\n let sessionPersisted = false;\n\n async function persistSession(req: Request) {\n if (!req.session || sessionPersisted) {\n // There is no session to do anything with.\n return;\n }\n\n sessionPersisted = true;\n\n // If this is a new session, we would have already persisted it to the\n // store, so we don't need to take that into consideration here.\n //\n // If the hash of the session data changed, we'll unconditionally persist\n // the updated data to the store. However, if the hash didn't change, we\n // only want to persist it if the expiration changed *and* if we can set\n // a cookie to reflect the updated expiration date.\n const hashChanged = hashSession(req.session) !== originalHash;\n const expirationChanged =\n originalExpirationDate.getTime() !== req.session.getExpirationDate().getTime();\n if (hashChanged || expirationChanged) {\n await store.set(\n req.session.id,\n req.session,\n // Cookies only support second-level resolution. To ensure consistency\n // between the cookie and the store, truncate the expiration date to\n // the nearest second.\n truncateExpirationDate(req.session.getExpirationDate()),\n );\n }\n }\n\n // We'll attempt to persist the session at the end of the request. This\n // hacky strategy is borrowed from `express-session`.\n beforeEnd(res, next, async () => {\n await persistSession(req);\n });\n\n // We'll also attempt to persist the session before performing a redirect.\n // This is necessary because browsers and `fetch()` implementations aren't\n // required to wait for a response body to be received before following a\n // redirect. So, we need to make sure that the session is persisted before\n // we send the redirect response. This way, the subsequent GET will be able\n // to load the latest session data.\n const originalRedirect = res.redirect as any;\n res.redirect = function redirect(...args: any[]) {\n persistSession(req).then(\n () => originalRedirect.apply(res, args),\n (err) => next(err),\n );\n };\n\n next();\n });\n}\n\nfunction signSessionId(sessionId: string, secret: string): string {\n return signature.sign(sessionId, secret);\n}\n"]}
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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","sourcesContent":["import cookie from 'cookie';\nimport signature from 'cookie-signature';\nimport type { NextFunction, Request, Response } from 'express';\nimport asyncHandler from 'express-async-handler';\nimport onHeaders from 'on-headers';\n\nimport { beforeEnd } from './before-end.js';\nimport { type CookieSecure, getSessionIdFromCookie, shouldSecureCookie } from './cookie.js';\nimport {\n type Session,\n generateSessionId,\n hashSession,\n loadSession,\n truncateExpirationDate,\n} from './session.js';\nimport { type SessionStore } from './store.js';\n\ndeclare global {\n // eslint-disable-next-line @typescript-eslint/no-namespace\n namespace Express {\n interface Request {\n session: Session;\n }\n }\n}\n\ninterface CookieOptions {\n secure?: CookieSecure;\n httpOnly?: boolean;\n domain?: string;\n sameSite?: boolean | 'none' | 'lax' | 'strict';\n maxAge?: number;\n}\n\nexport interface SessionOptions {\n secret: string | string[];\n store: SessionStore;\n cookie?: CookieOptions & {\n /**\n * The name of the session cookie. The session is always read from this\n * named cookie, but it may be written to multiple cookies if `writeNames`\n * is provided.\n */\n name?: string;\n /**\n * Multiple write names can be provided to allow for a session cookie to be\n * written to multiple names. This can be useful for a migration of a cookie\n * to an explicit subdomain, for example.\n */\n writeNames?: string[];\n /**\n * Used with `writeNames` to provide additional options for each written cookie.\n */\n writeOverrides?: Omit<CookieOptions, 'secure'>[];\n };\n}\n\nexport type { SessionStore };\n\nconst DEFAULT_COOKIE_NAME = 'session';\nconst DEFAULT_COOKIE_MAX_AGE = 86400000; // 1 day\n\nexport function createSessionMiddleware(options: SessionOptions) {\n const secrets = Array.isArray(options.secret) ? options.secret : [options.secret];\n const cookieName = options.cookie?.name ?? DEFAULT_COOKIE_NAME;\n const cookieMaxAge = options.cookie?.maxAge ?? DEFAULT_COOKIE_MAX_AGE;\n const store = options.store;\n\n // Ensure that the session cookie that we're reading from will be written to.\n const writeCookieNames = options.cookie?.writeNames ?? [cookieName];\n if (!writeCookieNames.includes(cookieName)) {\n throw new Error('cookie.name must be included in cookie.writeNames');\n }\n\n // Validate write overrides.\n if (options.cookie?.writeOverrides && !options.cookie.writeNames) {\n throw new Error('cookie.writeOverrides must be used with cookie.writeNames');\n }\n if (\n options.cookie?.writeOverrides &&\n options.cookie.writeOverrides.length !== writeCookieNames.length\n ) {\n throw new Error('cookie.writeOverrides must have the same length as cookie.writeNames');\n }\n\n return asyncHandler(async function sessionMiddleware(\n req: Request,\n res: Response,\n next: NextFunction,\n ) {\n const cookies = cookie.parse(req.headers.cookie ?? '');\n const sessionCookie = cookies[cookieName];\n const cookieSessionId = getSessionIdFromCookie(sessionCookie, secrets);\n const sessionId = cookieSessionId ?? (await generateSessionId());\n req.session = await loadSession(sessionId, req, store, cookieMaxAge);\n\n const originalHash = hashSession(req.session);\n const originalExpirationDate = req.session.getExpirationDate();\n\n onHeaders(res, () => {\n if (!req.session) {\n if (cookieSessionId) {\n // If the request arrived with a session cookie but the session was\n // destroyed, clear the cookie.\n //\n // To cover all our bases, we'll clear *all* known session cookies to\n // ensure that state sessions aren't left behind. We'll also send commands\n // to clear the cookies both on and off the explicit domain, to handle\n // the case where the application has moved from one domain to another.\n writeCookieNames.forEach((cookieName, i) => {\n res.clearCookie(cookieName);\n const domain = options.cookie?.writeOverrides?.[i]?.domain ?? options.cookie?.domain;\n if (domain) {\n res.clearCookie(cookieName, { domain: options.cookie?.domain });\n }\n });\n return;\n }\n\n // There is no session to do anything with.\n return;\n }\n\n const secureCookie = shouldSecureCookie(req, options.cookie?.secure ?? 'auto');\n if (secureCookie && req.protocol !== 'https') {\n // Avoid sending cookie over insecure connection.\n return;\n }\n\n // Ensure that all known session cookies are set to the same value.\n const hasAllCookies = writeCookieNames.every((cookieName) => !!cookies[cookieName]);\n const isNewSession = !cookieSessionId || cookieSessionId !== req.session.id;\n const didExpirationChange =\n originalExpirationDate.getTime() !== req.session.getExpirationDate().getTime();\n if (isNewSession || didExpirationChange || !hasAllCookies) {\n const signedSessionId = signSessionId(req.session.id, secrets[0]);\n writeCookieNames.forEach((cookieName, i) => {\n res.cookie(cookieName, signedSessionId, {\n secure: secureCookie,\n httpOnly: options.cookie?.httpOnly ?? true,\n domain: options.cookie?.domain,\n sameSite: options.cookie?.sameSite ?? false,\n expires: req.session.getExpirationDate(),\n ...options.cookie?.writeOverrides?.[i],\n });\n });\n }\n });\n\n let sessionPersisted = false;\n\n async function persistSession(req: Request) {\n if (!req.session || sessionPersisted) {\n // There is no session to do anything with.\n return;\n }\n\n sessionPersisted = true;\n\n // If this is a new session, we would have already persisted it to the\n // store, so we don't need to take that into consideration here.\n //\n // If the hash of the session data changed, we'll unconditionally persist\n // the updated data to the store. However, if the hash didn't change, we\n // only want to persist it if the expiration changed *and* if we can set\n // a cookie to reflect the updated expiration date.\n const hashChanged = hashSession(req.session) !== originalHash;\n const expirationChanged =\n originalExpirationDate.getTime() !== req.session.getExpirationDate().getTime();\n if (hashChanged || expirationChanged) {\n await store.set(\n req.session.id,\n req.session,\n // Cookies only support second-level resolution. To ensure consistency\n // between the cookie and the store, truncate the expiration date to\n // the nearest second.\n truncateExpirationDate(req.session.getExpirationDate()),\n );\n }\n }\n\n // We'll attempt to persist the session at the end of the request. This\n // hacky strategy is borrowed from `express-session`.\n beforeEnd(res, next, async () => {\n await persistSession(req);\n });\n\n // We'll also attempt to persist the session before performing a redirect.\n // This is necessary because browsers and `fetch()` implementations aren't\n // required to wait for a response body to be received before following a\n // redirect. So, we need to make sure that the session is persisted before\n // we send the redirect response. This way, the subsequent GET will be able\n // to load the latest session data.\n const originalRedirect = res.redirect as any;\n res.redirect = function redirect(...args: any[]) {\n persistSession(req).then(\n () => originalRedirect.apply(res, args),\n (err) => next(err),\n );\n };\n\n next();\n });\n}\n\nfunction signSessionId(sessionId: string, secret: string): string {\n return signature.sign(sessionId, secret);\n}\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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","sourcesContent":["import cookie from 'cookie';\nimport signature from 'cookie-signature';\nimport type { NextFunction, Request, Response } from 'express';\nimport asyncHandler from 'express-async-handler';\nimport onHeaders from 'on-headers';\n\nimport { beforeEnd } from './before-end.js';\nimport { type CookieSecure, getSessionIdFromCookie, shouldSecureCookie } from './cookie.js';\nimport {\n type Session,\n generateSessionId,\n hashSession,\n loadSession,\n truncateExpirationDate,\n} from './session.js';\nimport { type SessionStore } from './store.js';\n\ndeclare global {\n // eslint-disable-next-line @typescript-eslint/no-namespace\n namespace Express {\n interface Request {\n session: Session;\n }\n }\n}\n\ninterface CookieOptions {\n secure?: CookieSecure;\n httpOnly?: boolean;\n domain?: string;\n sameSite?: boolean | 'none' | 'lax' | 'strict';\n maxAge?: number;\n}\n\nexport interface SessionOptions {\n secret: string | string[];\n store: SessionStore;\n cookie?: CookieOptions & {\n /**\n * The name of the session cookie. The session is always read from this\n * named cookie, but it may be written to multiple cookies if `writeNames`\n * is provided.\n */\n name?: string;\n /**\n * Multiple write names can be provided to allow for a session cookie to be\n * written to multiple names. This can be useful for a migration of a cookie\n * to an explicit subdomain, for example.\n */\n writeNames?: string[];\n /**\n * Used with `writeNames` to provide additional options for each written cookie.\n */\n writeOverrides?: Omit<CookieOptions, 'secure'>[];\n };\n}\n\nexport type { SessionStore };\n\nconst DEFAULT_COOKIE_NAME = 'session';\nconst DEFAULT_COOKIE_MAX_AGE = 86400000; // 1 day\n\nexport function createSessionMiddleware(options: SessionOptions) {\n const secrets = Array.isArray(options.secret) ? options.secret : [options.secret];\n const cookieName = options.cookie?.name ?? DEFAULT_COOKIE_NAME;\n const cookieMaxAge = options.cookie?.maxAge ?? DEFAULT_COOKIE_MAX_AGE;\n const store = options.store;\n\n // Ensure that the session cookie that we're reading from will be written to.\n const writeCookieNames = options.cookie?.writeNames ?? [cookieName];\n if (!writeCookieNames.includes(cookieName)) {\n throw new Error('cookie.name must be included in cookie.writeNames');\n }\n\n // Validate write overrides.\n if (options.cookie?.writeOverrides && !options.cookie.writeNames) {\n throw new Error('cookie.writeOverrides must be used with cookie.writeNames');\n }\n if (\n options.cookie?.writeOverrides &&\n options.cookie.writeOverrides.length !== writeCookieNames.length\n ) {\n throw new Error('cookie.writeOverrides must have the same length as cookie.writeNames');\n }\n\n return asyncHandler(async function sessionMiddleware(\n req: Request,\n res: Response,\n next: NextFunction,\n ) {\n const cookies = cookie.parse(req.headers.cookie ?? '');\n const sessionCookie = cookies[cookieName];\n const cookieSessionId = getSessionIdFromCookie(sessionCookie, secrets);\n const sessionId = cookieSessionId ?? (await generateSessionId());\n req.session = await loadSession(sessionId, req, store, cookieMaxAge);\n\n const originalHash = hashSession(req.session);\n const originalExpirationDate = req.session.getExpirationDate();\n\n onHeaders(res, () => {\n if (!req.session) {\n if (cookieSessionId) {\n // If the request arrived with a session cookie but the session was\n // destroyed, clear the cookie.\n //\n // To cover all our bases, we'll clear *all* known session cookies to\n // ensure that state sessions aren't left behind. We'll also send commands\n // to clear the cookies both on and off the explicit domain, to handle\n // the case where the application has moved from one domain to another.\n writeCookieNames.forEach((cookieName, i) => {\n res.clearCookie(cookieName);\n const domain = options.cookie?.writeOverrides?.[i]?.domain ?? options.cookie?.domain;\n if (domain) {\n res.clearCookie(cookieName, { domain: options.cookie?.domain });\n }\n });\n return;\n }\n\n // There is no session to do anything with.\n return;\n }\n\n const secureCookie = shouldSecureCookie(req, options.cookie?.secure ?? 'auto');\n if (secureCookie && req.protocol !== 'https') {\n // Avoid sending cookie over insecure connection.\n return;\n }\n\n // Ensure that all known session cookies are set to the same value.\n const hasAllCookies = writeCookieNames.every((cookieName) => !!cookies[cookieName]);\n const isNewSession = !cookieSessionId || cookieSessionId !== req.session.id;\n const didExpirationChange =\n originalExpirationDate.getTime() !== req.session.getExpirationDate().getTime();\n if (isNewSession || didExpirationChange || !hasAllCookies) {\n const signedSessionId = signSessionId(req.session.id, secrets[0]);\n writeCookieNames.forEach((cookieName, i) => {\n res.cookie(cookieName, signedSessionId, {\n secure: secureCookie,\n httpOnly: options.cookie?.httpOnly ?? true,\n domain: options.cookie?.domain,\n sameSite: options.cookie?.sameSite ?? false,\n expires: req.session.getExpirationDate(),\n ...options.cookie?.writeOverrides?.[i],\n });\n });\n }\n });\n\n let sessionPersisted = false;\n\n async function persistSession(req: Request) {\n if (!req.session || sessionPersisted) {\n // There is no session to do anything with.\n return;\n }\n\n sessionPersisted = true;\n\n // If this is a new session, we would have already persisted it to the\n // store, so we don't need to take that into consideration here.\n //\n // If the hash of the session data changed, we'll unconditionally persist\n // the updated data to the store. However, if the hash didn't change, we\n // only want to persist it if the expiration changed *and* if we can set\n // a cookie to reflect the updated expiration date.\n const hashChanged = hashSession(req.session) !== originalHash;\n const expirationChanged =\n originalExpirationDate.getTime() !== req.session.getExpirationDate().getTime();\n if (hashChanged || expirationChanged) {\n await store.set(\n req.session.id,\n req.session,\n // Cookies only support second-level resolution. To ensure consistency\n // between the cookie and the store, truncate the expiration date to\n // the nearest second.\n truncateExpirationDate(req.session.getExpirationDate()),\n );\n }\n }\n\n // We'll attempt to persist the session at the end of the request. This\n // hacky strategy is borrowed from `express-session`.\n beforeEnd(res, next, async () => {\n await persistSession(req);\n });\n\n // We'll also attempt to persist the session before performing a redirect.\n // This is necessary because browsers and `fetch()` implementations aren't\n // required to wait for a response body to be received before following a\n // redirect. So, we need to make sure that the session is persisted before\n // we send the redirect response. This way, the subsequent GET will be able\n // to load the latest session data.\n const originalRedirect = res.redirect as any;\n res.redirect = function redirect(...args: any[]) {\n persistSession(req).then(\n () => originalRedirect.apply(res, args),\n (err) => next(err),\n );\n };\n\n next();\n });\n}\n\nfunction signSessionId(sessionId: string, secret: string): string {\n return signature.sign(sessionId, secret);\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"index.test.d.ts","sourceRoot":"","sources":["../src/index.test.ts"],"names":[],"mappings":""}
1
+ {"version":3,"file":"index.test.d.ts","sourceRoot":"","sources":["../src/index.test.ts"],"names":[],"mappings":"","sourcesContent":["import express from 'express';\nimport asyncHandler from 'express-async-handler';\nimport fetchCookie from 'fetch-cookie';\nimport fetch from 'node-fetch';\nimport setCookie from 'set-cookie-parser';\nimport { assert, describe, it } from 'vitest';\n\nimport { withServer } from '@prairielearn/express-test-utils';\n\nimport { MemoryStore } from './memory-store.js';\n\nimport { createSessionMiddleware } from './index.js';\n\nconst TEST_SECRET = 'test-secret';\n\nfunction parseSetCookie(header: string) {\n // TODO: The types for `set-cookie-parser` are outdated. See https://github.com/PrairieLearn/PrairieLearn/pull/13764.\n // For now, we will rely on the \"soft-deprecated\" fields and not change how this works.\n return setCookie.parse(setCookie.splitCookiesString(header));\n}\n\ndescribe('session middleware', () => {\n it('sets a session cookie', async () => {\n const app = express();\n app.use(createSessionMiddleware({ secret: TEST_SECRET, store: new MemoryStore() }));\n app.get('/', (_req, res) => res.sendStatus(200));\n\n await withServer(app, async ({ url }) => {\n const res = await fetch(url);\n assert.equal(res.status, 200);\n\n const header = res.headers.get('set-cookie');\n const cookies = parseSetCookie(header ?? '');\n assert.equal(cookies.length, 1);\n assert.equal(cookies[0].name, 'session');\n assert.equal(cookies[0].path, '/');\n });\n });\n\n it('sets a session cookie with options', async () => {\n const app = express();\n app.use(\n createSessionMiddleware({\n secret: TEST_SECRET,\n store: new MemoryStore(),\n cookie: {\n name: 'prairielearn_session',\n httpOnly: true,\n domain: '.localhost',\n sameSite: 'strict',\n maxAge: 1000,\n },\n }),\n );\n app.get('/', (_req, res) => res.sendStatus(200));\n\n await withServer(app, async ({ url }) => {\n const res = await fetch(url);\n assert.equal(res.status, 200);\n\n const header = res.headers.get('set-cookie');\n const cookies = parseSetCookie(header ?? '');\n assert.equal(cookies.length, 1);\n assert.equal(cookies[0].name, 'prairielearn_session');\n assert.equal(cookies[0].path, '/');\n assert.isTrue(cookies[0].httpOnly);\n assert.equal(cookies[0].domain, '.localhost');\n assert.equal(cookies[0].sameSite, 'Strict');\n });\n });\n\n it('sets a secure cookie for proxied HTTPS request', async () => {\n const app = express();\n app.enable('trust proxy');\n app.use(\n createSessionMiddleware({\n secret: TEST_SECRET,\n store: new MemoryStore(),\n cookie: {\n secure: true,\n },\n }),\n );\n app.get('/', (_req, res) => res.sendStatus(200));\n\n await withServer(app, async ({ url }) => {\n const res = await fetch(url, {\n headers: {\n 'X-Forwarded-Proto': 'https',\n },\n });\n assert.equal(res.status, 200);\n\n const header = res.headers.get('set-cookie');\n const cookies = parseSetCookie(header ?? '');\n assert.equal(cookies.length, 1);\n assert.equal(cookies[0].name, 'session');\n assert.equal(cookies[0].path, '/');\n assert.isTrue(cookies[0].secure);\n });\n });\n\n it('does not set a secure cookie for proxied HTTP request', async () => {\n const app = express();\n app.enable('trust proxy');\n app.use(\n createSessionMiddleware({\n secret: TEST_SECRET,\n store: new MemoryStore(),\n cookie: {\n secure: true,\n },\n }),\n );\n app.get('/', (_req, res) => res.sendStatus(200));\n\n await withServer(app, async ({ url }) => {\n const res = await fetch(url, {\n headers: {\n 'X-Forwarded-Proto': 'http',\n },\n });\n assert.equal(res.status, 200);\n\n const header = res.headers.get('set-cookie');\n assert.isNull(header);\n });\n });\n\n it('automatically sets secure for proxied HTTPS request', async () => {\n const app = express();\n app.enable('trust proxy');\n app.use(\n createSessionMiddleware({\n secret: TEST_SECRET,\n store: new MemoryStore(),\n cookie: {\n secure: 'auto',\n },\n }),\n );\n app.get('/', (_req, res) => res.sendStatus(200));\n\n await withServer(app, async ({ url }) => {\n const res = await fetch(url, {\n headers: {\n 'X-Forwarded-Proto': 'https',\n },\n });\n assert.equal(res.status, 200);\n\n const header = res.headers.get('set-cookie');\n const cookies = parseSetCookie(header ?? '');\n assert.equal(cookies.length, 1);\n assert.equal(cookies[0].name, 'session');\n assert.equal(cookies[0].path, '/');\n assert.isTrue(cookies[0].secure);\n });\n });\n\n it('automatically sets secure for proxied HTTP request', async () => {\n const app = express();\n app.enable('trust proxy');\n app.use(\n createSessionMiddleware({\n secret: TEST_SECRET,\n store: new MemoryStore(),\n cookie: {\n secure: 'auto',\n },\n }),\n );\n app.get('/', (_req, res) => res.sendStatus(200));\n\n await withServer(app, async ({ url }) => {\n const res = await fetch(url, {\n headers: {\n 'X-Forwarded-Proto': 'http',\n },\n });\n assert.equal(res.status, 200);\n\n const header = res.headers.get('set-cookie');\n const cookies = parseSetCookie(header ?? '');\n assert.equal(cookies.length, 1);\n assert.equal(cookies[0].name, 'session');\n assert.equal(cookies[0].path, '/');\n assert.isUndefined(cookies[0].secure);\n });\n });\n\n it('sets secure cookie based on a function', async () => {\n const app = express();\n app.enable('trust proxy');\n app.use(\n createSessionMiddleware({\n secret: TEST_SECRET,\n store: new MemoryStore(),\n cookie: {\n secure: (req) => req.hostname === 'example.com',\n },\n }),\n );\n app.get('/', (_req, res) => res.sendStatus(200));\n\n await withServer(app, async ({ url }) => {\n const insecureRes = await fetch(url, {\n headers: {\n 'X-Forwarded-Host': 'subdomain.example.com',\n 'X-Forwarded-Proto': 'http',\n },\n });\n assert.equal(insecureRes.status, 200);\n\n const insecureHeader = insecureRes.headers.get('set-cookie');\n const insecureCookie = parseSetCookie(insecureHeader ?? '');\n assert.equal(insecureCookie.length, 1);\n assert.equal(insecureCookie[0].name, 'session');\n assert.equal(insecureCookie[0].path, '/');\n assert.isUndefined(insecureCookie[0].secure);\n\n const secureRes = await fetch(url, {\n headers: {\n 'X-Forwarded-Host': 'example.com',\n 'X-Forwarded-Proto': 'https',\n },\n });\n assert.equal(secureRes.status, 200);\n\n const secureHeader = secureRes.headers.get('set-cookie');\n const secureCookies = parseSetCookie(secureHeader ?? '');\n assert.equal(secureCookies.length, 1);\n assert.equal(secureCookies[0].name, 'session');\n assert.equal(secureCookies[0].path, '/');\n assert.isTrue(secureCookies[0].secure);\n });\n });\n\n it('persists session data across requests', async () => {\n const app = express();\n app.use(\n createSessionMiddleware({\n store: new MemoryStore(),\n secret: TEST_SECRET,\n }),\n );\n app.get('/', (req, res) => {\n req.session.count ??= 0;\n req.session.count += 1;\n res.send(req.session.count.toString());\n });\n\n await withServer(app, async ({ url }) => {\n const fetchWithCookies = fetchCookie(fetch);\n\n let res = await fetchWithCookies(url);\n assert.equal(res.status, 200);\n assert.equal(await res.text(), '1');\n\n res = await fetchWithCookies(url);\n assert.equal(res.status, 200);\n assert.equal(await res.text(), '2');\n });\n });\n\n it('commits the session before sending a redirect', async () => {\n const app = express();\n app.use(\n createSessionMiddleware({\n store: new MemoryStore(),\n secret: TEST_SECRET,\n }),\n );\n app.post('/', (req, res) => {\n req.session.test = 'test';\n res.redirect(req.originalUrl);\n });\n app.get('/', (req, res) => {\n res.send(req.session.test ?? 'NO VALUE');\n });\n\n await withServer(app, async ({ url }) => {\n const res = await fetchCookie(fetch)(url, {\n method: 'POST',\n });\n assert.equal(res.status, 200);\n\n const body = await res.text();\n assert.equal(body, 'test');\n });\n });\n\n it('destroys session', async () => {\n const store = new MemoryStore();\n\n const app = express();\n app.use(\n createSessionMiddleware({\n store,\n secret: TEST_SECRET,\n }),\n );\n app.get('/', (_req, res) => res.sendStatus(200));\n app.use(\n '/destroy',\n asyncHandler(async (req, res) => {\n await req.session.destroy();\n res.sendStatus(200);\n }),\n );\n\n await withServer(app, async ({ url }) => {\n const fetchWithCookies = fetchCookie(fetch);\n\n // Generate a new session.\n const res = await fetchWithCookies(url);\n assert.equal(res.status, 200);\n await res.text();\n\n // Destroy the session.\n const destroyRes = await fetchWithCookies(`${url}/destroy`);\n assert.equal(destroyRes.status, 200);\n await destroyRes.text();\n\n // Ensure the session cookie was cleared in the response.\n const header = destroyRes.headers.get('set-cookie');\n const cookies = parseSetCookie(header ?? '');\n assert.equal(cookies.length, 1);\n assert.equal(cookies[0].name, 'session');\n assert.equal(cookies[0].path, '/');\n assert.equal(cookies[0].expires?.getTime(), 0);\n\n // Ensure the session was destroyed in the session store.\n const sessionId = cookies[0].value.split('.')[0];\n assert.isNull(await store.get(sessionId));\n });\n });\n\n it('regenerates session', async () => {\n const store = new MemoryStore();\n\n const app = express();\n app.use(\n createSessionMiddleware({\n store,\n secret: TEST_SECRET,\n }),\n );\n app.get('/', (req, res) => {\n res.send(req.session.regenerated ? 'true' : 'false');\n });\n app.get(\n '/regenerate',\n asyncHandler(async (req, res) => {\n await req.session.regenerate();\n req.session.regenerated = true;\n res.send('true');\n }),\n );\n\n await withServer(app, async ({ url }) => {\n const fetchWithCookies = fetchCookie(fetch);\n\n // Generate a new session.\n let res = await fetchWithCookies(url);\n assert.equal(res.status, 200);\n assert.equal(await res.text(), 'false');\n\n // Extract the original cookie value.\n let header = res.headers.get('set-cookie');\n let cookies = parseSetCookie(header ?? '');\n assert.equal(cookies.length, 1);\n const originalCookieValue = cookies[0].value;\n\n // Regenerate the session.\n res = await fetchWithCookies(`${url}/regenerate`);\n assert.equal(res.status, 200);\n assert.equal(await res.text(), 'true');\n\n // Ensure that the session cookie was changed.\n header = res.headers.get('set-cookie');\n cookies = parseSetCookie(header ?? '');\n assert.equal(cookies.length, 1);\n const newCookieValue = cookies[0].value;\n assert.notEqual(newCookieValue, originalCookieValue);\n\n // Ensure the original session is no longer present in the session store.\n const originalSessionId = originalCookieValue.split('.')[0];\n assert.isNull(await store.get(originalSessionId));\n\n // Ensure that the regenerated session data was persisted.\n res = await fetchWithCookies(url);\n assert.equal(res.status, 200);\n assert.equal(await res.text(), 'true');\n });\n });\n\n it('creates a new session if signature checks fail', async () => {\n const store = new MemoryStore();\n\n const app = express();\n app.use(\n createSessionMiddleware({\n store,\n secret: TEST_SECRET,\n }),\n );\n app.get('/', (req, res) => res.send(req.session.id));\n\n await withServer(app, async ({ url }) => {\n const cookieJar = new fetchCookie.toughCookie.CookieJar();\n const fetchWithCookies = fetchCookie(fetch, cookieJar);\n\n // Generate a new session.\n let res = await fetchWithCookies(url);\n assert.equal(res.status, 200);\n const originalSessionId = await res.text();\n\n // Tamper with the session cookie.\n const cookie = cookieJar.getCookiesSync(url)[0];\n cookie.value = 'tampered';\n cookieJar.setCookieSync(cookie, url);\n\n // Make sure we get a new session.\n res = await fetchWithCookies(url);\n assert.equal(res.status, 200);\n const newSessionId = await res.text();\n assert.notEqual(newSessionId, originalSessionId);\n\n // Make sure the existing session is still present in the store. We don't\n // want someone to be able to evict other sessions by submitting invalid\n // cookies.\n assert.isNotNull(await store.get(originalSessionId));\n });\n });\n\n it('does not re-set the cookie on subsequent requests', async () => {\n const app = express();\n app.use(\n createSessionMiddleware({\n store: new MemoryStore(),\n secret: TEST_SECRET,\n }),\n );\n app.get('/', (_req, res) => res.sendStatus(200));\n\n await withServer(app, async ({ url }) => {\n const fetchWithCookies = fetchCookie(fetch);\n\n // Generate a new session.\n let res = await fetchWithCookies(url);\n assert.equal(res.status, 200);\n\n // Make another request with the same session.\n res = await fetchWithCookies(url);\n assert.equal(res.status, 200);\n\n // Ensure that the cookie wasn't set again.\n const header = res.headers.get('set-cookie');\n assert.isNull(header);\n });\n });\n\n // For unknown reasons, this test is flaky in CI. It's likely due to a race\n // condition with the session store and the way that the middleware hooks\n // into the response lifecycle. We haven't been able to isolate the specific\n // cause, so we'll retry the test a few times to try to avoid a failure in CI.\n it('extends the expiration date of the cookie', { retry: 3 }, async () => {\n const store = new MemoryStore();\n\n const app = express();\n app.use(\n createSessionMiddleware({\n store,\n secret: TEST_SECRET,\n cookie: {\n maxAge: 1000,\n },\n }),\n );\n app.get('/', (_req, res) => res.sendStatus(200));\n app.get('/extend', (req, res) => {\n req.session.setExpiration(10000);\n res.sendStatus(200);\n });\n\n await withServer(app, async ({ url }) => {\n const fetchWithCookies = fetchCookie(fetch);\n\n // Generate a new session.\n let res = await fetchWithCookies(url);\n assert.equal(res.status, 200);\n await res.text();\n\n // Grab the original expiration date.\n let header = res.headers.get('set-cookie');\n let cookies = parseSetCookie(header ?? '');\n assert.equal(cookies.length, 1);\n assert.isUndefined(cookies[0].maxAge);\n const originalExpirationDate = cookies[0].expires;\n assert(originalExpirationDate);\n\n // Grab the session ID from the cookie.\n const sessionId = cookies[0].value.split('.')[0];\n\n // Ensure that the expiration dates are consistent between the cookie and the store.\n const session = await store.get(sessionId);\n assert.equal(originalExpirationDate.getTime(), session?.expiresAt.getTime());\n\n // Make another request with the same session.\n res = await fetchWithCookies(`${url}/extend`);\n assert.equal(res.status, 200);\n await res.text();\n\n // Ensure that the cookie was set again.\n header = res.headers.get('set-cookie');\n cookies = parseSetCookie(header ?? '');\n assert.equal(cookies.length, 1);\n assert.isUndefined(cookies[0].maxAge);\n const newExpirationDate = cookies[0].expires;\n assert(newExpirationDate);\n\n // Ensure that the expiration date was extended.\n assert.notEqual(newExpirationDate.getTime(), originalExpirationDate.getTime());\n\n // Ensure that the expiration dates are consistent between the cookie and the store.\n const newSession = await store.get(sessionId);\n assert.equal(newExpirationDate.getTime(), newSession?.expiresAt.getTime());\n });\n });\n\n it('does not persist session data if the session did not change', async () => {\n const store = new MemoryStore();\n\n let setCount = 0;\n const originalStoreSet = store.set.bind(store);\n store.set = async (...args) => {\n setCount += 1;\n await originalStoreSet(...args);\n };\n\n const app = express();\n app.use(\n createSessionMiddleware({\n store,\n secret: TEST_SECRET,\n }),\n );\n app.get('/', (_req, res) => res.sendStatus(200));\n\n await withServer(app, async ({ url }) => {\n const fetchWithCookies = fetchCookie(fetch);\n // Generate a new session.\n let res = await fetchWithCookies(url);\n assert.equal(res.status, 200);\n await res.text();\n\n // Ensure the session was persisted.\n assert.equal(setCount, 1);\n\n // Make another request with the same session.\n res = await fetchWithCookies(url);\n assert.equal(res.status, 200);\n await res.text();\n\n // Ensure the session was not persisted.\n assert.equal(setCount, 1);\n });\n });\n\n it('rotates to a new cookie when needed', async () => {\n const fetchWithCookies = fetchCookie(fetch);\n const store = new MemoryStore();\n\n // Will create \"legacy\" sessions.\n const legacyApp = express();\n legacyApp.use(\n createSessionMiddleware({\n store,\n secret: TEST_SECRET,\n cookie: {\n name: 'legacy_session',\n },\n }),\n );\n legacyApp.get('/', (req, res) => res.send(req.session.id.toString()));\n\n // Will create \"new\" sessions and upgrade \"legacy\" sessions.\n const app = express();\n app.use(\n createSessionMiddleware({\n store,\n secret: TEST_SECRET,\n cookie: {\n name: 'legacy_session',\n writeNames: ['legacy_session', 'session'],\n writeOverrides: [{ domain: undefined }, { domain: '.example.com' }],\n },\n }),\n );\n app.get('/', (req, res) => res.send(req.session.id.toString()));\n\n let legacySessionId: string | null = null;\n\n await withServer(legacyApp, async ({ url }) => {\n // Generate a legacy session.\n const res = await fetchWithCookies(url);\n assert.equal(res.status, 200);\n\n legacySessionId = await res.text();\n });\n\n await withServer(app, async ({ url }) => {\n const res = await fetchWithCookies(url);\n assert.equal(res.status, 200);\n\n const newSessionId = await res.text();\n\n // Ensure that the new session cookie was set.\n const header = res.headers.get('set-cookie');\n assert.isNotNull(header);\n const cookies = parseSetCookie(header ?? '');\n assert.equal(cookies.length, 2);\n assert.equal(cookies[0].name, 'legacy_session');\n assert.isUndefined(cookies[0].domain);\n assert.equal(cookies[1].name, 'session');\n assert.equal(cookies[1].domain, '.example.com');\n\n // Ensure that the legacy session is migrated to a new session.\n assert.equal(newSessionId, legacySessionId);\n });\n });\n\n it('persists the session immediately after creation', async () => {\n const store = new MemoryStore();\n\n const app = express();\n app.use(\n createSessionMiddleware({\n store,\n secret: TEST_SECRET,\n }),\n );\n app.get(\n '/',\n asyncHandler(async (req, res) => {\n const persistedSession = await store.get(req.session.id);\n res.status(persistedSession == null ? 500 : 200).send();\n }),\n );\n\n await withServer(app, async ({ url }) => {\n const fetchWithCookies = fetchCookie(fetch);\n\n const res = await fetchWithCookies(url);\n assert.equal(res.status, 200);\n });\n });\n});\n"]}
@@ -9,6 +9,8 @@ import { MemoryStore } from './memory-store.js';
9
9
  import { createSessionMiddleware } from './index.js';
10
10
  const TEST_SECRET = 'test-secret';
11
11
  function parseSetCookie(header) {
12
+ // TODO: The types for `set-cookie-parser` are outdated. See https://github.com/PrairieLearn/PrairieLearn/pull/13764.
13
+ // For now, we will rely on the "soft-deprecated" fields and not change how this works.
12
14
  return setCookie.parse(setCookie.splitCookiesString(header));
13
15
  }
14
16
  describe('session middleware', () => {
@@ -1 +1 @@
1
- {"version":3,"file":"index.test.js","sourceRoot":"","sources":["../src/index.test.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,MAAM,SAAS,CAAC;AAC9B,OAAO,YAAY,MAAM,uBAAuB,CAAC;AACjD,OAAO,WAAW,MAAM,cAAc,CAAC;AACvC,OAAO,KAAK,MAAM,YAAY,CAAC;AAC/B,OAAO,SAAS,MAAM,mBAAmB,CAAC;AAC1C,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAE9C,OAAO,EAAE,UAAU,EAAE,MAAM,kCAAkC,CAAC;AAE9D,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEhD,OAAO,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC;AAErD,MAAM,WAAW,GAAG,aAAa,CAAC;AAElC,SAAS,cAAc,CAAC,MAAc;IACpC,OAAO,SAAS,CAAC,KAAK,CAAC,SAAS,CAAC,kBAAkB,CAAC,MAAM,CAAC,CAAC,CAAC;AAC/D,CAAC;AAED,QAAQ,CAAC,oBAAoB,EAAE,GAAG,EAAE;IAClC,EAAE,CAAC,uBAAuB,EAAE,KAAK,IAAI,EAAE;QACrC,MAAM,GAAG,GAAG,OAAO,EAAE,CAAC;QACtB,GAAG,CAAC,GAAG,CAAC,uBAAuB,CAAC,EAAE,MAAM,EAAE,WAAW,EAAE,KAAK,EAAE,IAAI,WAAW,EAAE,EAAE,CAAC,CAAC,CAAC;QACpF,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC;QAEjD,MAAM,UAAU,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE;YACtC,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,CAAC;YAC7B,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;YAE9B,MAAM,MAAM,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;YAC7C,MAAM,OAAO,GAAG,cAAc,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC;YAC7C,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;YAChC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;YACzC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;QACrC,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oCAAoC,EAAE,KAAK,IAAI,EAAE;QAClD,MAAM,GAAG,GAAG,OAAO,EAAE,CAAC;QACtB,GAAG,CAAC,GAAG,CACL,uBAAuB,CAAC;YACtB,MAAM,EAAE,WAAW;YACnB,KAAK,EAAE,IAAI,WAAW,EAAE;YACxB,MAAM,EAAE;gBACN,IAAI,EAAE,sBAAsB;gBAC5B,QAAQ,EAAE,IAAI;gBACd,MAAM,EAAE,YAAY;gBACpB,QAAQ,EAAE,QAAQ;gBAClB,MAAM,EAAE,IAAI;aACb;SACF,CAAC,CACH,CAAC;QACF,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC;QAEjD,MAAM,UAAU,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE;YACtC,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,CAAC;YAC7B,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;YAE9B,MAAM,MAAM,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;YAC7C,MAAM,OAAO,GAAG,cAAc,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC;YAC7C,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;YAChC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,sBAAsB,CAAC,CAAC;YACtD,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;YACnC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;YACnC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;YAC9C,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;QAC9C,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gDAAgD,EAAE,KAAK,IAAI,EAAE;QAC9D,MAAM,GAAG,GAAG,OAAO,EAAE,CAAC;QACtB,GAAG,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC;QAC1B,GAAG,CAAC,GAAG,CACL,uBAAuB,CAAC;YACtB,MAAM,EAAE,WAAW;YACnB,KAAK,EAAE,IAAI,WAAW,EAAE;YACxB,MAAM,EAAE;gBACN,MAAM,EAAE,IAAI;aACb;SACF,CAAC,CACH,CAAC;QACF,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC;QAEjD,MAAM,UAAU,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE;YACtC,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;gBAC3B,OAAO,EAAE;oBACP,mBAAmB,EAAE,OAAO;iBAC7B;aACF,CAAC,CAAC;YACH,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;YAE9B,MAAM,MAAM,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;YAC7C,MAAM,OAAO,GAAG,cAAc,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC;YAC7C,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;YAChC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;YACzC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;YACnC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;QACnC,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uDAAuD,EAAE,KAAK,IAAI,EAAE;QACrE,MAAM,GAAG,GAAG,OAAO,EAAE,CAAC;QACtB,GAAG,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC;QAC1B,GAAG,CAAC,GAAG,CACL,uBAAuB,CAAC;YACtB,MAAM,EAAE,WAAW;YACnB,KAAK,EAAE,IAAI,WAAW,EAAE;YACxB,MAAM,EAAE;gBACN,MAAM,EAAE,IAAI;aACb;SACF,CAAC,CACH,CAAC;QACF,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC;QAEjD,MAAM,UAAU,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE;YACtC,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;gBAC3B,OAAO,EAAE;oBACP,mBAAmB,EAAE,MAAM;iBAC5B;aACF,CAAC,CAAC;YACH,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;YAE9B,MAAM,MAAM,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;YAC7C,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QACxB,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qDAAqD,EAAE,KAAK,IAAI,EAAE;QACnE,MAAM,GAAG,GAAG,OAAO,EAAE,CAAC;QACtB,GAAG,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC;QAC1B,GAAG,CAAC,GAAG,CACL,uBAAuB,CAAC;YACtB,MAAM,EAAE,WAAW;YACnB,KAAK,EAAE,IAAI,WAAW,EAAE;YACxB,MAAM,EAAE;gBACN,MAAM,EAAE,MAAM;aACf;SACF,CAAC,CACH,CAAC;QACF,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC;QAEjD,MAAM,UAAU,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE;YACtC,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;gBAC3B,OAAO,EAAE;oBACP,mBAAmB,EAAE,OAAO;iBAC7B;aACF,CAAC,CAAC;YACH,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;YAE9B,MAAM,MAAM,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;YAC7C,MAAM,OAAO,GAAG,cAAc,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC;YAC7C,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;YAChC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;YACzC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;YACnC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;QACnC,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oDAAoD,EAAE,KAAK,IAAI,EAAE;QAClE,MAAM,GAAG,GAAG,OAAO,EAAE,CAAC;QACtB,GAAG,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC;QAC1B,GAAG,CAAC,GAAG,CACL,uBAAuB,CAAC;YACtB,MAAM,EAAE,WAAW;YACnB,KAAK,EAAE,IAAI,WAAW,EAAE;YACxB,MAAM,EAAE;gBACN,MAAM,EAAE,MAAM;aACf;SACF,CAAC,CACH,CAAC;QACF,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC;QAEjD,MAAM,UAAU,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE;YACtC,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;gBAC3B,OAAO,EAAE;oBACP,mBAAmB,EAAE,MAAM;iBAC5B;aACF,CAAC,CAAC;YACH,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;YAE9B,MAAM,MAAM,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;YAC7C,MAAM,OAAO,GAAG,cAAc,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC;YAC7C,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;YAChC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;YACzC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;YACnC,MAAM,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;QACxC,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wCAAwC,EAAE,KAAK,IAAI,EAAE;QACtD,MAAM,GAAG,GAAG,OAAO,EAAE,CAAC;QACtB,GAAG,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC;QAC1B,GAAG,CAAC,GAAG,CACL,uBAAuB,CAAC;YACtB,MAAM,EAAE,WAAW;YACnB,KAAK,EAAE,IAAI,WAAW,EAAE;YACxB,MAAM,EAAE;gBACN,MAAM,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,QAAQ,KAAK,aAAa;aAChD;SACF,CAAC,CACH,CAAC;QACF,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC;QAEjD,MAAM,UAAU,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE;YACtC,MAAM,WAAW,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;gBACnC,OAAO,EAAE;oBACP,kBAAkB,EAAE,uBAAuB;oBAC3C,mBAAmB,EAAE,MAAM;iBAC5B;aACF,CAAC,CAAC;YACH,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;YAEtC,MAAM,cAAc,GAAG,WAAW,CAAC,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;YAC7D,MAAM,cAAc,GAAG,cAAc,CAAC,cAAc,IAAI,EAAE,CAAC,CAAC;YAC5D,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;YACvC,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;YAChD,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;YAC1C,MAAM,CAAC,WAAW,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;YAE7C,MAAM,SAAS,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;gBACjC,OAAO,EAAE;oBACP,kBAAkB,EAAE,aAAa;oBACjC,mBAAmB,EAAE,OAAO;iBAC7B;aACF,CAAC,CAAC;YACH,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;YAEpC,MAAM,YAAY,GAAG,SAAS,CAAC,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;YACzD,MAAM,aAAa,GAAG,cAAc,CAAC,YAAY,IAAI,EAAE,CAAC,CAAC;YACzD,MAAM,CAAC,KAAK,CAAC,aAAa,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;YACtC,MAAM,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;YAC/C,MAAM,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;YACzC,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;QACzC,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uCAAuC,EAAE,KAAK,IAAI,EAAE;QACrD,MAAM,GAAG,GAAG,OAAO,EAAE,CAAC;QACtB,GAAG,CAAC,GAAG,CACL,uBAAuB,CAAC;YACtB,KAAK,EAAE,IAAI,WAAW,EAAE;YACxB,MAAM,EAAE,WAAW;SACpB,CAAC,CACH,CAAC;QACF,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;YACxB,GAAG,CAAC,OAAO,CAAC,KAAK,KAAK,CAAC,CAAC;YACxB,GAAG,CAAC,OAAO,CAAC,KAAK,IAAI,CAAC,CAAC;YACvB,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC;QACzC,CAAC,CAAC,CAAC;QAEH,MAAM,UAAU,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE;YACtC,MAAM,gBAAgB,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC;YAE5C,IAAI,GAAG,GAAG,MAAM,gBAAgB,CAAC,GAAG,CAAC,CAAC;YACtC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;YAC9B,MAAM,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,EAAE,GAAG,CAAC,CAAC;YAEpC,GAAG,GAAG,MAAM,gBAAgB,CAAC,GAAG,CAAC,CAAC;YAClC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;YAC9B,MAAM,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,EAAE,GAAG,CAAC,CAAC;QACtC,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+CAA+C,EAAE,KAAK,IAAI,EAAE;QAC7D,MAAM,GAAG,GAAG,OAAO,EAAE,CAAC;QACtB,GAAG,CAAC,GAAG,CACL,uBAAuB,CAAC;YACtB,KAAK,EAAE,IAAI,WAAW,EAAE;YACxB,MAAM,EAAE,WAAW;SACpB,CAAC,CACH,CAAC;QACF,GAAG,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;YACzB,GAAG,CAAC,OAAO,CAAC,IAAI,GAAG,MAAM,CAAC;YAC1B,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;QAChC,CAAC,CAAC,CAAC;QACH,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;YACxB,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,IAAI,UAAU,CAAC,CAAC;QAC3C,CAAC,CAAC,CAAC;QAEH,MAAM,UAAU,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE;YACtC,MAAM,GAAG,GAAG,MAAM,WAAW,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE;gBACxC,MAAM,EAAE,MAAM;aACf,CAAC,CAAC;YACH,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;YAE9B,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;YAC9B,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QAC7B,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kBAAkB,EAAE,KAAK,IAAI,EAAE;QAChC,MAAM,KAAK,GAAG,IAAI,WAAW,EAAE,CAAC;QAEhC,MAAM,GAAG,GAAG,OAAO,EAAE,CAAC;QACtB,GAAG,CAAC,GAAG,CACL,uBAAuB,CAAC;YACtB,KAAK;YACL,MAAM,EAAE,WAAW;SACpB,CAAC,CACH,CAAC;QACF,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC;QACjD,GAAG,CAAC,GAAG,CACL,UAAU,EACV,YAAY,CAAC,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE;YAC9B,MAAM,GAAG,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;YAC5B,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;QACtB,CAAC,CAAC,CACH,CAAC;QAEF,MAAM,UAAU,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE;YACtC,MAAM,gBAAgB,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC;YAE5C,0BAA0B;YAC1B,MAAM,GAAG,GAAG,MAAM,gBAAgB,CAAC,GAAG,CAAC,CAAC;YACxC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;YAC9B,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;YAEjB,uBAAuB;YACvB,MAAM,UAAU,GAAG,MAAM,gBAAgB,CAAC,GAAG,GAAG,UAAU,CAAC,CAAC;YAC5D,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;YACrC,MAAM,UAAU,CAAC,IAAI,EAAE,CAAC;YAExB,yDAAyD;YACzD,MAAM,MAAM,GAAG,UAAU,CAAC,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;YACpD,MAAM,OAAO,GAAG,cAAc,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC;YAC7C,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;YAChC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;YACzC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;YACnC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;YAE/C,yDAAyD;YACzD,MAAM,SAAS,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;YACjD,MAAM,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC;QAC5C,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qBAAqB,EAAE,KAAK,IAAI,EAAE;QACnC,MAAM,KAAK,GAAG,IAAI,WAAW,EAAE,CAAC;QAEhC,MAAM,GAAG,GAAG,OAAO,EAAE,CAAC;QACtB,GAAG,CAAC,GAAG,CACL,uBAAuB,CAAC;YACtB,KAAK;YACL,MAAM,EAAE,WAAW;SACpB,CAAC,CACH,CAAC;QACF,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;YACxB,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;QACvD,CAAC,CAAC,CAAC;QACH,GAAG,CAAC,GAAG,CACL,aAAa,EACb,YAAY,CAAC,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE;YAC9B,MAAM,GAAG,CAAC,OAAO,CAAC,UAAU,EAAE,CAAC;YAC/B,GAAG,CAAC,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC;YAC/B,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACnB,CAAC,CAAC,CACH,CAAC;QAEF,MAAM,UAAU,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE;YACtC,MAAM,gBAAgB,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC;YAE5C,0BAA0B;YAC1B,IAAI,GAAG,GAAG,MAAM,gBAAgB,CAAC,GAAG,CAAC,CAAC;YACtC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;YAC9B,MAAM,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,EAAE,OAAO,CAAC,CAAC;YAExC,qCAAqC;YACrC,IAAI,MAAM,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;YAC3C,IAAI,OAAO,GAAG,cAAc,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC;YAC3C,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;YAChC,MAAM,mBAAmB,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;YAE7C,0BAA0B;YAC1B,GAAG,GAAG,MAAM,gBAAgB,CAAC,GAAG,GAAG,aAAa,CAAC,CAAC;YAClD,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;YAC9B,MAAM,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,EAAE,MAAM,CAAC,CAAC;YAEvC,8CAA8C;YAC9C,MAAM,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;YACvC,OAAO,GAAG,cAAc,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC;YACvC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;YAChC,MAAM,cAAc,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;YACxC,MAAM,CAAC,QAAQ,CAAC,cAAc,EAAE,mBAAmB,CAAC,CAAC;YAErD,yEAAyE;YACzE,MAAM,iBAAiB,GAAG,mBAAmB,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;YAC5D,MAAM,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC,CAAC;YAElD,0DAA0D;YAC1D,GAAG,GAAG,MAAM,gBAAgB,CAAC,GAAG,CAAC,CAAC;YAClC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;YAC9B,MAAM,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,EAAE,MAAM,CAAC,CAAC;QACzC,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gDAAgD,EAAE,KAAK,IAAI,EAAE;QAC9D,MAAM,KAAK,GAAG,IAAI,WAAW,EAAE,CAAC;QAEhC,MAAM,GAAG,GAAG,OAAO,EAAE,CAAC;QACtB,GAAG,CAAC,GAAG,CACL,uBAAuB,CAAC;YACtB,KAAK;YACL,MAAM,EAAE,WAAW;SACpB,CAAC,CACH,CAAC;QACF,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC;QAErD,MAAM,UAAU,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE;YACtC,MAAM,SAAS,GAAG,IAAI,WAAW,CAAC,WAAW,CAAC,SAAS,EAAE,CAAC;YAC1D,MAAM,gBAAgB,GAAG,WAAW,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC;YAEvD,0BAA0B;YAC1B,IAAI,GAAG,GAAG,MAAM,gBAAgB,CAAC,GAAG,CAAC,CAAC;YACtC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;YAC9B,MAAM,iBAAiB,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;YAE3C,kCAAkC;YAClC,MAAM,MAAM,GAAG,SAAS,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;YAChD,MAAM,CAAC,KAAK,GAAG,UAAU,CAAC;YAC1B,SAAS,CAAC,aAAa,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;YAErC,kCAAkC;YAClC,GAAG,GAAG,MAAM,gBAAgB,CAAC,GAAG,CAAC,CAAC;YAClC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;YAC9B,MAAM,YAAY,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;YACtC,MAAM,CAAC,QAAQ,CAAC,YAAY,EAAE,iBAAiB,CAAC,CAAC;YAEjD,yEAAyE;YACzE,wEAAwE;YACxE,WAAW;YACX,MAAM,CAAC,SAAS,CAAC,MAAM,KAAK,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC,CAAC;QACvD,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mDAAmD,EAAE,KAAK,IAAI,EAAE;QACjE,MAAM,GAAG,GAAG,OAAO,EAAE,CAAC;QACtB,GAAG,CAAC,GAAG,CACL,uBAAuB,CAAC;YACtB,KAAK,EAAE,IAAI,WAAW,EAAE;YACxB,MAAM,EAAE,WAAW;SACpB,CAAC,CACH,CAAC;QACF,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC;QAEjD,MAAM,UAAU,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE;YACtC,MAAM,gBAAgB,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC;YAE5C,0BAA0B;YAC1B,IAAI,GAAG,GAAG,MAAM,gBAAgB,CAAC,GAAG,CAAC,CAAC;YACtC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;YAE9B,8CAA8C;YAC9C,GAAG,GAAG,MAAM,gBAAgB,CAAC,GAAG,CAAC,CAAC;YAClC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;YAE9B,2CAA2C;YAC3C,MAAM,MAAM,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;YAC7C,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QACxB,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,2EAA2E;IAC3E,yEAAyE;IACzE,4EAA4E;IAC5E,8EAA8E;IAC9E,EAAE,CAAC,2CAA2C,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE,KAAK,IAAI,EAAE;QACvE,MAAM,KAAK,GAAG,IAAI,WAAW,EAAE,CAAC;QAEhC,MAAM,GAAG,GAAG,OAAO,EAAE,CAAC;QACtB,GAAG,CAAC,GAAG,CACL,uBAAuB,CAAC;YACtB,KAAK;YACL,MAAM,EAAE,WAAW;YACnB,MAAM,EAAE;gBACN,MAAM,EAAE,IAAI;aACb;SACF,CAAC,CACH,CAAC;QACF,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC;QACjD,GAAG,CAAC,GAAG,CAAC,SAAS,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;YAC9B,GAAG,CAAC,OAAO,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;YACjC,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;QACtB,CAAC,CAAC,CAAC;QAEH,MAAM,UAAU,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE;YACtC,MAAM,gBAAgB,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC;YAE5C,0BAA0B;YAC1B,IAAI,GAAG,GAAG,MAAM,gBAAgB,CAAC,GAAG,CAAC,CAAC;YACtC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;YAC9B,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;YAEjB,qCAAqC;YACrC,IAAI,MAAM,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;YAC3C,IAAI,OAAO,GAAG,cAAc,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC;YAC3C,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;YAChC,MAAM,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;YACtC,MAAM,sBAAsB,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;YAClD,MAAM,CAAC,sBAAsB,CAAC,CAAC;YAE/B,uCAAuC;YACvC,MAAM,SAAS,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;YAEjD,oFAAoF;YACpF,MAAM,OAAO,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;YAC3C,MAAM,CAAC,KAAK,CAAC,sBAAsB,CAAC,OAAO,EAAE,EAAE,OAAO,EAAE,SAAS,CAAC,OAAO,EAAE,CAAC,CAAC;YAE7E,8CAA8C;YAC9C,GAAG,GAAG,MAAM,gBAAgB,CAAC,GAAG,GAAG,SAAS,CAAC,CAAC;YAC9C,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;YAC9B,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;YAEjB,wCAAwC;YACxC,MAAM,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;YACvC,OAAO,GAAG,cAAc,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC;YACvC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;YAChC,MAAM,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;YACtC,MAAM,iBAAiB,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;YAC7C,MAAM,CAAC,iBAAiB,CAAC,CAAC;YAE1B,gDAAgD;YAChD,MAAM,CAAC,QAAQ,CAAC,iBAAiB,CAAC,OAAO,EAAE,EAAE,sBAAsB,CAAC,OAAO,EAAE,CAAC,CAAC;YAE/E,oFAAoF;YACpF,MAAM,UAAU,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;YAC9C,MAAM,CAAC,KAAK,CAAC,iBAAiB,CAAC,OAAO,EAAE,EAAE,UAAU,EAAE,SAAS,CAAC,OAAO,EAAE,CAAC,CAAC;QAC7E,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6DAA6D,EAAE,KAAK,IAAI,EAAE;QAC3E,MAAM,KAAK,GAAG,IAAI,WAAW,EAAE,CAAC;QAEhC,IAAI,QAAQ,GAAG,CAAC,CAAC;QACjB,MAAM,gBAAgB,GAAG,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC/C,KAAK,CAAC,GAAG,GAAG,KAAK,EAAE,GAAG,IAAI,EAAE,EAAE;YAC5B,QAAQ,IAAI,CAAC,CAAC;YACd,MAAM,gBAAgB,CAAC,GAAG,IAAI,CAAC,CAAC;QAClC,CAAC,CAAC;QAEF,MAAM,GAAG,GAAG,OAAO,EAAE,CAAC;QACtB,GAAG,CAAC,GAAG,CACL,uBAAuB,CAAC;YACtB,KAAK;YACL,MAAM,EAAE,WAAW;SACpB,CAAC,CACH,CAAC;QACF,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC;QAEjD,MAAM,UAAU,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE;YACtC,MAAM,gBAAgB,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC;YAC5C,0BAA0B;YAC1B,IAAI,GAAG,GAAG,MAAM,gBAAgB,CAAC,GAAG,CAAC,CAAC;YACtC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;YAC9B,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;YAEjB,oCAAoC;YACpC,MAAM,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC;YAE1B,8CAA8C;YAC9C,GAAG,GAAG,MAAM,gBAAgB,CAAC,GAAG,CAAC,CAAC;YAClC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;YAC9B,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;YAEjB,wCAAwC;YACxC,MAAM,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC;QAC5B,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qCAAqC,EAAE,KAAK,IAAI,EAAE;QACnD,MAAM,gBAAgB,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC;QAC5C,MAAM,KAAK,GAAG,IAAI,WAAW,EAAE,CAAC;QAEhC,iCAAiC;QACjC,MAAM,SAAS,GAAG,OAAO,EAAE,CAAC;QAC5B,SAAS,CAAC,GAAG,CACX,uBAAuB,CAAC;YACtB,KAAK;YACL,MAAM,EAAE,WAAW;YACnB,MAAM,EAAE;gBACN,IAAI,EAAE,gBAAgB;aACvB;SACF,CAAC,CACH,CAAC;QACF,SAAS,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC;QAEtE,4DAA4D;QAC5D,MAAM,GAAG,GAAG,OAAO,EAAE,CAAC;QACtB,GAAG,CAAC,GAAG,CACL,uBAAuB,CAAC;YACtB,KAAK;YACL,MAAM,EAAE,WAAW;YACnB,MAAM,EAAE;gBACN,IAAI,EAAE,gBAAgB;gBACtB,UAAU,EAAE,CAAC,gBAAgB,EAAE,SAAS,CAAC;gBACzC,cAAc,EAAE,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,EAAE,EAAE,MAAM,EAAE,cAAc,EAAE,CAAC;aACpE;SACF,CAAC,CACH,CAAC;QACF,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC;QAEhE,IAAI,eAAe,GAAkB,IAAI,CAAC;QAE1C,MAAM,UAAU,CAAC,SAAS,EAAE,KAAK,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE;YAC5C,6BAA6B;YAC7B,MAAM,GAAG,GAAG,MAAM,gBAAgB,CAAC,GAAG,CAAC,CAAC;YACxC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;YAE9B,eAAe,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;QACrC,CAAC,CAAC,CAAC;QAEH,MAAM,UAAU,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE;YACtC,MAAM,GAAG,GAAG,MAAM,gBAAgB,CAAC,GAAG,CAAC,CAAC;YACxC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;YAE9B,MAAM,YAAY,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;YAEtC,8CAA8C;YAC9C,MAAM,MAAM,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;YAC7C,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;YACzB,MAAM,OAAO,GAAG,cAAc,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC;YAC7C,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;YAChC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,gBAAgB,CAAC,CAAC;YAChD,MAAM,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;YACtC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;YACzC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;YAEhD,+DAA+D;YAC/D,MAAM,CAAC,KAAK,CAAC,YAAY,EAAE,eAAe,CAAC,CAAC;QAC9C,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iDAAiD,EAAE,KAAK,IAAI,EAAE;QAC/D,MAAM,KAAK,GAAG,IAAI,WAAW,EAAE,CAAC;QAEhC,MAAM,GAAG,GAAG,OAAO,EAAE,CAAC;QACtB,GAAG,CAAC,GAAG,CACL,uBAAuB,CAAC;YACtB,KAAK;YACL,MAAM,EAAE,WAAW;SACpB,CAAC,CACH,CAAC;QACF,GAAG,CAAC,GAAG,CACL,GAAG,EACH,YAAY,CAAC,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE;YAC9B,MAAM,gBAAgB,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;YACzD,GAAG,CAAC,MAAM,CAAC,gBAAgB,IAAI,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;QAC1D,CAAC,CAAC,CACH,CAAC;QAEF,MAAM,UAAU,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE;YACtC,MAAM,gBAAgB,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC;YAE5C,MAAM,GAAG,GAAG,MAAM,gBAAgB,CAAC,GAAG,CAAC,CAAC;YACxC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QAChC,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC","sourcesContent":["import express from 'express';\nimport asyncHandler from 'express-async-handler';\nimport fetchCookie from 'fetch-cookie';\nimport fetch from 'node-fetch';\nimport setCookie from 'set-cookie-parser';\nimport { assert, describe, it } from 'vitest';\n\nimport { withServer } from '@prairielearn/express-test-utils';\n\nimport { MemoryStore } from './memory-store.js';\n\nimport { createSessionMiddleware } from './index.js';\n\nconst TEST_SECRET = 'test-secret';\n\nfunction parseSetCookie(header: string) {\n return setCookie.parse(setCookie.splitCookiesString(header));\n}\n\ndescribe('session middleware', () => {\n it('sets a session cookie', async () => {\n const app = express();\n app.use(createSessionMiddleware({ secret: TEST_SECRET, store: new MemoryStore() }));\n app.get('/', (_req, res) => res.sendStatus(200));\n\n await withServer(app, async ({ url }) => {\n const res = await fetch(url);\n assert.equal(res.status, 200);\n\n const header = res.headers.get('set-cookie');\n const cookies = parseSetCookie(header ?? '');\n assert.equal(cookies.length, 1);\n assert.equal(cookies[0].name, 'session');\n assert.equal(cookies[0].path, '/');\n });\n });\n\n it('sets a session cookie with options', async () => {\n const app = express();\n app.use(\n createSessionMiddleware({\n secret: TEST_SECRET,\n store: new MemoryStore(),\n cookie: {\n name: 'prairielearn_session',\n httpOnly: true,\n domain: '.localhost',\n sameSite: 'strict',\n maxAge: 1000,\n },\n }),\n );\n app.get('/', (_req, res) => res.sendStatus(200));\n\n await withServer(app, async ({ url }) => {\n const res = await fetch(url);\n assert.equal(res.status, 200);\n\n const header = res.headers.get('set-cookie');\n const cookies = parseSetCookie(header ?? '');\n assert.equal(cookies.length, 1);\n assert.equal(cookies[0].name, 'prairielearn_session');\n assert.equal(cookies[0].path, '/');\n assert.isTrue(cookies[0].httpOnly);\n assert.equal(cookies[0].domain, '.localhost');\n assert.equal(cookies[0].sameSite, 'Strict');\n });\n });\n\n it('sets a secure cookie for proxied HTTPS request', async () => {\n const app = express();\n app.enable('trust proxy');\n app.use(\n createSessionMiddleware({\n secret: TEST_SECRET,\n store: new MemoryStore(),\n cookie: {\n secure: true,\n },\n }),\n );\n app.get('/', (_req, res) => res.sendStatus(200));\n\n await withServer(app, async ({ url }) => {\n const res = await fetch(url, {\n headers: {\n 'X-Forwarded-Proto': 'https',\n },\n });\n assert.equal(res.status, 200);\n\n const header = res.headers.get('set-cookie');\n const cookies = parseSetCookie(header ?? '');\n assert.equal(cookies.length, 1);\n assert.equal(cookies[0].name, 'session');\n assert.equal(cookies[0].path, '/');\n assert.isTrue(cookies[0].secure);\n });\n });\n\n it('does not set a secure cookie for proxied HTTP request', async () => {\n const app = express();\n app.enable('trust proxy');\n app.use(\n createSessionMiddleware({\n secret: TEST_SECRET,\n store: new MemoryStore(),\n cookie: {\n secure: true,\n },\n }),\n );\n app.get('/', (_req, res) => res.sendStatus(200));\n\n await withServer(app, async ({ url }) => {\n const res = await fetch(url, {\n headers: {\n 'X-Forwarded-Proto': 'http',\n },\n });\n assert.equal(res.status, 200);\n\n const header = res.headers.get('set-cookie');\n assert.isNull(header);\n });\n });\n\n it('automatically sets secure for proxied HTTPS request', async () => {\n const app = express();\n app.enable('trust proxy');\n app.use(\n createSessionMiddleware({\n secret: TEST_SECRET,\n store: new MemoryStore(),\n cookie: {\n secure: 'auto',\n },\n }),\n );\n app.get('/', (_req, res) => res.sendStatus(200));\n\n await withServer(app, async ({ url }) => {\n const res = await fetch(url, {\n headers: {\n 'X-Forwarded-Proto': 'https',\n },\n });\n assert.equal(res.status, 200);\n\n const header = res.headers.get('set-cookie');\n const cookies = parseSetCookie(header ?? '');\n assert.equal(cookies.length, 1);\n assert.equal(cookies[0].name, 'session');\n assert.equal(cookies[0].path, '/');\n assert.isTrue(cookies[0].secure);\n });\n });\n\n it('automatically sets secure for proxied HTTP request', async () => {\n const app = express();\n app.enable('trust proxy');\n app.use(\n createSessionMiddleware({\n secret: TEST_SECRET,\n store: new MemoryStore(),\n cookie: {\n secure: 'auto',\n },\n }),\n );\n app.get('/', (_req, res) => res.sendStatus(200));\n\n await withServer(app, async ({ url }) => {\n const res = await fetch(url, {\n headers: {\n 'X-Forwarded-Proto': 'http',\n },\n });\n assert.equal(res.status, 200);\n\n const header = res.headers.get('set-cookie');\n const cookies = parseSetCookie(header ?? '');\n assert.equal(cookies.length, 1);\n assert.equal(cookies[0].name, 'session');\n assert.equal(cookies[0].path, '/');\n assert.isUndefined(cookies[0].secure);\n });\n });\n\n it('sets secure cookie based on a function', async () => {\n const app = express();\n app.enable('trust proxy');\n app.use(\n createSessionMiddleware({\n secret: TEST_SECRET,\n store: new MemoryStore(),\n cookie: {\n secure: (req) => req.hostname === 'example.com',\n },\n }),\n );\n app.get('/', (_req, res) => res.sendStatus(200));\n\n await withServer(app, async ({ url }) => {\n const insecureRes = await fetch(url, {\n headers: {\n 'X-Forwarded-Host': 'subdomain.example.com',\n 'X-Forwarded-Proto': 'http',\n },\n });\n assert.equal(insecureRes.status, 200);\n\n const insecureHeader = insecureRes.headers.get('set-cookie');\n const insecureCookie = parseSetCookie(insecureHeader ?? '');\n assert.equal(insecureCookie.length, 1);\n assert.equal(insecureCookie[0].name, 'session');\n assert.equal(insecureCookie[0].path, '/');\n assert.isUndefined(insecureCookie[0].secure);\n\n const secureRes = await fetch(url, {\n headers: {\n 'X-Forwarded-Host': 'example.com',\n 'X-Forwarded-Proto': 'https',\n },\n });\n assert.equal(secureRes.status, 200);\n\n const secureHeader = secureRes.headers.get('set-cookie');\n const secureCookies = parseSetCookie(secureHeader ?? '');\n assert.equal(secureCookies.length, 1);\n assert.equal(secureCookies[0].name, 'session');\n assert.equal(secureCookies[0].path, '/');\n assert.isTrue(secureCookies[0].secure);\n });\n });\n\n it('persists session data across requests', async () => {\n const app = express();\n app.use(\n createSessionMiddleware({\n store: new MemoryStore(),\n secret: TEST_SECRET,\n }),\n );\n app.get('/', (req, res) => {\n req.session.count ??= 0;\n req.session.count += 1;\n res.send(req.session.count.toString());\n });\n\n await withServer(app, async ({ url }) => {\n const fetchWithCookies = fetchCookie(fetch);\n\n let res = await fetchWithCookies(url);\n assert.equal(res.status, 200);\n assert.equal(await res.text(), '1');\n\n res = await fetchWithCookies(url);\n assert.equal(res.status, 200);\n assert.equal(await res.text(), '2');\n });\n });\n\n it('commits the session before sending a redirect', async () => {\n const app = express();\n app.use(\n createSessionMiddleware({\n store: new MemoryStore(),\n secret: TEST_SECRET,\n }),\n );\n app.post('/', (req, res) => {\n req.session.test = 'test';\n res.redirect(req.originalUrl);\n });\n app.get('/', (req, res) => {\n res.send(req.session.test ?? 'NO VALUE');\n });\n\n await withServer(app, async ({ url }) => {\n const res = await fetchCookie(fetch)(url, {\n method: 'POST',\n });\n assert.equal(res.status, 200);\n\n const body = await res.text();\n assert.equal(body, 'test');\n });\n });\n\n it('destroys session', async () => {\n const store = new MemoryStore();\n\n const app = express();\n app.use(\n createSessionMiddleware({\n store,\n secret: TEST_SECRET,\n }),\n );\n app.get('/', (_req, res) => res.sendStatus(200));\n app.use(\n '/destroy',\n asyncHandler(async (req, res) => {\n await req.session.destroy();\n res.sendStatus(200);\n }),\n );\n\n await withServer(app, async ({ url }) => {\n const fetchWithCookies = fetchCookie(fetch);\n\n // Generate a new session.\n const res = await fetchWithCookies(url);\n assert.equal(res.status, 200);\n await res.text();\n\n // Destroy the session.\n const destroyRes = await fetchWithCookies(`${url}/destroy`);\n assert.equal(destroyRes.status, 200);\n await destroyRes.text();\n\n // Ensure the session cookie was cleared in the response.\n const header = destroyRes.headers.get('set-cookie');\n const cookies = parseSetCookie(header ?? '');\n assert.equal(cookies.length, 1);\n assert.equal(cookies[0].name, 'session');\n assert.equal(cookies[0].path, '/');\n assert.equal(cookies[0].expires?.getTime(), 0);\n\n // Ensure the session was destroyed in the session store.\n const sessionId = cookies[0].value.split('.')[0];\n assert.isNull(await store.get(sessionId));\n });\n });\n\n it('regenerates session', async () => {\n const store = new MemoryStore();\n\n const app = express();\n app.use(\n createSessionMiddleware({\n store,\n secret: TEST_SECRET,\n }),\n );\n app.get('/', (req, res) => {\n res.send(req.session.regenerated ? 'true' : 'false');\n });\n app.get(\n '/regenerate',\n asyncHandler(async (req, res) => {\n await req.session.regenerate();\n req.session.regenerated = true;\n res.send('true');\n }),\n );\n\n await withServer(app, async ({ url }) => {\n const fetchWithCookies = fetchCookie(fetch);\n\n // Generate a new session.\n let res = await fetchWithCookies(url);\n assert.equal(res.status, 200);\n assert.equal(await res.text(), 'false');\n\n // Extract the original cookie value.\n let header = res.headers.get('set-cookie');\n let cookies = parseSetCookie(header ?? '');\n assert.equal(cookies.length, 1);\n const originalCookieValue = cookies[0].value;\n\n // Regenerate the session.\n res = await fetchWithCookies(`${url}/regenerate`);\n assert.equal(res.status, 200);\n assert.equal(await res.text(), 'true');\n\n // Ensure that the session cookie was changed.\n header = res.headers.get('set-cookie');\n cookies = parseSetCookie(header ?? '');\n assert.equal(cookies.length, 1);\n const newCookieValue = cookies[0].value;\n assert.notEqual(newCookieValue, originalCookieValue);\n\n // Ensure the original session is no longer present in the session store.\n const originalSessionId = originalCookieValue.split('.')[0];\n assert.isNull(await store.get(originalSessionId));\n\n // Ensure that the regenerated session data was persisted.\n res = await fetchWithCookies(url);\n assert.equal(res.status, 200);\n assert.equal(await res.text(), 'true');\n });\n });\n\n it('creates a new session if signature checks fail', async () => {\n const store = new MemoryStore();\n\n const app = express();\n app.use(\n createSessionMiddleware({\n store,\n secret: TEST_SECRET,\n }),\n );\n app.get('/', (req, res) => res.send(req.session.id));\n\n await withServer(app, async ({ url }) => {\n const cookieJar = new fetchCookie.toughCookie.CookieJar();\n const fetchWithCookies = fetchCookie(fetch, cookieJar);\n\n // Generate a new session.\n let res = await fetchWithCookies(url);\n assert.equal(res.status, 200);\n const originalSessionId = await res.text();\n\n // Tamper with the session cookie.\n const cookie = cookieJar.getCookiesSync(url)[0];\n cookie.value = 'tampered';\n cookieJar.setCookieSync(cookie, url);\n\n // Make sure we get a new session.\n res = await fetchWithCookies(url);\n assert.equal(res.status, 200);\n const newSessionId = await res.text();\n assert.notEqual(newSessionId, originalSessionId);\n\n // Make sure the existing session is still present in the store. We don't\n // want someone to be able to evict other sessions by submitting invalid\n // cookies.\n assert.isNotNull(await store.get(originalSessionId));\n });\n });\n\n it('does not re-set the cookie on subsequent requests', async () => {\n const app = express();\n app.use(\n createSessionMiddleware({\n store: new MemoryStore(),\n secret: TEST_SECRET,\n }),\n );\n app.get('/', (_req, res) => res.sendStatus(200));\n\n await withServer(app, async ({ url }) => {\n const fetchWithCookies = fetchCookie(fetch);\n\n // Generate a new session.\n let res = await fetchWithCookies(url);\n assert.equal(res.status, 200);\n\n // Make another request with the same session.\n res = await fetchWithCookies(url);\n assert.equal(res.status, 200);\n\n // Ensure that the cookie wasn't set again.\n const header = res.headers.get('set-cookie');\n assert.isNull(header);\n });\n });\n\n // For unknown reasons, this test is flaky in CI. It's likely due to a race\n // condition with the session store and the way that the middleware hooks\n // into the response lifecycle. We haven't been able to isolate the specific\n // cause, so we'll retry the test a few times to try to avoid a failure in CI.\n it('extends the expiration date of the cookie', { retry: 3 }, async () => {\n const store = new MemoryStore();\n\n const app = express();\n app.use(\n createSessionMiddleware({\n store,\n secret: TEST_SECRET,\n cookie: {\n maxAge: 1000,\n },\n }),\n );\n app.get('/', (_req, res) => res.sendStatus(200));\n app.get('/extend', (req, res) => {\n req.session.setExpiration(10000);\n res.sendStatus(200);\n });\n\n await withServer(app, async ({ url }) => {\n const fetchWithCookies = fetchCookie(fetch);\n\n // Generate a new session.\n let res = await fetchWithCookies(url);\n assert.equal(res.status, 200);\n await res.text();\n\n // Grab the original expiration date.\n let header = res.headers.get('set-cookie');\n let cookies = parseSetCookie(header ?? '');\n assert.equal(cookies.length, 1);\n assert.isUndefined(cookies[0].maxAge);\n const originalExpirationDate = cookies[0].expires;\n assert(originalExpirationDate);\n\n // Grab the session ID from the cookie.\n const sessionId = cookies[0].value.split('.')[0];\n\n // Ensure that the expiration dates are consistent between the cookie and the store.\n const session = await store.get(sessionId);\n assert.equal(originalExpirationDate.getTime(), session?.expiresAt.getTime());\n\n // Make another request with the same session.\n res = await fetchWithCookies(`${url}/extend`);\n assert.equal(res.status, 200);\n await res.text();\n\n // Ensure that the cookie was set again.\n header = res.headers.get('set-cookie');\n cookies = parseSetCookie(header ?? '');\n assert.equal(cookies.length, 1);\n assert.isUndefined(cookies[0].maxAge);\n const newExpirationDate = cookies[0].expires;\n assert(newExpirationDate);\n\n // Ensure that the expiration date was extended.\n assert.notEqual(newExpirationDate.getTime(), originalExpirationDate.getTime());\n\n // Ensure that the expiration dates are consistent between the cookie and the store.\n const newSession = await store.get(sessionId);\n assert.equal(newExpirationDate.getTime(), newSession?.expiresAt.getTime());\n });\n });\n\n it('does not persist session data if the session did not change', async () => {\n const store = new MemoryStore();\n\n let setCount = 0;\n const originalStoreSet = store.set.bind(store);\n store.set = async (...args) => {\n setCount += 1;\n await originalStoreSet(...args);\n };\n\n const app = express();\n app.use(\n createSessionMiddleware({\n store,\n secret: TEST_SECRET,\n }),\n );\n app.get('/', (_req, res) => res.sendStatus(200));\n\n await withServer(app, async ({ url }) => {\n const fetchWithCookies = fetchCookie(fetch);\n // Generate a new session.\n let res = await fetchWithCookies(url);\n assert.equal(res.status, 200);\n await res.text();\n\n // Ensure the session was persisted.\n assert.equal(setCount, 1);\n\n // Make another request with the same session.\n res = await fetchWithCookies(url);\n assert.equal(res.status, 200);\n await res.text();\n\n // Ensure the session was not persisted.\n assert.equal(setCount, 1);\n });\n });\n\n it('rotates to a new cookie when needed', async () => {\n const fetchWithCookies = fetchCookie(fetch);\n const store = new MemoryStore();\n\n // Will create \"legacy\" sessions.\n const legacyApp = express();\n legacyApp.use(\n createSessionMiddleware({\n store,\n secret: TEST_SECRET,\n cookie: {\n name: 'legacy_session',\n },\n }),\n );\n legacyApp.get('/', (req, res) => res.send(req.session.id.toString()));\n\n // Will create \"new\" sessions and upgrade \"legacy\" sessions.\n const app = express();\n app.use(\n createSessionMiddleware({\n store,\n secret: TEST_SECRET,\n cookie: {\n name: 'legacy_session',\n writeNames: ['legacy_session', 'session'],\n writeOverrides: [{ domain: undefined }, { domain: '.example.com' }],\n },\n }),\n );\n app.get('/', (req, res) => res.send(req.session.id.toString()));\n\n let legacySessionId: string | null = null;\n\n await withServer(legacyApp, async ({ url }) => {\n // Generate a legacy session.\n const res = await fetchWithCookies(url);\n assert.equal(res.status, 200);\n\n legacySessionId = await res.text();\n });\n\n await withServer(app, async ({ url }) => {\n const res = await fetchWithCookies(url);\n assert.equal(res.status, 200);\n\n const newSessionId = await res.text();\n\n // Ensure that the new session cookie was set.\n const header = res.headers.get('set-cookie');\n assert.isNotNull(header);\n const cookies = parseSetCookie(header ?? '');\n assert.equal(cookies.length, 2);\n assert.equal(cookies[0].name, 'legacy_session');\n assert.isUndefined(cookies[0].domain);\n assert.equal(cookies[1].name, 'session');\n assert.equal(cookies[1].domain, '.example.com');\n\n // Ensure that the legacy session is migrated to a new session.\n assert.equal(newSessionId, legacySessionId);\n });\n });\n\n it('persists the session immediately after creation', async () => {\n const store = new MemoryStore();\n\n const app = express();\n app.use(\n createSessionMiddleware({\n store,\n secret: TEST_SECRET,\n }),\n );\n app.get(\n '/',\n asyncHandler(async (req, res) => {\n const persistedSession = await store.get(req.session.id);\n res.status(persistedSession == null ? 500 : 200).send();\n }),\n );\n\n await withServer(app, async ({ url }) => {\n const fetchWithCookies = fetchCookie(fetch);\n\n const res = await fetchWithCookies(url);\n assert.equal(res.status, 200);\n });\n });\n});\n"]}
1
+ {"version":3,"file":"index.test.js","sourceRoot":"","sources":["../src/index.test.ts"],"names":[],"mappings":"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","sourcesContent":["import express from 'express';\nimport asyncHandler from 'express-async-handler';\nimport fetchCookie from 'fetch-cookie';\nimport fetch from 'node-fetch';\nimport setCookie from 'set-cookie-parser';\nimport { assert, describe, it } from 'vitest';\n\nimport { withServer } from '@prairielearn/express-test-utils';\n\nimport { MemoryStore } from './memory-store.js';\n\nimport { createSessionMiddleware } from './index.js';\n\nconst TEST_SECRET = 'test-secret';\n\nfunction parseSetCookie(header: string) {\n // TODO: The types for `set-cookie-parser` are outdated. See https://github.com/PrairieLearn/PrairieLearn/pull/13764.\n // For now, we will rely on the \"soft-deprecated\" fields and not change how this works.\n return setCookie.parse(setCookie.splitCookiesString(header));\n}\n\ndescribe('session middleware', () => {\n it('sets a session cookie', async () => {\n const app = express();\n app.use(createSessionMiddleware({ secret: TEST_SECRET, store: new MemoryStore() }));\n app.get('/', (_req, res) => res.sendStatus(200));\n\n await withServer(app, async ({ url }) => {\n const res = await fetch(url);\n assert.equal(res.status, 200);\n\n const header = res.headers.get('set-cookie');\n const cookies = parseSetCookie(header ?? '');\n assert.equal(cookies.length, 1);\n assert.equal(cookies[0].name, 'session');\n assert.equal(cookies[0].path, '/');\n });\n });\n\n it('sets a session cookie with options', async () => {\n const app = express();\n app.use(\n createSessionMiddleware({\n secret: TEST_SECRET,\n store: new MemoryStore(),\n cookie: {\n name: 'prairielearn_session',\n httpOnly: true,\n domain: '.localhost',\n sameSite: 'strict',\n maxAge: 1000,\n },\n }),\n );\n app.get('/', (_req, res) => res.sendStatus(200));\n\n await withServer(app, async ({ url }) => {\n const res = await fetch(url);\n assert.equal(res.status, 200);\n\n const header = res.headers.get('set-cookie');\n const cookies = parseSetCookie(header ?? '');\n assert.equal(cookies.length, 1);\n assert.equal(cookies[0].name, 'prairielearn_session');\n assert.equal(cookies[0].path, '/');\n assert.isTrue(cookies[0].httpOnly);\n assert.equal(cookies[0].domain, '.localhost');\n assert.equal(cookies[0].sameSite, 'Strict');\n });\n });\n\n it('sets a secure cookie for proxied HTTPS request', async () => {\n const app = express();\n app.enable('trust proxy');\n app.use(\n createSessionMiddleware({\n secret: TEST_SECRET,\n store: new MemoryStore(),\n cookie: {\n secure: true,\n },\n }),\n );\n app.get('/', (_req, res) => res.sendStatus(200));\n\n await withServer(app, async ({ url }) => {\n const res = await fetch(url, {\n headers: {\n 'X-Forwarded-Proto': 'https',\n },\n });\n assert.equal(res.status, 200);\n\n const header = res.headers.get('set-cookie');\n const cookies = parseSetCookie(header ?? '');\n assert.equal(cookies.length, 1);\n assert.equal(cookies[0].name, 'session');\n assert.equal(cookies[0].path, '/');\n assert.isTrue(cookies[0].secure);\n });\n });\n\n it('does not set a secure cookie for proxied HTTP request', async () => {\n const app = express();\n app.enable('trust proxy');\n app.use(\n createSessionMiddleware({\n secret: TEST_SECRET,\n store: new MemoryStore(),\n cookie: {\n secure: true,\n },\n }),\n );\n app.get('/', (_req, res) => res.sendStatus(200));\n\n await withServer(app, async ({ url }) => {\n const res = await fetch(url, {\n headers: {\n 'X-Forwarded-Proto': 'http',\n },\n });\n assert.equal(res.status, 200);\n\n const header = res.headers.get('set-cookie');\n assert.isNull(header);\n });\n });\n\n it('automatically sets secure for proxied HTTPS request', async () => {\n const app = express();\n app.enable('trust proxy');\n app.use(\n createSessionMiddleware({\n secret: TEST_SECRET,\n store: new MemoryStore(),\n cookie: {\n secure: 'auto',\n },\n }),\n );\n app.get('/', (_req, res) => res.sendStatus(200));\n\n await withServer(app, async ({ url }) => {\n const res = await fetch(url, {\n headers: {\n 'X-Forwarded-Proto': 'https',\n },\n });\n assert.equal(res.status, 200);\n\n const header = res.headers.get('set-cookie');\n const cookies = parseSetCookie(header ?? '');\n assert.equal(cookies.length, 1);\n assert.equal(cookies[0].name, 'session');\n assert.equal(cookies[0].path, '/');\n assert.isTrue(cookies[0].secure);\n });\n });\n\n it('automatically sets secure for proxied HTTP request', async () => {\n const app = express();\n app.enable('trust proxy');\n app.use(\n createSessionMiddleware({\n secret: TEST_SECRET,\n store: new MemoryStore(),\n cookie: {\n secure: 'auto',\n },\n }),\n );\n app.get('/', (_req, res) => res.sendStatus(200));\n\n await withServer(app, async ({ url }) => {\n const res = await fetch(url, {\n headers: {\n 'X-Forwarded-Proto': 'http',\n },\n });\n assert.equal(res.status, 200);\n\n const header = res.headers.get('set-cookie');\n const cookies = parseSetCookie(header ?? '');\n assert.equal(cookies.length, 1);\n assert.equal(cookies[0].name, 'session');\n assert.equal(cookies[0].path, '/');\n assert.isUndefined(cookies[0].secure);\n });\n });\n\n it('sets secure cookie based on a function', async () => {\n const app = express();\n app.enable('trust proxy');\n app.use(\n createSessionMiddleware({\n secret: TEST_SECRET,\n store: new MemoryStore(),\n cookie: {\n secure: (req) => req.hostname === 'example.com',\n },\n }),\n );\n app.get('/', (_req, res) => res.sendStatus(200));\n\n await withServer(app, async ({ url }) => {\n const insecureRes = await fetch(url, {\n headers: {\n 'X-Forwarded-Host': 'subdomain.example.com',\n 'X-Forwarded-Proto': 'http',\n },\n });\n assert.equal(insecureRes.status, 200);\n\n const insecureHeader = insecureRes.headers.get('set-cookie');\n const insecureCookie = parseSetCookie(insecureHeader ?? '');\n assert.equal(insecureCookie.length, 1);\n assert.equal(insecureCookie[0].name, 'session');\n assert.equal(insecureCookie[0].path, '/');\n assert.isUndefined(insecureCookie[0].secure);\n\n const secureRes = await fetch(url, {\n headers: {\n 'X-Forwarded-Host': 'example.com',\n 'X-Forwarded-Proto': 'https',\n },\n });\n assert.equal(secureRes.status, 200);\n\n const secureHeader = secureRes.headers.get('set-cookie');\n const secureCookies = parseSetCookie(secureHeader ?? '');\n assert.equal(secureCookies.length, 1);\n assert.equal(secureCookies[0].name, 'session');\n assert.equal(secureCookies[0].path, '/');\n assert.isTrue(secureCookies[0].secure);\n });\n });\n\n it('persists session data across requests', async () => {\n const app = express();\n app.use(\n createSessionMiddleware({\n store: new MemoryStore(),\n secret: TEST_SECRET,\n }),\n );\n app.get('/', (req, res) => {\n req.session.count ??= 0;\n req.session.count += 1;\n res.send(req.session.count.toString());\n });\n\n await withServer(app, async ({ url }) => {\n const fetchWithCookies = fetchCookie(fetch);\n\n let res = await fetchWithCookies(url);\n assert.equal(res.status, 200);\n assert.equal(await res.text(), '1');\n\n res = await fetchWithCookies(url);\n assert.equal(res.status, 200);\n assert.equal(await res.text(), '2');\n });\n });\n\n it('commits the session before sending a redirect', async () => {\n const app = express();\n app.use(\n createSessionMiddleware({\n store: new MemoryStore(),\n secret: TEST_SECRET,\n }),\n );\n app.post('/', (req, res) => {\n req.session.test = 'test';\n res.redirect(req.originalUrl);\n });\n app.get('/', (req, res) => {\n res.send(req.session.test ?? 'NO VALUE');\n });\n\n await withServer(app, async ({ url }) => {\n const res = await fetchCookie(fetch)(url, {\n method: 'POST',\n });\n assert.equal(res.status, 200);\n\n const body = await res.text();\n assert.equal(body, 'test');\n });\n });\n\n it('destroys session', async () => {\n const store = new MemoryStore();\n\n const app = express();\n app.use(\n createSessionMiddleware({\n store,\n secret: TEST_SECRET,\n }),\n );\n app.get('/', (_req, res) => res.sendStatus(200));\n app.use(\n '/destroy',\n asyncHandler(async (req, res) => {\n await req.session.destroy();\n res.sendStatus(200);\n }),\n );\n\n await withServer(app, async ({ url }) => {\n const fetchWithCookies = fetchCookie(fetch);\n\n // Generate a new session.\n const res = await fetchWithCookies(url);\n assert.equal(res.status, 200);\n await res.text();\n\n // Destroy the session.\n const destroyRes = await fetchWithCookies(`${url}/destroy`);\n assert.equal(destroyRes.status, 200);\n await destroyRes.text();\n\n // Ensure the session cookie was cleared in the response.\n const header = destroyRes.headers.get('set-cookie');\n const cookies = parseSetCookie(header ?? '');\n assert.equal(cookies.length, 1);\n assert.equal(cookies[0].name, 'session');\n assert.equal(cookies[0].path, '/');\n assert.equal(cookies[0].expires?.getTime(), 0);\n\n // Ensure the session was destroyed in the session store.\n const sessionId = cookies[0].value.split('.')[0];\n assert.isNull(await store.get(sessionId));\n });\n });\n\n it('regenerates session', async () => {\n const store = new MemoryStore();\n\n const app = express();\n app.use(\n createSessionMiddleware({\n store,\n secret: TEST_SECRET,\n }),\n );\n app.get('/', (req, res) => {\n res.send(req.session.regenerated ? 'true' : 'false');\n });\n app.get(\n '/regenerate',\n asyncHandler(async (req, res) => {\n await req.session.regenerate();\n req.session.regenerated = true;\n res.send('true');\n }),\n );\n\n await withServer(app, async ({ url }) => {\n const fetchWithCookies = fetchCookie(fetch);\n\n // Generate a new session.\n let res = await fetchWithCookies(url);\n assert.equal(res.status, 200);\n assert.equal(await res.text(), 'false');\n\n // Extract the original cookie value.\n let header = res.headers.get('set-cookie');\n let cookies = parseSetCookie(header ?? '');\n assert.equal(cookies.length, 1);\n const originalCookieValue = cookies[0].value;\n\n // Regenerate the session.\n res = await fetchWithCookies(`${url}/regenerate`);\n assert.equal(res.status, 200);\n assert.equal(await res.text(), 'true');\n\n // Ensure that the session cookie was changed.\n header = res.headers.get('set-cookie');\n cookies = parseSetCookie(header ?? '');\n assert.equal(cookies.length, 1);\n const newCookieValue = cookies[0].value;\n assert.notEqual(newCookieValue, originalCookieValue);\n\n // Ensure the original session is no longer present in the session store.\n const originalSessionId = originalCookieValue.split('.')[0];\n assert.isNull(await store.get(originalSessionId));\n\n // Ensure that the regenerated session data was persisted.\n res = await fetchWithCookies(url);\n assert.equal(res.status, 200);\n assert.equal(await res.text(), 'true');\n });\n });\n\n it('creates a new session if signature checks fail', async () => {\n const store = new MemoryStore();\n\n const app = express();\n app.use(\n createSessionMiddleware({\n store,\n secret: TEST_SECRET,\n }),\n );\n app.get('/', (req, res) => res.send(req.session.id));\n\n await withServer(app, async ({ url }) => {\n const cookieJar = new fetchCookie.toughCookie.CookieJar();\n const fetchWithCookies = fetchCookie(fetch, cookieJar);\n\n // Generate a new session.\n let res = await fetchWithCookies(url);\n assert.equal(res.status, 200);\n const originalSessionId = await res.text();\n\n // Tamper with the session cookie.\n const cookie = cookieJar.getCookiesSync(url)[0];\n cookie.value = 'tampered';\n cookieJar.setCookieSync(cookie, url);\n\n // Make sure we get a new session.\n res = await fetchWithCookies(url);\n assert.equal(res.status, 200);\n const newSessionId = await res.text();\n assert.notEqual(newSessionId, originalSessionId);\n\n // Make sure the existing session is still present in the store. We don't\n // want someone to be able to evict other sessions by submitting invalid\n // cookies.\n assert.isNotNull(await store.get(originalSessionId));\n });\n });\n\n it('does not re-set the cookie on subsequent requests', async () => {\n const app = express();\n app.use(\n createSessionMiddleware({\n store: new MemoryStore(),\n secret: TEST_SECRET,\n }),\n );\n app.get('/', (_req, res) => res.sendStatus(200));\n\n await withServer(app, async ({ url }) => {\n const fetchWithCookies = fetchCookie(fetch);\n\n // Generate a new session.\n let res = await fetchWithCookies(url);\n assert.equal(res.status, 200);\n\n // Make another request with the same session.\n res = await fetchWithCookies(url);\n assert.equal(res.status, 200);\n\n // Ensure that the cookie wasn't set again.\n const header = res.headers.get('set-cookie');\n assert.isNull(header);\n });\n });\n\n // For unknown reasons, this test is flaky in CI. It's likely due to a race\n // condition with the session store and the way that the middleware hooks\n // into the response lifecycle. We haven't been able to isolate the specific\n // cause, so we'll retry the test a few times to try to avoid a failure in CI.\n it('extends the expiration date of the cookie', { retry: 3 }, async () => {\n const store = new MemoryStore();\n\n const app = express();\n app.use(\n createSessionMiddleware({\n store,\n secret: TEST_SECRET,\n cookie: {\n maxAge: 1000,\n },\n }),\n );\n app.get('/', (_req, res) => res.sendStatus(200));\n app.get('/extend', (req, res) => {\n req.session.setExpiration(10000);\n res.sendStatus(200);\n });\n\n await withServer(app, async ({ url }) => {\n const fetchWithCookies = fetchCookie(fetch);\n\n // Generate a new session.\n let res = await fetchWithCookies(url);\n assert.equal(res.status, 200);\n await res.text();\n\n // Grab the original expiration date.\n let header = res.headers.get('set-cookie');\n let cookies = parseSetCookie(header ?? '');\n assert.equal(cookies.length, 1);\n assert.isUndefined(cookies[0].maxAge);\n const originalExpirationDate = cookies[0].expires;\n assert(originalExpirationDate);\n\n // Grab the session ID from the cookie.\n const sessionId = cookies[0].value.split('.')[0];\n\n // Ensure that the expiration dates are consistent between the cookie and the store.\n const session = await store.get(sessionId);\n assert.equal(originalExpirationDate.getTime(), session?.expiresAt.getTime());\n\n // Make another request with the same session.\n res = await fetchWithCookies(`${url}/extend`);\n assert.equal(res.status, 200);\n await res.text();\n\n // Ensure that the cookie was set again.\n header = res.headers.get('set-cookie');\n cookies = parseSetCookie(header ?? '');\n assert.equal(cookies.length, 1);\n assert.isUndefined(cookies[0].maxAge);\n const newExpirationDate = cookies[0].expires;\n assert(newExpirationDate);\n\n // Ensure that the expiration date was extended.\n assert.notEqual(newExpirationDate.getTime(), originalExpirationDate.getTime());\n\n // Ensure that the expiration dates are consistent between the cookie and the store.\n const newSession = await store.get(sessionId);\n assert.equal(newExpirationDate.getTime(), newSession?.expiresAt.getTime());\n });\n });\n\n it('does not persist session data if the session did not change', async () => {\n const store = new MemoryStore();\n\n let setCount = 0;\n const originalStoreSet = store.set.bind(store);\n store.set = async (...args) => {\n setCount += 1;\n await originalStoreSet(...args);\n };\n\n const app = express();\n app.use(\n createSessionMiddleware({\n store,\n secret: TEST_SECRET,\n }),\n );\n app.get('/', (_req, res) => res.sendStatus(200));\n\n await withServer(app, async ({ url }) => {\n const fetchWithCookies = fetchCookie(fetch);\n // Generate a new session.\n let res = await fetchWithCookies(url);\n assert.equal(res.status, 200);\n await res.text();\n\n // Ensure the session was persisted.\n assert.equal(setCount, 1);\n\n // Make another request with the same session.\n res = await fetchWithCookies(url);\n assert.equal(res.status, 200);\n await res.text();\n\n // Ensure the session was not persisted.\n assert.equal(setCount, 1);\n });\n });\n\n it('rotates to a new cookie when needed', async () => {\n const fetchWithCookies = fetchCookie(fetch);\n const store = new MemoryStore();\n\n // Will create \"legacy\" sessions.\n const legacyApp = express();\n legacyApp.use(\n createSessionMiddleware({\n store,\n secret: TEST_SECRET,\n cookie: {\n name: 'legacy_session',\n },\n }),\n );\n legacyApp.get('/', (req, res) => res.send(req.session.id.toString()));\n\n // Will create \"new\" sessions and upgrade \"legacy\" sessions.\n const app = express();\n app.use(\n createSessionMiddleware({\n store,\n secret: TEST_SECRET,\n cookie: {\n name: 'legacy_session',\n writeNames: ['legacy_session', 'session'],\n writeOverrides: [{ domain: undefined }, { domain: '.example.com' }],\n },\n }),\n );\n app.get('/', (req, res) => res.send(req.session.id.toString()));\n\n let legacySessionId: string | null = null;\n\n await withServer(legacyApp, async ({ url }) => {\n // Generate a legacy session.\n const res = await fetchWithCookies(url);\n assert.equal(res.status, 200);\n\n legacySessionId = await res.text();\n });\n\n await withServer(app, async ({ url }) => {\n const res = await fetchWithCookies(url);\n assert.equal(res.status, 200);\n\n const newSessionId = await res.text();\n\n // Ensure that the new session cookie was set.\n const header = res.headers.get('set-cookie');\n assert.isNotNull(header);\n const cookies = parseSetCookie(header ?? '');\n assert.equal(cookies.length, 2);\n assert.equal(cookies[0].name, 'legacy_session');\n assert.isUndefined(cookies[0].domain);\n assert.equal(cookies[1].name, 'session');\n assert.equal(cookies[1].domain, '.example.com');\n\n // Ensure that the legacy session is migrated to a new session.\n assert.equal(newSessionId, legacySessionId);\n });\n });\n\n it('persists the session immediately after creation', async () => {\n const store = new MemoryStore();\n\n const app = express();\n app.use(\n createSessionMiddleware({\n store,\n secret: TEST_SECRET,\n }),\n );\n app.get(\n '/',\n asyncHandler(async (req, res) => {\n const persistedSession = await store.get(req.session.id);\n res.status(persistedSession == null ? 500 : 200).send();\n }),\n );\n\n await withServer(app, async ({ url }) => {\n const fetchWithCookies = fetchCookie(fetch);\n\n const res = await fetchWithCookies(url);\n assert.equal(res.status, 200);\n });\n });\n});\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"memory-store.d.ts","sourceRoot":"","sources":["../src/memory-store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,YAAY,EAAE,KAAK,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEtE,qBAAa,WAAY,YAAW,YAAY;IAC9C,OAAO,CAAC,QAAQ,CAAwD;IAElE,GAAG,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,EAAE,SAAS,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAO7D,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC;IASjD,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAGzC"}
1
+ {"version":3,"file":"memory-store.d.ts","sourceRoot":"","sources":["../src/memory-store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,YAAY,EAAE,KAAK,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEtE,qBAAa,WAAY,YAAW,YAAY;IAC9C,OAAO,CAAC,QAAQ,CAAwD;IAElE,GAAG,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,EAAE,SAAS,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAKlE;IAEK,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAOtD;IAEK,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAEvC;CACF","sourcesContent":["import { type SessionStore, type SessionStoreData } from './store.js';\n\nexport class MemoryStore implements SessionStore {\n private sessions = new Map<string, { expiresAt: Date; data: string }>();\n\n async set(id: string, session: any, expiresAt: Date): Promise<void> {\n this.sessions.set(id, {\n expiresAt,\n data: JSON.stringify(session),\n });\n }\n\n async get(id: string): Promise<SessionStoreData | null> {\n const value = this.sessions.get(id);\n if (!value) return null;\n return {\n expiresAt: value.expiresAt,\n data: JSON.parse(value.data),\n };\n }\n\n async destroy(id: string): Promise<void> {\n this.sessions.delete(id);\n }\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"memory-store.js","sourceRoot":"","sources":["../src/memory-store.ts"],"names":[],"mappings":"AAAA,OAAO,EAA4C,MAAM,YAAY,CAAC;AAEtE,MAAM,OAAO,WAAW;IACd,QAAQ,GAAG,IAAI,GAAG,EAA6C,CAAC;IAExE,KAAK,CAAC,GAAG,CAAC,EAAU,EAAE,OAAY,EAAE,SAAe;QACjD,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,EAAE;YACpB,SAAS;YACT,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC;SAC9B,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,EAAU;QAClB,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACpC,IAAI,CAAC,KAAK;YAAE,OAAO,IAAI,CAAC;QACxB,OAAO;YACL,SAAS,EAAE,KAAK,CAAC,SAAS;YAC1B,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC;SAC7B,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,EAAU;QACtB,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IAC3B,CAAC;CACF","sourcesContent":["import { type SessionStore, type SessionStoreData } from './store.js';\n\nexport class MemoryStore implements SessionStore {\n private sessions = new Map<string, { expiresAt: Date; data: string }>();\n\n async set(id: string, session: any, expiresAt: Date): Promise<void> {\n this.sessions.set(id, {\n expiresAt,\n data: JSON.stringify(session),\n });\n }\n\n async get(id: string): Promise<SessionStoreData | null> {\n const value = this.sessions.get(id);\n if (!value) return null;\n return {\n expiresAt: value.expiresAt,\n data: JSON.parse(value.data),\n };\n }\n\n async destroy(id: string): Promise<void> {\n this.sessions.delete(id);\n }\n}\n"]}
1
+ {"version":3,"file":"memory-store.js","sourceRoot":"","sources":["../src/memory-store.ts"],"names":[],"mappings":"AAAA,OAAO,EAA4C,MAAM,YAAY,CAAC;AAEtE,MAAM,OAAO,WAAW;IACd,QAAQ,GAAG,IAAI,GAAG,EAA6C,CAAC;IAExE,KAAK,CAAC,GAAG,CAAC,EAAU,EAAE,OAAY,EAAE,SAAe,EAAiB;QAClE,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,EAAE;YACpB,SAAS;YACT,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC;SAC9B,CAAC,CAAC;IAAA,CACJ;IAED,KAAK,CAAC,GAAG,CAAC,EAAU,EAAoC;QACtD,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACpC,IAAI,CAAC,KAAK;YAAE,OAAO,IAAI,CAAC;QACxB,OAAO;YACL,SAAS,EAAE,KAAK,CAAC,SAAS;YAC1B,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC;SAC7B,CAAC;IAAA,CACH;IAED,KAAK,CAAC,OAAO,CAAC,EAAU,EAAiB;QACvC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IAAA,CAC1B;CACF","sourcesContent":["import { type SessionStore, type SessionStoreData } from './store.js';\n\nexport class MemoryStore implements SessionStore {\n private sessions = new Map<string, { expiresAt: Date; data: string }>();\n\n async set(id: string, session: any, expiresAt: Date): Promise<void> {\n this.sessions.set(id, {\n expiresAt,\n data: JSON.stringify(session),\n });\n }\n\n async get(id: string): Promise<SessionStoreData | null> {\n const value = this.sessions.get(id);\n if (!value) return null;\n return {\n expiresAt: value.expiresAt,\n data: JSON.parse(value.data),\n };\n }\n\n async destroy(id: string): Promise<void> {\n this.sessions.delete(id);\n }\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAGvC,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,YAAY,CAAC;AAE/C,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACzB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5B,aAAa,CAAC,MAAM,EAAE,IAAI,GAAG,MAAM,GAAG,IAAI,CAAC;IAC3C,iBAAiB,IAAI,IAAI,CAAC;IAC1B,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACpB;AAED,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,MAAM,CAAC,CAEzD;AAED,wBAAsB,WAAW,CAC/B,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,OAAO,EACZ,KAAK,EAAE,YAAY,EACnB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,OAAO,CAAC,CA8BlB;AAED,wBAAgB,WAAW,CACzB,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,OAAO,EACZ,KAAK,EAAE,YAAY,EACnB,cAAc,EAAE,IAAI,GAAG,IAAI,EAC3B,MAAM,EAAE,MAAM,GACb,OAAO,CAiCT;AAED,wBAAgB,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAGpD;AAWD,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,IAAI,QAIhD"}
1
+ {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAGvC,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,YAAY,CAAC;AAE/C,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACzB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5B,aAAa,CAAC,MAAM,EAAE,IAAI,GAAG,MAAM,GAAG,IAAI,CAAC;IAC3C,iBAAiB,IAAI,IAAI,CAAC;IAC1B,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACpB;AAED,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,MAAM,CAAC,CAEzD;AAED,wBAAsB,WAAW,CAC/B,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,OAAO,EACZ,KAAK,EAAE,YAAY,EACnB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,OAAO,CAAC,CA8BlB;AAED,wBAAgB,WAAW,CACzB,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,OAAO,EACZ,KAAK,EAAE,YAAY,EACnB,cAAc,EAAE,IAAI,GAAG,IAAI,EAC3B,MAAM,EAAE,MAAM,GACb,OAAO,CAiCT;AAED,wBAAgB,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAGpD;AAWD,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,IAAI,QAIhD","sourcesContent":["import crypto from 'node:crypto';\n\nimport type { Request } from 'express';\nimport uid from 'uid-safe';\n\nimport { type SessionStore } from './store.js';\n\nexport interface Session {\n id: string;\n destroy(): Promise<void>;\n regenerate(): Promise<void>;\n setExpiration(expiry: Date | number): void;\n getExpirationDate(): Date;\n [key: string]: any;\n}\n\nexport async function generateSessionId(): Promise<string> {\n return await uid(24);\n}\n\nexport async function loadSession(\n sessionId: string,\n req: Request,\n store: SessionStore,\n maxAge: number,\n): Promise<Session> {\n const sessionStoreData = await store.get(sessionId);\n const expiresAt = sessionStoreData?.expiresAt ?? null;\n\n const session = makeSession(sessionId, req, store, expiresAt, maxAge);\n\n if (sessionStoreData == null) {\n // Immediately persis the new session to the store so that it's assigned\n // an ID and available to query later on in the same request.\n await store.set(\n sessionId,\n session,\n // Cookies only support second-level resolution. To ensure consistency\n // between the cookie and the store, truncate the expiration date to\n // the nearest second.\n truncateExpirationDate(session.getExpirationDate()),\n );\n }\n\n // Copy session data into the session object.\n if (sessionStoreData != null) {\n const { data } = sessionStoreData;\n for (const prop in data) {\n if (!(prop in session)) {\n session[prop] = data[prop];\n }\n }\n }\n\n return session;\n}\n\nexport function makeSession(\n sessionId: string,\n req: Request,\n store: SessionStore,\n expirationDate: Date | null,\n maxAge: number,\n): Session {\n const session = {};\n\n let expiresAt = expirationDate;\n\n defineStaticProperty<Session['id']>(session, 'id', sessionId);\n\n defineStaticProperty<Session['destroy']>(session, 'destroy', async () => {\n delete (req as any).session;\n await store.destroy(sessionId);\n });\n\n defineStaticProperty<Session['regenerate']>(session, 'regenerate', async () => {\n await store.destroy(sessionId);\n req.session = makeSession(await generateSessionId(), req, store, null, maxAge);\n });\n\n defineStaticProperty<Session['getExpirationDate']>(session, 'getExpirationDate', () => {\n if (expiresAt == null) {\n expiresAt = new Date(Date.now() + maxAge);\n }\n return expiresAt;\n });\n\n defineStaticProperty<Session['setExpiration']>(session, 'setExpiration', (expiration) => {\n if (typeof expiration === 'number') {\n expiresAt = new Date(Date.now() + expiration);\n } else {\n expiresAt = expiration;\n }\n });\n\n return session as Session;\n}\n\nexport function hashSession(session: Session): string {\n const str = JSON.stringify(session);\n return crypto.createHash('sha1').update(str, 'utf8').digest('hex');\n}\n\nfunction defineStaticProperty<T>(obj: object, name: string, fn: T) {\n Object.defineProperty(obj, name, {\n configurable: false,\n enumerable: false,\n writable: false,\n value: fn,\n });\n}\n\nexport function truncateExpirationDate(date: Date) {\n const time = date.getTime();\n const truncatedTime = Math.floor(time / 1000) * 1000;\n return new Date(truncatedTime);\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"session.js","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"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","sourcesContent":["import crypto from 'node:crypto';\n\nimport type { Request } from 'express';\nimport uid from 'uid-safe';\n\nimport { type SessionStore } from './store.js';\n\nexport interface Session {\n id: string;\n destroy(): Promise<void>;\n regenerate(): Promise<void>;\n setExpiration(expiry: Date | number): void;\n getExpirationDate(): Date;\n [key: string]: any;\n}\n\nexport async function generateSessionId(): Promise<string> {\n return await uid(24);\n}\n\nexport async function loadSession(\n sessionId: string,\n req: Request,\n store: SessionStore,\n maxAge: number,\n): Promise<Session> {\n const sessionStoreData = await store.get(sessionId);\n const expiresAt = sessionStoreData?.expiresAt ?? null;\n\n const session = makeSession(sessionId, req, store, expiresAt, maxAge);\n\n if (sessionStoreData == null) {\n // Immediately persis the new session to the store so that it's assigned\n // an ID and available to query later on in the same request.\n await store.set(\n sessionId,\n session,\n // Cookies only support second-level resolution. To ensure consistency\n // between the cookie and the store, truncate the expiration date to\n // the nearest second.\n truncateExpirationDate(session.getExpirationDate()),\n );\n }\n\n // Copy session data into the session object.\n if (sessionStoreData != null) {\n const { data } = sessionStoreData;\n for (const prop in data) {\n if (!(prop in session)) {\n session[prop] = data[prop];\n }\n }\n }\n\n return session;\n}\n\nexport function makeSession(\n sessionId: string,\n req: Request,\n store: SessionStore,\n expirationDate: Date | null,\n maxAge: number,\n): Session {\n const session = {};\n\n let expiresAt = expirationDate;\n\n defineStaticProperty<Session['id']>(session, 'id', sessionId);\n\n defineStaticProperty<Session['destroy']>(session, 'destroy', async () => {\n delete (req as any).session;\n await store.destroy(sessionId);\n });\n\n defineStaticProperty<Session['regenerate']>(session, 'regenerate', async () => {\n await store.destroy(sessionId);\n req.session = makeSession(await generateSessionId(), req, store, null, maxAge);\n });\n\n defineStaticProperty<Session['getExpirationDate']>(session, 'getExpirationDate', () => {\n if (expiresAt == null) {\n expiresAt = new Date(Date.now() + maxAge);\n }\n return expiresAt;\n });\n\n defineStaticProperty<Session['setExpiration']>(session, 'setExpiration', (expiration) => {\n if (typeof expiration === 'number') {\n expiresAt = new Date(Date.now() + expiration);\n } else {\n expiresAt = expiration;\n }\n });\n\n return session as Session;\n}\n\nexport function hashSession(session: Session): string {\n const str = JSON.stringify(session);\n return crypto.createHash('sha1').update(str, 'utf8').digest('hex');\n}\n\nfunction defineStaticProperty<T>(obj: object, name: string, fn: T) {\n Object.defineProperty(obj, name, {\n configurable: false,\n enumerable: false,\n writable: false,\n value: fn,\n });\n}\n\nexport function truncateExpirationDate(date: Date) {\n const time = date.getTime();\n const truncatedTime = Math.floor(time / 1000) * 1000;\n return new Date(truncatedTime);\n}\n"]}
1
+ {"version":3,"file":"session.js","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"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","sourcesContent":["import crypto from 'node:crypto';\n\nimport type { Request } from 'express';\nimport uid from 'uid-safe';\n\nimport { type SessionStore } from './store.js';\n\nexport interface Session {\n id: string;\n destroy(): Promise<void>;\n regenerate(): Promise<void>;\n setExpiration(expiry: Date | number): void;\n getExpirationDate(): Date;\n [key: string]: any;\n}\n\nexport async function generateSessionId(): Promise<string> {\n return await uid(24);\n}\n\nexport async function loadSession(\n sessionId: string,\n req: Request,\n store: SessionStore,\n maxAge: number,\n): Promise<Session> {\n const sessionStoreData = await store.get(sessionId);\n const expiresAt = sessionStoreData?.expiresAt ?? null;\n\n const session = makeSession(sessionId, req, store, expiresAt, maxAge);\n\n if (sessionStoreData == null) {\n // Immediately persis the new session to the store so that it's assigned\n // an ID and available to query later on in the same request.\n await store.set(\n sessionId,\n session,\n // Cookies only support second-level resolution. To ensure consistency\n // between the cookie and the store, truncate the expiration date to\n // the nearest second.\n truncateExpirationDate(session.getExpirationDate()),\n );\n }\n\n // Copy session data into the session object.\n if (sessionStoreData != null) {\n const { data } = sessionStoreData;\n for (const prop in data) {\n if (!(prop in session)) {\n session[prop] = data[prop];\n }\n }\n }\n\n return session;\n}\n\nexport function makeSession(\n sessionId: string,\n req: Request,\n store: SessionStore,\n expirationDate: Date | null,\n maxAge: number,\n): Session {\n const session = {};\n\n let expiresAt = expirationDate;\n\n defineStaticProperty<Session['id']>(session, 'id', sessionId);\n\n defineStaticProperty<Session['destroy']>(session, 'destroy', async () => {\n delete (req as any).session;\n await store.destroy(sessionId);\n });\n\n defineStaticProperty<Session['regenerate']>(session, 'regenerate', async () => {\n await store.destroy(sessionId);\n req.session = makeSession(await generateSessionId(), req, store, null, maxAge);\n });\n\n defineStaticProperty<Session['getExpirationDate']>(session, 'getExpirationDate', () => {\n if (expiresAt == null) {\n expiresAt = new Date(Date.now() + maxAge);\n }\n return expiresAt;\n });\n\n defineStaticProperty<Session['setExpiration']>(session, 'setExpiration', (expiration) => {\n if (typeof expiration === 'number') {\n expiresAt = new Date(Date.now() + expiration);\n } else {\n expiresAt = expiration;\n }\n });\n\n return session as Session;\n}\n\nexport function hashSession(session: Session): string {\n const str = JSON.stringify(session);\n return crypto.createHash('sha1').update(str, 'utf8').digest('hex');\n}\n\nfunction defineStaticProperty<T>(obj: object, name: string, fn: T) {\n Object.defineProperty(obj, name, {\n configurable: false,\n enumerable: false,\n writable: false,\n value: fn,\n });\n}\n\nexport function truncateExpirationDate(date: Date) {\n const time = date.getTime();\n const truncatedTime = Math.floor(time / 1000) * 1000;\n return new Date(truncatedTime);\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"session.test.d.ts","sourceRoot":"","sources":["../src/session.test.ts"],"names":[],"mappings":""}
1
+ {"version":3,"file":"session.test.d.ts","sourceRoot":"","sources":["../src/session.test.ts"],"names":[],"mappings":"","sourcesContent":["import { assert, describe, it } from 'vitest';\n\nimport { MemoryStore } from './memory-store.js';\nimport { loadSession, makeSession } from './session.js';\n\nconst SESSION_MAX_AGE = 10000;\nconst SESSION_EXPIRATION_DATE = new Date(Date.now() + SESSION_MAX_AGE);\n\ndescribe('session', () => {\n describe('loadSession', () => {\n it('loads session that does not exist', async () => {\n const store = new MemoryStore();\n\n const req = {} as any;\n const session = await loadSession('123', req, store, SESSION_MAX_AGE);\n\n assert.equal(session.id, '123');\n });\n\n it('loads session from store', async () => {\n const store = new MemoryStore();\n await store.set('123', { foo: 'bar' }, SESSION_EXPIRATION_DATE);\n\n const req = {} as any;\n const session = await loadSession('123', req, store, SESSION_MAX_AGE);\n\n assert.equal(session.id, '123');\n assert.equal(session.foo, 'bar');\n });\n\n it('does not try to overwrite existing session properties', async () => {\n const store = new MemoryStore();\n await store.set('123', { foo: 'bar', id: '456' }, SESSION_EXPIRATION_DATE);\n\n const req = {} as any;\n const session = await loadSession('123', req, store, SESSION_MAX_AGE);\n\n assert.equal(session.id, '123');\n assert.equal(session.foo, 'bar');\n });\n });\n\n describe('makeSession', () => {\n it('has immutable properties', () => {\n const store = new MemoryStore();\n\n const req = {} as any;\n const session = makeSession('123', req, store, SESSION_EXPIRATION_DATE, SESSION_MAX_AGE);\n\n assert.equal(session.id, '123');\n\n const originalId = session.id;\n const originalDestroy = session.destroy;\n const originalRegenerate = session.regenerate;\n\n assert.throw(() => {\n session.id = '456';\n });\n\n assert.throw(() => {\n session.destroy = async () => {};\n });\n\n assert.throw(() => {\n session.regenerate = async () => {};\n });\n\n assert.equal(session.id, originalId);\n assert.equal(session.destroy, originalDestroy);\n assert.equal(session.regenerate, originalRegenerate);\n });\n\n it('has immutable destroy property', async () => {\n const store = new MemoryStore();\n\n const req = {} as any;\n const session = makeSession('123', req, store, SESSION_EXPIRATION_DATE, SESSION_MAX_AGE);\n\n assert.throw(() => {\n session.destroy = async () => {};\n });\n });\n\n it('can destroy itself', async () => {\n const store = new MemoryStore();\n\n const req = {} as any;\n const session = makeSession('123', req, store, SESSION_EXPIRATION_DATE, SESSION_MAX_AGE);\n req.session = session;\n\n await session.destroy();\n\n assert.isUndefined(req.session);\n assert.isNull(await store.get('123'));\n });\n\n it('can regenerate itself', async () => {\n const store = new MemoryStore();\n\n const req = {} as any;\n const session = makeSession('123', req, store, SESSION_EXPIRATION_DATE, SESSION_MAX_AGE);\n req.session = session;\n\n await session.regenerate();\n\n assert.notEqual(req.session, session);\n assert.notEqual(req.session.id, '123');\n assert.isNull(await store.get('123'));\n });\n });\n});\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"session.test.js","sourceRoot":"","sources":["../src/session.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { assert, describe, it } from 'vitest';\n\nimport { MemoryStore } from './memory-store.js';\nimport { loadSession, makeSession } from './session.js';\n\nconst SESSION_MAX_AGE = 10000;\nconst SESSION_EXPIRATION_DATE = new Date(Date.now() + SESSION_MAX_AGE);\n\ndescribe('session', () => {\n describe('loadSession', () => {\n it('loads session that does not exist', async () => {\n const store = new MemoryStore();\n\n const req = {} as any;\n const session = await loadSession('123', req, store, SESSION_MAX_AGE);\n\n assert.equal(session.id, '123');\n });\n\n it('loads session from store', async () => {\n const store = new MemoryStore();\n await store.set('123', { foo: 'bar' }, SESSION_EXPIRATION_DATE);\n\n const req = {} as any;\n const session = await loadSession('123', req, store, SESSION_MAX_AGE);\n\n assert.equal(session.id, '123');\n assert.equal(session.foo, 'bar');\n });\n\n it('does not try to overwrite existing session properties', async () => {\n const store = new MemoryStore();\n await store.set('123', { foo: 'bar', id: '456' }, SESSION_EXPIRATION_DATE);\n\n const req = {} as any;\n const session = await loadSession('123', req, store, SESSION_MAX_AGE);\n\n assert.equal(session.id, '123');\n assert.equal(session.foo, 'bar');\n });\n });\n\n describe('makeSession', () => {\n it('has immutable properties', () => {\n const store = new MemoryStore();\n\n const req = {} as any;\n const session = makeSession('123', req, store, SESSION_EXPIRATION_DATE, SESSION_MAX_AGE);\n\n assert.equal(session.id, '123');\n\n const originalId = session.id;\n const originalDestroy = session.destroy;\n const originalRegenerate = session.regenerate;\n\n assert.throw(() => {\n session.id = '456';\n });\n\n assert.throw(() => {\n session.destroy = async () => {};\n });\n\n assert.throw(() => {\n session.regenerate = async () => {};\n });\n\n assert.equal(session.id, originalId);\n assert.equal(session.destroy, originalDestroy);\n assert.equal(session.regenerate, originalRegenerate);\n });\n\n it('has immutable destroy property', async () => {\n const store = new MemoryStore();\n\n const req = {} as any;\n const session = makeSession('123', req, store, SESSION_EXPIRATION_DATE, SESSION_MAX_AGE);\n\n assert.throw(() => {\n session.destroy = async () => {};\n });\n });\n\n it('can destroy itself', async () => {\n const store = new MemoryStore();\n\n const req = {} as any;\n const session = makeSession('123', req, store, SESSION_EXPIRATION_DATE, SESSION_MAX_AGE);\n req.session = session;\n\n await session.destroy();\n\n assert.isUndefined(req.session);\n assert.isNull(await store.get('123'));\n });\n\n it('can regenerate itself', async () => {\n const store = new MemoryStore();\n\n const req = {} as any;\n const session = makeSession('123', req, store, SESSION_EXPIRATION_DATE, SESSION_MAX_AGE);\n req.session = session;\n\n await session.regenerate();\n\n assert.notEqual(req.session, session);\n assert.notEqual(req.session.id, '123');\n assert.isNull(await store.get('123'));\n });\n });\n});\n"]}
1
+ {"version":3,"file":"session.test.js","sourceRoot":"","sources":["../src/session.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { assert, describe, it } from 'vitest';\n\nimport { MemoryStore } from './memory-store.js';\nimport { loadSession, makeSession } from './session.js';\n\nconst SESSION_MAX_AGE = 10000;\nconst SESSION_EXPIRATION_DATE = new Date(Date.now() + SESSION_MAX_AGE);\n\ndescribe('session', () => {\n describe('loadSession', () => {\n it('loads session that does not exist', async () => {\n const store = new MemoryStore();\n\n const req = {} as any;\n const session = await loadSession('123', req, store, SESSION_MAX_AGE);\n\n assert.equal(session.id, '123');\n });\n\n it('loads session from store', async () => {\n const store = new MemoryStore();\n await store.set('123', { foo: 'bar' }, SESSION_EXPIRATION_DATE);\n\n const req = {} as any;\n const session = await loadSession('123', req, store, SESSION_MAX_AGE);\n\n assert.equal(session.id, '123');\n assert.equal(session.foo, 'bar');\n });\n\n it('does not try to overwrite existing session properties', async () => {\n const store = new MemoryStore();\n await store.set('123', { foo: 'bar', id: '456' }, SESSION_EXPIRATION_DATE);\n\n const req = {} as any;\n const session = await loadSession('123', req, store, SESSION_MAX_AGE);\n\n assert.equal(session.id, '123');\n assert.equal(session.foo, 'bar');\n });\n });\n\n describe('makeSession', () => {\n it('has immutable properties', () => {\n const store = new MemoryStore();\n\n const req = {} as any;\n const session = makeSession('123', req, store, SESSION_EXPIRATION_DATE, SESSION_MAX_AGE);\n\n assert.equal(session.id, '123');\n\n const originalId = session.id;\n const originalDestroy = session.destroy;\n const originalRegenerate = session.regenerate;\n\n assert.throw(() => {\n session.id = '456';\n });\n\n assert.throw(() => {\n session.destroy = async () => {};\n });\n\n assert.throw(() => {\n session.regenerate = async () => {};\n });\n\n assert.equal(session.id, originalId);\n assert.equal(session.destroy, originalDestroy);\n assert.equal(session.regenerate, originalRegenerate);\n });\n\n it('has immutable destroy property', async () => {\n const store = new MemoryStore();\n\n const req = {} as any;\n const session = makeSession('123', req, store, SESSION_EXPIRATION_DATE, SESSION_MAX_AGE);\n\n assert.throw(() => {\n session.destroy = async () => {};\n });\n });\n\n it('can destroy itself', async () => {\n const store = new MemoryStore();\n\n const req = {} as any;\n const session = makeSession('123', req, store, SESSION_EXPIRATION_DATE, SESSION_MAX_AGE);\n req.session = session;\n\n await session.destroy();\n\n assert.isUndefined(req.session);\n assert.isNull(await store.get('123'));\n });\n\n it('can regenerate itself', async () => {\n const store = new MemoryStore();\n\n const req = {} as any;\n const session = makeSession('123', req, store, SESSION_EXPIRATION_DATE, SESSION_MAX_AGE);\n req.session = session;\n\n await session.regenerate();\n\n assert.notEqual(req.session, session);\n assert.notEqual(req.session.id, '123');\n assert.isNull(await store.get('123'));\n });\n });\n});\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,GAAG,CAAC;IACV,SAAS,EAAE,IAAI,CAAC;CACjB;AAED,MAAM,WAAW,YAAY;IAC3B,GAAG,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,EAAE,SAAS,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9D,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAAC;IAClD,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACpC"}
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,GAAG,CAAC;IACV,SAAS,EAAE,IAAI,CAAC;CACjB;AAED,MAAM,WAAW,YAAY;IAC3B,GAAG,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,EAAE,SAAS,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9D,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAAC;IAClD,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACpC","sourcesContent":["export interface SessionStoreData {\n data: any;\n expiresAt: Date;\n}\n\nexport interface SessionStore {\n set(id: string, session: any, expiresAt: Date): Promise<void>;\n get(id: string): Promise<SessionStoreData | null>;\n destroy(id: string): Promise<void>;\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"test-utils.d.ts","sourceRoot":"","sources":["../src/test-utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,MAAM,EAAE,MAAM,WAAW,CAAC;AAExC,OAAO,EAAE,KAAK,OAAO,EAAE,MAAM,SAAS,CAAC;AAEvC,UAAU,iBAAiB;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;CACb;AAED,wBAAsB,UAAU,CAAC,GAAG,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,GAAG,EAAE,iBAAiB,KAAK,OAAO,CAAC,IAAI,CAAC,iBAiB3F"}
1
+ {"version":3,"file":"test-utils.d.ts","sourceRoot":"","sources":["../src/test-utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,MAAM,EAAE,MAAM,WAAW,CAAC;AAExC,OAAO,EAAE,KAAK,OAAO,EAAE,MAAM,SAAS,CAAC;AAEvC,UAAU,iBAAiB;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;CACb;AAED,wBAAsB,UAAU,CAAC,GAAG,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,GAAG,EAAE,iBAAiB,KAAK,OAAO,CAAC,IAAI,CAAC,iBAiB3F","sourcesContent":["import { type Server } from 'node:http';\n\nimport { type Express } from 'express';\n\ninterface WithServerContext {\n server: Server;\n port: number;\n url: string;\n}\n\nexport async function withServer(app: Express, fn: (ctx: WithServerContext) => Promise<void>) {\n const server = app.listen();\n\n await new Promise<void>((resolve, reject) => {\n server.on('listening', () => resolve());\n server.on('error', (err) => reject(err));\n });\n\n try {\n await fn({\n server,\n port: getServerPort(server),\n url: `http://localhost:${getServerPort(server)}`,\n });\n } finally {\n server.close();\n }\n}\n\nfunction getServerPort(server: Server): number {\n const address = server.address();\n\n // istanbul ignore next\n if (!address) throw new Error('Server is not listening');\n\n // istanbul ignore next\n if (typeof address === 'string') throw new Error('Server is listening on a pipe');\n\n return address.port;\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"test-utils.js","sourceRoot":"","sources":["../src/test-utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,MAAM,WAAW,CAAC;AAExC,OAAO,EAAgB,MAAM,SAAS,CAAC;AAQvC,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,GAAY,EAAE,EAA6C;IAC1F,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,CAAC;IAE5B,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QAC1C,MAAM,CAAC,EAAE,CAAC,WAAW,EAAE,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC;QACxC,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;IAC3C,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC;QACH,MAAM,EAAE,CAAC;YACP,MAAM;YACN,IAAI,EAAE,aAAa,CAAC,MAAM,CAAC;YAC3B,GAAG,EAAE,oBAAoB,aAAa,CAAC,MAAM,CAAC,EAAE;SACjD,CAAC,CAAC;IACL,CAAC;YAAS,CAAC;QACT,MAAM,CAAC,KAAK,EAAE,CAAC;IACjB,CAAC;AACH,CAAC;AAED,SAAS,aAAa,CAAC,MAAc;IACnC,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,EAAE,CAAC;IAEjC,uBAAuB;IACvB,IAAI,CAAC,OAAO;QAAE,MAAM,IAAI,KAAK,CAAC,yBAAyB,CAAC,CAAC;IAEzD,uBAAuB;IACvB,IAAI,OAAO,OAAO,KAAK,QAAQ;QAAE,MAAM,IAAI,KAAK,CAAC,+BAA+B,CAAC,CAAC;IAElF,OAAO,OAAO,CAAC,IAAI,CAAC;AACtB,CAAC","sourcesContent":["import { type Server } from 'node:http';\n\nimport { type Express } from 'express';\n\ninterface WithServerContext {\n server: Server;\n port: number;\n url: string;\n}\n\nexport async function withServer(app: Express, fn: (ctx: WithServerContext) => Promise<void>) {\n const server = app.listen();\n\n await new Promise<void>((resolve, reject) => {\n server.on('listening', () => resolve());\n server.on('error', (err) => reject(err));\n });\n\n try {\n await fn({\n server,\n port: getServerPort(server),\n url: `http://localhost:${getServerPort(server)}`,\n });\n } finally {\n server.close();\n }\n}\n\nfunction getServerPort(server: Server): number {\n const address = server.address();\n\n // istanbul ignore next\n if (!address) throw new Error('Server is not listening');\n\n // istanbul ignore next\n if (typeof address === 'string') throw new Error('Server is listening on a pipe');\n\n return address.port;\n}\n"]}
1
+ {"version":3,"file":"test-utils.js","sourceRoot":"","sources":["../src/test-utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,MAAM,WAAW,CAAC;AAExC,OAAO,EAAgB,MAAM,SAAS,CAAC;AAQvC,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,GAAY,EAAE,EAA6C,EAAE;IAC5F,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,CAAC;IAE5B,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,CAAC;QAC3C,MAAM,CAAC,EAAE,CAAC,WAAW,EAAE,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC;QACxC,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;IAAA,CAC1C,CAAC,CAAC;IAEH,IAAI,CAAC;QACH,MAAM,EAAE,CAAC;YACP,MAAM;YACN,IAAI,EAAE,aAAa,CAAC,MAAM,CAAC;YAC3B,GAAG,EAAE,oBAAoB,aAAa,CAAC,MAAM,CAAC,EAAE;SACjD,CAAC,CAAC;IACL,CAAC;YAAS,CAAC;QACT,MAAM,CAAC,KAAK,EAAE,CAAC;IACjB,CAAC;AAAA,CACF;AAED,SAAS,aAAa,CAAC,MAAc,EAAU;IAC7C,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,EAAE,CAAC;IAEjC,uBAAuB;IACvB,IAAI,CAAC,OAAO;QAAE,MAAM,IAAI,KAAK,CAAC,yBAAyB,CAAC,CAAC;IAEzD,uBAAuB;IACvB,IAAI,OAAO,OAAO,KAAK,QAAQ;QAAE,MAAM,IAAI,KAAK,CAAC,+BAA+B,CAAC,CAAC;IAElF,OAAO,OAAO,CAAC,IAAI,CAAC;AAAA,CACrB","sourcesContent":["import { type Server } from 'node:http';\n\nimport { type Express } from 'express';\n\ninterface WithServerContext {\n server: Server;\n port: number;\n url: string;\n}\n\nexport async function withServer(app: Express, fn: (ctx: WithServerContext) => Promise<void>) {\n const server = app.listen();\n\n await new Promise<void>((resolve, reject) => {\n server.on('listening', () => resolve());\n server.on('error', (err) => reject(err));\n });\n\n try {\n await fn({\n server,\n port: getServerPort(server),\n url: `http://localhost:${getServerPort(server)}`,\n });\n } finally {\n server.close();\n }\n}\n\nfunction getServerPort(server: Server): number {\n const address = server.address();\n\n // istanbul ignore next\n if (!address) throw new Error('Server is not listening');\n\n // istanbul ignore next\n if (typeof address === 'string') throw new Error('Server is listening on a pipe');\n\n return address.port;\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prairielearn/session",
3
- "version": "3.0.24",
3
+ "version": "3.0.26",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -9,8 +9,8 @@
9
9
  },
10
10
  "main": "dist/index.js",
11
11
  "scripts": {
12
- "build": "tsc",
13
- "dev": "tsc --watch --preserveWatchOutput",
12
+ "build": "tsgo",
13
+ "dev": "tsgo --watch --preserveWatchOutput",
14
14
  "test": "vitest run --coverage"
15
15
  },
16
16
  "dependencies": {
@@ -22,23 +22,24 @@
22
22
  "uid-safe": "^2.1.5"
23
23
  },
24
24
  "devDependencies": {
25
- "@prairielearn/express-test-utils": "^2.0.5",
25
+ "@prairielearn/express-test-utils": "^2.0.6",
26
26
  "@prairielearn/tsconfig": "^0.0.0",
27
27
  "@types/cookie": "^0.6.0",
28
28
  "@types/cookie-signature": "^1.1.2",
29
- "@types/node": "^22.18.13",
29
+ "@types/node": "^22.19.5",
30
30
  "@types/node-fetch": "^2.6.13",
31
31
  "@types/on-headers": "^1.0.4",
32
32
  "@types/set-cookie-parser": "^2.4.10",
33
33
  "@types/uid-safe": "^2.1.5",
34
- "@vitest/coverage-v8": "^4.0.6",
35
- "express": "^4.21.2",
36
- "fetch-cookie": "^3.1.0",
34
+ "@typescript/native-preview": "^7.0.0-dev.20260106.1",
35
+ "@vitest/coverage-v8": "^4.0.17",
36
+ "express": "^4.22.1",
37
+ "fetch-cookie": "^3.2.0",
37
38
  "node-fetch": "^3.3.2",
38
- "set-cookie-parser": "^2.7.2",
39
+ "set-cookie-parser": "^3.0.1",
39
40
  "source-map-support": "^0.5.21",
40
- "tsx": "^4.20.6",
41
+ "tsx": "^4.21.0",
41
42
  "typescript": "^5.9.3",
42
- "vitest": "^4.0.6"
43
+ "vitest": "^4.0.17"
43
44
  }
44
45
  }
package/src/index.test.ts CHANGED
@@ -14,6 +14,8 @@ import { createSessionMiddleware } from './index.js';
14
14
  const TEST_SECRET = 'test-secret';
15
15
 
16
16
  function parseSetCookie(header: string) {
17
+ // TODO: The types for `set-cookie-parser` are outdated. See https://github.com/PrairieLearn/PrairieLearn/pull/13764.
18
+ // For now, we will rely on the "soft-deprecated" fields and not change how this works.
17
19
  return setCookie.parse(setCookie.splitCookiesString(header));
18
20
  }
19
21