@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.
@@ -2,6 +2,11 @@ name: CI
2
2
 
3
3
  on:
4
4
  push:
5
+ branches:
6
+ - main
7
+ - master
8
+ - next
9
+ - 'v*'
5
10
  paths-ignore:
6
11
  - 'docs/**'
7
12
  - '*.md'
package/.taprc CHANGED
@@ -1,3 +1,2 @@
1
- 100: true
2
- check-coverage: true
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": "8.2.0",
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 -J \"test/*.test.js\"",
14
- "unit:report": "npm run unit -- --coverage-report=html",
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": "^18.0.0",
43
+ "@types/node": "^20.1.0",
44
44
  "benchmark": "^2.1.4",
45
45
  "fastify": "^4.0.0",
46
- "sinon": "^14.0.0",
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.24.1"
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
- function fastifyCookieSetCookie (reply, name, value, options, signer) {
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 = signer.sign(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
- const serialized = cookie.serialize(name, value, opts)
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 enableRotation = Array.isArray(secret)
92
- const algorithm = options.algorithm || 'sha256'
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('cookie', setCookie)
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, signer)
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
  }
@@ -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(11)
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, 2)
152
+ t.equal(cookies.length, 1)
153
153
  t.equal(cookies[0].name, 'foo')
154
- t.equal(cookies[0].value, sign('foo', secret))
155
- t.equal(cookies[0].maxAge, 36000)
154
+ t.equal(cookies[0].value, '')
155
+ t.equal(cookies[0].maxAge, undefined)
156
156
 
157
- t.equal(cookies[1].name, 'foo')
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(11)
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, 2)
188
+ t.equal(cookies.length, 1)
192
189
  t.equal(cookies[0].name, 'foo')
193
- t.equal(cookies[0].value, sign('foo', secret))
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(14)
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, 2)
982
+ t.equal(cookies.length, 1)
979
983
  t.equal(cookies[0].name, 'foo')
980
- t.equal(cookies[0].value, 'foo')
981
- t.equal(cookies[0].maxAge, 36000)
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, '/test')
988
- t.equal(cookies[1].domain, 'example.com')
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
  })
@@ -28,13 +28,15 @@ test('default', t => {
28
28
  })
29
29
 
30
30
  t.test('sign', (t) => {
31
- t.plan(3)
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(4)
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 CookieSerializeOptions {
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 milliseconds that specifies the `Expires` attribute by adding the specified milliseconds to the current date. If both `expires` and `maxAge` are set, then `expires` is used. */
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`, `node` or `strict`. */
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 | Array<string>, algorithm?: string) => SignerBase;
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;
@@ -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')