@fastify/cookie 11.1.0 → 11.1.2

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/README.md CHANGED
@@ -14,11 +14,13 @@ upon this plugin's actions.
14
14
  It is also possible to [import the low-level cookie parsing and serialization functions](#importing-serialize-and-parse).
15
15
 
16
16
  ## Install
17
+
17
18
  ```sh
18
19
  npm i @fastify/cookie
19
20
  ```
20
21
 
21
22
  ### Compatibility
23
+
22
24
  | Plugin version | Fastify version |
23
25
  | ---------------|-----------------|
24
26
  | `>=10.x` | `^5.x` |
@@ -162,14 +164,13 @@ attribute. When truthy, the `Partitioned` attribute is set, otherwise it is not.
162
164
 
163
165
  More information about this can be found in [the proposal](https://github.com/privacycg/CHIPS).
164
166
 
165
-
166
167
  ##### priority
167
168
 
168
169
  Specifies the `string` to be the value for the [`Priority` `Set-Cookie` attribute](https://datatracker.ietf.org/doc/html/draft-west-cookie-priority-00#section-4.1).
169
170
 
170
- - `'low'` will set the `Priority` attribute to `Low`.
171
- - `'medium'` will set the `Priority` attribute to `Medium`, the default priority when not set.
172
- - `'high'` will set the `Priority` attribute to `High`.
171
+ - `'low'` will set the `Priority` attribute to `Low`.
172
+ - `'medium'` will set the `Priority` attribute to `Medium`, the default priority when not set.
173
+ - `'high'` will set the `Priority` attribute to `High`.
173
174
 
174
175
  More information about the different priority levels can be found in
175
176
  [the specification](https://datatracker.ietf.org/doc/html/draft-west-cookie-priority-00#section-4.1).
@@ -185,11 +186,11 @@ is considered the ["default path"](https://datatracker.ietf.org/doc/html/rfc6265
185
186
 
186
187
  Specifies the `boolean` or `string` to be the value for the [`SameSite` `Set-Cookie` attribute](https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-09#section-5.4.7).
187
188
 
188
- - `true` will set the `SameSite` attribute to `Strict` for strict same site enforcement.
189
- - `false` will not set the `SameSite` attribute.
190
- - `'lax'` will set the `SameSite` attribute to `Lax` for lax same site enforcement.
191
- - `'none'` will set the `SameSite` attribute to `None` for an explicit cross-site cookie.
192
- - `'strict'` will set the `SameSite` attribute to `Strict` for strict same site enforcement.
189
+ - `true` will set the `SameSite` attribute to `Strict` for strict same site enforcement.
190
+ - `false` will not set the `SameSite` attribute.
191
+ - `'lax'` will set the `SameSite` attribute to `Lax` for lax same site enforcement.
192
+ - `'none'` will set the `SameSite` attribute to `None` for an explicit cross-site cookie.
193
+ - `'strict'` will set the `SameSite` attribute to `Strict` for strict same site enforcement.
193
194
 
194
195
  More information about the different enforcement levels can be found in
195
196
  [the specification](https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-09#section-5.4.7).
@@ -214,7 +215,7 @@ as an object named `cookies`. Thus, if a request contains the header
214
215
  `Cookie: foo=foo` then, within your handler, `req.cookies.foo` would equal
215
216
  `'foo'`.
216
217
 
217
- You can pass options to the [cookie parse](https://github.com/jshttp/cookie#cookieparsestr-options) by setting an object named `parseOptions` in the plugin config object.
218
+ You can pass options to the [cookie parse](https://github.com/jshttp/cookie#cookieparsecookiestr-options) by setting an object named `parseOptions` in the plugin config object.
218
219
 
219
220
  ### Sending
220
221
 
@@ -223,11 +224,11 @@ via the Fastify `decorateReply` API. Thus, in a request handler,
223
224
  `reply.setCookie('foo', 'foo', {path: '/'})` will set a cookie named `foo`
224
225
  with a value of `'foo'` on the cookie path `/`.
225
226
 
226
- + `name`: a string name for the cookie to be set
227
- + `value`: a string value for the cookie
228
- + `options`: an options object as described in the [cookie serialize][cs] documentation
229
- + `options.signed`: the cookie should be signed
230
- + `options.secure`: if set to `true` it will set the Secure-flag. If it is set to `"auto"` Secure-flag is set when the connection is using tls.
227
+ - `name`: a string name for the cookie to be set
228
+ - `value`: a string value for the cookie
229
+ - `options`: an options object as described in the [cookie serialize][cs] documentation
230
+ - `options.signed`: the cookie should be signed
231
+ - `options.secure`: if set to `true` it will set the Secure-flag. If it is set to `"auto"` Secure-flag is set when the connection is using tls.
231
232
 
232
233
  #### Securing the cookie
233
234
 
@@ -245,8 +246,8 @@ via the Fastify `decorateReply` API. Thus, in a request handler,
245
246
  `reply.clearCookie('foo', {path: '/'})` will clear a cookie named `foo`
246
247
  on the cookie path `/`.
247
248
 
248
- + `name`: a string name for the cookie to be cleared
249
- + `options`: an options object as described in the [cookie serialize][cs]
249
+ - `name`: a string name for the cookie to be cleared
250
+ - `options`: an options object as described in the [cookie serialize][cs]
250
251
  documentation. It is optional to pass an `options` object
251
252
 
252
253
  ### Manual cookie parsing
@@ -255,14 +256,16 @@ The method `parseCookie(cookieHeader)` is added to the `fastify` instance
255
256
  via the Fastify `decorate` API. Thus, `fastify.parseCookie('sessionId=aYb4uTIhdBXC')`
256
257
  will parse the raw cookie header and return an object `{ "sessionId": "aYb4uTIhdBXC" }`.
257
258
 
258
- [cs]: https://www.npmjs.com/package/cookie#options-1
259
+ [cs]: https://github.com/jshttp/cookie#cookiestringifysetcookiesetcookieobj-options
259
260
 
260
261
  <a id="rotating-secret"></a>
262
+
261
263
  ### Rotating signing secret
262
264
 
263
265
  Key rotation is when an encryption key is retired and replaced by generating a new cryptographic key. To implement rotation, supply an `Array` of keys to `secret` option.
264
266
 
265
267
  **Example:**
268
+
266
269
  ```js
267
270
  fastify.register(require('@fastify/cookie'), {
268
271
  secret: [key1, key2]
@@ -272,11 +275,13 @@ fastify.register(require('@fastify/cookie'), {
272
275
  The plugin will **always** use the first key (`key1`) to sign cookies. When parsing incoming cookies, it will iterate over the supplied array to see if any of the available keys are able to decode the given signed cookie. This ensures that any old signed cookies are still valid.
273
276
 
274
277
  Note:
278
+
275
279
  - Key rotation is **only** achieved by redeploying the server again with the new `secret` array.
276
280
  - Iterating through all secrets is an expensive process, so the rotation list should contain as few keys as possible. Ideally, only the current key and the most recently retired key.
277
281
  - Although previously signed cookies are valid even after rotation, cookies should be updated with the new key as soon as possible. See the following example for how to accomplish this.
278
282
 
279
283
  **Example:**
284
+
280
285
  ```js
281
286
  fastify.get('/', (req, reply) => {
282
287
  const result = reply.unsignCookie(req.cookies.myCookie)
@@ -293,11 +298,13 @@ fastify.get('/', (req, reply) => {
293
298
  ```
294
299
 
295
300
  <a id="custom-cookie-signer"></a>
301
+
296
302
  ### Custom cookie signer
297
303
 
298
304
  The `secret` option optionally accepts an object with `sign` and `unsign` functions. This allows for implementing a custom cookie signing mechanism. See the following example:
299
305
 
300
306
  **Example:**
307
+
301
308
  ```js
302
309
  fastify.register(require('@fastify/cookie'), {
303
310
  secret: {
@@ -382,7 +389,6 @@ const signedValue = fastifyCookie.sign('test', 'secret');
382
389
  const unsignedvalue = fastifyCookie.unsign(signedValue, 'secret');
383
390
  ```
384
391
 
385
-
386
392
  ## License
387
393
 
388
394
  Licensed under [MIT](./LICENSE).
package/index.js CHANGED
@@ -1,10 +1,25 @@
1
1
  'use strict'
2
2
 
3
3
  const fp = require('fastify-plugin')
4
- const cookie = require('cookie')
5
4
 
6
5
  const { Signer, sign, unsign } = require('./signer')
7
6
 
7
+ // TODO: Use require(ESM) when Node.js 20 is nolonger supported or Fastify@6
8
+ let cookieModule = null
9
+ async function dynamicLoadCookie () {
10
+ if (cookieModule == null) {
11
+ cookieModule = await import('cookie')
12
+ }
13
+ }
14
+
15
+ function serialize (name, value, options) {
16
+ return cookieModule.stringifySetCookie(Object.assign({ name, value }, options))
17
+ }
18
+
19
+ function parse (header, options) {
20
+ return cookieModule.parseCookie(header, options)
21
+ }
22
+
8
23
  const kReplySetCookies = Symbol('fastify.reply.setCookies')
9
24
  const kReplySetCookiesHookRan = Symbol('fastify.reply.setCookiesHookRan')
10
25
 
@@ -29,7 +44,11 @@ function fastifyCookieSetCookie (reply, name, value, options) {
29
44
  }
30
45
  }
31
46
 
32
- reply[kReplySetCookies].set(`${name};${opts.domain};${opts.path || '/'}`, { name, value, opts })
47
+ reply[kReplySetCookies].set(`${name};${opts.domain};${opts.path || '/'}`, {
48
+ name,
49
+ value,
50
+ opts
51
+ })
33
52
 
34
53
  if (reply[kReplySetCookiesHookRan]) {
35
54
  setCookies(reply)
@@ -77,7 +96,7 @@ function setCookies (reply) {
77
96
  if (reply[kReplySetCookies].size === 1) {
78
97
  // Fast path for single cookie
79
98
  const c = reply[kReplySetCookies].values().next().value
80
- reply.header('Set-Cookie', cookie.serialize(c.name, c.value, c.opts))
99
+ reply.header('Set-Cookie', serialize(c.name, c.value, c.opts))
81
100
  reply[kReplySetCookies].clear()
82
101
  return
83
102
  }
@@ -90,7 +109,7 @@ function setCookies (reply) {
90
109
  }
91
110
 
92
111
  for (const c of reply[kReplySetCookies].values()) {
93
- cookieValue.push(cookie.serialize(c.name, c.value, c.opts))
112
+ cookieValue.push(serialize(c.name, c.value, c.opts))
94
113
  }
95
114
 
96
115
  reply.removeHeader('Set-Cookie')
@@ -113,16 +132,22 @@ function fastifyCookieOnSendHandler (_fastifyReq, fastifyRes, _payload, done) {
113
132
  done()
114
133
  }
115
134
 
116
- function plugin (fastify, options, next) {
135
+ async function plugin (fastify, options) {
117
136
  const secret = options.secret
118
137
  const hook = getHook(options.hook)
119
138
  if (hook === undefined) {
120
- return next(new Error('@fastify/cookie: Invalid value provided for the hook-option. You can set the hook-option only to false, \'onRequest\' , \'preParsing\' , \'preValidation\' or \'preHandler\''))
121
- }
122
- const isSigner = !secret || (typeof secret.sign === 'function' && typeof secret.unsign === 'function')
123
- const signer = isSigner ? secret : new Signer(secret, options.algorithm || 'sha256')
124
-
125
- fastify.decorate('serializeCookie', cookie.serialize)
139
+ throw new Error(
140
+ "@fastify/cookie: Invalid value provided for the hook-option. You can set the hook-option only to false, 'onRequest' , 'preParsing' , 'preValidation' or 'preHandler'"
141
+ )
142
+ }
143
+ const isSigner =
144
+ !secret ||
145
+ (typeof secret.sign === 'function' && typeof secret.unsign === 'function')
146
+ const signer = isSigner
147
+ ? secret
148
+ : new Signer(secret, options.algorithm || 'sha256')
149
+
150
+ fastify.decorate('serializeCookie', serialize)
126
151
  fastify.decorate('parseCookie', parseCookie)
127
152
 
128
153
  if (secret !== undefined) {
@@ -146,14 +171,16 @@ function plugin (fastify, options, next) {
146
171
 
147
172
  if (hook) {
148
173
  fastify.addHook(hook, onReqHandlerWrapper(fastify, hook))
149
- fastify.addHook('onSend', fastifyCookieOnSendHandler)
150
174
  }
175
+ // The onSend writer is independent of the parsing hook: `hook: false` only
176
+ // disables cookie autoparsing, not the ability to set cookies on the reply.
177
+ fastify.addHook('onSend', fastifyCookieOnSendHandler)
151
178
 
152
- next()
179
+ await dynamicLoadCookie()
153
180
 
154
181
  // ***************************
155
182
  function parseCookie (cookieHeader) {
156
- return cookie.parse(cookieHeader, options.parseOptions)
183
+ return parse(cookieHeader, options.parseOptions)
157
184
  }
158
185
 
159
186
  function signCookie (value) {
@@ -206,8 +233,8 @@ module.exports = fastifyCookie
206
233
  module.exports.default = fastifyCookie // supersedes fastifyCookie.default = fastifyCookie
207
234
  module.exports.fastifyCookie = fastifyCookie // supersedes fastifyCookie.fastifyCookie = fastifyCookie
208
235
 
209
- module.exports.serialize = cookie.serialize
210
- module.exports.parse = cookie.parse
236
+ module.exports.serialize = serialize
237
+ module.exports.parse = parse
211
238
 
212
239
  module.exports.signerFactory = Signer
213
240
  module.exports.Signer = Signer
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fastify/cookie",
3
- "version": "11.1.0",
3
+ "version": "11.1.2",
4
4
  "description": "Plugin for fastify to add support for cookies",
5
5
  "main": "index.js",
6
6
  "type": "commonjs",
@@ -68,7 +68,7 @@
68
68
  "tstyche": "^7.0.0"
69
69
  },
70
70
  "dependencies": {
71
- "cookie": "^1.0.0",
71
+ "cookie": "^2.0.0",
72
72
  "fastify-plugin": "^6.0.0"
73
73
  },
74
74
  "publishConfig": {
@@ -929,6 +929,25 @@ test('dont add auto cookie parsing to onRequest-hook if hook-option is set to fa
929
929
  t.assert.strictEqual(res.statusCode, 200)
930
930
  })
931
931
 
932
+ test('setCookie writes Set-Cookie even when hook-option is set to false', async (t) => {
933
+ t.plan(2)
934
+
935
+ const fastify = Fastify()
936
+ fastify.register(plugin, { hook: false })
937
+
938
+ fastify.get('/', (req, reply) => {
939
+ reply.setCookie('foo', 'bar', { path: '/' }).send({ hello: 'world' })
940
+ })
941
+
942
+ const res = await fastify.inject({
943
+ method: 'GET',
944
+ url: '/'
945
+ })
946
+
947
+ t.assert.strictEqual(res.statusCode, 200)
948
+ t.assert.ok(res.headers['set-cookie'], 'Set-Cookie should be written even with hook:false')
949
+ })
950
+
932
951
  test('result in an error if hook-option is set to an invalid value', async (t) => {
933
952
  t.plan(1)
934
953
 
package/types/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  /// <reference types='node' />
2
2
 
3
- import { FastifyPluginCallback } from 'fastify'
3
+ import { FastifyPluginAsync } from 'fastify'
4
4
 
5
5
  declare module 'fastify' {
6
6
  interface FastifyInstance extends SignerMethods {
@@ -94,7 +94,7 @@ declare module 'fastify' {
94
94
  }
95
95
  }
96
96
 
97
- type FastifyCookiePlugin = FastifyPluginCallback<
97
+ type FastifyCookiePlugin = FastifyPluginAsync<
98
98
  NonNullable<fastifyCookie.FastifyCookieOptions>
99
99
  >
100
100