@fastify/cookie 8.2.0 → 9.0.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/.github/workflows/ci.yml +5 -0
- package/.taprc +2 -3
- package/README.md +2 -2
- package/benchmark/cookie-multi.js +24 -0
- package/benchmark/cookie.js +20 -0
- package/package.json +9 -9
- package/plugin.js +45 -20
- package/signer.js +2 -2
- package/test/cookie.test.js +188 -22
- package/test/signer.test.js +10 -6
- package/types/plugin.d.ts +24 -10
- package/types/plugin.test-d.ts +6 -0
package/.github/workflows/ci.yml
CHANGED
package/.taprc
CHANGED
|
@@ -1,3 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
coverage: true
|
|
1
|
+
files:
|
|
2
|
+
- test/**/*.test.js
|
package/README.md
CHANGED
|
@@ -72,8 +72,8 @@ app.register(cookie, {
|
|
|
72
72
|
|
|
73
73
|
## Options
|
|
74
74
|
|
|
75
|
-
- `secret` (`String` | `Array` | `Object`):
|
|
76
|
-
- A `String` can be passed to use as secret to sign the cookie using [`cookie-signature`](http://npm.im/cookie-signature).
|
|
75
|
+
- `secret` (`String` | `Array` | `Buffer` | `Object`):
|
|
76
|
+
- A `String` or `Buffer` can be passed to use as secret to sign the cookie using [`cookie-signature`](http://npm.im/cookie-signature).
|
|
77
77
|
- An `Array` can be passed if key rotation is desired. Read more about it in [Rotating signing secret](#rotating-secret).
|
|
78
78
|
- More sophisticated cookie signing mechanisms can be implemented by supplying an `Object`. Read more about it in [Custom cookie signer](#custom-cookie-signer).
|
|
79
79
|
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const Fastify = require('fastify')
|
|
4
|
+
const plugin = require('../')
|
|
5
|
+
|
|
6
|
+
const secret = 'testsecret'
|
|
7
|
+
|
|
8
|
+
const fastify = Fastify()
|
|
9
|
+
fastify.register(plugin, { secret })
|
|
10
|
+
|
|
11
|
+
fastify.get('/', (req, reply) => {
|
|
12
|
+
reply
|
|
13
|
+
.setCookie('foo', 'foo')
|
|
14
|
+
.setCookie('foo', 'foo', { path: '/1' })
|
|
15
|
+
.setCookie('boo', 'boo', { path: '/' })
|
|
16
|
+
.setCookie('foo', 'foo-different', { path: '/' })
|
|
17
|
+
.setCookie('foo', 'foo', { path: '/2' })
|
|
18
|
+
.send({ hello: 'world' })
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
fastify.listen({ host: '127.0.0.1', port: 5001 }, (err, address) => {
|
|
22
|
+
if (err) throw err
|
|
23
|
+
console.log(address)
|
|
24
|
+
})
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const Fastify = require('fastify')
|
|
4
|
+
const plugin = require('../')
|
|
5
|
+
|
|
6
|
+
const secret = 'testsecret'
|
|
7
|
+
|
|
8
|
+
const fastify = Fastify()
|
|
9
|
+
fastify.register(plugin, { secret })
|
|
10
|
+
|
|
11
|
+
fastify.get('/', (req, reply) => {
|
|
12
|
+
reply
|
|
13
|
+
.setCookie('foo', 'foo', { path: '/' })
|
|
14
|
+
.send({ hello: 'world' })
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
fastify.listen({ host: '127.0.0.1', port: 5001 }, (err, address) => {
|
|
18
|
+
if (err) throw err
|
|
19
|
+
console.log(address)
|
|
20
|
+
})
|
package/package.json
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fastify/cookie",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "9.0.0",
|
|
4
4
|
"description": "Plugin for fastify to add support for cookies",
|
|
5
5
|
"main": "plugin.js",
|
|
6
6
|
"types": "types/plugin.d.ts",
|
|
7
7
|
"scripts": {
|
|
8
|
+
"coverage": "npm run test:unit -- --coverage-report=html",
|
|
8
9
|
"lint": "standard | snazzy",
|
|
9
10
|
"lint:ci": "standard",
|
|
10
11
|
"lint:fix": "standard --fix",
|
|
11
|
-
"test": "npm run unit && npm run typescript",
|
|
12
|
-
"typescript": "tsd",
|
|
13
|
-
"unit": "tap
|
|
14
|
-
"unit:
|
|
15
|
-
"unit:verbose": "npm run unit -- -Rspec"
|
|
12
|
+
"test": "npm run test:unit && npm run test:typescript",
|
|
13
|
+
"test:typescript": "tsd",
|
|
14
|
+
"test:unit": "tap",
|
|
15
|
+
"test:unit:verbose": "npm run test:unit -- -Rspec"
|
|
16
16
|
},
|
|
17
17
|
"precommit": [
|
|
18
18
|
"lint",
|
|
@@ -40,14 +40,14 @@
|
|
|
40
40
|
"homepage": "https://github.com/fastify/fastify-cookie#readme",
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@fastify/pre-commit": "^2.0.2",
|
|
43
|
-
"@types/node": "^
|
|
43
|
+
"@types/node": "^20.1.0",
|
|
44
44
|
"benchmark": "^2.1.4",
|
|
45
45
|
"fastify": "^4.0.0",
|
|
46
|
-
"sinon": "^
|
|
46
|
+
"sinon": "^15.0.0",
|
|
47
47
|
"snazzy": "^9.0.0",
|
|
48
48
|
"standard": "^17.0.0",
|
|
49
49
|
"tap": "^16.0.0",
|
|
50
|
-
"tsd": "^0.
|
|
50
|
+
"tsd": "^0.28.0"
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
53
|
"cookie": "^0.5.0",
|
package/plugin.js
CHANGED
|
@@ -5,14 +5,17 @@ const cookie = require('cookie')
|
|
|
5
5
|
|
|
6
6
|
const { Signer, sign, unsign } = require('./signer')
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
const kReplySetCookies = Symbol('fastify.reply.setCookies')
|
|
9
|
+
|
|
10
|
+
function fastifyCookieSetCookie (reply, name, value, options) {
|
|
9
11
|
const opts = Object.assign({}, options)
|
|
12
|
+
|
|
10
13
|
if (opts.expires && Number.isInteger(opts.expires)) {
|
|
11
14
|
opts.expires = new Date(opts.expires)
|
|
12
15
|
}
|
|
13
16
|
|
|
14
17
|
if (opts.signed) {
|
|
15
|
-
value =
|
|
18
|
+
value = reply.signCookie(value)
|
|
16
19
|
}
|
|
17
20
|
|
|
18
21
|
if (opts.secure === 'auto') {
|
|
@@ -24,20 +27,8 @@ function fastifyCookieSetCookie (reply, name, value, options, signer) {
|
|
|
24
27
|
}
|
|
25
28
|
}
|
|
26
29
|
|
|
27
|
-
|
|
28
|
-
let setCookie = reply.getHeader('Set-Cookie')
|
|
29
|
-
if (!setCookie) {
|
|
30
|
-
reply.header('Set-Cookie', serialized)
|
|
31
|
-
return reply
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
if (typeof setCookie === 'string') {
|
|
35
|
-
setCookie = [setCookie]
|
|
36
|
-
}
|
|
30
|
+
reply[kReplySetCookies].set(`${name};${opts.domain};${opts.path || '/'}`, { name, value, opts })
|
|
37
31
|
|
|
38
|
-
setCookie.push(serialized)
|
|
39
|
-
reply.removeHeader('Set-Cookie')
|
|
40
|
-
reply.header('Set-Cookie', setCookie)
|
|
41
32
|
return reply
|
|
42
33
|
}
|
|
43
34
|
|
|
@@ -58,6 +49,7 @@ function onReqHandlerWrapper (fastify, hook) {
|
|
|
58
49
|
if (cookieHeader) {
|
|
59
50
|
fastifyReq.cookies = fastify.parseCookie(cookieHeader)
|
|
60
51
|
}
|
|
52
|
+
fastifyRes[kReplySetCookies] = new Map()
|
|
61
53
|
done()
|
|
62
54
|
}
|
|
63
55
|
: function fastifyCookieHandler (fastifyReq, fastifyRes, done) {
|
|
@@ -66,10 +58,41 @@ function onReqHandlerWrapper (fastify, hook) {
|
|
|
66
58
|
if (cookieHeader) {
|
|
67
59
|
fastifyReq.cookies = fastify.parseCookie(cookieHeader)
|
|
68
60
|
}
|
|
61
|
+
fastifyRes[kReplySetCookies] = new Map()
|
|
69
62
|
done()
|
|
70
63
|
}
|
|
71
64
|
}
|
|
72
65
|
|
|
66
|
+
function fastifyCookieOnSendHandler (fastifyReq, fastifyRes, payload, done) {
|
|
67
|
+
if (fastifyRes[kReplySetCookies].size) {
|
|
68
|
+
let setCookie = fastifyRes.getHeader('Set-Cookie')
|
|
69
|
+
|
|
70
|
+
/* istanbul ignore else */
|
|
71
|
+
if (setCookie === undefined) {
|
|
72
|
+
if (fastifyRes[kReplySetCookies].size === 1) {
|
|
73
|
+
for (const c of fastifyRes[kReplySetCookies].values()) {
|
|
74
|
+
fastifyRes.header('Set-Cookie', cookie.serialize(c.name, c.value, c.opts))
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return done()
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
setCookie = []
|
|
81
|
+
} else if (typeof setCookie === 'string') {
|
|
82
|
+
setCookie = [setCookie]
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
for (const c of fastifyRes[kReplySetCookies].values()) {
|
|
86
|
+
setCookie.push(cookie.serialize(c.name, c.value, c.opts))
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
fastifyRes.removeHeader('Set-Cookie')
|
|
90
|
+
fastifyRes.header('Set-Cookie', setCookie)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
done()
|
|
94
|
+
}
|
|
95
|
+
|
|
73
96
|
function getHook (hook = 'onRequest') {
|
|
74
97
|
const hooks = {
|
|
75
98
|
onRequest: 'onRequest',
|
|
@@ -88,10 +111,10 @@ function plugin (fastify, options, next) {
|
|
|
88
111
|
if (hook === undefined) {
|
|
89
112
|
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\''))
|
|
90
113
|
}
|
|
91
|
-
const
|
|
92
|
-
const
|
|
93
|
-
const signer = typeof secret === 'string' || enableRotation ? new Signer(secret, algorithm) : secret
|
|
114
|
+
const isSigner = !secret || (typeof secret.sign === 'function' && typeof secret.unsign === 'function')
|
|
115
|
+
const signer = isSigner ? secret : new Signer(secret, options.algorithm || 'sha256')
|
|
94
116
|
|
|
117
|
+
fastify.decorate('serializeCookie', cookie.serialize)
|
|
95
118
|
fastify.decorate('parseCookie', parseCookie)
|
|
96
119
|
|
|
97
120
|
if (typeof secret !== 'undefined') {
|
|
@@ -106,13 +129,15 @@ function plugin (fastify, options, next) {
|
|
|
106
129
|
}
|
|
107
130
|
|
|
108
131
|
fastify.decorateRequest('cookies', null)
|
|
109
|
-
fastify.decorateReply(
|
|
132
|
+
fastify.decorateReply(kReplySetCookies, null)
|
|
110
133
|
|
|
134
|
+
fastify.decorateReply('cookie', setCookie)
|
|
111
135
|
fastify.decorateReply('setCookie', setCookie)
|
|
112
136
|
fastify.decorateReply('clearCookie', clearCookie)
|
|
113
137
|
|
|
114
138
|
if (hook) {
|
|
115
139
|
fastify.addHook(hook, onReqHandlerWrapper(fastify, hook))
|
|
140
|
+
fastify.addHook('onSend', fastifyCookieOnSendHandler)
|
|
116
141
|
}
|
|
117
142
|
|
|
118
143
|
next()
|
|
@@ -132,7 +157,7 @@ function plugin (fastify, options, next) {
|
|
|
132
157
|
|
|
133
158
|
function setCookie (name, value, cookieOptions) {
|
|
134
159
|
const opts = Object.assign({}, options.parseOptions, cookieOptions)
|
|
135
|
-
return fastifyCookieSetCookie(this, name, value, opts
|
|
160
|
+
return fastifyCookieSetCookie(this, name, value, opts)
|
|
136
161
|
}
|
|
137
162
|
|
|
138
163
|
function clearCookie (name, cookieOptions) {
|
package/signer.js
CHANGED
|
@@ -25,8 +25,8 @@ function Signer (secrets, algorithm = 'sha256') {
|
|
|
25
25
|
|
|
26
26
|
function validateSecrets (secrets) {
|
|
27
27
|
for (const secret of secrets) {
|
|
28
|
-
if (typeof secret !== 'string') {
|
|
29
|
-
throw new TypeError('Secret key must be a string.')
|
|
28
|
+
if (typeof secret !== 'string' && Buffer.isBuffer(secret) === false) {
|
|
29
|
+
throw new TypeError('Secret key must be a string or Buffer.')
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
32
|
}
|
package/test/cookie.test.js
CHANGED
|
@@ -123,7 +123,7 @@ test('cookies get set correctly with millisecond dates', (t) => {
|
|
|
123
123
|
})
|
|
124
124
|
|
|
125
125
|
test('share options for setCookie and clearCookie', (t) => {
|
|
126
|
-
t.plan(
|
|
126
|
+
t.plan(8)
|
|
127
127
|
const fastify = Fastify()
|
|
128
128
|
const secret = 'testsecret'
|
|
129
129
|
fastify.register(plugin, { secret })
|
|
@@ -149,20 +149,17 @@ test('share options for setCookie and clearCookie', (t) => {
|
|
|
149
149
|
t.same(JSON.parse(res.body), { hello: 'world' })
|
|
150
150
|
|
|
151
151
|
const cookies = res.cookies
|
|
152
|
-
t.equal(cookies.length,
|
|
152
|
+
t.equal(cookies.length, 1)
|
|
153
153
|
t.equal(cookies[0].name, 'foo')
|
|
154
|
-
t.equal(cookies[0].value,
|
|
155
|
-
t.equal(cookies[0].maxAge,
|
|
154
|
+
t.equal(cookies[0].value, '')
|
|
155
|
+
t.equal(cookies[0].maxAge, undefined)
|
|
156
156
|
|
|
157
|
-
t.
|
|
158
|
-
t.equal(cookies[1].value, '')
|
|
159
|
-
t.equal(cookies[1].path, '/')
|
|
160
|
-
t.ok(new Date(cookies[1].expires) < new Date())
|
|
157
|
+
t.ok(new Date(cookies[0].expires) < new Date())
|
|
161
158
|
})
|
|
162
159
|
})
|
|
163
160
|
|
|
164
161
|
test('expires should not be overridden in clearCookie', (t) => {
|
|
165
|
-
t.plan(
|
|
162
|
+
t.plan(7)
|
|
166
163
|
const fastify = Fastify()
|
|
167
164
|
const secret = 'testsecret'
|
|
168
165
|
fastify.register(plugin, { secret })
|
|
@@ -188,16 +185,11 @@ test('expires should not be overridden in clearCookie', (t) => {
|
|
|
188
185
|
t.same(JSON.parse(res.body), { hello: 'world' })
|
|
189
186
|
|
|
190
187
|
const cookies = res.cookies
|
|
191
|
-
t.equal(cookies.length,
|
|
188
|
+
t.equal(cookies.length, 1)
|
|
192
189
|
t.equal(cookies[0].name, 'foo')
|
|
193
|
-
t.equal(cookies[0].value,
|
|
190
|
+
t.equal(cookies[0].value, '')
|
|
194
191
|
const expires = new Date(cookies[0].expires)
|
|
195
192
|
t.ok(expires < new Date(Date.now() + 5000))
|
|
196
|
-
|
|
197
|
-
t.equal(cookies[1].name, 'foo')
|
|
198
|
-
t.equal(cookies[1].value, '')
|
|
199
|
-
t.equal(cookies[1].path, '/')
|
|
200
|
-
t.equal(Number(cookies[1].expires), 0)
|
|
201
193
|
})
|
|
202
194
|
})
|
|
203
195
|
|
|
@@ -711,6 +703,18 @@ test('issue 53', (t) => {
|
|
|
711
703
|
})
|
|
712
704
|
})
|
|
713
705
|
|
|
706
|
+
test('serialize cookie manually using decorator', (t) => {
|
|
707
|
+
t.plan(2)
|
|
708
|
+
const fastify = Fastify()
|
|
709
|
+
fastify.register(plugin)
|
|
710
|
+
|
|
711
|
+
fastify.ready(() => {
|
|
712
|
+
t.ok(fastify.serializeCookie)
|
|
713
|
+
t.same(fastify.serializeCookie('foo', 'bar', {}), 'foo=bar')
|
|
714
|
+
t.end()
|
|
715
|
+
})
|
|
716
|
+
})
|
|
717
|
+
|
|
714
718
|
test('parse cookie manually using decorator', (t) => {
|
|
715
719
|
t.plan(2)
|
|
716
720
|
const fastify = Fastify()
|
|
@@ -944,7 +948,7 @@ test('if cookies are not set, then the handler creates an empty req.cookies obje
|
|
|
944
948
|
})
|
|
945
949
|
|
|
946
950
|
test('clearCookie should include parseOptions', (t) => {
|
|
947
|
-
t.plan(
|
|
951
|
+
t.plan(10)
|
|
948
952
|
const fastify = Fastify()
|
|
949
953
|
fastify.register(plugin, {
|
|
950
954
|
parseOptions: {
|
|
@@ -975,17 +979,179 @@ test('clearCookie should include parseOptions', (t) => {
|
|
|
975
979
|
|
|
976
980
|
const cookies = res.cookies
|
|
977
981
|
|
|
978
|
-
t.equal(cookies.length,
|
|
982
|
+
t.equal(cookies.length, 1)
|
|
979
983
|
t.equal(cookies[0].name, 'foo')
|
|
980
|
-
t.equal(cookies[0].value, '
|
|
981
|
-
t.equal(cookies[0].maxAge,
|
|
984
|
+
t.equal(cookies[0].value, '')
|
|
985
|
+
t.equal(cookies[0].maxAge, undefined)
|
|
982
986
|
t.equal(cookies[0].path, '/test')
|
|
983
987
|
t.equal(cookies[0].domain, 'example.com')
|
|
984
988
|
|
|
989
|
+
t.ok(new Date(cookies[0].expires) < new Date())
|
|
990
|
+
})
|
|
991
|
+
})
|
|
992
|
+
|
|
993
|
+
test('should update a cookie value when setCookie is called multiple times', (t) => {
|
|
994
|
+
t.plan(15)
|
|
995
|
+
const fastify = Fastify()
|
|
996
|
+
const secret = 'testsecret'
|
|
997
|
+
fastify.register(plugin, { secret })
|
|
998
|
+
|
|
999
|
+
const cookieOptions = {
|
|
1000
|
+
signed: true,
|
|
1001
|
+
path: '/foo',
|
|
1002
|
+
maxAge: 36000
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
const cookieOptions2 = {
|
|
1006
|
+
signed: true,
|
|
1007
|
+
maxAge: 36000
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
fastify.get('/test1', (req, reply) => {
|
|
1011
|
+
reply
|
|
1012
|
+
.setCookie('foo', 'foo', cookieOptions)
|
|
1013
|
+
.clearCookie('foo', cookieOptions)
|
|
1014
|
+
.setCookie('foo', 'foo', cookieOptions2)
|
|
1015
|
+
.setCookie('foos', 'foos', cookieOptions)
|
|
1016
|
+
.setCookie('foos', 'foosy', cookieOptions)
|
|
1017
|
+
.send({ hello: 'world' })
|
|
1018
|
+
})
|
|
1019
|
+
|
|
1020
|
+
fastify.inject({
|
|
1021
|
+
method: 'GET',
|
|
1022
|
+
url: '/test1'
|
|
1023
|
+
}, (err, res) => {
|
|
1024
|
+
t.error(err)
|
|
1025
|
+
t.equal(res.statusCode, 200)
|
|
1026
|
+
t.same(JSON.parse(res.body), { hello: 'world' })
|
|
1027
|
+
|
|
1028
|
+
const cookies = res.cookies
|
|
1029
|
+
t.equal(cookies.length, 3)
|
|
1030
|
+
|
|
1031
|
+
t.equal(cookies[0].name, 'foo')
|
|
1032
|
+
t.equal(cookies[0].value, '')
|
|
1033
|
+
t.equal(cookies[0].path, '/foo')
|
|
1034
|
+
|
|
1035
|
+
t.equal(cookies[1].name, 'foo')
|
|
1036
|
+
t.equal(cookies[1].value, sign('foo', secret))
|
|
1037
|
+
t.equal(cookies[1].maxAge, 36000)
|
|
1038
|
+
|
|
1039
|
+
t.equal(cookies[2].name, 'foos')
|
|
1040
|
+
t.equal(cookies[2].value, sign('foosy', secret))
|
|
1041
|
+
t.equal(cookies[2].path, '/foo')
|
|
1042
|
+
t.equal(cookies[2].maxAge, 36000)
|
|
1043
|
+
|
|
1044
|
+
t.ok(new Date(cookies[0].expires) < new Date())
|
|
1045
|
+
})
|
|
1046
|
+
})
|
|
1047
|
+
|
|
1048
|
+
test('should update a cookie value when setCookie is called multiple times (empty header)', (t) => {
|
|
1049
|
+
t.plan(15)
|
|
1050
|
+
const fastify = Fastify()
|
|
1051
|
+
const secret = 'testsecret'
|
|
1052
|
+
fastify.register(plugin, { secret })
|
|
1053
|
+
|
|
1054
|
+
const cookieOptions = {
|
|
1055
|
+
signed: true,
|
|
1056
|
+
path: '/foo',
|
|
1057
|
+
maxAge: 36000
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
const cookieOptions2 = {
|
|
1061
|
+
signed: true,
|
|
1062
|
+
maxAge: 36000
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
fastify.get('/test1', (req, reply) => {
|
|
1066
|
+
reply
|
|
1067
|
+
.header('Set-Cookie', '', cookieOptions)
|
|
1068
|
+
.setCookie('foo', 'foo', cookieOptions)
|
|
1069
|
+
.clearCookie('foo', cookieOptions)
|
|
1070
|
+
.setCookie('foo', 'foo', cookieOptions2)
|
|
1071
|
+
.setCookie('foos', 'foos', cookieOptions)
|
|
1072
|
+
.setCookie('foos', 'foosy', cookieOptions)
|
|
1073
|
+
.send({ hello: 'world' })
|
|
1074
|
+
})
|
|
1075
|
+
|
|
1076
|
+
fastify.inject({
|
|
1077
|
+
method: 'GET',
|
|
1078
|
+
url: '/test1'
|
|
1079
|
+
}, (err, res) => {
|
|
1080
|
+
t.error(err)
|
|
1081
|
+
t.equal(res.statusCode, 200)
|
|
1082
|
+
t.same(JSON.parse(res.body), { hello: 'world' })
|
|
1083
|
+
|
|
1084
|
+
const cookies = res.cookies
|
|
1085
|
+
t.equal(cookies.length, 3)
|
|
1086
|
+
|
|
1087
|
+
t.equal(cookies[0].name, 'foo')
|
|
1088
|
+
t.equal(cookies[0].value, '')
|
|
1089
|
+
t.equal(cookies[0].path, '/foo')
|
|
1090
|
+
|
|
1091
|
+
t.equal(cookies[1].name, 'foo')
|
|
1092
|
+
t.equal(cookies[1].value, sign('foo', secret))
|
|
1093
|
+
t.equal(cookies[1].maxAge, 36000)
|
|
1094
|
+
|
|
1095
|
+
t.equal(cookies[2].name, 'foos')
|
|
1096
|
+
t.equal(cookies[2].value, sign('foosy', secret))
|
|
1097
|
+
t.equal(cookies[2].path, '/foo')
|
|
1098
|
+
t.equal(cookies[2].maxAge, 36000)
|
|
1099
|
+
|
|
1100
|
+
t.ok(new Date(cookies[0].expires) < new Date())
|
|
1101
|
+
})
|
|
1102
|
+
})
|
|
1103
|
+
|
|
1104
|
+
test('should update a cookie value when setCookie is called multiple times (non-empty header)', (t) => {
|
|
1105
|
+
t.plan(15)
|
|
1106
|
+
const fastify = Fastify()
|
|
1107
|
+
const secret = 'testsecret'
|
|
1108
|
+
fastify.register(plugin, { secret })
|
|
1109
|
+
|
|
1110
|
+
const cookieOptions = {
|
|
1111
|
+
signed: true,
|
|
1112
|
+
path: '/foo',
|
|
1113
|
+
maxAge: 36000
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
const cookieOptions2 = {
|
|
1117
|
+
signed: true,
|
|
1118
|
+
maxAge: 36000
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
fastify.get('/test1', (req, reply) => {
|
|
1122
|
+
reply
|
|
1123
|
+
.header('Set-Cookie', 'manual=manual', cookieOptions)
|
|
1124
|
+
.setCookie('foo', 'foo', cookieOptions)
|
|
1125
|
+
.clearCookie('foo', cookieOptions)
|
|
1126
|
+
.setCookie('foo', 'foo', cookieOptions2)
|
|
1127
|
+
.setCookie('foos', 'foos', cookieOptions)
|
|
1128
|
+
.setCookie('foos', 'foosy', cookieOptions)
|
|
1129
|
+
.send({ hello: 'world' })
|
|
1130
|
+
})
|
|
1131
|
+
|
|
1132
|
+
fastify.inject({
|
|
1133
|
+
method: 'GET',
|
|
1134
|
+
url: '/test1'
|
|
1135
|
+
}, (err, res) => {
|
|
1136
|
+
t.error(err)
|
|
1137
|
+
t.equal(res.statusCode, 200)
|
|
1138
|
+
t.same(JSON.parse(res.body), { hello: 'world' })
|
|
1139
|
+
|
|
1140
|
+
const cookies = res.cookies
|
|
1141
|
+
t.equal(cookies.length, 4)
|
|
1142
|
+
|
|
985
1143
|
t.equal(cookies[1].name, 'foo')
|
|
986
1144
|
t.equal(cookies[1].value, '')
|
|
987
|
-
t.equal(cookies[1].path, '/
|
|
988
|
-
|
|
1145
|
+
t.equal(cookies[1].path, '/foo')
|
|
1146
|
+
|
|
1147
|
+
t.equal(cookies[2].name, 'foo')
|
|
1148
|
+
t.equal(cookies[2].value, sign('foo', secret))
|
|
1149
|
+
t.equal(cookies[2].maxAge, 36000)
|
|
1150
|
+
|
|
1151
|
+
t.equal(cookies[3].name, 'foos')
|
|
1152
|
+
t.equal(cookies[3].value, sign('foosy', secret))
|
|
1153
|
+
t.equal(cookies[3].path, '/foo')
|
|
1154
|
+
t.equal(cookies[3].maxAge, 36000)
|
|
989
1155
|
|
|
990
1156
|
t.ok(new Date(cookies[1].expires) < new Date())
|
|
991
1157
|
})
|
package/test/signer.test.js
CHANGED
|
@@ -28,13 +28,15 @@ test('default', t => {
|
|
|
28
28
|
})
|
|
29
29
|
|
|
30
30
|
t.test('sign', (t) => {
|
|
31
|
-
t.plan(
|
|
31
|
+
t.plan(5)
|
|
32
32
|
|
|
33
33
|
const input = 'some-value'
|
|
34
34
|
const result = signer.sign(input)
|
|
35
35
|
|
|
36
36
|
t.equal(result, sign(input, secret))
|
|
37
37
|
t.equal(result, sign(input, [secret]))
|
|
38
|
+
t.equal(result, sign(input, Buffer.from(secret)))
|
|
39
|
+
t.equal(result, sign(input, [Buffer.from(secret)]))
|
|
38
40
|
|
|
39
41
|
t.throws(() => sign(undefined), 'Cookie value must be provided as a string.')
|
|
40
42
|
})
|
|
@@ -61,7 +63,7 @@ test('default', t => {
|
|
|
61
63
|
t.equal(result.renew, false)
|
|
62
64
|
t.equal(result.value, 'some-value')
|
|
63
65
|
t.same(result, unsign(input, [secret]))
|
|
64
|
-
t.throws(() => unsign(undefined), 'Secret key must be a string.')
|
|
66
|
+
t.throws(() => unsign(undefined), 'Secret key must be a string or Buffer.')
|
|
65
67
|
t.throws(() => unsign(undefined, secret), 'Signed cookie string must be provided.')
|
|
66
68
|
})
|
|
67
69
|
})
|
|
@@ -117,12 +119,14 @@ test('key rotation', (t) => {
|
|
|
117
119
|
test('Signer', t => {
|
|
118
120
|
t.plan(2)
|
|
119
121
|
|
|
120
|
-
t.test('Signer needs a string as secret', (t) => {
|
|
121
|
-
t.plan(
|
|
122
|
-
t.throws(() => Signer(1), 'Secret key must be a string.')
|
|
123
|
-
t.throws(() => Signer(undefined), 'Secret key must be a string.')
|
|
122
|
+
t.test('Signer needs a string or Buffer as secret', (t) => {
|
|
123
|
+
t.plan(6)
|
|
124
|
+
t.throws(() => Signer(1), 'Secret key must be a string or Buffer.')
|
|
125
|
+
t.throws(() => Signer(undefined), 'Secret key must be a string or Buffer.')
|
|
124
126
|
t.doesNotThrow(() => Signer('secret'))
|
|
125
127
|
t.doesNotThrow(() => Signer(['secret']))
|
|
128
|
+
t.doesNotThrow(() => Signer(Buffer.from('deadbeef76543210', 'hex')))
|
|
129
|
+
t.doesNotThrow(() => Signer([Buffer.from('deadbeef76543210', 'hex')]))
|
|
126
130
|
})
|
|
127
131
|
|
|
128
132
|
t.test('Signer handles algorithm properly', (t) => {
|
package/types/plugin.d.ts
CHANGED
|
@@ -4,6 +4,15 @@ import { FastifyPluginCallback } from "fastify";
|
|
|
4
4
|
|
|
5
5
|
declare module "fastify" {
|
|
6
6
|
interface FastifyInstance extends SignerMethods {
|
|
7
|
+
/**
|
|
8
|
+
* Serialize a cookie name-value pair into a Set-Cookie header string
|
|
9
|
+
* @param name Cookie name
|
|
10
|
+
* @param value Cookie value
|
|
11
|
+
* @param opts Options
|
|
12
|
+
* @throws {TypeError} When maxAge option is invalid
|
|
13
|
+
*/
|
|
14
|
+
serializeCookie(name: string, value: string, opts?: fastifyCookie.SerializeOptions): string;
|
|
15
|
+
|
|
7
16
|
/**
|
|
8
17
|
* Manual cookie parsing method
|
|
9
18
|
* @docs https://github.com/fastify/fastify-cookie#manual-cookie-parsing
|
|
@@ -100,27 +109,32 @@ declare namespace fastifyCookie {
|
|
|
100
109
|
}
|
|
101
110
|
|
|
102
111
|
export class Signer implements SignerBase {
|
|
103
|
-
constructor (secrets: string | Array<string>, algorithm?: string)
|
|
112
|
+
constructor (secrets: string | Array<string> | Buffer | Array<Buffer>, algorithm?: string)
|
|
104
113
|
sign: (value: string) => string;
|
|
105
114
|
unsign: (input: string) => UnsignResult;
|
|
106
115
|
}
|
|
107
116
|
|
|
108
|
-
export interface
|
|
117
|
+
export interface SerializeOptions {
|
|
109
118
|
/** The `Domain` attribute. */
|
|
110
119
|
domain?: string;
|
|
120
|
+
/** Specifies a function that will be used to encode a cookie's value. Since value of a cookie has a limited character set (and must be a simple string), this function can be used to encode a value into a string suited for a cookie's value. */
|
|
111
121
|
encode?(val: string): string;
|
|
112
122
|
/** The expiration `date` used for the `Expires` attribute. If both `expires` and `maxAge` are set, then `expires` is used. */
|
|
113
123
|
expires?: Date;
|
|
114
124
|
/** The `boolean` value of the `HttpOnly` attribute. Defaults to true. */
|
|
115
125
|
httpOnly?: boolean;
|
|
116
|
-
/** A `number` in
|
|
126
|
+
/** A `number` in seconds that specifies the `Expires` attribute by adding the specified seconds to the current date. If both `expires` and `maxAge` are set, then `expires` is used. */
|
|
117
127
|
maxAge?: number;
|
|
118
128
|
/** The `Path` attribute. Defaults to `/` (the root path). */
|
|
119
129
|
path?: string;
|
|
120
130
|
priority?: "low" | "medium" | "high";
|
|
121
|
-
/** A `boolean` or one of the `SameSite` string attributes. E.g.: `lax`, `
|
|
131
|
+
/** A `boolean` or one of the `SameSite` string attributes. E.g.: `lax`, `none` or `strict`. */
|
|
122
132
|
sameSite?: 'lax' | 'none' | 'strict' | boolean;
|
|
123
133
|
/** The `boolean` value of the `Secure` attribute. Set this option to false when communicating over an unencrypted (HTTP) connection. Value can be set to `auto`; in this case the `Secure` attribute will be set to false for HTTP request, in case of HTTPS it will be set to true. Defaults to true. */
|
|
134
|
+
secure?: boolean;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export interface CookieSerializeOptions extends Omit<SerializeOptions, 'secure'> {
|
|
124
138
|
secure?: boolean | 'auto';
|
|
125
139
|
signed?: boolean;
|
|
126
140
|
}
|
|
@@ -128,14 +142,14 @@ declare namespace fastifyCookie {
|
|
|
128
142
|
type HookType = 'onRequest' | 'preParsing' | 'preValidation' | 'preHandler' | 'preSerialization';
|
|
129
143
|
|
|
130
144
|
export interface FastifyCookieOptions {
|
|
131
|
-
secret?: string | string[] | Signer;
|
|
145
|
+
secret?: string | string[] | Buffer | Buffer[] | Signer;
|
|
132
146
|
hook?: HookType | false;
|
|
133
147
|
parseOptions?: fastifyCookie.CookieSerializeOptions;
|
|
134
148
|
}
|
|
135
149
|
|
|
136
|
-
export type Sign = (value: string, secret: string, algorithm?: string) => string;
|
|
137
|
-
export type Unsign = (input: string, secret: string, algorithm?: string) => UnsignResult;
|
|
138
|
-
export type SignerFactory = (secrets: string |
|
|
150
|
+
export type Sign = (value: string, secret: string | Buffer, algorithm?: string) => string;
|
|
151
|
+
export type Unsign = (input: string, secret: string | Buffer, algorithm?: string) => UnsignResult;
|
|
152
|
+
export type SignerFactory = (secrets: string | string[] | Buffer | Buffer[], algorithm?: string) => SignerBase;
|
|
139
153
|
|
|
140
154
|
export interface UnsignResult {
|
|
141
155
|
valid: boolean;
|
|
@@ -157,7 +171,7 @@ declare namespace fastifyCookie {
|
|
|
157
171
|
export const fastifyCookie: FastifyCookie;
|
|
158
172
|
|
|
159
173
|
export interface FastifyCookieOptions {
|
|
160
|
-
secret?: string | string[] | SignerBase;
|
|
174
|
+
secret?: string | string[] | Buffer | Buffer[] | SignerBase;
|
|
161
175
|
algorithm?: string;
|
|
162
176
|
parseOptions?: CookieSerializeOptions;
|
|
163
177
|
}
|
|
@@ -169,4 +183,4 @@ declare function fastifyCookie(
|
|
|
169
183
|
...params: Parameters<FastifyCookiePlugin>
|
|
170
184
|
): ReturnType<FastifyCookiePlugin>;
|
|
171
185
|
|
|
172
|
-
export = fastifyCookie;
|
|
186
|
+
export = fastifyCookie;
|
package/types/plugin.test-d.ts
CHANGED
|
@@ -39,6 +39,10 @@ const server = fastify();
|
|
|
39
39
|
server.register(cookie);
|
|
40
40
|
|
|
41
41
|
server.after((_err) => {
|
|
42
|
+
expectType< string >(
|
|
43
|
+
server.serializeCookie('sessionId', 'aYb4uTIhdBXC')
|
|
44
|
+
);
|
|
45
|
+
|
|
42
46
|
expectType<{ [key: string]: string }>(
|
|
43
47
|
// See https://github.com/fastify/fastify-cookie#manual-cookie-parsing
|
|
44
48
|
server.parseCookie('sessionId=aYb4uTIhdBXC')
|
|
@@ -211,6 +215,8 @@ appWithCustomSigner.after(() => {
|
|
|
211
215
|
|
|
212
216
|
new fastifyCookieStar.Signer('secretString')
|
|
213
217
|
new fastifyCookieStar.Signer(['secretStringInArray'])
|
|
218
|
+
new fastifyCookieStar.Signer(Buffer.from('secretString'))
|
|
219
|
+
new fastifyCookieStar.Signer([Buffer.from('secretStringInArray')])
|
|
214
220
|
const signer = new fastifyCookieStar.Signer(['secretStringInArray'], 'sha256')
|
|
215
221
|
signer.sign('Lorem Ipsum')
|
|
216
222
|
signer.unsign('Lorem Ipsum')
|