1 | var assert = require('assert');
|
2 | var _ = require('lodash');
|
3 | var jwt = require('jwt-simple');
|
4 | var MemoryStore = require('ac-node').MemoryStore;
|
5 | var Tenants = require('ac-node').Tenants;
|
6 | var authenticator = require('..').authenticator;
|
7 | var fixtures = require('./fixtures');
|
8 |
|
9 | var tenant = fixtures.load('tenant.json');
|
10 |
|
11 | describe('ac hipchat authenticator', function () {
|
12 |
|
13 | var store;
|
14 | var tenantStore;
|
15 | var tenants;
|
16 |
|
17 | beforeEach(function *() {
|
18 | store = MemoryStore();
|
19 | tenantStore = store.narrow('tenant');
|
20 | tenants = Tenants(tenantStore);
|
21 | yield tenantStore.set(tenant.id, tenant);
|
22 | });
|
23 |
|
24 | it('should create an authenticate fn from tenants and nodeEnv', function *() {
|
25 | assert.ok(typeof authenticator('production', tenants) === 'function');
|
26 | });
|
27 |
|
28 | it('should fail to authenticate given neither a signedRequestParam nor an authorizationHeader', function *() {
|
29 | var authenticate = authenticator('production', tenants);
|
30 | try {
|
31 | yield authenticate();
|
32 | assert.fail();
|
33 | } catch (err) {
|
34 | assert.equal(err.message, 'Request signature required but not found');
|
35 | }
|
36 | });
|
37 |
|
38 | it('should fail to authenticate given a malformed authorization header', function *() {
|
39 | var authenticate = authenticator('production', tenants);
|
40 | try {
|
41 | yield authenticate(null, 'malformed');
|
42 | assert.fail();
|
43 | } catch (err) {
|
44 | assert.equal(err.message, 'Authorization header was either incompatible or malformed');
|
45 | }
|
46 | });
|
47 |
|
48 | it('should fail to authenticate given a claim that lacks an issuer', function *() {
|
49 | var authenticate = authenticator('production', tenants);
|
50 | var claims = createClaims({
|
51 | iss: null
|
52 | });
|
53 | var token = createToken(claims);
|
54 | try {
|
55 | yield authenticate(token);
|
56 | assert.fail();
|
57 | } catch (err) {
|
58 | assert.equal(err.message, 'Request signature did not contain an issuer claim');
|
59 | }
|
60 | });
|
61 |
|
62 | it('should fail to authenticate given a claim that lacks an issued-at timestamp', function *() {
|
63 | var authenticate = authenticator('production', tenants);
|
64 | var claims = createClaims({
|
65 | iat: null
|
66 | });
|
67 | var token = createToken(claims);
|
68 | try {
|
69 | yield authenticate(token);
|
70 | assert.fail();
|
71 | } catch (err) {
|
72 | assert.equal(err.message, 'Request signature did not contain an issued-at timestamp');
|
73 | }
|
74 | });
|
75 |
|
76 | it('should fail to authenticate given a claim issued by an unknown tenant', function *() {
|
77 | var authenticate = authenticator('production', tenants);
|
78 | var claims = createClaims({
|
79 | iss: 'unknown'
|
80 | });
|
81 | var token = createToken(claims);
|
82 | try {
|
83 | yield authenticate(token);
|
84 | assert.fail();
|
85 | } catch (err) {
|
86 | assert.equal(err.message, 'Request signature contained unknown issuer: unknown');
|
87 | }
|
88 | });
|
89 |
|
90 | it('should fail to authenticate given a claim with an invalid signature', function *() {
|
91 | var authenticate = authenticator('production', tenants);
|
92 | var claims = createClaims();
|
93 | var token = createToken(claims) + 'fail';
|
94 | try {
|
95 | yield authenticate(token);
|
96 | assert.fail();
|
97 | } catch (err) {
|
98 | assert.ok(err.message.indexOf('Request signature verification failed: ') === 0);
|
99 | }
|
100 | });
|
101 |
|
102 | it('should fail to authenticate given a claim with an expired expiry', function *() {
|
103 | var authenticate = authenticator('production', tenants);
|
104 | var claims = createClaims({
|
105 | iat: 0,
|
106 | exp: 1
|
107 | });
|
108 | var token = createToken(claims);
|
109 | try {
|
110 | yield authenticate(token);
|
111 | assert.fail();
|
112 | } catch (err) {
|
113 | assert.equal(err.message, 'Request signature expired');
|
114 | }
|
115 | });
|
116 |
|
117 | it('should ignore expiry expiration when not running in production mode', function *() {
|
118 | var authenticate = authenticator('development', tenants);
|
119 | var claims = createClaims({
|
120 | iat: 0,
|
121 | exp: 1
|
122 | });
|
123 | var token = createToken(claims);
|
124 | yield authenticate(token);
|
125 | });
|
126 |
|
127 | it('should fail to authenticate given a claim with an issued-at timestamp greater than authTokenTtl minutes ago', function *() {
|
128 | var authenticate = authenticator('production', tenants, {
|
129 | authTokenTtl: 1
|
130 | });
|
131 | var claims = createClaims({
|
132 | iat: Math.floor(Date.now() / 1000) - 62
|
133 | });
|
134 | var token = createToken(claims);
|
135 | try {
|
136 | yield authenticate(token);
|
137 | assert.fail();
|
138 | } catch (err) {
|
139 | assert.equal(err.message, 'Request signature expired');
|
140 | }
|
141 | });
|
142 |
|
143 | it('should ignore authTokenTtl expiration when not running in production mode', function *() {
|
144 | var authenticate = authenticator('development', tenants, {
|
145 | authTokenTtl: 1
|
146 | });
|
147 | var claims = createClaims({
|
148 | iat: Math.floor(Date.now() / 1000) - 62
|
149 | });
|
150 | var token = createToken(claims);
|
151 | yield authenticate(token);
|
152 | });
|
153 |
|
154 | it('should successfully authenticate given a valid signed request param', function *() {
|
155 | var authenticate = authenticator('production', tenants);
|
156 | var claims = createClaims();
|
157 | var token = createToken(claims);
|
158 | var authentication = yield authenticate(token);
|
159 | assert.ok(authentication);
|
160 | assert.equal(authentication.issuer, tenant);
|
161 | assert.ok(authentication.issued > claims.iat);
|
162 | assert.equal(authentication.userId, claims.prn);
|
163 | assert.equal(authentication.expiry, authentication.issued + (15 * 60));
|
164 | assert.deepEqual(authentication.context, claims.context);
|
165 | jwt.decode(authentication.token, tenant.secret);
|
166 | });
|
167 |
|
168 | it('should successfully authenticate given a valid authorization header', function *() {
|
169 | var authenticate = authenticator('production', tenants);
|
170 | var claims = createClaims();
|
171 | var token = createToken(claims);
|
172 | var authentication = yield authenticate(null, 'JWT token=' + token);
|
173 | assert.ok(authentication);
|
174 | assert.equal(authentication.issuer, tenant);
|
175 | assert.ok(authentication.issued > claims.iat);
|
176 | assert.equal(authentication.userId, claims.prn);
|
177 | assert.equal(authentication.expiry, authentication.issued + (15 * 60));
|
178 | assert.deepEqual(authentication.context, claims.context);
|
179 | jwt.decode(authentication.token, tenant.secret);
|
180 | });
|
181 |
|
182 | });
|
183 |
|
184 | function createClaims(overrides) {
|
185 | return _.extend({
|
186 | iss: tenant.id,
|
187 | iat: Math.floor(Date.now() / 1000) - 10,
|
188 | prn: 1,
|
189 | context: {tz: 'GMT'}
|
190 | }, overrides);
|
191 | }
|
192 |
|
193 | function createToken(claims) {
|
194 | return jwt.encode(claims, tenant.secret);
|
195 | }
|