UNPKG

3.27 kBJavaScriptView Raw
1var _ = require('lodash');
2var jwt = require('jwt-simple');
3var check = require('check-types');
4var verify = check.verify;
5var Promise = require('rsvp').Promise;
6
7var AUTH_TOKEN_TTL = 15;
8var AUTH_TOKEN_TTL_DEV = Number.MAX_VALUE;
9
10module.exports = function (nodeEnv, tenants, opts) {
11 verify.string(nodeEnv);
12 verify.object(tenants);
13 verify.maybe.object(opts);
14 opts = opts || {};
15 return function (signedRequestParam, authorizationHeader) {
16 var token;
17 var claims;
18 var tenantId;
19 var issuedAt;
20 return new Promise(function (resolve) {
21 // require the request be signed either using the signed_request param or auth header
22 token = signedRequestParam;
23 if (!token) {
24 token = authorizationHeader;
25 if (!token) {
26 throw new Error('Request signature required but not found');
27 }
28 var match = /^JWT\s+token\s*=\s*(.+)\s*$/i.exec(token);
29 if (!match || !match[1]) {
30 throw new Error('Authorization header was either incompatible or malformed');
31 }
32 token = match[1];
33 }
34
35 // read unverified claims to identifier the tenant
36 claims = jwt.decode(token, null, true);
37
38 // validate
39 tenantId = claims.iss;
40 if (!check.string(claims.iss)) {
41 throw new Error('Request signature did not contain an issuer claim');
42 }
43 issuedAt = claims.iat;
44 if (!check.number(claims.iat)) {
45 throw new Error('Request signature did not contain an issued-at timestamp');
46 }
47
48 // fetch the tenant
49 resolve(tenants.get(tenantId));
50 }).then(function (tenant) {
51 // when the tenant fetch completes, make sure something was returned
52 if (!tenant) {
53 throw new Error('Request signature contained unknown issuer: ' + tenantId);
54 }
55
56 // verify the tenant's claims
57 try {
58 claims = jwt.decode(token, tenant.secret);
59 } catch (err) {
60 throw new Error('Request signature verification failed: ' + (err.message || err.toString()));
61 }
62
63 var now = Math.floor(Date.now() / 1000);
64
65 // if the claims specify an expiry, then honor it
66 if (nodeEnv === 'production' && claims.exp && now >= claims.exp) {
67 throw new Error('Request signature expired');
68 }
69
70 // check for expiration based on configurable timeout
71 var authTokenTtl = nodeEnv !== 'production'
72 ? AUTH_TOKEN_TTL_DEV
73 : Math.abs((opts.authTokenTtl >>> 0) || AUTH_TOKEN_TTL);
74 // convert min to sec
75 authTokenTtl = authTokenTtl * 60;
76
77 // verify the signature hasn't expired
78 var expiresAt = issuedAt + authTokenTtl;
79 if (now >= expiresAt) {
80 throw new Error('Request signature expired');
81 }
82
83 // refresh the signed request to produce a token usable in future request
84 var refreshed = _.extend({}, claims);
85 refreshed.iat = now;
86 refreshed.exp = now + authTokenTtl;
87 token = jwt.encode(refreshed, tenant.secret);
88
89 // return an authentication object for use by later middleware and routes
90 return {
91 issuer: tenant,
92 issued: refreshed.iat,
93 userId: refreshed.prn,
94 expiry: refreshed.exp,
95 context: refreshed.context,
96 token: token
97 };
98 });
99 };
100};