@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 +25 -19
- package/index.js +43 -16
- package/package.json +2 -2
- package/test/cookie.test.js +19 -0
- package/types/index.d.ts +2 -2
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
|
-
|
|
171
|
-
|
|
172
|
-
|
|
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
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
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#
|
|
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
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
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
|
-
|
|
249
|
-
|
|
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://
|
|
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 || '/'}`, {
|
|
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',
|
|
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(
|
|
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
|
|
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
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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
|
-
|
|
179
|
+
await dynamicLoadCookie()
|
|
153
180
|
|
|
154
181
|
// ***************************
|
|
155
182
|
function parseCookie (cookieHeader) {
|
|
156
|
-
return
|
|
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 =
|
|
210
|
-
module.exports.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.
|
|
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": "^
|
|
71
|
+
"cookie": "^2.0.0",
|
|
72
72
|
"fastify-plugin": "^6.0.0"
|
|
73
73
|
},
|
|
74
74
|
"publishConfig": {
|
package/test/cookie.test.js
CHANGED
|
@@ -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 {
|
|
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 =
|
|
97
|
+
type FastifyCookiePlugin = FastifyPluginAsync<
|
|
98
98
|
NonNullable<fastifyCookie.FastifyCookieOptions>
|
|
99
99
|
>
|
|
100
100
|
|