ac-node-hipchat
Version:
A common module plugin for building Atlassian Connect add-ons for HipChat
101 lines (88 loc) • 3.27 kB
JavaScript
var _ = require('lodash');
var jwt = require('jwt-simple');
var check = require('check-types');
var verify = check.verify;
var Promise = require('rsvp').Promise;
var AUTH_TOKEN_TTL = 15;
var AUTH_TOKEN_TTL_DEV = Number.MAX_VALUE;
module.exports = function (nodeEnv, tenants, opts) {
verify.string(nodeEnv);
verify.object(tenants);
verify.maybe.object(opts);
opts = opts || {};
return function (signedRequestParam, authorizationHeader) {
var token;
var claims;
var tenantId;
var issuedAt;
return new Promise(function (resolve) {
// require the request be signed either using the signed_request param or auth header
token = signedRequestParam;
if (!token) {
token = authorizationHeader;
if (!token) {
throw new Error('Request signature required but not found');
}
var match = /^JWT\s+token\s*=\s*(.+)\s*$/i.exec(token);
if (!match || !match[1]) {
throw new Error('Authorization header was either incompatible or malformed');
}
token = match[1];
}
// read unverified claims to identifier the tenant
claims = jwt.decode(token, null, true);
// validate
tenantId = claims.iss;
if (!check.string(claims.iss)) {
throw new Error('Request signature did not contain an issuer claim');
}
issuedAt = claims.iat;
if (!check.number(claims.iat)) {
throw new Error('Request signature did not contain an issued-at timestamp');
}
// fetch the tenant
resolve(tenants.get(tenantId));
}).then(function (tenant) {
// when the tenant fetch completes, make sure something was returned
if (!tenant) {
throw new Error('Request signature contained unknown issuer: ' + tenantId);
}
// verify the tenant's claims
try {
claims = jwt.decode(token, tenant.secret);
} catch (err) {
throw new Error('Request signature verification failed: ' + (err.message || err.toString()));
}
var now = Math.floor(Date.now() / 1000);
// if the claims specify an expiry, then honor it
if (nodeEnv === 'production' && claims.exp && now >= claims.exp) {
throw new Error('Request signature expired');
}
// check for expiration based on configurable timeout
var authTokenTtl = nodeEnv !== 'production'
? AUTH_TOKEN_TTL_DEV
: Math.abs((opts.authTokenTtl >>> 0) || AUTH_TOKEN_TTL);
// convert min to sec
authTokenTtl = authTokenTtl * 60;
// verify the signature hasn't expired
var expiresAt = issuedAt + authTokenTtl;
if (now >= expiresAt) {
throw new Error('Request signature expired');
}
// refresh the signed request to produce a token usable in future request
var refreshed = _.extend({}, claims);
refreshed.iat = now;
refreshed.exp = now + authTokenTtl;
token = jwt.encode(refreshed, tenant.secret);
// return an authentication object for use by later middleware and routes
return {
issuer: tenant,
issued: refreshed.iat,
userId: refreshed.prn,
expiry: refreshed.exp,
context: refreshed.context,
token: token
};
});
};
};