UNPKG

fastify-api-key

Version:

Fastify plugin to authenticate HTTP requests based on api key and signature

369 lines (313 loc) 9.47 kB
const { test } = require('tap') const Fastify = require('fastify') const crypto = require('crypto') const apiKey = require('./index') test('register', (t) => { t.plan(2) t.test('Should expose api key methods', (t) => { t.plan(1) const fastify = Fastify() fastify.register(apiKey, { getSecret: async () => { return 'test' } }) fastify.get('/methods', (request) => { t.ok(request.apiKeyVerify) }) fastify.inject({ method: 'get', url: '/methods' }) }) t.test('should failed if "getSecret" is missing', (t) => { t.plan(1) const fastify = Fastify() fastify.register(apiKey).ready((error) => { t.is(error.message, 'missing getSecret') }) }) }) test('requestVerify', (t) => { t.plan(6) const keyId = '123456789' const secret = 'secret' const date = (new Date()).toString() const host = 'http://localhost' const signingString = `(request-target): get /verify\nhost: ${host}\ndate: ${date}` const signature = crypto.createHmac('sha1', secret).update(signingString).digest('base64') const authorization = `Signature keyId="${keyId}",algorithm="hmac-sha1",headers="(request-target) host date",signature="${signature}"` t.test('getSecret', (t) => { t.plan(3) async function runWithGetSecret (t, getSecret) { const fastify = Fastify() fastify.register(apiKey, { getSecret }) fastify.get('/verify', async (request) => { await request.apiKeyVerify() return 'test' }) await fastify.ready() const verifyResponse = await fastify.inject({ method: 'get', url: '/verify', headers: { authorization, date, host } }) t.is(verifyResponse.payload, 'test') } t.test('getSecret as a function with callback', (t) => { return runWithGetSecret(t, (request, keyId, callback) => { callback(null, secret) }) }) t.test('getSecret as a function returning a promise', (t) => { return runWithGetSecret(t, () => { return Promise.resolve(secret) }) }) t.test('getSecret as an async function', (t) => { return runWithGetSecret(t, async () => { return secret }) }) }) t.test('disable the checking of request expiration', (t) => { t.plan(1) const fastify = Fastify() fastify.register(apiKey, { requestLifetime: null, getSecret: (request, keyId, cb) => { cb(null, secret) } }) fastify.get('/verify', async (request) => { await request.apiKeyVerify() return 'test' }) const date = new Date('1970-01-01').toString() const signingString = `date: ${date}` const signature = crypto.createHmac('sha1', secret).update(signingString).digest('base64') const authorizationWithoutHeader = `Signature keyId="${keyId}",algorithm="hmac-sha1",signature="${signature}"` fastify.inject({ method: 'get', url: '/verify', headers: { authorization: authorizationWithoutHeader, date } }).then((response) => { t.is(response.payload, 'test') }) }) t.test('Authorization signature without headers value', (t) => { t.plan(1) const fastify = Fastify() fastify.register(apiKey, { getSecret: (request, keyId, cb) => { cb(null, secret) } }) fastify.get('/verify', async (request) => { await request.apiKeyVerify() return 'test' }) const signingString = `date: ${date}` const signature = crypto.createHmac('sha1', secret).update(signingString).digest('base64') const authorizationWithoutHeader = `Signature keyId="${keyId}",algorithm="hmac-sha1",signature="${signature}"` fastify.inject({ method: 'get', url: '/verify', headers: { authorization: authorizationWithoutHeader, date } }).then((response) => { t.is(response.payload, 'test') }) }) t.test('synchronous requestVerify', (t) => { t.plan(1) const fastify = Fastify() fastify.register(apiKey, { getSecret: (request, keyId, cb) => { cb(null, secret) } }) fastify.get('/verify', async (request) => { await request.apiKeyVerify() return 'test' }) fastify.inject({ method: 'get', url: '/verify', headers: { authorization, date, host } }).then((response) => { t.is(response.payload, 'test') }) }) t.test('asynchronous requestVerify', (t) => { t.plan(1) const fastify = Fastify() fastify.register(apiKey, { getSecret: (request, keyId, cb) => { cb(null, secret) } }) fastify.get('/verify', (request, reply) => { request.apiKeyVerify((error) => { return reply.send(error || 'test') }) }) fastify.inject({ method: 'get', url: '/verify', headers: { authorization, date, host } }).then((response) => { t.is(response.payload, 'test') }) }) t.test('errors', (t) => { t.plan(8) const signature = 'Signature keyid="123456789",algorithm="hmac-sha1",headers="host date",signature="signature"' const fastify = Fastify() fastify.register(apiKey, { getSecret: (request, keyId, cb) => { cb(null, 'secret') } }) fastify.get('/verify', (request, reply) => { request.apiKeyVerify().then(() => { return reply.send('test') }).catch((error) => { return reply.send(error) }) }) t.test('should failed if HTTP header "Authorization" is not present', (t) => { t.plan(2) fastify.inject({ method: 'get', url: '/verify' }).then((response) => { const error = JSON.parse(response.payload) t.is(error.message, 'Missing required HTTP headers : authorization') t.is(response.statusCode, 401) }) }) t.test('should failed if the auth scheme of the HTTP header "Authorization" is not valid', (t) => { t.plan(2) fastify.inject({ method: 'get', url: '/verify', headers: { authorization: 'Invalid Format' } }).then((response) => { const error = JSON.parse(response.payload) t.is(error.message, 'Bad value format for the HTTP header Authorization. Expected format : Signature [params]') t.is(response.statusCode, 400) }) }) t.test('should throw if the signature parameters are not valid', (t) => { t.plan(2) fastify.inject({ method: 'get', url: '/verify', headers: { authorization: 'Signature bad_params' } }).then((response) => { const error = JSON.parse(response.payload) t.is(error.message, 'Missing required signature parameters : keyid, algorithm, signature') t.is(response.statusCode, 400) }) }) t.test('should failed if the algorithm is not supported', (t) => { t.plan(2) fastify.inject({ method: 'get', url: '/verify', headers: { authorization: 'Signature keyid="123456789",algorithm="unknown_algorithm",headers="host date",signature="test"' } }).then((response) => { const error = JSON.parse(response.payload) t.is(error.message, 'Unsupported algorithm') t.is(response.statusCode, 400) }) }) t.test('should failed if required headers for signature are missing', (t) => { t.plan(2) fastify.inject({ method: 'get', url: '/verify', headers: { authorization: signature } }).then((response) => { const error = JSON.parse(response.payload) t.is(error.message, 'Missing required HTTP headers : date') t.is(response.statusCode, 400) }) }) t.test('should failed if the HTTP header date is malformed', (t) => { t.plan(2) fastify.inject({ method: 'get', url: '/verify', headers: { authorization: signature, date: 'malformed_date' } }).then((response) => { const error = JSON.parse(response.payload) t.is(error.message, 'Bad value format for the HTTP header date. Expected format : <day-name>, <day> <month> <year> <hour>:<minute>:<second> GMT') t.is(response.statusCode, 400) }) }) t.test('should failed if the request is expired', (t) => { t.plan(2) fastify.inject({ method: 'get', url: '/verify', headers: { authorization: signature, date: 'Wed, 24 Mar 2021 06:00:00 GMT' } }).then((response) => { const error = JSON.parse(response.payload) t.is(error.message, 'Request has expired') t.is(response.statusCode, 400) }) }) t.test('should failed if the signature is invalid', (t) => { t.plan(2) fastify.inject({ method: 'get', url: '/verify', headers: { authorization: signature, date: (new Date()).toString() } }).then((response) => { const error = JSON.parse(response.payload) t.is(error.message, 'Authorization signature is invalid') t.is(response.statusCode, 401) }) }) }) })