Skip to content

Instantly share code, notes, and snippets.

@gordysc
Last active November 19, 2016 17:07
Show Gist options
  • Save gordysc/cc158f33ca41329a720e7777aefa5604 to your computer and use it in GitHub Desktop.
Save gordysc/cc158f33ca41329a720e7777aefa5604 to your computer and use it in GitHub Desktop.
Verifying signed URL's w/ Hapi
// URL signature has following format:
// http://<uri>?timestamp=<timestamp>&signature=<signature>
const Boom = require('boom')
const crypto = require('crypto')
const algorithm = process.env.SIGNATURE_ALGORITHM || 'sha1'
const TIMESTAMP_RANGE = parseInt(process.env.TIMESTAMP_RANGE) || 5 * 60 * 1000
const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i
const internals = {
getApplicationSecret (id) {
return (id === '43D96179-8E71-47CC-9755-363AB555EE55') ? 'secret' : false
}
}
exports.register = (server, options, next) => {
server.ext('onPreHandler', (request, reply) => {
// Verify the request has the required parameters
const { signature, timestamp } = request.query
if (!signature || !timestamp) {
console.log('A signature or a timestamp was not given')
return reply(Boom.unauthorized())
}
// Verify timestamp is within a specific range of time (5 minutes unless configured)
const diff = Math.abs(new Date().getTime() - timestamp)
if (!diff || (diff > TIMESTAMP_RANGE)) {
console.log('The provided timestamp is outside an acceptable time range for a request')
return reply(Boom.unauthorized())
}
// Verify the signature has the appropriate format
const args = signature.split(':')
if (args.length !== 2) {
console.log('The request signature does not have the appropriate format')
return reply(Boom.unauthorized())
}
// Verify the 1st part of the signature is a valid v4 UUID
const id = args[0]
if (!UUID_REGEX.test(id)) {
console.log('The application ID is not a valid v4 UUID')
return reply(Boom.unauthorized())
}
// Get the application secret (if it exists)
const secret = internals.getApplicationSecret(id)
if (!secret) {
console.log('This application does not exist')
return reply(Boom.unauthorized())
}
// Calculate the expected hash
const { path } = request
const hmac = crypto.createHmac(algorithm, secret)
const expected = hmac.update(`${path}?timestamp=${timestamp}`).digest('hex')
// Verify the 2nd part of the signature matches the expected generated hash
const hash = args[1]
if (hash !== expected) {
console.log('The provided hash did not match the expected result')
return reply(Boom.unauthorized())
}
// We have a successful signature, continue
return reply.continue()
})
return next()
}
exports.register.attributes = {
name: 'pre-handler-hook',
version: process.env.npm_package_version,
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment