@fastify/cookie 9.2.0 → 9.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -1
- package/cookie.js +38 -42
- package/package.json +2 -2
- package/plugin.js +17 -15
- package/test/cookie-module.test.js +1 -1
- package/test/cookie.test.js +33 -0
- package/types/plugin.d.ts +7 -5
- package/types/plugin.test-d.ts +3 -0
package/README.md
CHANGED
|
@@ -4,13 +4,15 @@
|
|
|
4
4
|
[](https://www.npmjs.com/package/@fastify/cookie)
|
|
5
5
|
[](https://standardjs.com/)
|
|
6
6
|
|
|
7
|
-
A plugin for [Fastify](http://fastify.
|
|
7
|
+
A plugin for [Fastify](http://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,
|
|
11
11
|
you should register it prior to any other `onRequest` hooks that will depend
|
|
12
12
|
upon this plugin's actions.
|
|
13
13
|
|
|
14
|
+
It is also possible to [import the low-level cookie parsing and serialization functions](#importing-serialize-and-parse).
|
|
15
|
+
|
|
14
16
|
`@fastify/cookie` [v2.x](https://github.com/fastify/fastify-cookie/tree/v2.x)
|
|
15
17
|
supports both Fastify@1 and Fastify@2.
|
|
16
18
|
`@fastify/cookie` v3 only supports Fastify@2.
|
|
@@ -70,6 +72,23 @@ app.register(cookie, {
|
|
|
70
72
|
} as FastifyCookieOptions)
|
|
71
73
|
```
|
|
72
74
|
|
|
75
|
+
## Importing `serialize` and `parse`
|
|
76
|
+
|
|
77
|
+
```js
|
|
78
|
+
const { serialize, parse } = require('@fastify/cookie')
|
|
79
|
+
const fastify = require('fastify')()
|
|
80
|
+
|
|
81
|
+
fastify.get('/', (req, reply) => {
|
|
82
|
+
const cookie = serialize('lang', 'en', {
|
|
83
|
+
maxAge: 60_000,
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
reply.header('Set-Cookie', cookie)
|
|
87
|
+
|
|
88
|
+
reply.send('Language set!')
|
|
89
|
+
})
|
|
90
|
+
```
|
|
91
|
+
|
|
73
92
|
## Options
|
|
74
93
|
|
|
75
94
|
- `secret` (`String` | `Array` | `Buffer` | `Object`):
|
package/cookie.js
CHANGED
|
@@ -28,22 +28,6 @@
|
|
|
28
28
|
|
|
29
29
|
'use strict'
|
|
30
30
|
|
|
31
|
-
/**
|
|
32
|
-
* Module exports.
|
|
33
|
-
* @public
|
|
34
|
-
*/
|
|
35
|
-
|
|
36
|
-
exports.parse = parse
|
|
37
|
-
exports.serialize = serialize
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Module variables.
|
|
41
|
-
* @private
|
|
42
|
-
*/
|
|
43
|
-
|
|
44
|
-
const decode = decodeURIComponent
|
|
45
|
-
const encode = encodeURIComponent
|
|
46
|
-
|
|
47
31
|
/**
|
|
48
32
|
* RegExp to match field-content in RFC 7230 sec 3.2
|
|
49
33
|
*
|
|
@@ -61,51 +45,49 @@ const fieldContentRegExp = /^[\u0009\u0020-\u007e\u0080-\u00ff]+$/ // eslint-dis
|
|
|
61
45
|
* The object has the various cookies as keys(names) => values
|
|
62
46
|
*
|
|
63
47
|
* @param {string} str
|
|
64
|
-
* @param {object} [
|
|
48
|
+
* @param {object} [opt]
|
|
65
49
|
* @return {object}
|
|
66
50
|
* @public
|
|
67
51
|
*/
|
|
68
52
|
|
|
69
|
-
function parse (str,
|
|
53
|
+
function parse (str, opt) {
|
|
70
54
|
if (typeof str !== 'string') {
|
|
71
55
|
throw new TypeError('argument str must be a string')
|
|
72
56
|
}
|
|
73
57
|
|
|
58
|
+
const dec = opt?.decode || decodeURIComponent
|
|
74
59
|
const result = {}
|
|
75
|
-
const dec = (options && options.decode) || decode
|
|
76
60
|
|
|
61
|
+
const strLen = str.length
|
|
77
62
|
let pos = 0
|
|
78
63
|
let terminatorPos = 0
|
|
79
|
-
let eqIdx = 0
|
|
80
|
-
|
|
81
64
|
while (true) {
|
|
82
|
-
if (terminatorPos ===
|
|
83
|
-
break
|
|
84
|
-
}
|
|
65
|
+
if (terminatorPos === strLen) break
|
|
85
66
|
terminatorPos = str.indexOf(';', pos)
|
|
86
|
-
|
|
87
|
-
eqIdx = str.indexOf('=', pos)
|
|
67
|
+
if (terminatorPos === -1) terminatorPos = strLen // This is the last pair
|
|
88
68
|
|
|
89
|
-
|
|
90
|
-
if (eqIdx === -1
|
|
69
|
+
let eqIdx = str.indexOf('=', pos)
|
|
70
|
+
if (eqIdx === -1) break // No key-value pairs left
|
|
71
|
+
if (eqIdx > terminatorPos) {
|
|
72
|
+
// Malformed key-value pair
|
|
91
73
|
pos = terminatorPos + 1
|
|
92
74
|
continue
|
|
93
75
|
}
|
|
94
76
|
|
|
95
77
|
const key = str.substring(pos, eqIdx++).trim()
|
|
96
|
-
|
|
97
|
-
// only assign once
|
|
98
78
|
if (result[key] === undefined) {
|
|
99
|
-
const val =
|
|
79
|
+
const val = str.charCodeAt(eqIdx) === 0x22
|
|
100
80
|
? str.substring(eqIdx + 1, terminatorPos - 1).trim()
|
|
101
81
|
: str.substring(eqIdx, terminatorPos).trim()
|
|
102
82
|
|
|
103
|
-
result[key] = (dec
|
|
83
|
+
result[key] = !(dec === decodeURIComponent && val.indexOf('%') === -1)
|
|
104
84
|
? tryDecode(val, dec)
|
|
105
85
|
: val
|
|
106
86
|
}
|
|
87
|
+
|
|
107
88
|
pos = terminatorPos + 1
|
|
108
89
|
}
|
|
90
|
+
|
|
109
91
|
return result
|
|
110
92
|
}
|
|
111
93
|
|
|
@@ -120,19 +102,18 @@ function parse (str, options) {
|
|
|
120
102
|
*
|
|
121
103
|
* @param {string} name
|
|
122
104
|
* @param {string} val
|
|
123
|
-
* @param {object} [
|
|
105
|
+
* @param {object} [opt]
|
|
124
106
|
* @return {string}
|
|
125
107
|
* @public
|
|
126
108
|
*/
|
|
127
109
|
|
|
128
|
-
function serialize (name, val,
|
|
129
|
-
const
|
|
130
|
-
const enc = opt.encode || encode
|
|
110
|
+
function serialize (name, val, opt) {
|
|
111
|
+
const enc = opt?.encode || encodeURIComponent
|
|
131
112
|
if (typeof enc !== 'function') {
|
|
132
113
|
throw new TypeError('option encode is invalid')
|
|
133
114
|
}
|
|
134
115
|
|
|
135
|
-
if (!fieldContentRegExp.test(name)) {
|
|
116
|
+
if (name && !fieldContentRegExp.test(name)) {
|
|
136
117
|
throw new TypeError('argument name is invalid')
|
|
137
118
|
}
|
|
138
119
|
|
|
@@ -142,13 +123,17 @@ function serialize (name, val, options) {
|
|
|
142
123
|
}
|
|
143
124
|
|
|
144
125
|
let str = name + '=' + value
|
|
126
|
+
|
|
127
|
+
if (opt == null) return str
|
|
128
|
+
|
|
145
129
|
if (opt.maxAge != null) {
|
|
146
|
-
const maxAge = opt.maxAge
|
|
147
|
-
|
|
130
|
+
const maxAge = +opt.maxAge
|
|
131
|
+
|
|
132
|
+
if (!isFinite(maxAge)) {
|
|
148
133
|
throw new TypeError('option maxAge is invalid')
|
|
149
134
|
}
|
|
150
135
|
|
|
151
|
-
str += '; Max-Age=' + Math.
|
|
136
|
+
str += '; Max-Age=' + Math.trunc(maxAge)
|
|
152
137
|
}
|
|
153
138
|
|
|
154
139
|
if (opt.domain) {
|
|
@@ -193,6 +178,7 @@ function serialize (name, val, options) {
|
|
|
193
178
|
const sameSite = typeof opt.sameSite === 'string'
|
|
194
179
|
? opt.sameSite.toLowerCase()
|
|
195
180
|
: opt.sameSite
|
|
181
|
+
|
|
196
182
|
switch (sameSite) {
|
|
197
183
|
case true:
|
|
198
184
|
str += '; SameSite=Strict'
|
|
@@ -219,13 +205,23 @@ function serialize (name, val, options) {
|
|
|
219
205
|
*
|
|
220
206
|
* @param {string} str
|
|
221
207
|
* @param {function} decode
|
|
208
|
+
* @returns {string}
|
|
222
209
|
* @private
|
|
223
210
|
*/
|
|
224
|
-
|
|
225
211
|
function tryDecode (str, decode) {
|
|
226
212
|
try {
|
|
227
213
|
return decode(str)
|
|
228
|
-
} catch
|
|
214
|
+
} catch {
|
|
229
215
|
return str
|
|
230
216
|
}
|
|
231
217
|
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Module exports.
|
|
221
|
+
* @public
|
|
222
|
+
*/
|
|
223
|
+
|
|
224
|
+
module.exports = {
|
|
225
|
+
parse,
|
|
226
|
+
serialize
|
|
227
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fastify/cookie",
|
|
3
|
-
"version": "9.
|
|
3
|
+
"version": "9.3.0",
|
|
4
4
|
"description": "Plugin for fastify to add support for cookies",
|
|
5
5
|
"main": "plugin.js",
|
|
6
6
|
"type": "commonjs",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"snazzy": "^9.0.0",
|
|
49
49
|
"standard": "^17.0.0",
|
|
50
50
|
"tap": "^16.0.0",
|
|
51
|
-
"tsd": "^0.
|
|
51
|
+
"tsd": "^0.30.0"
|
|
52
52
|
},
|
|
53
53
|
"dependencies": {
|
|
54
54
|
"fastify-plugin": "^4.0.0",
|
package/plugin.js
CHANGED
|
@@ -70,32 +70,31 @@ function onReqHandlerWrapper (fastify, hook) {
|
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
function setCookies (reply) {
|
|
73
|
-
|
|
74
|
-
|
|
73
|
+
const setCookieHeaderValue = reply.getHeader('Set-Cookie')
|
|
74
|
+
let cookieValue
|
|
75
75
|
|
|
76
|
-
|
|
77
|
-
if (setCookieIsUndefined) {
|
|
76
|
+
if (setCookieHeaderValue === undefined) {
|
|
78
77
|
if (reply[kReplySetCookies].size === 1) {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
78
|
+
// Fast path for single cookie
|
|
79
|
+
const c = reply[kReplySetCookies].values().next().value
|
|
80
|
+
reply.header('Set-Cookie', cookie.serialize(c.name, c.value, c.opts))
|
|
83
81
|
reply[kReplySetCookies].clear()
|
|
84
|
-
|
|
85
82
|
return
|
|
86
83
|
}
|
|
87
84
|
|
|
88
|
-
|
|
89
|
-
} else if (typeof
|
|
90
|
-
|
|
85
|
+
cookieValue = []
|
|
86
|
+
} else if (typeof setCookieHeaderValue === 'string') {
|
|
87
|
+
cookieValue = [setCookieHeaderValue]
|
|
88
|
+
} else {
|
|
89
|
+
cookieValue = setCookieHeaderValue
|
|
91
90
|
}
|
|
92
91
|
|
|
93
92
|
for (const c of reply[kReplySetCookies].values()) {
|
|
94
|
-
|
|
93
|
+
cookieValue.push(cookie.serialize(c.name, c.value, c.opts))
|
|
95
94
|
}
|
|
96
95
|
|
|
97
|
-
|
|
98
|
-
reply.header('Set-Cookie',
|
|
96
|
+
reply.removeHeader('Set-Cookie')
|
|
97
|
+
reply.header('Set-Cookie', cookieValue)
|
|
99
98
|
reply[kReplySetCookies].clear()
|
|
100
99
|
}
|
|
101
100
|
|
|
@@ -214,6 +213,9 @@ module.exports = fastifyCookie
|
|
|
214
213
|
module.exports.default = fastifyCookie // supersedes fastifyCookie.default = fastifyCookie
|
|
215
214
|
module.exports.fastifyCookie = fastifyCookie // supersedes fastifyCookie.fastifyCookie = fastifyCookie
|
|
216
215
|
|
|
216
|
+
module.exports.serialize = cookie.serialize
|
|
217
|
+
module.exports.parse = cookie.parse
|
|
218
|
+
|
|
217
219
|
module.exports.signerFactory = Signer
|
|
218
220
|
module.exports.Signer = Signer
|
|
219
221
|
module.exports.sign = sign
|
package/test/cookie.test.js
CHANGED
|
@@ -132,6 +132,39 @@ test('should set multiple cookies', (t) => {
|
|
|
132
132
|
})
|
|
133
133
|
})
|
|
134
134
|
|
|
135
|
+
test('should set multiple cookies (an array already exists)', (t) => {
|
|
136
|
+
t.plan(10)
|
|
137
|
+
const fastify = Fastify()
|
|
138
|
+
fastify.register(plugin)
|
|
139
|
+
|
|
140
|
+
fastify.get('/test1', (req, reply) => {
|
|
141
|
+
reply
|
|
142
|
+
.header('Set-Cookie', ['bar=bar'])
|
|
143
|
+
.setCookie('foo', 'foo', { path: '/' })
|
|
144
|
+
.setCookie('foo', 'foo', { path: '/path' })
|
|
145
|
+
.send({ hello: 'world' })
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
fastify.inject({
|
|
149
|
+
method: 'GET',
|
|
150
|
+
url: '/test1'
|
|
151
|
+
}, (err, res) => {
|
|
152
|
+
t.error(err)
|
|
153
|
+
t.equal(res.statusCode, 200)
|
|
154
|
+
t.same(JSON.parse(res.body), { hello: 'world' })
|
|
155
|
+
|
|
156
|
+
const cookies = res.cookies
|
|
157
|
+
t.equal(cookies.length, 3)
|
|
158
|
+
t.equal(cookies[0].name, 'bar')
|
|
159
|
+
t.equal(cookies[0].value, 'bar')
|
|
160
|
+
t.equal(cookies[0].path, undefined)
|
|
161
|
+
|
|
162
|
+
t.equal(cookies[1].name, 'foo')
|
|
163
|
+
t.equal(cookies[1].value, 'foo')
|
|
164
|
+
t.equal(cookies[2].path, '/path')
|
|
165
|
+
})
|
|
166
|
+
})
|
|
167
|
+
|
|
135
168
|
test('cookies get set correctly with millisecond dates', (t) => {
|
|
136
169
|
t.plan(8)
|
|
137
170
|
const fastify = Fastify()
|
package/types/plugin.d.ts
CHANGED
|
@@ -65,11 +65,7 @@ declare module "fastify" {
|
|
|
65
65
|
* @param value Cookie value
|
|
66
66
|
* @param options Serialize options
|
|
67
67
|
*/
|
|
68
|
-
setCookie
|
|
69
|
-
name: string,
|
|
70
|
-
value: string,
|
|
71
|
-
options?: fastifyCookie.CookieSerializeOptions
|
|
72
|
-
): this;
|
|
68
|
+
setCookie: setCookieWrapper;
|
|
73
69
|
|
|
74
70
|
/**
|
|
75
71
|
* @alias setCookie
|
|
@@ -139,6 +135,10 @@ declare namespace fastifyCookie {
|
|
|
139
135
|
signed?: boolean;
|
|
140
136
|
}
|
|
141
137
|
|
|
138
|
+
export interface ParseOptions {
|
|
139
|
+
decode?: (encodedURIComponent: string) => string;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
142
|
type HookType = 'onRequest' | 'preParsing' | 'preValidation' | 'preHandler' | 'preSerialization';
|
|
143
143
|
|
|
144
144
|
export interface FastifyCookieOptions {
|
|
@@ -162,6 +162,8 @@ declare namespace fastifyCookie {
|
|
|
162
162
|
export const unsign: Unsign;
|
|
163
163
|
|
|
164
164
|
export interface FastifyCookie extends FastifyCookiePlugin {
|
|
165
|
+
parse: (cookieHeader: string, opts?: ParseOptions) => { [key: string]: string };
|
|
166
|
+
serialize: (name: string, value: string, opts?: SerializeOptions) => string;
|
|
165
167
|
signerFactory: SignerFactory;
|
|
166
168
|
Signer: Signer;
|
|
167
169
|
sign: Sign;
|
package/types/plugin.test-d.ts
CHANGED
|
@@ -232,3 +232,6 @@ appWithHook.register(cookie, { hook: 'preSerialization' });
|
|
|
232
232
|
appWithHook.register(cookie, { hook: 'preValidation' });
|
|
233
233
|
expectError(appWithHook.register(cookie, { hook: true }));
|
|
234
234
|
expectError(appWithHook.register(cookie, { hook: 'false' }));
|
|
235
|
+
|
|
236
|
+
expectType<(cookieHeader: string, opts?: fastifyCookieStar.ParseOptions) => { [key: string]: string }>(fastifyCookieDefault.parse);
|
|
237
|
+
expectType<(name: string, value: string, opts?: fastifyCookieStar.SerializeOptions) => string>(fastifyCookieDefault.serialize);
|