@fastify/cookie 7.2.0 → 7.4.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
@@ -99,7 +99,8 @@ with a value of `'foo'` on the cookie path `/`.
99
99
  + `name`: a string name for the cookie to be set
100
100
  + `value`: a string value for the cookie
101
101
  + `options`: an options object as described in the [cookie serialize][cs] documentation
102
- with a extra param "signed" for signed cookie
102
+ + `options.signed`: the cookie should be signed
103
+ + `options.secure`: if set to `true` it will set the Secure-flag. If it is set to `"auto"` Secure-flag is set when the connection is using tls.
103
104
 
104
105
  #### Securing the cookie
105
106
 
@@ -235,12 +236,12 @@ test('Request requires signed cookie', async () => {
235
236
 
236
237
  ### Manual signing/unsigning with low level utilities
237
238
 
238
- with signerFactory
239
+ with Signer
239
240
 
240
241
  ```js
241
- const { signerFactory } = require('@fastify/cookie');
242
+ const { Signer } = require('@fastify/cookie');
242
243
 
243
- const signer = signerFactory('secret');
244
+ const signer = new Signer('secret');
244
245
  const signedValue = signer.sign('test');
245
246
  const {valid, renew, value } = signer.unsign(signedValue);
246
247
  ```
@@ -248,10 +249,10 @@ const {valid, renew, value } = signer.unsign(signedValue);
248
249
  with sign/unsign utilities
249
250
 
250
251
  ```js
251
- const { sign, unsign } = require('@fastify/cookie');
252
+ const { fastifyCookie } = require('@fastify/cookie');
252
253
 
253
- const signedValue = sign('test', 'secret');
254
- const unsignedvalue = unsign(signedValue, 'secret');
254
+ const signedValue = fastifyCookie.sign('test', 'secret');
255
+ const unsignedvalue = fastifyCookie.unsign(signedValue, 'secret');
255
256
  ```
256
257
 
257
258
 
@@ -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,6 +1,6 @@
1
1
  {
2
2
  "name": "@fastify/cookie",
3
- "version": "7.2.0",
3
+ "version": "7.4.0",
4
4
  "description": "Plugin for fastify to add support for cookies",
5
5
  "main": "plugin.js",
6
6
  "types": "plugin.d.ts",
@@ -39,9 +39,10 @@
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",
44
+ "benchmark": "^2.1.4",
43
45
  "fastify": "^4.0.0-rc.2",
44
- "pre-commit": "^1.2.2",
45
46
  "sinon": "^14.0.0",
46
47
  "snazzy": "^9.0.0",
47
48
  "standard": "^17.0.0",
@@ -51,8 +52,7 @@
51
52
  },
52
53
  "dependencies": {
53
54
  "cookie": "^0.5.0",
54
- "cookie-signature": "^1.1.0",
55
- "fastify-plugin": "^3.0.1"
55
+ "fastify-plugin": "^4.0.0"
56
56
  },
57
57
  "tsd": {
58
58
  "directory": "test"
package/plugin.d.ts CHANGED
@@ -1,18 +1,14 @@
1
1
  /// <reference types='node' />
2
2
 
3
- import { FastifyPluginCallback } from 'fastify';
3
+ import { FastifyPluginCallback } from "fastify";
4
4
 
5
- declare module 'fastify' {
5
+ declare module "fastify" {
6
6
  interface FastifyInstance {
7
7
  /**
8
8
  * Unsigns the specified cookie using the secret provided.
9
9
  * @param value Cookie value
10
10
  */
11
- unsignCookie(value: string): {
12
- valid: boolean;
13
- renew: boolean;
14
- value: string | null;
15
- };
11
+ unsignCookie(value: string): fastifyCookie.UnsignResult;
16
12
  /**
17
13
  * Manual cookie parsing method
18
14
  * @docs https://github.com/fastify/fastify-cookie#manual-cookie-parsing
@@ -39,17 +35,13 @@ declare module 'fastify' {
39
35
  * Unsigns the specified cookie using the secret provided.
40
36
  * @param value Cookie value
41
37
  */
42
- unsignCookie(value: string): {
43
- valid: boolean;
44
- renew: boolean;
45
- value: string | null;
46
- };
38
+ unsignCookie(value: string): fastifyCookie.UnsignResult;
47
39
  }
48
40
 
49
41
  export type setCookieWrapper = (
50
42
  name: string,
51
43
  value: string,
52
- options?: CookieSerializeOptions
44
+ options?: fastifyCookie.CookieSerializeOptions
53
45
  ) => FastifyReply;
54
46
 
55
47
  interface FastifyReply {
@@ -63,65 +55,104 @@ declare module 'fastify' {
63
55
  setCookie(
64
56
  name: string,
65
57
  value: string,
66
- options?: CookieSerializeOptions
58
+ options?: fastifyCookie.CookieSerializeOptions
67
59
  ): this;
68
60
 
69
61
  /**
70
62
  * @alias setCookie
71
63
  */
72
- cookie(name: string, value: string, options?: CookieSerializeOptions): this;
64
+ cookie(
65
+ name: string,
66
+ value: string,
67
+ options?: fastifyCookie.CookieSerializeOptions
68
+ ): this;
73
69
 
74
70
  /**
75
71
  * clear response cookie
76
72
  * @param name Cookie name
77
73
  * @param options Serialize options
78
74
  */
79
- clearCookie(name: string, options?: CookieSerializeOptions): this;
75
+ clearCookie(
76
+ name: string,
77
+ options?: fastifyCookie.CookieSerializeOptions
78
+ ): this;
80
79
 
81
80
  /**
82
81
  * Unsigns the specified cookie using the secret provided.
83
82
  * @param value Cookie value
84
83
  */
85
- unsignCookie(value: string): {
86
- valid: boolean;
87
- renew: boolean;
88
- value: string | null;
89
- };
84
+ unsignCookie(value: string): fastifyCookie.UnsignResult;
90
85
  }
91
86
  }
92
87
 
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;
103
- signed?: boolean;
104
- }
88
+ type FastifyCookiePlugin = FastifyPluginCallback<
89
+ NonNullable<fastifyCookie.FastifyCookieOptions>
90
+ >;
91
+
92
+ declare namespace fastifyCookie {
93
+ interface SignerBase {
94
+ sign: (value: string) => string;
95
+ unsign: (input: string) => UnsignResult;
96
+ }
97
+
98
+ export class Signer implements SignerBase {
99
+ constructor (secrets: string | Array<string>, algorithm?: string)
100
+ sign: (value: string) => string;
101
+ unsign: (input: string) => UnsignResult;
102
+ }
105
103
 
106
- interface Signer {
107
- sign: (input: string) => string;
108
- unsign: (input: string) => {
104
+ export interface CookieSerializeOptions {
105
+ domain?: string;
106
+ encode?(val: string): string;
107
+ expires?: Date;
108
+ httpOnly?: boolean;
109
+ maxAge?: number;
110
+ path?: string;
111
+ priority?: "low" | "medium" | "high";
112
+ sameSite?: boolean | "lax" | "strict" | "none";
113
+ secure?: boolean;
114
+ signed?: boolean;
115
+ }
116
+
117
+ export interface FastifyCookieOptions {
118
+ secret?: string | string[] | Signer;
119
+ parseOptions?: fastifyCookie.CookieSerializeOptions;
120
+ }
121
+
122
+ export type Sign = (value: string, secret: string, algorithm?: string) => string;
123
+ export type Unsign = (input: string, secret: string, algorithm?: string) => UnsignResult;
124
+ export type SignerFactory = (secrets: string | Array<string>, algorithm?: string) => SignerBase;
125
+
126
+ export interface UnsignResult {
109
127
  valid: boolean;
110
128
  renew: boolean;
111
129
  value: string | null;
112
- };
113
- }
130
+ }
131
+
132
+ export const signerFactory: SignerFactory;
133
+ export const sign: Sign;
134
+ export const unsign: Unsign;
135
+
136
+ export interface FastifyCookie extends FastifyCookiePlugin {
137
+ signerFactory: SignerFactory;
138
+ Signer: Signer;
139
+ sign: Sign;
140
+ unsign: Unsign;
141
+ }
142
+
143
+ export const fastifyCookie: FastifyCookie;
114
144
 
115
- declare const signerFactory: Signer;
116
- declare const sign: (value: string, secret: string) => string;
117
- declare const unsign: (input: string, secret: string) => string | false;
145
+ export interface FastifyCookieOptions {
146
+ secret?: string | string[] | SignerBase;
147
+ algorithm?: string;
148
+ parseOptions?: CookieSerializeOptions;
149
+ }
118
150
 
119
- export interface FastifyCookieOptions {
120
- secret?: string | string[] | Signer;
121
- parseOptions?: CookieSerializeOptions;
151
+ export { fastifyCookie as default };
122
152
  }
123
153
 
124
- declare const fastifyCookie: FastifyPluginCallback<NonNullable<FastifyCookieOptions>>;
154
+ declare function fastifyCookie(
155
+ ...params: Parameters<FastifyCookiePlugin>
156
+ ): ReturnType<FastifyCookiePlugin>;
125
157
 
126
- export default fastifyCookie;
127
- export { fastifyCookie, signerFactory, sign, unsign };
158
+ export = fastifyCookie;
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)
@@ -16,6 +15,15 @@ function fastifyCookieSetCookie (reply, name, value, options, signer) {
16
15
  value = signer.sign(value)
17
16
  }
18
17
 
18
+ if (opts.secure === 'auto') {
19
+ if (isConnectionSecure(reply.request)) {
20
+ opts.secure = true
21
+ } else {
22
+ opts.sameSite = 'lax'
23
+ opts.secure = false
24
+ }
25
+ }
26
+
19
27
  const serialized = cookie.serialize(name, value, opts)
20
28
  let setCookie = reply.getHeader('Set-Cookie')
21
29
  if (!setCookie) {
@@ -56,7 +64,8 @@ function onReqHandlerWrapper (fastify) {
56
64
  function plugin (fastify, options, next) {
57
65
  const secret = options.secret || ''
58
66
  const enableRotation = Array.isArray(secret)
59
- 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
60
69
 
61
70
  fastify.decorate('parseCookie', parseCookie)
62
71
  fastify.decorate('signCookie', signCookie)
@@ -96,6 +105,13 @@ function plugin (fastify, options, next) {
96
105
  }
97
106
  }
98
107
 
108
+ function isConnectionSecure (request) {
109
+ return (
110
+ request.raw.socket?.encrypted === true ||
111
+ request.headers['x-forwarded-proto'] === 'https'
112
+ )
113
+ }
114
+
99
115
  const fastifyCookie = fp(plugin, {
100
116
  fastify: '4.x',
101
117
  name: '@fastify/cookie'
@@ -111,14 +127,17 @@ const fastifyCookie = fp(plugin, {
111
127
  * - `import { fastifyCookie } from 'fastify-cookie'`
112
128
  * - `import fastifyCookie from 'fastify-cookie'`
113
129
  */
130
+ fastifyCookie.signerFactory = Signer
114
131
  fastifyCookie.fastifyCookie = fastifyCookie
115
132
  fastifyCookie.default = fastifyCookie
116
133
  module.exports = fastifyCookie
117
134
 
118
- fastifyCookie.fastifyCookie.signerFactory = signerFactory
135
+ fastifyCookie.fastifyCookie.signerFactory = Signer
136
+ fastifyCookie.fastifyCookie.Signer = Signer
119
137
  fastifyCookie.fastifyCookie.sign = sign
120
138
  fastifyCookie.fastifyCookie.unsign = unsign
121
139
 
122
- module.exports.signerFactory = signerFactory
140
+ module.exports.signerFactory = Signer
141
+ module.exports.Signer = Signer
123
142
  module.exports.sign = sign
124
143
  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)
@@ -776,3 +776,41 @@ test('create signed cookie manually using signCookie decorator', async (t) => {
776
776
  t.equal(res.statusCode, 200)
777
777
  t.same(JSON.parse(res.body), { unsigned: { value: 'bar', renew: false, valid: true } })
778
778
  })
779
+
780
+ test('handle secure:auto of cookieOptions', async (t) => {
781
+ const fastify = Fastify()
782
+
783
+ await fastify.register(plugin)
784
+
785
+ fastify.get('/test1', (req, reply) => {
786
+ reply
787
+ .setCookie('foo', 'foo', { path: '/', secure: 'auto' })
788
+ .send()
789
+ })
790
+
791
+ const res = await fastify.inject({
792
+ method: 'GET',
793
+ url: '/test1',
794
+ headers: { 'x-forwarded-proto': 'https' }
795
+ })
796
+
797
+ const cookies = res.cookies
798
+ t.equal(cookies.length, 1)
799
+ t.equal(cookies[0].name, 'foo')
800
+ t.equal(cookies[0].value, 'foo')
801
+ t.equal(cookies[0].secure, true)
802
+ t.equal(cookies[0].path, '/')
803
+
804
+ const res2 = await fastify.inject({
805
+ method: 'GET',
806
+ url: '/test1'
807
+ })
808
+
809
+ const cookies2 = res2.cookies
810
+ t.equal(cookies2.length, 1)
811
+ t.equal(cookies2[0].name, 'foo')
812
+ t.equal(cookies2[0].value, 'foo')
813
+ t.equal(cookies2[0].sameSite, 'Lax')
814
+ t.same(cookies2[0].secure, null)
815
+ t.equal(cookies2[0].path, '/')
816
+ })
@@ -1,13 +1,10 @@
1
- import fastify, { FastifyInstance, FastifyPluginCallback, FastifyReply, setCookieWrapper } from 'fastify';
2
- import { Server } from 'http';
1
+ import cookie from '../plugin';
3
2
  import { expectType } from 'tsd';
4
3
  import * as fastifyCookieStar from '..';
5
- import fastifyCookieDefault, {
6
- fastifyCookie as fastifyCookieNamed
7
- } from '..';
8
- import cookie, { FastifyCookieOptions } from '../plugin';
9
-
10
4
  import fastifyCookieCjsImport = require('..');
5
+ import fastifyCookieDefault, { fastifyCookie as fastifyCookieNamed } from '..';
6
+ import fastify, { FastifyInstance, FastifyReply, setCookieWrapper } from 'fastify';
7
+
11
8
  const fastifyCookieCjs = require('..');
12
9
 
13
10
  const app: FastifyInstance = fastify();
@@ -19,16 +16,24 @@ app.register(fastifyCookieCjsImport.fastifyCookie);
19
16
  app.register(fastifyCookieStar.default);
20
17
  app.register(fastifyCookieStar.fastifyCookie);
21
18
 
22
- expectType<FastifyPluginCallback<FastifyCookieOptions, Server>>(fastifyCookieNamed);
23
- expectType<FastifyPluginCallback<FastifyCookieOptions, Server>>(fastifyCookieDefault);
24
- expectType<FastifyPluginCallback<FastifyCookieOptions, Server>>(fastifyCookieCjsImport.default);
25
- expectType<FastifyPluginCallback<FastifyCookieOptions, Server>>(fastifyCookieCjsImport.fastifyCookie);
26
- expectType<FastifyPluginCallback<FastifyCookieOptions, Server>>(fastifyCookieStar.default);
27
- expectType<FastifyPluginCallback<FastifyCookieOptions, Server>>(
19
+ expectType<fastifyCookieStar.FastifyCookie>(fastifyCookieNamed);
20
+ expectType<fastifyCookieStar.FastifyCookie>(fastifyCookieDefault);
21
+ expectType<fastifyCookieStar.FastifyCookie>(fastifyCookieCjsImport.default);
22
+ expectType<fastifyCookieStar.FastifyCookie>(fastifyCookieCjsImport.fastifyCookie);
23
+ expectType<fastifyCookieStar.FastifyCookie>(fastifyCookieStar.default);
24
+ expectType<fastifyCookieStar.FastifyCookie>(
28
25
  fastifyCookieStar.fastifyCookie
29
26
  );
30
27
  expectType<any>(fastifyCookieCjs);
31
28
 
29
+ expectType<fastifyCookieStar.Sign>(fastifyCookieDefault.sign);
30
+ expectType<fastifyCookieStar.Unsign>(fastifyCookieDefault.unsign);
31
+ expectType<fastifyCookieStar.SignerFactory >(fastifyCookieDefault.signerFactory );
32
+
33
+ expectType<fastifyCookieStar.Sign>(fastifyCookieNamed.sign);
34
+ expectType<fastifyCookieStar.Unsign>(fastifyCookieNamed.unsign);
35
+ expectType<fastifyCookieStar.SignerFactory >(fastifyCookieNamed.signerFactory );
36
+
32
37
  const server = fastify();
33
38
 
34
39
  server.register(cookie);
@@ -104,6 +109,10 @@ const appWithImplicitHttpSigned = fastify();
104
109
  appWithImplicitHttpSigned.register(cookie, {
105
110
  secret: 'testsecret',
106
111
  });
112
+ appWithImplicitHttpSigned.register(cookie, {
113
+ secret: 'testsecret',
114
+ algorithm: 'sha512'
115
+ });
107
116
  appWithImplicitHttpSigned.after(() => {
108
117
  server.get('/', (request, reply) => {
109
118
  appWithImplicitHttpSigned.unsignCookie(request.cookies.test!);
@@ -189,3 +198,9 @@ appWithCustomSigner.after(() => {
189
198
  reply.send({ hello: 'world' })
190
199
  })
191
200
  })
201
+
202
+ new fastifyCookieStar.Signer('secretString')
203
+ new fastifyCookieStar.Signer(['secretStringInArray'])
204
+ const signer = new fastifyCookieStar.Signer(['secretStringInArray'], 'sha256')
205
+ signer.sign('Lorem Ipsum')
206
+ signer.unsign('Lorem Ipsum')
@@ -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
  })