@fastify/cookie 11.0.2 → 11.1.1
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/.github/dependabot.yml +41 -1
- package/.github/workflows/ci.yml +12 -2
- package/.github/workflows/lock-threads.yml +19 -0
- package/LICENSE +1 -1
- package/README.md +35 -29
- package/{plugin.js → index.js} +32 -14
- package/package.json +12 -20
- package/test/cookie.test.js +19 -0
- package/types/{plugin.test-d.ts → index.tst.ts} +102 -61
- package/.github/stale.yml +0 -21
- /package/types/{plugin.d.ts → index.d.ts} +0 -0
package/.github/dependabot.yml
CHANGED
|
@@ -2,12 +2,52 @@ version: 2
|
|
|
2
2
|
updates:
|
|
3
3
|
- package-ecosystem: "github-actions"
|
|
4
4
|
directory: "/"
|
|
5
|
+
commit-message:
|
|
6
|
+
# Prefix all commit messages with "chore: "
|
|
7
|
+
prefix: "chore"
|
|
5
8
|
schedule:
|
|
6
|
-
interval: "
|
|
9
|
+
interval: "weekly"
|
|
10
|
+
cooldown:
|
|
11
|
+
default-days: 7
|
|
12
|
+
allow:
|
|
13
|
+
- dependency-name: "*"
|
|
14
|
+
update-types:
|
|
15
|
+
- "version-update:semver-major"
|
|
7
16
|
open-pull-requests-limit: 10
|
|
8
17
|
|
|
9
18
|
- package-ecosystem: "npm"
|
|
10
19
|
directory: "/"
|
|
20
|
+
commit-message:
|
|
21
|
+
# Prefix all commit messages with "chore: "
|
|
22
|
+
prefix: "chore"
|
|
11
23
|
schedule:
|
|
12
24
|
interval: "weekly"
|
|
25
|
+
cooldown:
|
|
26
|
+
default-days: 7
|
|
27
|
+
versioning-strategy: "increase-if-necessary"
|
|
28
|
+
allow:
|
|
29
|
+
- dependency-name: "*"
|
|
30
|
+
update-types:
|
|
31
|
+
- "version-update:semver-major"
|
|
32
|
+
ignore:
|
|
33
|
+
# TODO: remove ignore until neostandard support ESLint 10
|
|
34
|
+
- dependency-name: "eslint"
|
|
35
|
+
- dependency-name: "neostandard"
|
|
36
|
+
- dependency-name: "@stylistic/*"
|
|
13
37
|
open-pull-requests-limit: 10
|
|
38
|
+
groups:
|
|
39
|
+
# Production dependencies with breaking changes
|
|
40
|
+
dependencies:
|
|
41
|
+
dependency-type: "production"
|
|
42
|
+
# ESLint related dependencies
|
|
43
|
+
dev-dependencies-eslint:
|
|
44
|
+
patterns:
|
|
45
|
+
- "eslint"
|
|
46
|
+
- "neostandard"
|
|
47
|
+
- "@stylistic/*"
|
|
48
|
+
# TypeScript related dependencies
|
|
49
|
+
dev-dependencies-typescript:
|
|
50
|
+
patterns:
|
|
51
|
+
- "@types/*"
|
|
52
|
+
- "tstyche"
|
|
53
|
+
- "typescript"
|
package/.github/workflows/ci.yml
CHANGED
|
@@ -4,7 +4,6 @@ on:
|
|
|
4
4
|
push:
|
|
5
5
|
branches:
|
|
6
6
|
- main
|
|
7
|
-
- master
|
|
8
7
|
- next
|
|
9
8
|
- 'v*'
|
|
10
9
|
paths-ignore:
|
|
@@ -15,9 +14,20 @@ on:
|
|
|
15
14
|
- 'docs/**'
|
|
16
15
|
- '*.md'
|
|
17
16
|
|
|
17
|
+
# This allows a subsequently queued workflow run to interrupt previous runs
|
|
18
|
+
concurrency:
|
|
19
|
+
group: "${{ github.workflow }}-${{ github.event.pull_request.head.label || github.head_ref || github.ref }}"
|
|
20
|
+
cancel-in-progress: true
|
|
21
|
+
|
|
22
|
+
permissions:
|
|
23
|
+
contents: read
|
|
24
|
+
|
|
18
25
|
jobs:
|
|
19
26
|
test:
|
|
20
|
-
|
|
27
|
+
permissions:
|
|
28
|
+
contents: write
|
|
29
|
+
pull-requests: write
|
|
30
|
+
uses: fastify/workflows/.github/workflows/plugins-ci.yml@v6
|
|
21
31
|
with:
|
|
22
32
|
license-check: true
|
|
23
33
|
lint: true
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
name: Lock Threads
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
schedule:
|
|
5
|
+
- cron: '0 0 1 * *'
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
|
|
8
|
+
concurrency:
|
|
9
|
+
group: lock
|
|
10
|
+
|
|
11
|
+
permissions:
|
|
12
|
+
contents: read
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
lock-threads:
|
|
16
|
+
permissions:
|
|
17
|
+
issues: write
|
|
18
|
+
pull-requests: write
|
|
19
|
+
uses: fastify/workflows/.github/workflows/lock-threads.yml@v6
|
package/LICENSE
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
MIT License
|
|
2
2
|
|
|
3
|
-
Copyright (c) Fastify
|
|
3
|
+
Copyright (c) 2017-present The Fastify team <https://github.com/fastify/fastify#team>
|
|
4
4
|
|
|
5
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
6
|
of this software and associated documentation files (the "Software"), to deal
|
package/README.md
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# @fastify/cookie
|
|
2
2
|
|
|
3
|
-
[](https://github.com/fastify/fastify-cookie/actions/workflows/ci.yml)
|
|
4
4
|
[](https://www.npmjs.com/package/@fastify/cookie)
|
|
5
5
|
[](https://github.com/neostandard/neostandard)
|
|
6
6
|
|
|
7
|
-
A plugin for [Fastify](
|
|
7
|
+
A plugin for [Fastify](https://fastify.dev/) that adds support for reading and
|
|
8
8
|
setting cookies.
|
|
9
9
|
|
|
10
10
|
This plugin's cookie parsing works via Fastify's `onRequest` hook. Therefore,
|
|
@@ -14,17 +14,19 @@ 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
|
-
|
|
|
25
|
-
|
|
|
26
|
-
|
|
|
27
|
-
|
|
|
26
|
+
| `>=10.x` | `^5.x` |
|
|
27
|
+
| `>=7.x <10.x` | `^4.x` |
|
|
28
|
+
| `>=4.x <7.x` | `^3.x` |
|
|
29
|
+
| `>=2.x <4.x` | `^2.x` |
|
|
28
30
|
| `^1.x` | `^1.x` |
|
|
29
31
|
|
|
30
32
|
Please note that if a Fastify version is out of support, then so are the corresponding versions of this plugin
|
|
@@ -95,7 +97,7 @@ fastify.get('/', (req, reply) => {
|
|
|
95
97
|
## Options
|
|
96
98
|
|
|
97
99
|
- `secret` (`String` | `Array` | `Buffer` | `Object`):
|
|
98
|
-
- A `String` or `Buffer` can be passed to use as secret to sign the cookie using [`cookie-signature`](
|
|
100
|
+
- A `String` or `Buffer` can be passed to use as secret to sign the cookie using [`cookie-signature`](https://www.npmjs.com/package/cookie-signature).
|
|
99
101
|
- An `Array` can be passed if key rotation is desired. Read more about it in [Rotating signing secret](#rotating-secret).
|
|
100
102
|
- More sophisticated cookie signing mechanisms can be implemented by supplying an `Object`. Read more about it in [Custom cookie signer](#custom-cookie-signer).
|
|
101
103
|
|
|
@@ -162,17 +164,16 @@ 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
|
-
Specifies the `string` to be the value for the [`Priority` `Set-Cookie` attribute](https://
|
|
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
|
-
[the specification](https://
|
|
176
|
+
[the specification](https://datatracker.ietf.org/doc/html/draft-west-cookie-priority-00#section-4.1).
|
|
176
177
|
|
|
177
178
|
⚠️ **Warning:** This is an attribute that has not yet been fully standardized, and may change in the future without reflecting the semver versioning. This also means many clients may ignore the attribute until they understand it.
|
|
178
179
|
|
|
@@ -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
|
|
|
@@ -235,7 +236,7 @@ Following are _some_ of the precautions that should be taken to ensure the integ
|
|
|
235
236
|
|
|
236
237
|
- It's important to use `options.httpOnly` cookies to prevent attacks like XSS.
|
|
237
238
|
- Use signed cookies (`options.signed`) to ensure they are not getting tampered wit client-side by an attacker.
|
|
238
|
-
- Use `__Host-` [Cookie Prefix](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#
|
|
239
|
+
- Use `__Host-` [Cookie Prefix](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Set-Cookie#attributes) to avoid Cookie Tossing attacks.
|
|
239
240
|
- It is important to [use HTTPS for your website/app](https://letsencrypt.org/) to avoid a bunch of other potential security issues like [MITM](https://en.wikipedia.org/wiki/Man-in-the-middle_attack) etc.
|
|
240
241
|
|
|
241
242
|
### Clearing
|
|
@@ -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/{plugin.js → index.js}
RENAMED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const fp = require('fastify-plugin')
|
|
4
|
-
const
|
|
4
|
+
const { parseCookie: parse, stringifySetCookie } = require('cookie')
|
|
5
5
|
|
|
6
6
|
const { Signer, sign, unsign } = require('./signer')
|
|
7
7
|
|
|
8
|
+
function serialize (name, value, options) {
|
|
9
|
+
return stringifySetCookie(Object.assign({ name, value }, options))
|
|
10
|
+
}
|
|
11
|
+
|
|
8
12
|
const kReplySetCookies = Symbol('fastify.reply.setCookies')
|
|
9
13
|
const kReplySetCookiesHookRan = Symbol('fastify.reply.setCookiesHookRan')
|
|
10
14
|
|
|
@@ -29,7 +33,11 @@ function fastifyCookieSetCookie (reply, name, value, options) {
|
|
|
29
33
|
}
|
|
30
34
|
}
|
|
31
35
|
|
|
32
|
-
reply[kReplySetCookies].set(`${name};${opts.domain};${opts.path || '/'}`, {
|
|
36
|
+
reply[kReplySetCookies].set(`${name};${opts.domain};${opts.path || '/'}`, {
|
|
37
|
+
name,
|
|
38
|
+
value,
|
|
39
|
+
opts
|
|
40
|
+
})
|
|
33
41
|
|
|
34
42
|
if (reply[kReplySetCookiesHookRan]) {
|
|
35
43
|
setCookies(reply)
|
|
@@ -77,7 +85,7 @@ function setCookies (reply) {
|
|
|
77
85
|
if (reply[kReplySetCookies].size === 1) {
|
|
78
86
|
// Fast path for single cookie
|
|
79
87
|
const c = reply[kReplySetCookies].values().next().value
|
|
80
|
-
reply.header('Set-Cookie',
|
|
88
|
+
reply.header('Set-Cookie', serialize(c.name, c.value, c.opts))
|
|
81
89
|
reply[kReplySetCookies].clear()
|
|
82
90
|
return
|
|
83
91
|
}
|
|
@@ -90,7 +98,7 @@ function setCookies (reply) {
|
|
|
90
98
|
}
|
|
91
99
|
|
|
92
100
|
for (const c of reply[kReplySetCookies].values()) {
|
|
93
|
-
cookieValue.push(
|
|
101
|
+
cookieValue.push(serialize(c.name, c.value, c.opts))
|
|
94
102
|
}
|
|
95
103
|
|
|
96
104
|
reply.removeHeader('Set-Cookie')
|
|
@@ -117,12 +125,20 @@ function plugin (fastify, options, next) {
|
|
|
117
125
|
const secret = options.secret
|
|
118
126
|
const hook = getHook(options.hook)
|
|
119
127
|
if (hook === undefined) {
|
|
120
|
-
return next(
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
128
|
+
return next(
|
|
129
|
+
new Error(
|
|
130
|
+
"@fastify/cookie: Invalid value provided for the hook-option. You can set the hook-option only to false, 'onRequest' , 'preParsing' , 'preValidation' or 'preHandler'"
|
|
131
|
+
)
|
|
132
|
+
)
|
|
133
|
+
}
|
|
134
|
+
const isSigner =
|
|
135
|
+
!secret ||
|
|
136
|
+
(typeof secret.sign === 'function' && typeof secret.unsign === 'function')
|
|
137
|
+
const signer = isSigner
|
|
138
|
+
? secret
|
|
139
|
+
: new Signer(secret, options.algorithm || 'sha256')
|
|
140
|
+
|
|
141
|
+
fastify.decorate('serializeCookie', serialize)
|
|
126
142
|
fastify.decorate('parseCookie', parseCookie)
|
|
127
143
|
|
|
128
144
|
if (secret !== undefined) {
|
|
@@ -146,14 +162,16 @@ function plugin (fastify, options, next) {
|
|
|
146
162
|
|
|
147
163
|
if (hook) {
|
|
148
164
|
fastify.addHook(hook, onReqHandlerWrapper(fastify, hook))
|
|
149
|
-
fastify.addHook('onSend', fastifyCookieOnSendHandler)
|
|
150
165
|
}
|
|
166
|
+
// The onSend writer is independent of the parsing hook: `hook: false` only
|
|
167
|
+
// disables cookie autoparsing, not the ability to set cookies on the reply.
|
|
168
|
+
fastify.addHook('onSend', fastifyCookieOnSendHandler)
|
|
151
169
|
|
|
152
170
|
next()
|
|
153
171
|
|
|
154
172
|
// ***************************
|
|
155
173
|
function parseCookie (cookieHeader) {
|
|
156
|
-
return
|
|
174
|
+
return parse(cookieHeader, options.parseOptions)
|
|
157
175
|
}
|
|
158
176
|
|
|
159
177
|
function signCookie (value) {
|
|
@@ -206,8 +224,8 @@ module.exports = fastifyCookie
|
|
|
206
224
|
module.exports.default = fastifyCookie // supersedes fastifyCookie.default = fastifyCookie
|
|
207
225
|
module.exports.fastifyCookie = fastifyCookie // supersedes fastifyCookie.fastifyCookie = fastifyCookie
|
|
208
226
|
|
|
209
|
-
module.exports.serialize =
|
|
210
|
-
module.exports.parse =
|
|
227
|
+
module.exports.serialize = serialize
|
|
228
|
+
module.exports.parse = parse
|
|
211
229
|
|
|
212
230
|
module.exports.signerFactory = Signer
|
|
213
231
|
module.exports.Signer = Signer
|
package/package.json
CHANGED
|
@@ -1,26 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fastify/cookie",
|
|
3
|
-
"version": "11.
|
|
3
|
+
"version": "11.1.1",
|
|
4
4
|
"description": "Plugin for fastify to add support for cookies",
|
|
5
|
-
"main": "
|
|
5
|
+
"main": "index.js",
|
|
6
6
|
"type": "commonjs",
|
|
7
|
-
"types": "types/
|
|
7
|
+
"types": "types/index.d.ts",
|
|
8
8
|
"scripts": {
|
|
9
9
|
"coverage": "npm run test:unit -- --coverage-report=html",
|
|
10
10
|
"lint": "eslint",
|
|
11
11
|
"lint:fix": "eslint --fix",
|
|
12
12
|
"test": "npm run test:unit && npm run test:typescript",
|
|
13
|
-
"test:typescript": "
|
|
13
|
+
"test:typescript": "tstyche",
|
|
14
14
|
"test:unit": "c8 --100 node --test",
|
|
15
15
|
"test:unit:verbose": "npm run test:unit -- -Rspec"
|
|
16
16
|
},
|
|
17
|
-
"precommit": [
|
|
18
|
-
"lint",
|
|
19
|
-
"test"
|
|
20
|
-
],
|
|
21
17
|
"repository": {
|
|
22
18
|
"type": "git",
|
|
23
|
-
"url": "git+
|
|
19
|
+
"url": "git+https://github.com/fastify/fastify-cookie.git"
|
|
24
20
|
},
|
|
25
21
|
"keywords": [
|
|
26
22
|
"fastify",
|
|
@@ -62,22 +58,18 @@
|
|
|
62
58
|
}
|
|
63
59
|
],
|
|
64
60
|
"devDependencies": {
|
|
65
|
-
"@
|
|
66
|
-
"@types/node": "^22.0.0",
|
|
61
|
+
"@types/node": "^26.0.1",
|
|
67
62
|
"benchmark": "^2.1.4",
|
|
68
|
-
"c8": "^
|
|
63
|
+
"c8": "^11.0.0",
|
|
69
64
|
"eslint": "^9.17.0",
|
|
70
65
|
"fastify": "^5.0.0",
|
|
71
|
-
"neostandard": "^0.
|
|
72
|
-
"sinon": "^
|
|
73
|
-
"
|
|
66
|
+
"neostandard": "^0.13.0",
|
|
67
|
+
"sinon": "^22.0.0",
|
|
68
|
+
"tstyche": "^7.0.0"
|
|
74
69
|
},
|
|
75
70
|
"dependencies": {
|
|
76
|
-
"cookie": "^
|
|
77
|
-
"fastify-plugin": "^
|
|
78
|
-
},
|
|
79
|
-
"tsd": {
|
|
80
|
-
"directory": "test"
|
|
71
|
+
"cookie": "^2.0.0",
|
|
72
|
+
"fastify-plugin": "^6.0.0"
|
|
81
73
|
},
|
|
82
74
|
"publishConfig": {
|
|
83
75
|
"access": "public"
|
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
|
|
|
@@ -1,8 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { expect } from 'tstyche'
|
|
2
2
|
import * as fastifyCookieStar from '..'
|
|
3
3
|
import fastifyCookieCjsImport = require('..')
|
|
4
4
|
import cookie, { fastifyCookie as fastifyCookieNamed, Signer } from '..'
|
|
5
|
-
import fastify, {
|
|
5
|
+
import fastify, {
|
|
6
|
+
type FastifyInstance,
|
|
7
|
+
type FastifyReply,
|
|
8
|
+
type setCookieWrapper
|
|
9
|
+
} from 'fastify'
|
|
6
10
|
|
|
7
11
|
const fastifyCookieCjs = require('..')
|
|
8
12
|
|
|
@@ -15,61 +19,72 @@ app.register(fastifyCookieCjsImport.fastifyCookie)
|
|
|
15
19
|
app.register(fastifyCookieStar.default)
|
|
16
20
|
app.register(fastifyCookieStar.fastifyCookie)
|
|
17
21
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
expect(fastifyCookieNamed).type.toBe<fastifyCookieStar.FastifyCookie>()
|
|
23
|
+
expect(cookie).type.toBe<fastifyCookieStar.FastifyCookie>()
|
|
24
|
+
expect(
|
|
25
|
+
fastifyCookieCjsImport.default
|
|
26
|
+
).type.toBe<fastifyCookieStar.FastifyCookie>()
|
|
27
|
+
expect(
|
|
28
|
+
fastifyCookieCjsImport.fastifyCookie
|
|
29
|
+
).type.toBe<fastifyCookieStar.FastifyCookie>()
|
|
30
|
+
expect(fastifyCookieStar.default).type.toBe<fastifyCookieStar.FastifyCookie>()
|
|
31
|
+
expect(
|
|
24
32
|
fastifyCookieStar.fastifyCookie
|
|
25
|
-
)
|
|
26
|
-
|
|
33
|
+
).type.toBe<fastifyCookieStar.FastifyCookie>()
|
|
34
|
+
expect(fastifyCookieCjs).type.toBe<any>()
|
|
27
35
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
36
|
+
expect(cookie.sign).type.toBe<fastifyCookieStar.Sign>()
|
|
37
|
+
expect(cookie.unsign).type.toBe<fastifyCookieStar.Unsign>()
|
|
38
|
+
expect(cookie.signerFactory).type.toBe<fastifyCookieStar.SignerFactory>()
|
|
31
39
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
40
|
+
expect(fastifyCookieNamed.sign).type.toBe<fastifyCookieStar.Sign>()
|
|
41
|
+
expect(fastifyCookieNamed.unsign).type.toBe<fastifyCookieStar.Unsign>()
|
|
42
|
+
expect(
|
|
43
|
+
fastifyCookieNamed.signerFactory
|
|
44
|
+
).type.toBe<fastifyCookieStar.SignerFactory>()
|
|
35
45
|
|
|
36
46
|
const server = fastify()
|
|
37
47
|
|
|
38
48
|
server.register(cookie)
|
|
39
49
|
|
|
40
50
|
server.after((_err) => {
|
|
41
|
-
|
|
51
|
+
expect(
|
|
42
52
|
server.serializeCookie('sessionId', 'aYb4uTIhdBXC')
|
|
43
|
-
)
|
|
53
|
+
).type.toBe<string>()
|
|
44
54
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
)
|
|
55
|
+
expect(server.parseCookie('sessionId=aYb4uTIhdBXC')).type.toBe<{
|
|
56
|
+
[key: string]: string;
|
|
57
|
+
}>()
|
|
49
58
|
|
|
50
59
|
server.get('/', (request, reply) => {
|
|
51
60
|
const test = request.cookies.test
|
|
52
|
-
|
|
61
|
+
expect(test).type.toBe<string | undefined>()
|
|
53
62
|
|
|
54
|
-
|
|
55
|
-
|
|
63
|
+
expect(reply.cookie).type.toBe<setCookieWrapper>()
|
|
64
|
+
expect(reply.setCookie).type.toBe<setCookieWrapper>()
|
|
56
65
|
|
|
57
|
-
|
|
66
|
+
expect(
|
|
58
67
|
reply
|
|
59
68
|
.setCookie('test', test!, { domain: 'example.com', path: '/' })
|
|
60
69
|
.clearCookie('foo')
|
|
61
70
|
.send({ hello: 'world' })
|
|
62
|
-
)
|
|
71
|
+
).type.toBe<FastifyReply>()
|
|
63
72
|
})
|
|
64
73
|
|
|
65
|
-
|
|
66
|
-
|
|
74
|
+
expect(server.signCookie).type.toBe<(value: string) => string>()
|
|
75
|
+
expect(server.unsignCookie).type.toBe<
|
|
76
|
+
(value: string) => fastifyCookieStar.UnsignResult
|
|
77
|
+
>()
|
|
67
78
|
|
|
68
79
|
server.get('/', (request, reply) => {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
80
|
+
expect(request.signCookie).type.toBe<(value: string) => string>()
|
|
81
|
+
expect(reply.signCookie).type.toBe<(value: string) => string>()
|
|
82
|
+
expect(request.unsignCookie).type.toBe<
|
|
83
|
+
(value: string) => fastifyCookieStar.UnsignResult
|
|
84
|
+
>()
|
|
85
|
+
expect(reply.unsignCookie).type.toBe<
|
|
86
|
+
(value: string) => fastifyCookieStar.UnsignResult
|
|
87
|
+
>()
|
|
73
88
|
})
|
|
74
89
|
})
|
|
75
90
|
|
|
@@ -97,11 +112,15 @@ testSamesiteOptionsApp.after(() => {
|
|
|
97
112
|
})
|
|
98
113
|
server.get('/test-samesite-option-false', (request, reply) => {
|
|
99
114
|
const test = request.cookies.test
|
|
100
|
-
reply
|
|
115
|
+
reply
|
|
116
|
+
.setCookie('test', test!, { sameSite: false })
|
|
117
|
+
.send({ hello: 'world' })
|
|
101
118
|
})
|
|
102
119
|
server.get('/test-samesite-option-lax', (request, reply) => {
|
|
103
120
|
const test = request.cookies.test
|
|
104
|
-
reply
|
|
121
|
+
reply
|
|
122
|
+
.setCookie('test', test!, { sameSite: 'lax' })
|
|
123
|
+
.send({ hello: 'world' })
|
|
105
124
|
})
|
|
106
125
|
server.get('/test-samesite-option-strict', (request, reply) => {
|
|
107
126
|
const test = request.cookies.test
|
|
@@ -120,7 +139,7 @@ testSamesiteOptionsApp.after(() => {
|
|
|
120
139
|
const appWithImplicitHttpSigned = fastify()
|
|
121
140
|
|
|
122
141
|
appWithImplicitHttpSigned.register(cookie, {
|
|
123
|
-
secret: 'testsecret'
|
|
142
|
+
secret: 'testsecret'
|
|
124
143
|
})
|
|
125
144
|
appWithImplicitHttpSigned.register(cookie, {
|
|
126
145
|
secret: 'testsecret',
|
|
@@ -144,20 +163,20 @@ appWithImplicitHttpSigned.after(() => {
|
|
|
144
163
|
const appWithRotationSecret = fastify()
|
|
145
164
|
|
|
146
165
|
appWithRotationSecret.register(cookie, {
|
|
147
|
-
secret: ['testsecret']
|
|
166
|
+
secret: ['testsecret']
|
|
148
167
|
})
|
|
149
168
|
appWithRotationSecret.after(() => {
|
|
150
169
|
server.get('/', (request, reply) => {
|
|
151
170
|
reply.unsignCookie(request.cookies.test!)
|
|
152
171
|
const unsigned = reply.unsignCookie('test')
|
|
153
172
|
|
|
154
|
-
|
|
173
|
+
expect(unsigned.valid).type.toBe<boolean>()
|
|
155
174
|
if (unsigned.valid) {
|
|
156
|
-
|
|
175
|
+
expect(unsigned.value).type.toBe<string>()
|
|
157
176
|
} else {
|
|
158
|
-
|
|
177
|
+
expect(unsigned.value).type.toBe<null>()
|
|
159
178
|
}
|
|
160
|
-
|
|
179
|
+
expect(unsigned.renew).type.toBe<boolean>()
|
|
161
180
|
|
|
162
181
|
reply.send({ hello: 'world' })
|
|
163
182
|
})
|
|
@@ -175,25 +194,25 @@ const parseOptions: fastifyCookieStar.CookieSerializeOptions = {
|
|
|
175
194
|
sameSite: 'lax',
|
|
176
195
|
secure: true,
|
|
177
196
|
signed: true,
|
|
178
|
-
partitioned: false
|
|
197
|
+
partitioned: false
|
|
179
198
|
}
|
|
180
|
-
|
|
199
|
+
expect(parseOptions).type.toBe<fastifyCookieStar.CookieSerializeOptions>()
|
|
181
200
|
|
|
182
201
|
appWithParseOptions.register(cookie, {
|
|
183
202
|
secret: 'testsecret',
|
|
184
|
-
parseOptions
|
|
203
|
+
parseOptions
|
|
185
204
|
})
|
|
186
205
|
appWithParseOptions.after(() => {
|
|
187
206
|
server.get('/', (request, reply) => {
|
|
188
207
|
const unsigned = reply.unsignCookie(request.cookies.test!)
|
|
189
208
|
|
|
190
|
-
|
|
209
|
+
expect(unsigned.valid).type.toBe<boolean>()
|
|
191
210
|
if (unsigned.valid) {
|
|
192
|
-
|
|
211
|
+
expect(unsigned.value).type.toBe<string>()
|
|
193
212
|
} else {
|
|
194
|
-
|
|
213
|
+
expect(unsigned.value).type.toBe<null>()
|
|
195
214
|
}
|
|
196
|
-
|
|
215
|
+
expect(unsigned.renew).type.toBe<boolean>()
|
|
197
216
|
})
|
|
198
217
|
})
|
|
199
218
|
|
|
@@ -203,7 +222,9 @@ appWithCustomSigner.register(cookie, {
|
|
|
203
222
|
secret: {
|
|
204
223
|
sign: (x) => x + '.signed',
|
|
205
224
|
unsign: (x) => {
|
|
206
|
-
if (x.endsWith('.signed')) {
|
|
225
|
+
if (x.endsWith('.signed')) {
|
|
226
|
+
return { renew: false, valid: true, value: x.slice(0, -7) }
|
|
227
|
+
}
|
|
207
228
|
return { renew: false, valid: false, value: null }
|
|
208
229
|
}
|
|
209
230
|
}
|
|
@@ -213,22 +234,28 @@ appWithCustomSigner.after(() => {
|
|
|
213
234
|
reply.unsignCookie(request.cookies.test!)
|
|
214
235
|
const unsigned = reply.unsignCookie('test')
|
|
215
236
|
|
|
216
|
-
|
|
237
|
+
expect(unsigned.valid).type.toBe<boolean>()
|
|
217
238
|
if (unsigned.valid) {
|
|
218
|
-
|
|
239
|
+
expect(unsigned.value).type.toBe<string>()
|
|
219
240
|
} else {
|
|
220
|
-
|
|
241
|
+
expect(unsigned.value).type.toBe<null>()
|
|
221
242
|
}
|
|
222
|
-
|
|
243
|
+
expect(unsigned.renew).type.toBe<boolean>()
|
|
223
244
|
|
|
224
245
|
reply.send({ hello: 'world' })
|
|
225
246
|
})
|
|
226
247
|
})
|
|
227
248
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
249
|
+
expect(new fastifyCookieStar.Signer('secretString')).type.toBe<Signer>()
|
|
250
|
+
expect(
|
|
251
|
+
new fastifyCookieStar.Signer(['secretStringInArray'])
|
|
252
|
+
).type.toBe<Signer>()
|
|
253
|
+
expect(
|
|
254
|
+
new fastifyCookieStar.Signer(Buffer.from('secretString'))
|
|
255
|
+
).type.toBe<Signer>()
|
|
256
|
+
expect(
|
|
257
|
+
new fastifyCookieStar.Signer([Buffer.from('secretStringInArray')])
|
|
258
|
+
).type.toBe<Signer>()
|
|
232
259
|
|
|
233
260
|
const signer = new fastifyCookieStar.Signer(['secretStringInArray'], 'sha256')
|
|
234
261
|
signer.sign('Lorem Ipsum')
|
|
@@ -242,8 +269,22 @@ appWithHook.register(cookie, { hook: 'preHandler' })
|
|
|
242
269
|
appWithHook.register(cookie, { hook: 'preParsing' })
|
|
243
270
|
appWithHook.register(cookie, { hook: 'preSerialization' })
|
|
244
271
|
appWithHook.register(cookie, { hook: 'preValidation' })
|
|
245
|
-
expectError(appWithHook.register(cookie, { hook: true }))
|
|
246
|
-
expectError(appWithHook.register(cookie, { hook: 'false' }))
|
|
247
272
|
|
|
248
|
-
|
|
249
|
-
|
|
273
|
+
expect(appWithHook.register)
|
|
274
|
+
.type.not.toBeCallableWith(cookie, { hook: true })
|
|
275
|
+
expect(appWithHook.register)
|
|
276
|
+
.type.not.toBeCallableWith(cookie, { hook: 'false' })
|
|
277
|
+
|
|
278
|
+
expect(cookie.parse).type.toBe<
|
|
279
|
+
(
|
|
280
|
+
cookieHeader: string,
|
|
281
|
+
opts?: fastifyCookieStar.ParseOptions
|
|
282
|
+
) => { [key: string]: string }
|
|
283
|
+
>()
|
|
284
|
+
expect(cookie.serialize).type.toBe<
|
|
285
|
+
(
|
|
286
|
+
name: string,
|
|
287
|
+
value: string,
|
|
288
|
+
opts?: fastifyCookieStar.SerializeOptions
|
|
289
|
+
) => string
|
|
290
|
+
>()
|
package/.github/stale.yml
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
# Number of days of inactivity before an issue becomes stale
|
|
2
|
-
daysUntilStale: 15
|
|
3
|
-
# Number of days of inactivity before a stale issue is closed
|
|
4
|
-
daysUntilClose: 7
|
|
5
|
-
# Issues with these labels will never be considered stale
|
|
6
|
-
exemptLabels:
|
|
7
|
-
- "discussion"
|
|
8
|
-
- "feature request"
|
|
9
|
-
- "bug"
|
|
10
|
-
- "help wanted"
|
|
11
|
-
- "plugin suggestion"
|
|
12
|
-
- "good first issue"
|
|
13
|
-
# Label to use when marking an issue as stale
|
|
14
|
-
staleLabel: stale
|
|
15
|
-
# Comment to post when marking an issue as stale. Set to `false` to disable
|
|
16
|
-
markComment: >
|
|
17
|
-
This issue has been automatically marked as stale because it has not had
|
|
18
|
-
recent activity. It will be closed if no further activity occurs. Thank you
|
|
19
|
-
for your contributions.
|
|
20
|
-
# Comment to post when closing a stale issue. Set to `false` to disable
|
|
21
|
-
closeComment: false
|
|
File without changes
|