@fastify/cookie
Advanced tools
Comparing version 7.3.1 to 7.4.0
{ | ||
"name": "@fastify/cookie", | ||
"version": "7.3.1", | ||
"version": "7.4.0", | ||
"description": "Plugin for fastify to add support for cookies", | ||
@@ -42,5 +42,6 @@ "main": "plugin.js", | ||
"devDependencies": { | ||
"@fastify/pre-commit": "^2.0.2", | ||
"@types/node": "^18.0.0", | ||
"benchmark": "^2.1.4", | ||
"fastify": "^4.0.0-rc.2", | ||
"pre-commit": "^1.2.2", | ||
"sinon": "^14.0.0", | ||
@@ -55,3 +56,2 @@ "snazzy": "^9.0.0", | ||
"cookie": "^0.5.0", | ||
"cookie-signature": "^1.1.0", | ||
"fastify-plugin": "^4.0.0" | ||
@@ -58,0 +58,0 @@ }, |
@@ -11,7 +11,3 @@ /// <reference types='node' /> | ||
*/ | ||
unsignCookie(value: string): { | ||
valid: boolean; | ||
renew: boolean; | ||
value: string | null; | ||
}; | ||
unsignCookie(value: string): fastifyCookie.UnsignResult; | ||
/** | ||
@@ -43,7 +39,3 @@ * Manual cookie parsing method | ||
*/ | ||
unsignCookie(value: string): { | ||
valid: boolean; | ||
renew: boolean; | ||
value: string | null; | ||
}; | ||
unsignCookie(value: string): fastifyCookie.UnsignResult; | ||
} | ||
@@ -94,7 +86,3 @@ | ||
*/ | ||
unsignCookie(value: string): { | ||
valid: boolean; | ||
renew: boolean; | ||
value: string | null; | ||
}; | ||
unsignCookie(value: string): fastifyCookie.UnsignResult; | ||
} | ||
@@ -108,11 +96,13 @@ } | ||
declare namespace fastifyCookie { | ||
export interface Signer { | ||
sign: (input: string) => string; | ||
unsign: (input: string) => { | ||
valid: boolean; | ||
renew: boolean; | ||
value: string | null; | ||
}; | ||
interface SignerBase { | ||
sign: (value: string) => string; | ||
unsign: (input: string) => UnsignResult; | ||
} | ||
export class Signer implements SignerBase { | ||
constructor (secrets: string | Array<string>, algorithm?: string) | ||
sign: (value: string) => string; | ||
unsign: (input: string) => UnsignResult; | ||
} | ||
export interface CookieSerializeOptions { | ||
@@ -136,6 +126,12 @@ domain?: string; | ||
export type Sign = (value: string, secret: string) => string; | ||
export type Unsign = (input: string, secret: string) => string | false; | ||
export type SignerFactory = (secret: string) => Signer; | ||
export type Sign = (value: string, secret: string, algorithm?: string) => string; | ||
export type Unsign = (input: string, secret: string, algorithm?: string) => UnsignResult; | ||
export type SignerFactory = (secrets: string | Array<string>, algorithm?: string) => SignerBase; | ||
export interface UnsignResult { | ||
valid: boolean; | ||
renew: boolean; | ||
value: string | null; | ||
} | ||
export const signerFactory: SignerFactory; | ||
@@ -147,2 +143,3 @@ export const sign: Sign; | ||
signerFactory: SignerFactory; | ||
Signer: Signer; | ||
sign: Sign; | ||
@@ -155,3 +152,4 @@ unsign: Unsign; | ||
export interface FastifyCookieOptions { | ||
secret?: string | string[] | Signer; | ||
secret?: string | string[] | SignerBase; | ||
algorithm?: string; | ||
parseOptions?: CookieSerializeOptions; | ||
@@ -167,2 +165,2 @@ } | ||
export = fastifyCookie; | ||
export = fastifyCookie; |
'use strict' | ||
const { sign, unsign } = require('cookie-signature') | ||
const fp = require('fastify-plugin') | ||
const cookie = require('cookie') | ||
const signerFactory = require('./signer') | ||
const { Signer, sign, unsign } = require('./signer') | ||
@@ -68,3 +67,4 @@ function fastifyCookieSetCookie (reply, name, value, options, signer) { | ||
const enableRotation = Array.isArray(secret) | ||
const signer = typeof secret === 'string' || enableRotation ? signerFactory(secret) : secret | ||
const algorithm = options.algorithm || 'sha256' | ||
const signer = typeof secret === 'string' || enableRotation ? new Signer(secret, algorithm) : secret | ||
@@ -131,2 +131,3 @@ fastify.decorate('parseCookie', parseCookie) | ||
*/ | ||
fastifyCookie.signerFactory = Signer | ||
fastifyCookie.fastifyCookie = fastifyCookie | ||
@@ -136,8 +137,10 @@ fastifyCookie.default = fastifyCookie | ||
fastifyCookie.fastifyCookie.signerFactory = signerFactory | ||
fastifyCookie.fastifyCookie.signerFactory = Signer | ||
fastifyCookie.fastifyCookie.Signer = Signer | ||
fastifyCookie.fastifyCookie.sign = sign | ||
fastifyCookie.fastifyCookie.unsign = unsign | ||
module.exports.signerFactory = signerFactory | ||
module.exports.signerFactory = Signer | ||
module.exports.Signer = Signer | ||
module.exports.sign = sign | ||
module.exports.unsign = unsign |
@@ -239,8 +239,8 @@ # @fastify/cookie | ||
with signerFactory | ||
with Signer | ||
```js | ||
const { fastifyCookie } = require('@fastify/cookie'); | ||
const { Signer } = require('@fastify/cookie'); | ||
const signer = fastifyCookie.signerFactory('secret'); | ||
const signer = new Signer('secret'); | ||
const signedValue = signer.sign('test'); | ||
@@ -247,0 +247,0 @@ const {valid, renew, value } = signer.unsign(signedValue); |
128
signer.js
'use strict' | ||
const cookieSignature = require('cookie-signature') | ||
// Inspired by node-cookie-signature | ||
// https://github.com/tj/node-cookie-signature | ||
// | ||
// The MIT License | ||
// Copyright (c) 2012–2022 LearnBoost <tj@learnboost.com> and other contributors; | ||
module.exports = function (secret) { | ||
const secrets = Array.isArray(secret) ? secret : [secret] | ||
const [signingKey] = secrets | ||
const crypto = require('crypto') | ||
return { | ||
sign (value) { | ||
return cookieSignature.sign(value, signingKey) | ||
}, | ||
unsign (signedValue) { | ||
let valid = false | ||
let renew = false | ||
let value = null | ||
const base64PaddingRE = /=/g | ||
for (const key of secrets) { | ||
const result = cookieSignature.unsign(signedValue, key) | ||
function Signer (secrets, algorithm = 'sha256') { | ||
if (!(this instanceof Signer)) { | ||
return new Signer(secrets, algorithm) | ||
} | ||
if (result !== false) { | ||
valid = true | ||
renew = key !== signingKey | ||
value = result | ||
break | ||
} | ||
} | ||
this.secrets = Array.isArray(secrets) ? secrets : [secrets] | ||
this.signingKey = this.secrets[0] | ||
this.algorithm = algorithm | ||
return { valid, renew, value } | ||
validateSecrets(this.secrets) | ||
validateAlgorithm(this.algorithm) | ||
} | ||
function validateSecrets (secrets) { | ||
for (const secret of secrets) { | ||
if (typeof secret !== 'string') { | ||
throw new TypeError('Secret key must be a string.') | ||
} | ||
} | ||
} | ||
function validateAlgorithm (algorithm) { | ||
// validate that the algorithm is supported by the node runtime | ||
try { | ||
crypto.createHmac(algorithm, 'dummyHmac') | ||
} catch (e) { | ||
throw new TypeError(`Algorithm ${algorithm} not supported.`) | ||
} | ||
} | ||
function _sign (value, secret, algorithm) { | ||
if (typeof value !== 'string') { | ||
throw new TypeError('Cookie value must be provided as a string.') | ||
} | ||
return value + '.' + crypto | ||
.createHmac(algorithm, secret) | ||
.update(value) | ||
.digest('base64') | ||
// remove base64 padding (=) as it has special meaning in cookies | ||
.replace(base64PaddingRE, '') | ||
} | ||
function _unsign (signedValue, secrets, algorithm) { | ||
if (typeof signedValue !== 'string') { | ||
throw new TypeError('Signed cookie string must be provided.') | ||
} | ||
const value = signedValue.slice(0, signedValue.lastIndexOf('.')) | ||
const actual = Buffer.from(signedValue.slice(signedValue.lastIndexOf('.') + 1)) | ||
for (const secret of secrets) { | ||
const expected = Buffer.from(crypto | ||
.createHmac(algorithm, secret) | ||
.update(value) | ||
.digest('base64') | ||
// remove base64 padding (=) as it has special meaning in cookies | ||
.replace(base64PaddingRE, '')) | ||
if ( | ||
expected.length === actual.length && | ||
crypto.timingSafeEqual(expected, actual) | ||
) { | ||
return { | ||
valid: true, | ||
renew: secret !== secrets[0], | ||
value | ||
} | ||
} | ||
} | ||
return { | ||
valid: false, | ||
renew: false, | ||
value: null | ||
} | ||
} | ||
Signer.prototype.sign = function (value) { | ||
return _sign(value, this.signingKey, this.algorithm) | ||
} | ||
Signer.prototype.unsign = function (signedValue) { | ||
return _unsign(signedValue, this.secrets, this.algorithm) | ||
} | ||
function sign (value, secret, algorithm = 'sha256') { | ||
const secrets = Array.isArray(secret) ? secret : [secret] | ||
validateSecrets(secrets) | ||
return _sign(value, secrets[0], algorithm) | ||
} | ||
function unsign (signedValue, secret, algorithm = 'sha256') { | ||
const secrets = Array.isArray(secret) ? secret : [secret] | ||
validateSecrets(secrets) | ||
return _unsign(signedValue, secrets, algorithm) | ||
} | ||
module.exports = Signer | ||
module.exports.signerFactory = Signer | ||
module.exports.Signer = Signer | ||
module.exports.sign = sign | ||
module.exports.unsign = unsign |
@@ -7,3 +7,3 @@ 'use strict' | ||
const sinon = require('sinon') | ||
const cookieSignature = require('cookie-signature') | ||
const { sign, unsign } = require('../signer') | ||
const plugin = require('../') | ||
@@ -155,3 +155,3 @@ | ||
t.equal(cookies[0].name, 'foo') | ||
t.equal(cookies[0].value, cookieSignature.sign('foo', secret)) | ||
t.equal(cookies[0].value, sign('foo', secret)) | ||
t.equal(cookies[0].maxAge, 36000) | ||
@@ -195,3 +195,3 @@ | ||
t.equal(cookies[0].name, 'foo') | ||
t.equal(cookies[0].value, cookieSignature.sign('foo', secret)) | ||
t.equal(cookies[0].value, sign('foo', secret)) | ||
const expires = new Date(cookies[0].expires) | ||
@@ -384,3 +384,3 @@ t.ok(expires < new Date(Date.now() + 5000)) | ||
t.equal(cookies[0].name, 'foo') | ||
t.equal(cookieSignature.unsign(cookies[0].value, secret), 'foo') | ||
t.same(unsign(cookies[0].value, secret), { valid: true, renew: false, value: 'foo' }) | ||
}) | ||
@@ -413,3 +413,3 @@ }) | ||
t.equal(cookies[0].name, 'foo') | ||
t.equal(cookieSignature.unsign(cookies[0].value, secret1), 'cookieVal') // decode using first key | ||
t.same(unsign(cookies[0].value, secret1), { valid: true, renew: false, value: 'cookieVal' }) // decode using first key | ||
}) | ||
@@ -435,3 +435,3 @@ }) | ||
headers: { | ||
cookie: `foo=${cookieSignature.sign('foo', secret)}` | ||
cookie: `foo=${sign('foo', secret)}` | ||
} | ||
@@ -461,3 +461,3 @@ }, (err, res) => { | ||
headers: { | ||
cookie: `foo=${cookieSignature.sign('foo', secret)}` | ||
cookie: `foo=${sign('foo', secret)}` | ||
} | ||
@@ -487,3 +487,3 @@ }, (err, res) => { | ||
headers: { | ||
cookie: `foo=${cookieSignature.sign('foo', secret)}` | ||
cookie: `foo=${sign('foo', secret)}` | ||
} | ||
@@ -514,3 +514,3 @@ }, (err, res) => { | ||
headers: { | ||
cookie: `foo=${cookieSignature.sign('foo', secret2)}` | ||
cookie: `foo=${sign('foo', secret2)}` | ||
} | ||
@@ -541,3 +541,3 @@ }, (err, res) => { | ||
headers: { | ||
cookie: `foo=${cookieSignature.sign('foo', secret2)}` | ||
cookie: `foo=${sign('foo', secret2)}` | ||
} | ||
@@ -568,3 +568,3 @@ }, (err, res) => { | ||
headers: { | ||
cookie: `foo=${cookieSignature.sign('foo', 'invalid-secret')}` | ||
cookie: `foo=${sign('foo', 'invalid-secret')}` | ||
} | ||
@@ -595,3 +595,3 @@ }, (err, res) => { | ||
headers: { | ||
cookie: `foo=${cookieSignature.sign('foo', 'invalid-secret')}` | ||
cookie: `foo=${sign('foo', 'invalid-secret')}` | ||
} | ||
@@ -598,0 +598,0 @@ }, (err, res) => { |
@@ -112,2 +112,6 @@ import cookie from '../plugin'; | ||
}); | ||
appWithImplicitHttpSigned.register(cookie, { | ||
secret: 'testsecret', | ||
algorithm: 'sha512' | ||
}); | ||
appWithImplicitHttpSigned.after(() => { | ||
@@ -198,1 +202,7 @@ server.get('/', (request, reply) => { | ||
}) | ||
new fastifyCookieStar.Signer('secretString') | ||
new fastifyCookieStar.Signer(['secretStringInArray']) | ||
const signer = new fastifyCookieStar.Signer(['secretStringInArray'], 'sha256') | ||
signer.sign('Lorem Ipsum') | ||
signer.unsign('Lorem Ipsum') |
@@ -5,24 +5,43 @@ 'use strict' | ||
const sinon = require('sinon') | ||
const cookieSignature = require('cookie-signature') | ||
const signerFactory = require('../signer') | ||
const crypto = require('crypto') | ||
const { Signer, sign, unsign } = require('../signer') | ||
test('default', (t) => { | ||
t.plan(2) | ||
test('default', t => { | ||
t.plan(5) | ||
const secret = 'my-secret' | ||
const signer = signerFactory(secret) | ||
const signer = Signer(secret) | ||
t.test('signer.sign', (t) => { | ||
t.test('signer.sign should throw if there is no value provided', (t) => { | ||
t.plan(1) | ||
t.throws(() => signer.sign(undefined), 'Cookie value must be provided as a string.') | ||
}) | ||
t.test('signer.sign', (t) => { | ||
t.plan(2) | ||
const input = 'some-value' | ||
const result = signer.sign(input) | ||
t.equal(result, cookieSignature.sign(input, secret)) | ||
t.equal(result, sign(input, secret)) | ||
t.throws(() => sign(undefined), 'Cookie value must be provided as a string.') | ||
}) | ||
t.test('signer.unsign', (t) => { | ||
t.test('sign', (t) => { | ||
t.plan(3) | ||
const input = cookieSignature.sign('some-value', secret) | ||
const input = 'some-value' | ||
const result = signer.sign(input) | ||
t.equal(result, sign(input, secret)) | ||
t.equal(result, sign(input, [secret])) | ||
t.throws(() => sign(undefined), 'Cookie value must be provided as a string.') | ||
}) | ||
t.test('signer.unsign', (t) => { | ||
t.plan(4) | ||
const input = signer.sign('some-value', secret) | ||
const result = signer.unsign(input) | ||
@@ -33,3 +52,18 @@ | ||
t.equal(result.value, 'some-value') | ||
t.throws(() => signer.unsign(undefined), 'Signed cookie string must be provided.') | ||
}) | ||
t.test('unsign', (t) => { | ||
t.plan(6) | ||
const input = sign('some-value', secret) | ||
const result = unsign(input, secret) | ||
t.equal(result.valid, true) | ||
t.equal(result.renew, false) | ||
t.equal(result.value, 'some-value') | ||
t.same(result, unsign(input, [secret])) | ||
t.throws(() => unsign(undefined), 'Secret key must be a string.') | ||
t.throws(() => unsign(undefined, secret), 'Signed cookie string must be provided.') | ||
}) | ||
}) | ||
@@ -42,7 +76,7 @@ | ||
const secret3 = 'my-secret-3' | ||
const signer = signerFactory([secret1, secret2, secret3]) | ||
const unsignSpy = sinon.spy(cookieSignature, 'unsign') | ||
const signer = Signer([secret1, secret2, secret3]) | ||
const signSpy = sinon.spy(crypto, 'createHmac') | ||
t.beforeEach(() => { | ||
unsignSpy.resetHistory() | ||
signSpy.resetHistory() | ||
}) | ||
@@ -56,3 +90,3 @@ | ||
t.equal(result, cookieSignature.sign(input, secret1)) | ||
t.equal(result, sign(input, secret1)) | ||
}) | ||
@@ -63,3 +97,4 @@ | ||
const input = cookieSignature.sign('some-value', secret2) | ||
const input = sign('some-value', secret2) | ||
signSpy.resetHistory() | ||
const result = signer.unsign(input) | ||
@@ -70,3 +105,3 @@ | ||
t.equal(result.value, 'some-value') | ||
t.equal(unsignSpy.callCount, 2) // should have returned early when the right key was found | ||
t.equal(signSpy.callCount, 2) // should have returned early when the right key was found | ||
}) | ||
@@ -77,3 +112,4 @@ | ||
const input = cookieSignature.sign('some-value', 'invalid-secret') | ||
const input = sign('some-value', 'invalid-secret') | ||
signSpy.resetHistory() | ||
const result = signer.unsign(input) | ||
@@ -84,4 +120,23 @@ | ||
t.equal(result.value, null) | ||
t.equal(unsignSpy.callCount, 3) // should have tried all 3 | ||
t.equal(signSpy.callCount, 3) // should have tried all 3 | ||
}) | ||
}) | ||
test('Signer', t => { | ||
t.plan(2) | ||
t.test('Signer needs a string as secret', (t) => { | ||
t.plan(4) | ||
t.throws(() => Signer(1), 'Secret key must be a string.') | ||
t.throws(() => Signer(undefined), 'Secret key must be a string.') | ||
t.doesNotThrow(() => Signer('secret')) | ||
t.doesNotThrow(() => Signer(['secret'])) | ||
}) | ||
t.test('Signer handles algorithm properly', (t) => { | ||
t.plan(3) | ||
t.throws(() => Signer('secret', 'invalid'), 'Algorithm invalid not supported.') | ||
t.doesNotThrow(() => Signer('secret', 'sha512')) | ||
t.doesNotThrow(() => Signer('secret', 'sha256')) | ||
}) | ||
}) |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
54549
2
16
1351
10
- Removedcookie-signature@^1.1.0
- Removedcookie-signature@1.2.1(transitive)