Skip to content

Commit

Permalink
feat: add support for Buffer secrets (#214) (#215)
Browse files Browse the repository at this point in the history
* Detect signers instead of secrets

* Allow Buffer as secret

* Add tests for Buffer secret

* Fix types

* Update README

* fix pr remarks

* Update signer.js

Co-authored-by: James Sumners <[email protected]>

Co-authored-by: Uzlopak <[email protected]>
Co-authored-by: Matteo Collina <[email protected]>
Co-authored-by: James Sumners <[email protected]>
  • Loading branch information
4 people authored Sep 25, 2022
1 parent e1394d0 commit 7667306
Show file tree
Hide file tree
Showing 6 changed files with 24 additions and 18 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,8 @@ app.register(cookie, {

## Options

- `secret` (`String` | `Array` | `Object`):
- A `String` can be passed to use as secret to sign the cookie using [`cookie-signature`](http://npm.im/cookie-signature).
- `secret` (`String` | `Array` | `Buffer` | `Object`):
- A `String` or `Buffer` can be passed to use as secret to sign the cookie using [`cookie-signature`](http://npm.im/cookie-signature).
- An `Array` can be passed if key rotation is desired. Read more about it in [Rotating signing secret](#rotating-secret).
- More sophisticated cookie signing mechanisms can be implemented by supplying an `Object`. Read more about it in [Custom cookie signer](#custom-cookie-signer).

Expand Down
4 changes: 2 additions & 2 deletions plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,9 @@ function plugin (fastify, options, next) {
if (hook === undefined) {
return next(new Error('@fastify/cookie: Invalid value provided for the hook-option. You can set the hook-option only to false, \'onRequest\' , \'preParsing\' , \'preValidation\' or \'preHandler\''))
}
const enableRotation = Array.isArray(secret)
const isSigner = !secret || (typeof secret.sign === 'function' && typeof secret.unsign === 'function')
const algorithm = options.algorithm || 'sha256'
const signer = typeof secret === 'string' || enableRotation ? new Signer(secret, algorithm) : secret
const signer = isSigner ? secret : new Signer(secret, algorithm)

fastify.decorate('parseCookie', parseCookie)

Expand Down
4 changes: 2 additions & 2 deletions signer.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ function Signer (secrets, algorithm = 'sha256') {

function validateSecrets (secrets) {
for (const secret of secrets) {
if (typeof secret !== 'string') {
throw new TypeError('Secret key must be a string.')
if (typeof secret !== 'string' && Buffer.isBuffer(secret) === false) {
throw new TypeError('Secret key must be a string or Buffer.')
}
}
}
Expand Down
16 changes: 10 additions & 6 deletions test/signer.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,15 @@ test('default', t => {
})

t.test('sign', (t) => {
t.plan(3)
t.plan(5)

const input = 'some-value'
const result = signer.sign(input)

t.equal(result, sign(input, secret))
t.equal(result, sign(input, [secret]))
t.equal(result, sign(input, Buffer.from(secret)))
t.equal(result, sign(input, [Buffer.from(secret)]))

t.throws(() => sign(undefined), 'Cookie value must be provided as a string.')
})
Expand All @@ -61,7 +63,7 @@ test('default', t => {
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 key must be a string or Buffer.')
t.throws(() => unsign(undefined, secret), 'Signed cookie string must be provided.')
})
})
Expand Down Expand Up @@ -117,12 +119,14 @@ test('key rotation', (t) => {
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.test('Signer needs a string or Buffer as secret', (t) => {
t.plan(6)
t.throws(() => Signer(1), 'Secret key must be a string or Buffer.')
t.throws(() => Signer(undefined), 'Secret key must be a string or Buffer.')
t.doesNotThrow(() => Signer('secret'))
t.doesNotThrow(() => Signer(['secret']))
t.doesNotThrow(() => Signer(Buffer.from('deadbeef76543210', 'hex')))
t.doesNotThrow(() => Signer([Buffer.from('deadbeef76543210', 'hex')]))
})

t.test('Signer handles algorithm properly', (t) => {
Expand Down
12 changes: 6 additions & 6 deletions types/plugin.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ declare namespace fastifyCookie {
}

export class Signer implements SignerBase {
constructor (secrets: string | Array<string>, algorithm?: string)
constructor (secrets: string | Array<string> | Buffer | Array<Buffer>, algorithm?: string)
sign: (value: string) => string;
unsign: (input: string) => UnsignResult;
}
Expand Down Expand Up @@ -128,14 +128,14 @@ declare namespace fastifyCookie {
type HookType = 'onRequest' | 'preParsing' | 'preValidation' | 'preHandler' | 'preSerialization';

export interface FastifyCookieOptions {
secret?: string | string[] | Signer;
secret?: string | string[] | Buffer | Buffer[] | Signer;
hook?: HookType | false;
parseOptions?: fastifyCookie.CookieSerializeOptions;
}

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 type Sign = (value: string, secret: string | Buffer, algorithm?: string) => string;
export type Unsign = (input: string, secret: string | Buffer, algorithm?: string) => UnsignResult;
export type SignerFactory = (secrets: string | string[] | Buffer | Buffer[], algorithm?: string) => SignerBase;

export interface UnsignResult {
valid: boolean;
Expand All @@ -157,7 +157,7 @@ declare namespace fastifyCookie {
export const fastifyCookie: FastifyCookie;

export interface FastifyCookieOptions {
secret?: string | string[] | SignerBase;
secret?: string | string[] | Buffer | Buffer[] | SignerBase;
algorithm?: string;
parseOptions?: CookieSerializeOptions;
}
Expand Down
2 changes: 2 additions & 0 deletions types/plugin.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,8 @@ appWithCustomSigner.after(() => {

new fastifyCookieStar.Signer('secretString')
new fastifyCookieStar.Signer(['secretStringInArray'])
new fastifyCookieStar.Signer(Buffer.from('secretString'))
new fastifyCookieStar.Signer([Buffer.from('secretStringInArray')])
const signer = new fastifyCookieStar.Signer(['secretStringInArray'], 'sha256')
signer.sign('Lorem Ipsum')
signer.unsign('Lorem Ipsum')
Expand Down

0 comments on commit 7667306

Please sign in to comment.