UNPKG

ac-node-hipchat

Version:

A common module plugin for building Atlassian Connect add-ons for HipChat

101 lines (88 loc) 3.27 kB
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 }; }); }; };