You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							135 lines
						
					
					
						
							3.5 KiB
						
					
					
				
			
		
		
	
	
							135 lines
						
					
					
						
							3.5 KiB
						
					
					
				'use strict'
 | 
						|
 | 
						|
const { test } = require('tap')
 | 
						|
const sinon = require('sinon')
 | 
						|
const crypto = require('crypto')
 | 
						|
const { Signer, sign, unsign } = require('../signer')
 | 
						|
 | 
						|
test('default', t => {
 | 
						|
  t.plan(5)
 | 
						|
 | 
						|
  const secret = 'my-secret'
 | 
						|
  const signer = Signer(secret)
 | 
						|
 | 
						|
  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, sign(input, secret))
 | 
						|
    t.throws(() => sign(undefined), 'Cookie value must be provided as a string.')
 | 
						|
  })
 | 
						|
 | 
						|
  t.test('sign', (t) => {
 | 
						|
    t.plan(3)
 | 
						|
 | 
						|
    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)
 | 
						|
 | 
						|
    t.equal(result.valid, true)
 | 
						|
    t.equal(result.renew, false)
 | 
						|
    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.')
 | 
						|
  })
 | 
						|
})
 | 
						|
 | 
						|
test('key rotation', (t) => {
 | 
						|
  t.plan(3)
 | 
						|
  const secret1 = 'my-secret-1'
 | 
						|
  const secret2 = 'my-secret-2'
 | 
						|
  const secret3 = 'my-secret-3'
 | 
						|
  const signer = Signer([secret1, secret2, secret3])
 | 
						|
  const signSpy = sinon.spy(crypto, 'createHmac')
 | 
						|
 | 
						|
  t.beforeEach(() => {
 | 
						|
    signSpy.resetHistory()
 | 
						|
  })
 | 
						|
 | 
						|
  t.test('signer.sign always signs using first key', (t) => {
 | 
						|
    t.plan(1)
 | 
						|
 | 
						|
    const input = 'some-value'
 | 
						|
    const result = signer.sign(input)
 | 
						|
 | 
						|
    t.equal(result, sign(input, secret1))
 | 
						|
  })
 | 
						|
 | 
						|
  t.test('signer.unsign tries to decode using all keys till it finds', (t) => {
 | 
						|
    t.plan(4)
 | 
						|
 | 
						|
    const input = sign('some-value', secret2)
 | 
						|
    signSpy.resetHistory()
 | 
						|
    const result = signer.unsign(input)
 | 
						|
 | 
						|
    t.equal(result.valid, true)
 | 
						|
    t.equal(result.renew, true)
 | 
						|
    t.equal(result.value, 'some-value')
 | 
						|
    t.equal(signSpy.callCount, 2) // should have returned early when the right key was found
 | 
						|
  })
 | 
						|
 | 
						|
  t.test('signer.unsign failure response', (t) => {
 | 
						|
    t.plan(4)
 | 
						|
 | 
						|
    const input = sign('some-value', 'invalid-secret')
 | 
						|
    signSpy.resetHistory()
 | 
						|
    const result = signer.unsign(input)
 | 
						|
 | 
						|
    t.equal(result.valid, false)
 | 
						|
    t.equal(result.renew, false)
 | 
						|
    t.equal(result.value, null)
 | 
						|
    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'))
 | 
						|
  })
 | 
						|
})
 |