@fastify/cookie 7.3.1 → 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.
- package/.github/workflows/ci.yml +1 -0
- package/README.md +3 -3
- package/benchmark/signer-multi.js +18 -0
- package/benchmark/signer.js +17 -0
- package/package.json +3 -3
- package/plugin.d.ts +25 -27
- package/plugin.js +8 -5
- package/signer.js +108 -24
- package/test/cookie.test.js +12 -12
- package/test/plugin.test-d.ts +10 -0
- package/test/signer.test.js +72 -17
package/.github/workflows/ci.yml
CHANGED
package/README.md
CHANGED
|
@@ -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
|
|
239
|
+
with Signer
|
|
240
240
|
|
|
241
241
|
```js
|
|
242
|
-
const {
|
|
242
|
+
const { Signer } = require('@fastify/cookie');
|
|
243
243
|
|
|
244
|
-
const signer =
|
|
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,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fastify/cookie",
|
|
3
|
-
"version": "7.
|
|
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,7 +52,6 @@
|
|
|
51
52
|
},
|
|
52
53
|
"dependencies": {
|
|
53
54
|
"cookie": "^0.5.0",
|
|
54
|
-
"cookie-signature": "^1.1.0",
|
|
55
55
|
"fastify-plugin": "^4.0.0"
|
|
56
56
|
},
|
|
57
57
|
"tsd": {
|
package/plugin.d.ts
CHANGED
|
@@ -8,11 +8,7 @@ declare module "fastify" {
|
|
|
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,11 +35,7 @@ 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 = (
|
|
@@ -89,11 +81,7 @@ declare module "fastify" {
|
|
|
89
81
|
* Unsigns the specified cookie using the secret provided.
|
|
90
82
|
* @param value Cookie value
|
|
91
83
|
*/
|
|
92
|
-
unsignCookie(value: string):
|
|
93
|
-
valid: boolean;
|
|
94
|
-
renew: boolean;
|
|
95
|
-
value: string | null;
|
|
96
|
-
};
|
|
84
|
+
unsignCookie(value: string): fastifyCookie.UnsignResult;
|
|
97
85
|
}
|
|
98
86
|
}
|
|
99
87
|
|
|
@@ -102,13 +90,15 @@ type FastifyCookiePlugin = FastifyPluginCallback<
|
|
|
102
90
|
>;
|
|
103
91
|
|
|
104
92
|
declare namespace fastifyCookie {
|
|
105
|
-
|
|
106
|
-
sign: (
|
|
107
|
-
unsign: (input: string) =>
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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;
|
|
112
102
|
}
|
|
113
103
|
|
|
114
104
|
export interface CookieSerializeOptions {
|
|
@@ -129,9 +119,15 @@ declare namespace fastifyCookie {
|
|
|
129
119
|
parseOptions?: fastifyCookie.CookieSerializeOptions;
|
|
130
120
|
}
|
|
131
121
|
|
|
132
|
-
export type Sign = (value: string, secret: string) => string;
|
|
133
|
-
export type Unsign = (input: string, secret: string
|
|
134
|
-
export type SignerFactory = (
|
|
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 {
|
|
127
|
+
valid: boolean;
|
|
128
|
+
renew: boolean;
|
|
129
|
+
value: string | null;
|
|
130
|
+
}
|
|
135
131
|
|
|
136
132
|
export const signerFactory: SignerFactory;
|
|
137
133
|
export const sign: Sign;
|
|
@@ -139,6 +135,7 @@ declare namespace fastifyCookie {
|
|
|
139
135
|
|
|
140
136
|
export interface FastifyCookie extends FastifyCookiePlugin {
|
|
141
137
|
signerFactory: SignerFactory;
|
|
138
|
+
Signer: Signer;
|
|
142
139
|
sign: Sign;
|
|
143
140
|
unsign: Unsign;
|
|
144
141
|
}
|
|
@@ -146,7 +143,8 @@ declare namespace fastifyCookie {
|
|
|
146
143
|
export const fastifyCookie: FastifyCookie;
|
|
147
144
|
|
|
148
145
|
export interface FastifyCookieOptions {
|
|
149
|
-
secret?: string | string[] |
|
|
146
|
+
secret?: string | string[] | SignerBase;
|
|
147
|
+
algorithm?: string;
|
|
150
148
|
parseOptions?: CookieSerializeOptions;
|
|
151
149
|
}
|
|
152
150
|
|
|
@@ -157,4 +155,4 @@ declare function fastifyCookie(
|
|
|
157
155
|
...params: Parameters<FastifyCookiePlugin>
|
|
158
156
|
): ReturnType<FastifyCookiePlugin>;
|
|
159
157
|
|
|
160
|
-
export = fastifyCookie;
|
|
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
|
|
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)
|
|
@@ -65,7 +64,8 @@ function onReqHandlerWrapper (fastify) {
|
|
|
65
64
|
function plugin (fastify, options, next) {
|
|
66
65
|
const secret = options.secret || ''
|
|
67
66
|
const enableRotation = Array.isArray(secret)
|
|
68
|
-
const
|
|
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
71
|
fastify.decorate('signCookie', signCookie)
|
|
@@ -127,14 +127,17 @@ const fastifyCookie = fp(plugin, {
|
|
|
127
127
|
* - `import { fastifyCookie } from 'fastify-cookie'`
|
|
128
128
|
* - `import fastifyCookie from 'fastify-cookie'`
|
|
129
129
|
*/
|
|
130
|
+
fastifyCookie.signerFactory = Signer
|
|
130
131
|
fastifyCookie.fastifyCookie = fastifyCookie
|
|
131
132
|
fastifyCookie.default = fastifyCookie
|
|
132
133
|
module.exports = fastifyCookie
|
|
133
134
|
|
|
134
|
-
fastifyCookie.fastifyCookie.signerFactory =
|
|
135
|
+
fastifyCookie.fastifyCookie.signerFactory = Signer
|
|
136
|
+
fastifyCookie.fastifyCookie.Signer = Signer
|
|
135
137
|
fastifyCookie.fastifyCookie.sign = sign
|
|
136
138
|
fastifyCookie.fastifyCookie.unsign = unsign
|
|
137
139
|
|
|
138
|
-
module.exports.signerFactory =
|
|
140
|
+
module.exports.signerFactory = Signer
|
|
141
|
+
module.exports.Signer = Signer
|
|
139
142
|
module.exports.sign = sign
|
|
140
143
|
module.exports.unsign = unsign
|
package/signer.js
CHANGED
|
@@ -1,32 +1,116 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
|
|
6
|
-
const secrets = Array.isArray(secret) ? secret : [secret]
|
|
7
|
-
const [signingKey] = secrets
|
|
9
|
+
const crypto = require('crypto')
|
|
8
10
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
-
|
|
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
|
package/test/cookie.test.js
CHANGED
|
@@ -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
|
|
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,
|
|
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,
|
|
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.
|
|
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.
|
|
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=${
|
|
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=${
|
|
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=${
|
|
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=${
|
|
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=${
|
|
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=${
|
|
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=${
|
|
584
|
+
cookie: `foo=${sign('foo', 'invalid-secret')}`
|
|
585
585
|
}
|
|
586
586
|
}, (err, res) => {
|
|
587
587
|
t.error(err)
|
package/test/plugin.test-d.ts
CHANGED
|
@@ -109,6 +109,10 @@ const appWithImplicitHttpSigned = fastify();
|
|
|
109
109
|
appWithImplicitHttpSigned.register(cookie, {
|
|
110
110
|
secret: 'testsecret',
|
|
111
111
|
});
|
|
112
|
+
appWithImplicitHttpSigned.register(cookie, {
|
|
113
|
+
secret: 'testsecret',
|
|
114
|
+
algorithm: 'sha512'
|
|
115
|
+
});
|
|
112
116
|
appWithImplicitHttpSigned.after(() => {
|
|
113
117
|
server.get('/', (request, reply) => {
|
|
114
118
|
appWithImplicitHttpSigned.unsignCookie(request.cookies.test!);
|
|
@@ -194,3 +198,9 @@ appWithCustomSigner.after(() => {
|
|
|
194
198
|
reply.send({ hello: 'world' })
|
|
195
199
|
})
|
|
196
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')
|
package/test/signer.test.js
CHANGED
|
@@ -2,33 +2,67 @@
|
|
|
2
2
|
|
|
3
3
|
const { test } = require('tap')
|
|
4
4
|
const sinon = require('sinon')
|
|
5
|
-
const
|
|
6
|
-
const
|
|
5
|
+
const crypto = require('crypto')
|
|
6
|
+
const { Signer, sign, unsign } = require('../signer')
|
|
7
7
|
|
|
8
|
-
test('default',
|
|
9
|
-
t.plan(
|
|
8
|
+
test('default', t => {
|
|
9
|
+
t.plan(5)
|
|
10
10
|
|
|
11
11
|
const secret = 'my-secret'
|
|
12
|
-
const signer =
|
|
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,
|
|
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('
|
|
30
|
+
t.test('sign', (t) => {
|
|
24
31
|
t.plan(3)
|
|
25
32
|
|
|
26
|
-
const input =
|
|
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 =
|
|
41
|
-
const
|
|
74
|
+
const signer = Signer([secret1, secret2, secret3])
|
|
75
|
+
const signSpy = sinon.spy(crypto, 'createHmac')
|
|
42
76
|
|
|
43
77
|
t.beforeEach(() => {
|
|
44
|
-
|
|
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,
|
|
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 =
|
|
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(
|
|
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 =
|
|
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(
|
|
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
|
})
|