@fastify/cookie 8.3.0 → 9.0.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/workflows/ci.yml +5 -0
- package/.taprc +2 -3
- package/benchmark/cookie-multi.js +24 -0
- package/benchmark/cookie.js +20 -0
- package/package.json +9 -9
- package/plugin.js +59 -17
- package/test/cookie.test.js +219 -22
- package/types/plugin.d.ts +18 -4
- package/types/plugin.test-d.ts +4 -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
|
|
@@ -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.1",
|
|
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,22 @@ 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) {
|
|
11
|
+
let sendHeaders = false
|
|
12
|
+
if (reply[kReplySetCookies] === null) {
|
|
13
|
+
sendHeaders = true
|
|
14
|
+
reply[kReplySetCookies] = new Map()
|
|
15
|
+
}
|
|
9
16
|
const opts = Object.assign({}, options)
|
|
17
|
+
|
|
10
18
|
if (opts.expires && Number.isInteger(opts.expires)) {
|
|
11
19
|
opts.expires = new Date(opts.expires)
|
|
12
20
|
}
|
|
13
21
|
|
|
14
22
|
if (opts.signed) {
|
|
15
|
-
value =
|
|
23
|
+
value = reply.signCookie(value)
|
|
16
24
|
}
|
|
17
25
|
|
|
18
26
|
if (opts.secure === 'auto') {
|
|
@@ -24,20 +32,12 @@ function fastifyCookieSetCookie (reply, name, value, options, signer) {
|
|
|
24
32
|
}
|
|
25
33
|
}
|
|
26
34
|
|
|
27
|
-
|
|
28
|
-
let setCookie = reply.getHeader('Set-Cookie')
|
|
29
|
-
if (!setCookie) {
|
|
30
|
-
reply.header('Set-Cookie', serialized)
|
|
31
|
-
return reply
|
|
32
|
-
}
|
|
35
|
+
reply[kReplySetCookies].set(`${name};${opts.domain};${opts.path || '/'}`, { name, value, opts })
|
|
33
36
|
|
|
34
|
-
if (
|
|
35
|
-
|
|
37
|
+
if (sendHeaders) {
|
|
38
|
+
setCookies(reply)
|
|
36
39
|
}
|
|
37
40
|
|
|
38
|
-
setCookie.push(serialized)
|
|
39
|
-
reply.removeHeader('Set-Cookie')
|
|
40
|
-
reply.header('Set-Cookie', setCookie)
|
|
41
41
|
return reply
|
|
42
42
|
}
|
|
43
43
|
|
|
@@ -58,6 +58,7 @@ function onReqHandlerWrapper (fastify, hook) {
|
|
|
58
58
|
if (cookieHeader) {
|
|
59
59
|
fastifyReq.cookies = fastify.parseCookie(cookieHeader)
|
|
60
60
|
}
|
|
61
|
+
fastifyRes[kReplySetCookies] = new Map()
|
|
61
62
|
done()
|
|
62
63
|
}
|
|
63
64
|
: function fastifyCookieHandler (fastifyReq, fastifyRes, done) {
|
|
@@ -66,10 +67,49 @@ function onReqHandlerWrapper (fastify, hook) {
|
|
|
66
67
|
if (cookieHeader) {
|
|
67
68
|
fastifyReq.cookies = fastify.parseCookie(cookieHeader)
|
|
68
69
|
}
|
|
70
|
+
fastifyRes[kReplySetCookies] = new Map()
|
|
69
71
|
done()
|
|
70
72
|
}
|
|
71
73
|
}
|
|
72
74
|
|
|
75
|
+
function setCookies (reply) {
|
|
76
|
+
let setCookie = reply.getHeader('Set-Cookie')
|
|
77
|
+
|
|
78
|
+
/* istanbul ignore else */
|
|
79
|
+
if (setCookie === undefined) {
|
|
80
|
+
if (reply[kReplySetCookies].size === 1) {
|
|
81
|
+
for (const c of reply[kReplySetCookies].values()) {
|
|
82
|
+
reply.header('Set-Cookie', cookie.serialize(c.name, c.value, c.opts))
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
setCookie = []
|
|
89
|
+
} else if (typeof setCookie === 'string') {
|
|
90
|
+
setCookie = [setCookie]
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
for (const c of reply[kReplySetCookies].values()) {
|
|
94
|
+
setCookie.push(cookie.serialize(c.name, c.value, c.opts))
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
reply.removeHeader('Set-Cookie')
|
|
98
|
+
reply.header('Set-Cookie', setCookie)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function fastifyCookieOnSendHandler (fastifyReq, fastifyRes, payload, done) {
|
|
102
|
+
if (fastifyRes[kReplySetCookies].size) {
|
|
103
|
+
setCookies(fastifyRes)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Explicitly set the property to null so that we can
|
|
107
|
+
// check if the header was already set
|
|
108
|
+
fastifyRes[kReplySetCookies] = null
|
|
109
|
+
|
|
110
|
+
done()
|
|
111
|
+
}
|
|
112
|
+
|
|
73
113
|
function getHook (hook = 'onRequest') {
|
|
74
114
|
const hooks = {
|
|
75
115
|
onRequest: 'onRequest',
|
|
@@ -89,9 +129,9 @@ function plugin (fastify, options, next) {
|
|
|
89
129
|
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
130
|
}
|
|
91
131
|
const isSigner = !secret || (typeof secret.sign === 'function' && typeof secret.unsign === 'function')
|
|
92
|
-
const
|
|
93
|
-
const signer = isSigner ? secret : new Signer(secret, algorithm)
|
|
132
|
+
const signer = isSigner ? secret : new Signer(secret, options.algorithm || 'sha256')
|
|
94
133
|
|
|
134
|
+
fastify.decorate('serializeCookie', cookie.serialize)
|
|
95
135
|
fastify.decorate('parseCookie', parseCookie)
|
|
96
136
|
|
|
97
137
|
if (typeof secret !== 'undefined') {
|
|
@@ -106,13 +146,15 @@ function plugin (fastify, options, next) {
|
|
|
106
146
|
}
|
|
107
147
|
|
|
108
148
|
fastify.decorateRequest('cookies', null)
|
|
109
|
-
fastify.decorateReply(
|
|
149
|
+
fastify.decorateReply(kReplySetCookies, null)
|
|
110
150
|
|
|
151
|
+
fastify.decorateReply('cookie', setCookie)
|
|
111
152
|
fastify.decorateReply('setCookie', setCookie)
|
|
112
153
|
fastify.decorateReply('clearCookie', clearCookie)
|
|
113
154
|
|
|
114
155
|
if (hook) {
|
|
115
156
|
fastify.addHook(hook, onReqHandlerWrapper(fastify, hook))
|
|
157
|
+
fastify.addHook('onSend', fastifyCookieOnSendHandler)
|
|
116
158
|
}
|
|
117
159
|
|
|
118
160
|
next()
|
|
@@ -132,7 +174,7 @@ function plugin (fastify, options, next) {
|
|
|
132
174
|
|
|
133
175
|
function setCookie (name, value, cookieOptions) {
|
|
134
176
|
const opts = Object.assign({}, options.parseOptions, cookieOptions)
|
|
135
|
-
return fastifyCookieSetCookie(this, name, value, opts
|
|
177
|
+
return fastifyCookieSetCookie(this, name, value, opts)
|
|
136
178
|
}
|
|
137
179
|
|
|
138
180
|
function clearCookie (name, cookieOptions) {
|
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,18 +979,211 @@ 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
|
})
|
|
992
1158
|
})
|
|
1159
|
+
|
|
1160
|
+
test('cookies get set correctly if set inside onSend', (t) => {
|
|
1161
|
+
t.plan(7)
|
|
1162
|
+
const fastify = Fastify()
|
|
1163
|
+
fastify.register(plugin)
|
|
1164
|
+
|
|
1165
|
+
fastify.addHook('onSend', async (req, reply, payload) => {
|
|
1166
|
+
reply.setCookie('foo', 'foo', { path: '/' })
|
|
1167
|
+
return payload
|
|
1168
|
+
})
|
|
1169
|
+
|
|
1170
|
+
fastify.get('/test1', (req, reply) => {
|
|
1171
|
+
reply
|
|
1172
|
+
.send({ hello: 'world' })
|
|
1173
|
+
})
|
|
1174
|
+
|
|
1175
|
+
fastify.inject({
|
|
1176
|
+
method: 'GET',
|
|
1177
|
+
url: '/test1'
|
|
1178
|
+
}, (err, res) => {
|
|
1179
|
+
t.error(err)
|
|
1180
|
+
t.equal(res.statusCode, 200)
|
|
1181
|
+
t.same(JSON.parse(res.body), { hello: 'world' })
|
|
1182
|
+
|
|
1183
|
+
const cookies = res.cookies
|
|
1184
|
+
t.equal(cookies.length, 1)
|
|
1185
|
+
t.equal(cookies[0].name, 'foo')
|
|
1186
|
+
t.equal(cookies[0].value, 'foo')
|
|
1187
|
+
t.equal(cookies[0].path, '/')
|
|
1188
|
+
})
|
|
1189
|
+
})
|
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
|
|
@@ -105,22 +114,27 @@ declare namespace fastifyCookie {
|
|
|
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
|
}
|
|
@@ -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')
|