@fastify/cookie 7.3.0 → 8.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.
@@ -14,4 +14,5 @@ jobs:
14
14
  test:
15
15
  uses: fastify/workflows/.github/workflows/plugins-ci.yml@v3
16
16
  with:
17
+ license-check: true
17
18
  lint: true
package/README.md CHANGED
@@ -192,9 +192,9 @@ fastify.register(require('@fastify/cookie'), {
192
192
 
193
193
  ### Manual cookie unsigning
194
194
 
195
- The method `unsignCookie(value)` is added to the `fastify` instance and the `reply` object
196
- via the Fastify `decorate` & `decorateReply` APIs. Using it on a signed cookie will call the
197
- the provided signer's (or the default signer if no custom implementation is provided) `unsign` method on the cookie.
195
+ The method `unsignCookie(value)` is added to the `fastify` instance, to the `request` and the `reply` object
196
+ via the Fastify `decorate`, `decorateRequest` and `decorateReply` APIs, if a secret was provided as option.
197
+ Using it on a signed cookie will call the the provided signer's (or the default signer if no custom implementation is provided) `unsign` method on the cookie.
198
198
 
199
199
  **Example:**
200
200
 
@@ -236,12 +236,12 @@ test('Request requires signed cookie', async () => {
236
236
 
237
237
  ### Manual signing/unsigning with low level utilities
238
238
 
239
- with signerFactory
239
+ with Signer
240
240
 
241
241
  ```js
242
- const { fastifyCookie } = require('@fastify/cookie');
242
+ const { Signer } = require('@fastify/cookie');
243
243
 
244
- const signer = fastifyCookie.signerFactory('secret');
244
+ const signer = new Signer('secret');
245
245
  const signedValue = signer.sign('test');
246
246
  const {valid, renew, value } = signer.unsign(signedValue);
247
247
  ```
@@ -0,0 +1,18 @@
1
+ const Benchmark = require('benchmark')
2
+ const Signer = require('../signer')
3
+ const suite = new Benchmark.Suite()
4
+
5
+ const signer = Signer(['abcd', 'asdf', 'vadfa'])
6
+ const signedValue = signer.sign('test', 'vadfa')
7
+
8
+ suite
9
+ .add('sign', function () {
10
+ signer.sign('test')
11
+ })
12
+ .add('unsign', function () {
13
+ signer.unsign(signedValue)
14
+ })
15
+ .on('cycle', function (event) {
16
+ console.log(String(event.target))
17
+ })
18
+ .run({ delay: 0 })
@@ -0,0 +1,17 @@
1
+ const Benchmark = require('benchmark')
2
+ const Signer = require('../signer')
3
+ const suite = new Benchmark.Suite()
4
+
5
+ const signer = Signer('abcd')
6
+ const signedValue = signer.sign('test')
7
+ suite
8
+ .add('sign', function () {
9
+ signer.sign('test')
10
+ })
11
+ .add('unsign', function () {
12
+ signer.unsign(signedValue)
13
+ })
14
+ .on('cycle', function (event) {
15
+ console.log(String(event.target))
16
+ })
17
+ .run({ delay: 0 })
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "@fastify/cookie",
3
- "version": "7.3.0",
3
+ "version": "8.0.0",
4
4
  "description": "Plugin for fastify to add support for cookies",
5
5
  "main": "plugin.js",
6
- "types": "plugin.d.ts",
6
+ "types": "types/plugin.d.ts",
7
7
  "scripts": {
8
8
  "lint": "standard | snazzy",
9
9
  "lint:ci": "standard",
@@ -39,19 +39,18 @@
39
39
  },
40
40
  "homepage": "https://github.com/fastify/fastify-cookie#readme",
41
41
  "devDependencies": {
42
+ "@fastify/pre-commit": "^2.0.2",
42
43
  "@types/node": "^18.0.0",
43
- "fastify": "^4.0.0-rc.2",
44
- "pre-commit": "^1.2.2",
44
+ "benchmark": "^2.1.4",
45
+ "fastify": "^4.0.0",
45
46
  "sinon": "^14.0.0",
46
47
  "snazzy": "^9.0.0",
47
48
  "standard": "^17.0.0",
48
49
  "tap": "^16.0.0",
49
- "tsd": "^0.22.0",
50
- "typescript": "^4.5.5"
50
+ "tsd": "^0.22.0"
51
51
  },
52
52
  "dependencies": {
53
53
  "cookie": "^0.5.0",
54
- "cookie-signature": "^1.1.0",
55
54
  "fastify-plugin": "^4.0.0"
56
55
  },
57
56
  "tsd": {
package/plugin.js CHANGED
@@ -1,10 +1,9 @@
1
1
  'use strict'
2
2
 
3
- const { sign, unsign } = require('cookie-signature')
4
3
  const fp = require('fastify-plugin')
5
4
  const cookie = require('cookie')
6
5
 
7
- const signerFactory = require('./signer')
6
+ const { Signer, sign, unsign } = require('./signer')
8
7
 
9
8
  function fastifyCookieSetCookie (reply, name, value, options, signer) {
10
9
  const opts = Object.assign({}, options)
@@ -63,20 +62,29 @@ function onReqHandlerWrapper (fastify) {
63
62
  }
64
63
 
65
64
  function plugin (fastify, options, next) {
66
- const secret = options.secret || ''
65
+ const secret = options.secret
67
66
  const enableRotation = Array.isArray(secret)
68
- const signer = typeof secret === 'string' || enableRotation ? signerFactory(secret) : secret
67
+ const algorithm = options.algorithm || 'sha256'
68
+ const signer = typeof secret === 'string' || enableRotation ? new Signer(secret, algorithm) : secret
69
69
 
70
70
  fastify.decorate('parseCookie', parseCookie)
71
- fastify.decorate('signCookie', signCookie)
72
- fastify.decorate('unsignCookie', unsignCookie)
71
+
72
+ if (typeof secret !== 'undefined') {
73
+ fastify.decorate('signCookie', signCookie)
74
+ fastify.decorate('unsignCookie', unsignCookie)
75
+
76
+ fastify.decorateRequest('signCookie', signCookie)
77
+ fastify.decorateRequest('unsignCookie', unsignCookie)
78
+
79
+ fastify.decorateReply('signCookie', signCookie)
80
+ fastify.decorateReply('unsignCookie', unsignCookie)
81
+ }
73
82
 
74
83
  fastify.decorateRequest('cookies', null)
75
- fastify.decorateRequest('unsignCookie', unsignCookie)
76
- fastify.decorateReply('setCookie', setCookie)
77
84
  fastify.decorateReply('cookie', setCookie)
85
+
86
+ fastify.decorateReply('setCookie', setCookie)
78
87
  fastify.decorateReply('clearCookie', clearCookie)
79
- fastify.decorateReply('unsignCookie', unsignCookie)
80
88
 
81
89
  fastify.addHook('onRequest', onReqHandlerWrapper(fastify))
82
90
 
@@ -127,14 +135,17 @@ const fastifyCookie = fp(plugin, {
127
135
  * - `import { fastifyCookie } from 'fastify-cookie'`
128
136
  * - `import fastifyCookie from 'fastify-cookie'`
129
137
  */
138
+ fastifyCookie.signerFactory = Signer
130
139
  fastifyCookie.fastifyCookie = fastifyCookie
131
140
  fastifyCookie.default = fastifyCookie
132
141
  module.exports = fastifyCookie
133
142
 
134
- fastifyCookie.fastifyCookie.signerFactory = signerFactory
143
+ fastifyCookie.fastifyCookie.signerFactory = Signer
144
+ fastifyCookie.fastifyCookie.Signer = Signer
135
145
  fastifyCookie.fastifyCookie.sign = sign
136
146
  fastifyCookie.fastifyCookie.unsign = unsign
137
147
 
138
- module.exports.signerFactory = signerFactory
148
+ module.exports.signerFactory = Signer
149
+ module.exports.Signer = Signer
139
150
  module.exports.sign = sign
140
151
  module.exports.unsign = unsign
package/signer.js CHANGED
@@ -1,32 +1,116 @@
1
1
  'use strict'
2
2
 
3
- const cookieSignature = require('cookie-signature')
3
+ // Inspired by node-cookie-signature
4
+ // https://github.com/tj/node-cookie-signature
5
+ //
6
+ // The MIT License
7
+ // Copyright (c) 2012–2022 LearnBoost <tj@learnboost.com> and other contributors;
4
8
 
5
- module.exports = function (secret) {
6
- const secrets = Array.isArray(secret) ? secret : [secret]
7
- const [signingKey] = secrets
9
+ const crypto = require('crypto')
8
10
 
9
- return {
10
- sign (value) {
11
- return cookieSignature.sign(value, signingKey)
12
- },
13
- unsign (signedValue) {
14
- let valid = false
15
- let renew = false
16
- let value = null
17
-
18
- for (const key of secrets) {
19
- const result = cookieSignature.unsign(signedValue, key)
20
-
21
- if (result !== false) {
22
- valid = true
23
- renew = key !== signingKey
24
- value = result
25
- break
26
- }
27
- }
11
+ const base64PaddingRE = /=/g
12
+
13
+ function Signer (secrets, algorithm = 'sha256') {
14
+ if (!(this instanceof Signer)) {
15
+ return new Signer(secrets, algorithm)
16
+ }
28
17
 
29
- return { valid, renew, value }
18
+ this.secrets = Array.isArray(secrets) ? secrets : [secrets]
19
+ this.signingKey = this.secrets[0]
20
+ this.algorithm = algorithm
21
+
22
+ validateSecrets(this.secrets)
23
+ validateAlgorithm(this.algorithm)
24
+ }
25
+
26
+ function validateSecrets (secrets) {
27
+ for (const secret of secrets) {
28
+ if (typeof secret !== 'string') {
29
+ throw new TypeError('Secret key must be a string.')
30
30
  }
31
31
  }
32
32
  }
33
+
34
+ function validateAlgorithm (algorithm) {
35
+ // validate that the algorithm is supported by the node runtime
36
+ try {
37
+ crypto.createHmac(algorithm, 'dummyHmac')
38
+ } catch (e) {
39
+ throw new TypeError(`Algorithm ${algorithm} not supported.`)
40
+ }
41
+ }
42
+
43
+ function _sign (value, secret, algorithm) {
44
+ if (typeof value !== 'string') {
45
+ throw new TypeError('Cookie value must be provided as a string.')
46
+ }
47
+ return value + '.' + crypto
48
+ .createHmac(algorithm, secret)
49
+ .update(value)
50
+ .digest('base64')
51
+ // remove base64 padding (=) as it has special meaning in cookies
52
+ .replace(base64PaddingRE, '')
53
+ }
54
+
55
+ function _unsign (signedValue, secrets, algorithm) {
56
+ if (typeof signedValue !== 'string') {
57
+ throw new TypeError('Signed cookie string must be provided.')
58
+ }
59
+ const value = signedValue.slice(0, signedValue.lastIndexOf('.'))
60
+ const actual = Buffer.from(signedValue.slice(signedValue.lastIndexOf('.') + 1))
61
+
62
+ for (const secret of secrets) {
63
+ const expected = Buffer.from(crypto
64
+ .createHmac(algorithm, secret)
65
+ .update(value)
66
+ .digest('base64')
67
+ // remove base64 padding (=) as it has special meaning in cookies
68
+ .replace(base64PaddingRE, ''))
69
+ if (
70
+ expected.length === actual.length &&
71
+ crypto.timingSafeEqual(expected, actual)
72
+ ) {
73
+ return {
74
+ valid: true,
75
+ renew: secret !== secrets[0],
76
+ value
77
+ }
78
+ }
79
+ }
80
+
81
+ return {
82
+ valid: false,
83
+ renew: false,
84
+ value: null
85
+ }
86
+ }
87
+
88
+ Signer.prototype.sign = function (value) {
89
+ return _sign(value, this.signingKey, this.algorithm)
90
+ }
91
+
92
+ Signer.prototype.unsign = function (signedValue) {
93
+ return _unsign(signedValue, this.secrets, this.algorithm)
94
+ }
95
+
96
+ function sign (value, secret, algorithm = 'sha256') {
97
+ const secrets = Array.isArray(secret) ? secret : [secret]
98
+
99
+ validateSecrets(secrets)
100
+
101
+ return _sign(value, secrets[0], algorithm)
102
+ }
103
+
104
+ function unsign (signedValue, secret, algorithm = 'sha256') {
105
+ const secrets = Array.isArray(secret) ? secret : [secret]
106
+
107
+ validateSecrets(secrets)
108
+
109
+ return _unsign(signedValue, secrets, algorithm)
110
+ }
111
+
112
+ module.exports = Signer
113
+ module.exports.signerFactory = Signer
114
+ module.exports.Signer = Signer
115
+ module.exports.sign = sign
116
+ module.exports.unsign = unsign
@@ -4,7 +4,7 @@ const tap = require('tap')
4
4
  const test = tap.test
5
5
  const Fastify = require('fastify')
6
6
  const sinon = require('sinon')
7
- const cookieSignature = require('cookie-signature')
7
+ const { sign, unsign } = require('../signer')
8
8
  const plugin = require('../')
9
9
 
10
10
  test('cookies get set correctly', (t) => {
@@ -151,7 +151,7 @@ test('share options for setCookie and clearCookie', (t) => {
151
151
  const cookies = res.cookies
152
152
  t.equal(cookies.length, 2)
153
153
  t.equal(cookies[0].name, 'foo')
154
- t.equal(cookies[0].value, cookieSignature.sign('foo', secret))
154
+ t.equal(cookies[0].value, sign('foo', secret))
155
155
  t.equal(cookies[0].maxAge, 36000)
156
156
 
157
157
  t.equal(cookies[1].name, 'foo')
@@ -190,7 +190,7 @@ test('expires should not be overridden in clearCookie', (t) => {
190
190
  const cookies = res.cookies
191
191
  t.equal(cookies.length, 2)
192
192
  t.equal(cookies[0].name, 'foo')
193
- t.equal(cookies[0].value, cookieSignature.sign('foo', secret))
193
+ t.equal(cookies[0].value, sign('foo', secret))
194
194
  const expires = new Date(cookies[0].expires)
195
195
  t.ok(expires < new Date(Date.now() + 5000))
196
196
 
@@ -378,7 +378,7 @@ test('cookies signature', (t) => {
378
378
  const cookies = res.cookies
379
379
  t.equal(cookies.length, 1)
380
380
  t.equal(cookies[0].name, 'foo')
381
- t.equal(cookieSignature.unsign(cookies[0].value, secret), 'foo')
381
+ t.same(unsign(cookies[0].value, secret), { valid: true, renew: false, value: 'foo' })
382
382
  })
383
383
  })
384
384
 
@@ -406,7 +406,7 @@ test('cookies signature', (t) => {
406
406
  const cookies = res.cookies
407
407
  t.equal(cookies.length, 1)
408
408
  t.equal(cookies[0].name, 'foo')
409
- t.equal(cookieSignature.unsign(cookies[0].value, secret1), 'cookieVal') // decode using first key
409
+ t.same(unsign(cookies[0].value, secret1), { valid: true, renew: false, value: 'cookieVal' }) // decode using first key
410
410
  })
411
411
  })
412
412
 
@@ -427,7 +427,7 @@ test('cookies signature', (t) => {
427
427
  method: 'GET',
428
428
  url: '/test1',
429
429
  headers: {
430
- cookie: `foo=${cookieSignature.sign('foo', secret)}`
430
+ cookie: `foo=${sign('foo', secret)}`
431
431
  }
432
432
  }, (err, res) => {
433
433
  t.error(err)
@@ -452,7 +452,7 @@ test('cookies signature', (t) => {
452
452
  method: 'GET',
453
453
  url: '/test1',
454
454
  headers: {
455
- cookie: `foo=${cookieSignature.sign('foo', secret)}`
455
+ cookie: `foo=${sign('foo', secret)}`
456
456
  }
457
457
  }, (err, res) => {
458
458
  t.error(err)
@@ -477,7 +477,7 @@ test('cookies signature', (t) => {
477
477
  method: 'GET',
478
478
  url: '/test1',
479
479
  headers: {
480
- cookie: `foo=${cookieSignature.sign('foo', secret)}`
480
+ cookie: `foo=${sign('foo', secret)}`
481
481
  }
482
482
  }, (err, res) => {
483
483
  t.error(err)
@@ -503,7 +503,7 @@ test('cookies signature', (t) => {
503
503
  method: 'GET',
504
504
  url: '/test1',
505
505
  headers: {
506
- cookie: `foo=${cookieSignature.sign('foo', secret2)}`
506
+ cookie: `foo=${sign('foo', secret2)}`
507
507
  }
508
508
  }, (err, res) => {
509
509
  t.error(err)
@@ -529,7 +529,7 @@ test('cookies signature', (t) => {
529
529
  method: 'GET',
530
530
  url: '/test1',
531
531
  headers: {
532
- cookie: `foo=${cookieSignature.sign('foo', secret2)}`
532
+ cookie: `foo=${sign('foo', secret2)}`
533
533
  }
534
534
  }, (err, res) => {
535
535
  t.error(err)
@@ -555,7 +555,7 @@ test('cookies signature', (t) => {
555
555
  method: 'GET',
556
556
  url: '/test1',
557
557
  headers: {
558
- cookie: `foo=${cookieSignature.sign('foo', 'invalid-secret')}`
558
+ cookie: `foo=${sign('foo', 'invalid-secret')}`
559
559
  }
560
560
  }, (err, res) => {
561
561
  t.error(err)
@@ -581,7 +581,7 @@ test('cookies signature', (t) => {
581
581
  method: 'GET',
582
582
  url: '/test1',
583
583
  headers: {
584
- cookie: `foo=${cookieSignature.sign('foo', 'invalid-secret')}`
584
+ cookie: `foo=${sign('foo', 'invalid-secret')}`
585
585
  }
586
586
  }, (err, res) => {
587
587
  t.error(err)
@@ -814,3 +814,36 @@ test('handle secure:auto of cookieOptions', async (t) => {
814
814
  t.same(cookies2[0].secure, null)
815
815
  t.equal(cookies2[0].path, '/')
816
816
  })
817
+
818
+ test('should not decorate fastify, request and reply if no secret was provided', async (t) => {
819
+ t.plan(8)
820
+ const fastify = Fastify()
821
+
822
+ await fastify.register(plugin)
823
+
824
+ t.notOk(fastify.signCookie)
825
+ t.notOk(fastify.unsignCookie)
826
+
827
+ fastify.get('/testDecorators', (req, reply) => {
828
+ t.notOk(req.signCookie)
829
+ t.notOk(reply.signCookie)
830
+ t.notOk(req.unsignCookie)
831
+ t.notOk(reply.unsignCookie)
832
+
833
+ reply.send({
834
+ unsigned: req.unsignCookie(req.cookies.foo)
835
+ })
836
+ })
837
+
838
+ const res = await fastify.inject({
839
+ method: 'GET',
840
+ url: '/testDecorators'
841
+ })
842
+
843
+ t.equal(res.statusCode, 500)
844
+ t.same(JSON.parse(res.body), {
845
+ statusCode: 500,
846
+ error: 'Internal Server Error',
847
+ message: 'req.unsignCookie is not a function'
848
+ })
849
+ })
@@ -2,33 +2,67 @@
2
2
 
3
3
  const { test } = require('tap')
4
4
  const sinon = require('sinon')
5
- const cookieSignature = require('cookie-signature')
6
- const signerFactory = require('../signer')
5
+ const crypto = require('crypto')
6
+ const { Signer, sign, unsign } = require('../signer')
7
7
 
8
- test('default', (t) => {
9
- t.plan(2)
8
+ test('default', t => {
9
+ t.plan(5)
10
10
 
11
11
  const secret = 'my-secret'
12
- const signer = signerFactory(secret)
12
+ const signer = Signer(secret)
13
13
 
14
- t.test('signer.sign', (t) => {
14
+ t.test('signer.sign should throw if there is no value provided', (t) => {
15
15
  t.plan(1)
16
16
 
17
+ t.throws(() => signer.sign(undefined), 'Cookie value must be provided as a string.')
18
+ })
19
+
20
+ t.test('signer.sign', (t) => {
21
+ t.plan(2)
22
+
17
23
  const input = 'some-value'
18
24
  const result = signer.sign(input)
19
25
 
20
- t.equal(result, cookieSignature.sign(input, secret))
26
+ t.equal(result, sign(input, secret))
27
+ t.throws(() => sign(undefined), 'Cookie value must be provided as a string.')
21
28
  })
22
29
 
23
- t.test('signer.unsign', (t) => {
30
+ t.test('sign', (t) => {
24
31
  t.plan(3)
25
32
 
26
- const input = cookieSignature.sign('some-value', secret)
33
+ const input = 'some-value'
34
+ const result = signer.sign(input)
35
+
36
+ t.equal(result, sign(input, secret))
37
+ t.equal(result, sign(input, [secret]))
38
+
39
+ t.throws(() => sign(undefined), 'Cookie value must be provided as a string.')
40
+ })
41
+
42
+ t.test('signer.unsign', (t) => {
43
+ t.plan(4)
44
+
45
+ const input = signer.sign('some-value', secret)
27
46
  const result = signer.unsign(input)
28
47
 
29
48
  t.equal(result.valid, true)
30
49
  t.equal(result.renew, false)
31
50
  t.equal(result.value, 'some-value')
51
+ t.throws(() => signer.unsign(undefined), 'Signed cookie string must be provided.')
52
+ })
53
+
54
+ t.test('unsign', (t) => {
55
+ t.plan(6)
56
+
57
+ const input = sign('some-value', secret)
58
+ const result = unsign(input, secret)
59
+
60
+ t.equal(result.valid, true)
61
+ t.equal(result.renew, false)
62
+ t.equal(result.value, 'some-value')
63
+ t.same(result, unsign(input, [secret]))
64
+ t.throws(() => unsign(undefined), 'Secret key must be a string.')
65
+ t.throws(() => unsign(undefined, secret), 'Signed cookie string must be provided.')
32
66
  })
33
67
  })
34
68
 
@@ -37,11 +71,11 @@ test('key rotation', (t) => {
37
71
  const secret1 = 'my-secret-1'
38
72
  const secret2 = 'my-secret-2'
39
73
  const secret3 = 'my-secret-3'
40
- const signer = signerFactory([secret1, secret2, secret3])
41
- const unsignSpy = sinon.spy(cookieSignature, 'unsign')
74
+ const signer = Signer([secret1, secret2, secret3])
75
+ const signSpy = sinon.spy(crypto, 'createHmac')
42
76
 
43
77
  t.beforeEach(() => {
44
- unsignSpy.resetHistory()
78
+ signSpy.resetHistory()
45
79
  })
46
80
 
47
81
  t.test('signer.sign always signs using first key', (t) => {
@@ -50,30 +84,51 @@ test('key rotation', (t) => {
50
84
  const input = 'some-value'
51
85
  const result = signer.sign(input)
52
86
 
53
- t.equal(result, cookieSignature.sign(input, secret1))
87
+ t.equal(result, sign(input, secret1))
54
88
  })
55
89
 
56
90
  t.test('signer.unsign tries to decode using all keys till it finds', (t) => {
57
91
  t.plan(4)
58
92
 
59
- const input = cookieSignature.sign('some-value', secret2)
93
+ const input = sign('some-value', secret2)
94
+ signSpy.resetHistory()
60
95
  const result = signer.unsign(input)
61
96
 
62
97
  t.equal(result.valid, true)
63
98
  t.equal(result.renew, true)
64
99
  t.equal(result.value, 'some-value')
65
- t.equal(unsignSpy.callCount, 2) // should have returned early when the right key was found
100
+ t.equal(signSpy.callCount, 2) // should have returned early when the right key was found
66
101
  })
67
102
 
68
103
  t.test('signer.unsign failure response', (t) => {
69
104
  t.plan(4)
70
105
 
71
- const input = cookieSignature.sign('some-value', 'invalid-secret')
106
+ const input = sign('some-value', 'invalid-secret')
107
+ signSpy.resetHistory()
72
108
  const result = signer.unsign(input)
73
109
 
74
110
  t.equal(result.valid, false)
75
111
  t.equal(result.renew, false)
76
112
  t.equal(result.value, null)
77
- t.equal(unsignSpy.callCount, 3) // should have tried all 3
113
+ t.equal(signSpy.callCount, 3) // should have tried all 3
114
+ })
115
+ })
116
+
117
+ test('Signer', t => {
118
+ t.plan(2)
119
+
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.')
124
+ t.doesNotThrow(() => Signer('secret'))
125
+ t.doesNotThrow(() => Signer(['secret']))
126
+ })
127
+
128
+ t.test('Signer handles algorithm properly', (t) => {
129
+ t.plan(3)
130
+ t.throws(() => Signer('secret', 'invalid'), 'Algorithm invalid not supported.')
131
+ t.doesNotThrow(() => Signer('secret', 'sha512'))
132
+ t.doesNotThrow(() => Signer('secret', 'sha256'))
78
133
  })
79
134
  })
@@ -0,0 +1,169 @@
1
+ /// <reference types='node' />
2
+
3
+ import { FastifyPluginCallback } from "fastify";
4
+
5
+ declare module "fastify" {
6
+ interface FastifyInstance extends SignerMethods {
7
+ /**
8
+ * Manual cookie parsing method
9
+ * @docs https://github.com/fastify/fastify-cookie#manual-cookie-parsing
10
+ * @param cookieHeader Raw cookie header value
11
+ */
12
+ parseCookie(cookieHeader: string): {
13
+ [key: string]: string;
14
+ };
15
+ }
16
+
17
+ interface FastifyRequest extends SignerMethods {
18
+ /**
19
+ * Request cookies
20
+ */
21
+ cookies: { [cookieName: string]: string | undefined };
22
+ }
23
+
24
+ interface FastifyReply extends SignerMethods {
25
+ /**
26
+ * Request cookies
27
+ */
28
+ cookies: { [cookieName: string]: string | undefined };
29
+ }
30
+
31
+ interface SignerMethods {
32
+ /**
33
+ * Signs the specified cookie using the secret/signer provided.
34
+ * @param value cookie value
35
+ */
36
+ signCookie(value: string): string;
37
+
38
+ /**
39
+ * Unsigns the specified cookie using the secret/signer provided.
40
+ * @param value Cookie value
41
+ */
42
+ unsignCookie(value: string): fastifyCookie.UnsignResult;
43
+ }
44
+
45
+ export type setCookieWrapper = (
46
+ name: string,
47
+ value: string,
48
+ options?: fastifyCookie.CookieSerializeOptions
49
+ ) => FastifyReply;
50
+
51
+ interface FastifyReply {
52
+ /**
53
+ * Set response cookie
54
+ * @name setCookie
55
+ * @param name Cookie name
56
+ * @param value Cookie value
57
+ * @param options Serialize options
58
+ */
59
+ setCookie(
60
+ name: string,
61
+ value: string,
62
+ options?: fastifyCookie.CookieSerializeOptions
63
+ ): this;
64
+
65
+ /**
66
+ * @alias setCookie
67
+ */
68
+ cookie(
69
+ name: string,
70
+ value: string,
71
+ options?: fastifyCookie.CookieSerializeOptions
72
+ ): this;
73
+
74
+ /**
75
+ * clear response cookie
76
+ * @param name Cookie name
77
+ * @param options Serialize options
78
+ */
79
+ clearCookie(
80
+ name: string,
81
+ options?: fastifyCookie.CookieSerializeOptions
82
+ ): this;
83
+
84
+ /**
85
+ * Unsigns the specified cookie using the secret provided.
86
+ * @param value Cookie value
87
+ */
88
+ unsignCookie(value: string): fastifyCookie.UnsignResult;
89
+ }
90
+ }
91
+
92
+ type FastifyCookiePlugin = FastifyPluginCallback<
93
+ NonNullable<fastifyCookie.FastifyCookieOptions>
94
+ >;
95
+
96
+ declare namespace fastifyCookie {
97
+ interface SignerBase {
98
+ sign: (value: string) => string;
99
+ unsign: (input: string) => UnsignResult;
100
+ }
101
+
102
+ export class Signer implements SignerBase {
103
+ constructor (secrets: string | Array<string>, algorithm?: string)
104
+ sign: (value: string) => string;
105
+ unsign: (input: string) => UnsignResult;
106
+ }
107
+
108
+ export interface CookieSerializeOptions {
109
+ /** The `Domain` attribute. */
110
+ domain?: string;
111
+ encode?(val: string): string;
112
+ /** The expiration `date` used for the `Expires` attribute. If both `expires` and `maxAge` are set, then `expires` is used. */
113
+ expires?: Date;
114
+ /** The `boolean` value of the `HttpOnly` attribute. Defaults to true. */
115
+ 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. */
117
+ maxAge?: number;
118
+ /** The `Path` attribute. Defaults to `/` (the root path). */
119
+ path?: string;
120
+ priority?: "low" | "medium" | "high";
121
+ /** A `boolean` or one of the `SameSite` string attributes. E.g.: `lax`, `node` or `strict`. */
122
+ sameSite?: 'lax' | 'none' | 'strict' | boolean;
123
+ /** 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. */
124
+ secure?: boolean | 'auto';
125
+ signed?: boolean;
126
+ }
127
+
128
+ export interface FastifyCookieOptions {
129
+ secret?: string | string[] | Signer;
130
+ parseOptions?: fastifyCookie.CookieSerializeOptions;
131
+ }
132
+
133
+ export type Sign = (value: string, secret: string, algorithm?: string) => string;
134
+ export type Unsign = (input: string, secret: string, algorithm?: string) => UnsignResult;
135
+ export type SignerFactory = (secrets: string | Array<string>, algorithm?: string) => SignerBase;
136
+
137
+ export interface UnsignResult {
138
+ valid: boolean;
139
+ renew: boolean;
140
+ value: string | null;
141
+ }
142
+
143
+ export const signerFactory: SignerFactory;
144
+ export const sign: Sign;
145
+ export const unsign: Unsign;
146
+
147
+ export interface FastifyCookie extends FastifyCookiePlugin {
148
+ signerFactory: SignerFactory;
149
+ Signer: Signer;
150
+ sign: Sign;
151
+ unsign: Unsign;
152
+ }
153
+
154
+ export const fastifyCookie: FastifyCookie;
155
+
156
+ export interface FastifyCookieOptions {
157
+ secret?: string | string[] | SignerBase;
158
+ algorithm?: string;
159
+ parseOptions?: CookieSerializeOptions;
160
+ }
161
+
162
+ export { fastifyCookie as default };
163
+ }
164
+
165
+ declare function fastifyCookie(
166
+ ...params: Parameters<FastifyCookiePlugin>
167
+ ): ReturnType<FastifyCookiePlugin>;
168
+
169
+ export = fastifyCookie;
@@ -1,4 +1,4 @@
1
- import cookie from '../plugin';
1
+ import cookie from '..';
2
2
  import { expectType } from 'tsd';
3
3
  import * as fastifyCookieStar from '..';
4
4
  import fastifyCookieCjsImport = require('..');
@@ -58,6 +58,16 @@ server.after((_err) => {
58
58
  .send({ hello: 'world' })
59
59
  );
60
60
  });
61
+
62
+ expectType<(value: string) => string>(server.signCookie)
63
+ expectType<(value: string) => fastifyCookieStar.UnsignResult>(server.unsignCookie)
64
+
65
+ server.get('/', (request, reply) => {
66
+ expectType<(value: string) => string>(request.signCookie)
67
+ expectType<(value: string) => string>(reply.signCookie)
68
+ expectType<(value: string) => fastifyCookieStar.UnsignResult>(request.unsignCookie)
69
+ expectType<(value: string) => fastifyCookieStar.UnsignResult>(reply.unsignCookie)
70
+ });
61
71
  });
62
72
 
63
73
  const serverWithHttp2 = fastify({ http2: true });
@@ -109,6 +119,10 @@ const appWithImplicitHttpSigned = fastify();
109
119
  appWithImplicitHttpSigned.register(cookie, {
110
120
  secret: 'testsecret',
111
121
  });
122
+ appWithImplicitHttpSigned.register(cookie, {
123
+ secret: 'testsecret',
124
+ algorithm: 'sha512'
125
+ });
112
126
  appWithImplicitHttpSigned.after(() => {
113
127
  server.get('/', (request, reply) => {
114
128
  appWithImplicitHttpSigned.unsignCookie(request.cookies.test!);
@@ -194,3 +208,9 @@ appWithCustomSigner.after(() => {
194
208
  reply.send({ hello: 'world' })
195
209
  })
196
210
  })
211
+
212
+ new fastifyCookieStar.Signer('secretString')
213
+ new fastifyCookieStar.Signer(['secretStringInArray'])
214
+ const signer = new fastifyCookieStar.Signer(['secretStringInArray'], 'sha256')
215
+ signer.sign('Lorem Ipsum')
216
+ signer.unsign('Lorem Ipsum')
package/plugin.d.ts DELETED
@@ -1,133 +0,0 @@
1
- /// <reference types='node' />
2
-
3
- import { FastifyPluginCallback } from 'fastify';
4
-
5
- declare module 'fastify' {
6
- interface FastifyInstance {
7
- /**
8
- * Unsigns the specified cookie using the secret provided.
9
- * @param value Cookie value
10
- */
11
- unsignCookie(value: string): {
12
- valid: boolean;
13
- renew: boolean;
14
- value: string | null;
15
- };
16
- /**
17
- * Manual cookie parsing method
18
- * @docs https://github.com/fastify/fastify-cookie#manual-cookie-parsing
19
- * @param cookieHeader Raw cookie header value
20
- */
21
- parseCookie(cookieHeader: string): {
22
- [key: string]: string;
23
- };
24
- /**
25
- * Manual cookie signing method
26
- * @docs https://github.com/fastify/fastify-cookie#manual-cookie-parsing
27
- * @param value cookie value
28
- */
29
- signCookie(value: string): string;
30
- }
31
-
32
- interface FastifyRequest {
33
- /**
34
- * Request cookies
35
- */
36
- cookies: { [cookieName: string]: string | undefined };
37
-
38
- /**
39
- * Unsigns the specified cookie using the secret provided.
40
- * @param value Cookie value
41
- */
42
- unsignCookie(value: string): {
43
- valid: boolean;
44
- renew: boolean;
45
- value: string | null;
46
- };
47
- }
48
-
49
- export type setCookieWrapper = (
50
- name: string,
51
- value: string,
52
- options?: CookieSerializeOptions
53
- ) => FastifyReply;
54
-
55
- interface FastifyReply {
56
- /**
57
- * Set response cookie
58
- * @name setCookie
59
- * @param name Cookie name
60
- * @param value Cookie value
61
- * @param options Serialize options
62
- */
63
- setCookie(
64
- name: string,
65
- value: string,
66
- options?: CookieSerializeOptions
67
- ): this;
68
-
69
- /**
70
- * @alias setCookie
71
- */
72
- cookie(name: string, value: string, options?: CookieSerializeOptions): this;
73
-
74
- /**
75
- * clear response cookie
76
- * @param name Cookie name
77
- * @param options Serialize options
78
- */
79
- clearCookie(name: string, options?: CookieSerializeOptions): this;
80
-
81
- /**
82
- * Unsigns the specified cookie using the secret provided.
83
- * @param value Cookie value
84
- */
85
- unsignCookie(value: string): {
86
- valid: boolean;
87
- renew: boolean;
88
- value: string | null;
89
- };
90
- }
91
- }
92
-
93
- export interface CookieSerializeOptions {
94
- domain?: string;
95
- encode?(val: string): string;
96
- expires?: Date;
97
- httpOnly?: boolean;
98
- maxAge?: number;
99
- path?: string;
100
- priority?: 'low' | 'medium' | 'high';
101
- sameSite?: boolean | 'lax' | 'strict' | 'none';
102
- secure?: boolean | 'auto';
103
- signed?: boolean;
104
- }
105
-
106
- export interface Signer {
107
- sign: (input: string) => string;
108
- unsign: (input: string) => {
109
- valid: boolean;
110
- renew: boolean;
111
- value: string | null;
112
- };
113
- }
114
-
115
- export interface FastifyCookieOptions {
116
- secret?: string | string[] | Signer;
117
- parseOptions?: CookieSerializeOptions;
118
- }
119
-
120
- export type Sign = (value: string, secret: string) => string;
121
- export type Unsign = (input: string, secret: string) => string | false;
122
- export type SignerFactory = (secret: string) => Signer;
123
-
124
- export interface FastifyCookie extends FastifyPluginCallback<NonNullable<FastifyCookieOptions>>{
125
- signerFactory: SignerFactory;
126
- sign: Sign;
127
- unsign: Unsign;
128
- }
129
-
130
- declare const fastifyCookie: FastifyCookie;
131
-
132
- export default fastifyCookie;
133
- export { fastifyCookie };