Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

alexa-ability-express-handler

Package Overview
Dependencies
Maintainers
1
Versions
10
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

alexa-ability-express-handler - npm Package Compare versions

Comparing version 0.3.1 to 0.4.0

test/fixtures/certificate.txt

190

lib/verifyRequest.js

@@ -7,23 +7,95 @@ 'use strict';

exports.verifyRequest = verifyRequest;
exports.validateUrl = validateUrl;
exports.getCertificate = getCertificate;
exports.validateCertificate = validateCertificate;
exports.validateBody = validateBody;
var _assert = require('assert');
var _assert2 = _interopRequireDefault(_assert);
var _bluebird = require('bluebird');
var _bluebird2 = _interopRequireDefault(_bluebird);
var _request = require('request');
var _request2 = _interopRequireDefault(_request);
var _crypto = require('crypto');
var _crypto2 = _interopRequireDefault(_crypto);
var _debug = require('debug');
var _debug2 = _interopRequireDefault(_debug);
var _url = require('url');
var _path = require('path');
var _x2 = require('x509');
var _startsWith = require('lodash/startsWith');
var _startsWith2 = _interopRequireDefault(_startsWith);
var _includes = require('lodash/includes');
var _includes2 = _interopRequireDefault(_includes);
var _get = require('lodash/get');
var _get2 = _interopRequireDefault(_get);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var log = (0, _debug2.default)('express-handler:verifyRequest');
var MAX_TOLERANCE = 60 * 2.5 * 1000; // 2.5 minutes
var CERT_HEADER = 'SignatureCertChainUrl';
var SIG_HEADER = 'Signature';
var DEFAULT_TIME = new Date(0).toISOString();
var CERT_PROTO = 'https:';
var CERT_HOST = 's3.amazonaws.com';
var CERT_PORT = 443;
var CERT_PATH = '/echo.api/';
var CERT_ALTNAME = 'echo-api.amazon.com';
function verifyRequest() {
var _ref = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
var _ref$tolerance = _ref.tolerance;
var tolerance = _ref$tolerance === undefined ? MAX_TOLERANCE : _ref$tolerance;
return function verifyRequestMiddleware(req, res, next) {
// get data
var chainUrl = req.headers[CERT_HEADER];
var signature = req.headers[SIG_HEADER];
var sig = req.headers[SIG_HEADER];
var body = req.body;
var timestamp = (0, _get2.default)(body, 'request.timestamp', DEFAULT_TIME);
log('cert-url: %s', chainUrl);
log('signature: %s', sig);
log('timestamp: %s', timestamp);
log('checking body: %o', body);
if (!isValidSignatureUrl(chainUrl)) {
return next(new Error());
// basic checks
if (!chainUrl) return next(new Error('No SignatureCertChainUrl header provided.'));
if (!sig) return next(new Error('No Signature header provided.'));
if (!body) return next(new Error('No body provided.'));
// check timestamp
var diff = Math.abs(new Date(timestamp).valueOf() - new Date().valueOf());
if (diff > tolerance) {
return next(new Error('Request timestamp is outside of allowed tolerance.'));
}
getCertificate(chainUrl).tap(validateCertificate).then(function (cert) {
return validateBody(cert, signature, req.body);
// validation
_bluebird2.default.resolve(chainUrl).then(validateUrl).then(getCertificate).then(validateCertificate).then(function (cert) {
return validateBody(cert, sig, body);
}).then(function () {
return next();
log('verified request');
next();
}, function (err) {
return next(err);
log('error verifiying request: %s', err);
next(err);
});

@@ -33,3 +105,16 @@ };

function isValidSignatureUrl(url) {
/**
* Validates that the certificate url is valid, as defined by
* https://developer.amazon.com/public/solutions/alexa/alexa-skills-kit/docs/developing-an-alexa-skill-as-a-web-service#verifying-the-signature-certificate-url
*
* Basically the rules are:
* 1. The protocol is equal to https (case insensitive).
* 2. The hostname is equal to s3.amazonaws.com (case insensitive).
* 3. The normalized path starts with /echo.api/ (case sensitive).
* 4. If a port is defined in the URL, the port is equal to 443.
*
* @param {String} url
* @return {String} url
*/
function validateUrl(url) {
var _parse = (0, _url.parse)(url);

@@ -42,14 +127,87 @@

if (protocol !== 'http') return false;
if (hostname !== 's3.amazonaws.com') return false;
if (!pathname.indexOf('/echo.api/')) return false;
if (port !== undefined && port !== '443') return false;
var path = (0, _path.normalize)(pathname);
return true;
_assert2.default.equal(protocol, CERT_PROTO);
_assert2.default.equal(hostname, CERT_HOST);
(0, _assert2.default)((0, _startsWith2.default)(path, CERT_PATH), path + ' does not start with ' + CERT_PATH);
_assert2.default.equal(port || CERT_PORT, CERT_PORT);
log('valid url: %s', url);
// return the url to make promise chaining easier
return url;
}
function getCertificate() {}
/**
* Get the x509 certificate from amazon
* TODO cache some number of certs
*
* @param {String} url
* @return {String} certificate
*/
function getCertificate(url) {
log('getting certificate');
function validateCertificate() {}
return new _bluebird2.default(function (res, rej) {
(0, _request2.default)(url, function (err, resp, body) {
if (err) {
log('error getting certificate');
return rej(err);
}
function validateBody() {}
if (resp.statusCode !== 200) {
log('invalid status code: %s', resp.statusCode);
return rej(new Error('Invalid certificate response.'));
}
log('got certificate');
res(body);
});
});
}
/**
* Validate a certificate, as defined by:
* https://developer.amazon.com/public/solutions/alexa/alexa-skills-kit/docs/developing-an-alexa-skill-as-a-web-service#Checking the Signature of the Request
* @param {String} cert
* @return {String} cert
*/
function validateCertificate(cert) {
log('validating certificate');
var _parseCert = (0, _x2.parseCert)(cert);
var altNames = _parseCert.altNames;
var notBefore = _parseCert.notBefore;
var notAfter = _parseCert.notAfter;
var now = new Date();
(0, _assert2.default)((0, _includes2.default)(altNames, CERT_ALTNAME), 'Invalid alt names.');
(0, _assert2.default)(now > new Date(notBefore), 'Certificate expired.');
(0, _assert2.default)(now < new Date(notAfter), 'Certificate expired.');
log('valid certificate');
// return cert to make promise chaining easier
return cert;
}
/**
* Check the body against the signature and validated cert
* TODO do we need to check against raw body?
* @param {String} cert
* @param {String} sig
* @param {Object} body
*/
function validateBody(cert, sig, body) {
log('checking body against signature');
var verifier = _crypto2.default.createVerify('SHA1');
verifier.update(JSON.stringify(body));
if (!verifier.verify(cert, sig, 'base64')) {
throw new Error('Could not verify request body.');
}
log('signature matches body');
}

14

package.json
{
"name": "alexa-ability-express-handler",
"version": "0.3.1",
"description": "Publish an alexa-ability as an express route",
"version": "0.4.0",
"description": "Expose an alexa-ability as an express route",
"main": "lib/index.js",

@@ -35,6 +35,9 @@ "scripts": {

"dependencies": {
"lodash": "^4.0.1"
"bluebird": "^3.3.1",
"debug": "^2.2.0",
"lodash": "^4.3.0",
"request": "^2.69.0",
"x509": "^0.2.3"
},
"devDependencies": {
"babel": "^6.3.26",
"babel-cli": "^6.4.5",

@@ -52,6 +55,3 @@ "babel-eslint": "^4.1.8",

"sinon-chai": "^2.8.0"
},
"peerDependencies": {
"alexa-ability": "^0.6.0"
}
}

@@ -9,6 +9,6 @@ # alexa-ability-express-handler [![Build Status](https://travis-ci.org/nickclaw/alexa-ability-express-handler.svg?branch=master)](https://travis-ci.org/nickclaw/alexa-ability-express-handler)

import { Ability, events } from 'alexa-ability';
import { handleAbility } from 'alexa-ability-express-handler';
import { verifyRequest, handleAbility } from 'alexa-ability-express-handler';
// build skill
const ability = new Ability();
ability.on(events.launch, function(req) {

@@ -20,3 +20,4 @@ req.say("Testing testing one two three.").end();

app.use(bodyParser.json());
app.use('/my-intent', handleAbility(ability));
app.use(verifyRequest());
app.post('/my-intent', handleAbility(ability));

@@ -27,1 +28,13 @@ app.listen(8000, function() {

```
### API
##### `verifyRequest(options) -> express middleware`
Creates a middleware function for express that implements the body verification process
required by Amazon for certification. Takes an optional options object with the
following values:
- `tolerance`: only accept requests with a timestamp within the given tolerance.
Value defaults to 2.5 minutes in milliseconds.
##### `handleAbility(ability) -> express handler`
Creates an express handler for the given ability.
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc