@webex/plugin-authorization-browser-first-party 3.0.0-beta.4 → 3.0.0-beta.400

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -19,28 +19,28 @@ import Authorization from '@webex/plugin-authorization-browser-first-party';
19
19
  // Necessary to require lodash this way in order to stub the method
20
20
  const lodash = require('lodash');
21
21
 
22
-
23
22
  browserOnly(describe)('plugin-authorization-browser-first-party', () => {
24
23
  describe('Authorization', () => {
25
- function makeWebex(href = 'https://example.com', csrfToken = undefined, pkceVerifier = undefined, config = {}) {
24
+ function makeWebex(
25
+ href = 'https://example.com',
26
+ csrfToken = undefined,
27
+ pkceVerifier = undefined,
28
+ config = {}
29
+ ) {
26
30
  const mockWindow = {
27
31
  history: {
28
32
  replaceState(a, b, location) {
29
33
  mockWindow.location.href = location;
30
- }
34
+ },
31
35
  },
32
36
  location: {
33
- href
37
+ href,
34
38
  },
35
39
  sessionStorage: {
36
- getItem: sinon.stub()
37
- .onCall(0)
38
- .returns(pkceVerifier)
39
- .onCall(1)
40
- .returns(csrfToken),
40
+ getItem: sinon.stub().onCall(0).returns(pkceVerifier).onCall(1).returns(csrfToken),
41
41
  removeItem: sinon.spy(),
42
- setItem: sinon.spy()
43
- }
42
+ setItem: sinon.spy(),
43
+ },
44
44
  };
45
45
 
46
46
  sinon.spy(mockWindow.history, 'replaceState');
@@ -49,42 +49,61 @@ browserOnly(describe)('plugin-authorization-browser-first-party', () => {
49
49
  children: {
50
50
  authorization: Authorization,
51
51
  credentials: Credentials,
52
- services: Services
52
+ services: Services,
53
53
  },
54
- request: sinon.stub().returns(Promise.resolve({body: {access_token: 'AT', token_type: 'Fake', refresh_token: 'RT'}})),
55
- config: merge({
56
- credentials: {
57
- idbroker: {
58
- url: process.env.IDBROKER_BASE_URL,
59
- defaultUrl: process.env.IDBROKER_BASE_URL
54
+ request: sinon
55
+ .stub()
56
+ .returns(
57
+ Promise.resolve({body: {access_token: 'AT', token_type: 'Fake', refresh_token: 'RT'}})
58
+ ),
59
+ config: merge(
60
+ {
61
+ credentials: {
62
+ idbroker: {
63
+ url: process.env.IDBROKER_BASE_URL,
64
+ defaultUrl: process.env.IDBROKER_BASE_URL,
65
+ },
66
+ identity: {
67
+ url: process.env.IDENTITY_BASE_URL,
68
+ defaultUrl: process.env.IDENTITY_BASE_URL,
69
+ },
70
+ activationUrl: `${
71
+ process.env.IDBROKER_BASE_URL || 'https://idbroker.webex.com'
72
+ }/idb/token/v1/actions/UserActivation/invoke`,
73
+ authorizeUrl: `${
74
+ process.env.IDBROKER_BASE_URL || 'https://idbroker.webex.com'
75
+ }/idb/oauth2/v1/authorize`,
76
+ setPasswordUrl: `${
77
+ process.env.IDBROKER_BASE_URL || 'https://identity.webex.com'
78
+ }/identity/scim/v1/Users`,
79
+ logoutUrl: `${
80
+ process.env.IDBROKER_BASE_URL || 'https://idbroker.webex.com'
81
+ }/idb/oauth2/v1/logout`,
82
+ // eslint-disable-next-line camelcase
83
+ client_id: 'fake',
84
+ // eslint-disable-next-line camelcase
85
+ client_secret: 'fake',
86
+ // eslint-disable-next-line camelcase
87
+ redirect_uri: 'http://example.com',
88
+ // eslint-disable-next-line camelcase
89
+ scope: 'scope:one',
90
+ refreshCallback: () => Promise.resolve(),
60
91
  },
61
- identity: {
62
- url: process.env.IDENTITY_BASE_URL,
63
- defaultUrl: process.env.IDENTITY_BASE_URL
64
- },
65
- activationUrl: `${process.env.IDBROKER_BASE_URL || 'https://idbroker.webex.com'}/idb/token/v1/actions/UserActivation/invoke`,
66
- authorizeUrl: `${process.env.IDBROKER_BASE_URL || 'https://idbroker.webex.com'}/idb/oauth2/v1/authorize`,
67
- setPasswordUrl: `${process.env.IDBROKER_BASE_URL || 'https://identity.webex.com'}/identity/scim/v1/Users`,
68
- logoutUrl: `${process.env.IDBROKER_BASE_URL || 'https://idbroker.webex.com'}/idb/oauth2/v1/logout`,
69
- // eslint-disable-next-line camelcase
70
- client_id: 'fake',
71
- // eslint-disable-next-line camelcase
72
- client_secret: 'fake',
73
- // eslint-disable-next-line camelcase
74
- redirect_uri: 'http://example.com',
75
- // eslint-disable-next-line camelcase
76
- scope: 'scope:one',
77
- refreshCallback: () => Promise.resolve()
78
- }
79
- }, config),
92
+ },
93
+ config
94
+ ),
80
95
  getWindow() {
81
96
  return mockWindow;
82
- }
97
+ },
83
98
  });
84
99
 
85
100
  return webex;
86
101
  }
87
102
 
103
+ afterEach(() => {
104
+ sinon.restore();
105
+ });
106
+
88
107
  describe('#initialize()', () => {
89
108
  describe('when there is a code in the url', () => {
90
109
  it('exchanges it for an access token and sets ready', () => {
@@ -93,15 +112,14 @@ browserOnly(describe)('plugin-authorization-browser-first-party', () => {
93
112
  assert.isFalse(webex.authorization.ready);
94
113
  assert.isFalse(webex.credentials.canAuthorize);
95
114
 
96
- return webex.authorization.when('change:ready')
97
- .then(() => {
98
- // Webex request gets called twice:
99
- // once for the pre-auth catalog
100
- // once for auth token exchange
101
- assert.calledTwice(webex.request);
102
- assert.isTrue(webex.authorization.ready);
103
- assert.isTrue(webex.credentials.canAuthorize);
104
- });
115
+ return webex.authorization.when('change:ready').then(() => {
116
+ // Webex request gets called twice:
117
+ // once for the pre-auth catalog
118
+ // once for auth token exchange
119
+ assert.calledTwice(webex.request);
120
+ assert.isTrue(webex.authorization.ready);
121
+ assert.isTrue(webex.credentials.canAuthorize);
122
+ });
105
123
  });
106
124
 
107
125
  it('validates the csrf token', () => {
@@ -109,12 +127,20 @@ browserOnly(describe)('plugin-authorization-browser-first-party', () => {
109
127
 
110
128
  assert.throws(() => {
111
129
  // eslint-disable-next-line no-unused-vars
112
- const webex = makeWebex(`http://example.com/?code=5&state=${base64.encode(JSON.stringify({csrf_token: 'someothertoken'}))}`, csrfToken);
130
+ const webex = makeWebex(
131
+ `http://example.com/?code=5&state=${base64.encode(
132
+ JSON.stringify({csrf_token: 'someothertoken'})
133
+ )}`,
134
+ csrfToken
135
+ );
113
136
  }, /CSRF token someothertoken does not match stored token abcd/);
114
137
 
115
138
  assert.throws(() => {
116
139
  // eslint-disable-next-line no-unused-vars
117
- const webex = makeWebex(`http://example.com/?code=5&state=${base64.encode(JSON.stringify({}))}`, csrfToken);
140
+ const webex = makeWebex(
141
+ `http://example.com/?code=5&state=${base64.encode(JSON.stringify({}))}`,
142
+ csrfToken
143
+ );
118
144
  }, /Expected CSRF token abcd, but not found in redirect query/);
119
145
 
120
146
  assert.throws(() => {
@@ -122,33 +148,67 @@ browserOnly(describe)('plugin-authorization-browser-first-party', () => {
122
148
  const webex = makeWebex('http://example.com/?code=5', csrfToken);
123
149
  }, /Expected CSRF token abcd, but not found in redirect query/);
124
150
 
125
- const webex = makeWebex(`http://example.com/?code=5&state=${base64.encode(JSON.stringify({csrf_token: csrfToken}))}`, csrfToken);
151
+ const webex = makeWebex(
152
+ `http://example.com/?code=5&state=${base64.encode(
153
+ JSON.stringify({csrf_token: csrfToken})
154
+ )}`,
155
+ csrfToken
156
+ );
126
157
 
127
- return webex.authorization.when('change:ready')
128
- .then(() => {
129
- assert.isTrue(webex.credentials.canAuthorize);
130
- assert.called(webex.getWindow().sessionStorage.removeItem);
131
- });
158
+ return webex.authorization.when('change:ready').then(() => {
159
+ assert.isTrue(webex.credentials.canAuthorize);
160
+ assert.called(webex.getWindow().sessionStorage.removeItem);
161
+ });
132
162
  });
133
163
 
134
164
  it('removes the oauth parameters from the url', () => {
135
165
  const csrfToken = 'abcd';
136
166
 
137
- const webex = makeWebex(`http://example.com/?code=5&state=${base64.encode(JSON.stringify({csrf_token: csrfToken, something: true}))}`, csrfToken);
167
+ const webex = makeWebex(
168
+ `http://example.com/?code=5&state=${base64.encode(
169
+ JSON.stringify({csrf_token: csrfToken, something: true})
170
+ )}`,
171
+ csrfToken
172
+ );
138
173
 
139
- return webex.authorization.when('change:ready')
140
- .then(() => {
141
- assert.isTrue(webex.credentials.canAuthorize);
142
- assert.called(webex.getWindow().sessionStorage.removeItem);
143
- assert.called(webex.getWindow().history.replaceState);
144
- assert.equal(webex.getWindow().location.href, `http://example.com/?state=${base64.encode(JSON.stringify({something: true}))}`);
145
- });
174
+ return webex.authorization.when('change:ready').then(() => {
175
+ assert.isTrue(webex.credentials.canAuthorize);
176
+ assert.called(webex.getWindow().sessionStorage.removeItem);
177
+ assert.called(webex.getWindow().history.replaceState);
178
+ assert.equal(
179
+ webex.getWindow().location.href,
180
+ `http://example.com/?state=${base64.encode(JSON.stringify({something: true}))}`
181
+ );
182
+ });
183
+ });
184
+
185
+ it('handles an error when exchanging an authorization code and becomes ready', () => {
186
+ const code = 'errors-when-exchanging';
187
+ const error = new Error('something bad happened');
188
+ const requestAuthorizationCodeGrantStub = sinon
189
+ .stub(Authorization.prototype, 'requestAuthorizationCodeGrant')
190
+ .throws(error);
191
+
192
+ const webex = makeWebex(`http://example.com?code=${code}`);
193
+
194
+ return webex.authorization.when('change:ready').then(() => {
195
+ assert.calledOnce(requestAuthorizationCodeGrantStub);
196
+ assert.calledWith(requestAuthorizationCodeGrantStub, {code, codeVerifier: undefined});
197
+ assert.calledOnce(webex.logger.warn);
198
+ assert.calledWith(
199
+ webex.logger.warn,
200
+ 'authorization: failed initial authorization code grant request',
201
+ error
202
+ );
203
+ });
146
204
  });
147
205
  });
148
206
  describe('when the url contains an error', () => {
149
207
  it('throws a grant error', () => {
150
208
  assert.throws(() => {
151
- makeWebex('http://127.0.0.1:8000/?error=invalid_scope&error_description=The%20requested%20scope%20is%20invalid.');
209
+ makeWebex(
210
+ 'http://127.0.0.1:8000/?error=invalid_scope&error_description=The%20requested%20scope%20is%20invalid.'
211
+ );
152
212
  }, /The requested scope is invalid./);
153
213
  });
154
214
  });
@@ -166,28 +226,14 @@ browserOnly(describe)('plugin-authorization-browser-first-party', () => {
166
226
  it('passes codeVerifier to requestAuthorizationCodeGrant', () => {
167
227
  const expectedVerifier = 'test verifier';
168
228
 
169
- const webex = makeWebex(
170
- 'http://example.com?code=5',
171
- undefined,
172
- expectedVerifier
173
- );
229
+ const webex = makeWebex('http://example.com?code=5', undefined, expectedVerifier);
174
230
 
175
- return webex.authorization.when('change:ready')
176
- .then(() => {
177
- assert.calledTwice(webex.request);
178
- assert.calledWith(
179
- webex.getWindow().sessionStorage.getItem,
180
- 'oauth2-code-verifier'
181
- );
182
- assert.calledWith(
183
- webex.getWindow().sessionStorage.removeItem,
184
- 'oauth2-code-verifier'
185
- );
186
- assert.equal(
187
- webex.request.getCall(1).args[0].form.code_verifier,
188
- expectedVerifier
189
- );
190
- });
231
+ return webex.authorization.when('change:ready').then(() => {
232
+ assert.calledTwice(webex.request);
233
+ assert.calledWith(webex.getWindow().sessionStorage.getItem, 'oauth2-code-verifier');
234
+ assert.calledWith(webex.getWindow().sessionStorage.removeItem, 'oauth2-code-verifier');
235
+ assert.equal(webex.request.getCall(1).args[0].form.code_verifier, expectedVerifier);
236
+ });
191
237
  });
192
238
  });
193
239
  });
@@ -196,95 +242,97 @@ browserOnly(describe)('plugin-authorization-browser-first-party', () => {
196
242
  it('calls #initiateAuthorizationCodeGrant()', () => {
197
243
  const webex = makeWebex(undefined, undefined, {
198
244
  credentials: {
199
- clientType: 'confidential'
200
- }
245
+ clientType: 'confidential',
246
+ },
201
247
  });
202
248
 
203
249
  sinon.spy(webex.authorization, 'initiateAuthorizationCodeGrant');
204
250
 
205
- return webex.authorization.initiateLogin()
206
- .then(() => {
207
- assert.called(webex.authorization.initiateAuthorizationCodeGrant);
208
- assert.include(webex.getWindow().location, 'response_type=code');
209
- });
251
+ return webex.authorization.initiateLogin().then(() => {
252
+ assert.called(webex.authorization.initiateAuthorizationCodeGrant);
253
+ assert.include(webex.getWindow().location, 'response_type=code');
254
+ });
210
255
  });
211
256
 
212
257
  it('adds a csrf_token to the login url and sessionStorage', () => {
213
258
  const webex = makeWebex(undefined, undefined, {
214
259
  credentials: {
215
- clientType: 'confidential'
216
- }
260
+ clientType: 'confidential',
261
+ },
217
262
  });
218
263
 
219
264
  sinon.spy(webex.authorization, 'initiateAuthorizationCodeGrant');
220
265
 
221
- return webex.authorization.initiateLogin()
222
- .then(() => {
223
- assert.called(webex.authorization.initiateAuthorizationCodeGrant);
224
- assert.include(webex.getWindow().location, 'response_type=code');
225
- const {query} = url.parse(webex.getWindow().location, true);
226
- let {state} = query;
227
-
228
- state = JSON.parse(base64.decode(state));
229
- assert.property(state, 'csrf_token');
230
- assert.isDefined(state.csrf_token);
231
- assert.match(state.csrf_token, patterns.uuid);
232
- assert.called(webex.getWindow().sessionStorage.setItem);
233
- assert.calledWith(webex.getWindow().sessionStorage.setItem, 'oauth2-csrf-token', state.csrf_token);
234
- });
266
+ return webex.authorization.initiateLogin().then(() => {
267
+ assert.called(webex.authorization.initiateAuthorizationCodeGrant);
268
+ assert.include(webex.getWindow().location, 'response_type=code');
269
+ const {query} = url.parse(webex.getWindow().location, true);
270
+ let {state} = query;
271
+
272
+ state = JSON.parse(base64.decode(state));
273
+ assert.property(state, 'csrf_token');
274
+ assert.isDefined(state.csrf_token);
275
+ assert.match(state.csrf_token, patterns.uuid);
276
+ assert.called(webex.getWindow().sessionStorage.setItem);
277
+ assert.calledWith(
278
+ webex.getWindow().sessionStorage.setItem,
279
+ 'oauth2-csrf-token',
280
+ state.csrf_token
281
+ );
282
+ });
235
283
  });
236
284
 
237
285
  it('adds a pkce code challenge', () => {
238
286
  const webex = makeWebex(undefined, undefined, {
239
287
  credentials: {
240
- clientType: 'confidential'
241
- }
288
+ clientType: 'confidential',
289
+ },
242
290
  });
243
291
 
244
292
  const expectedCodeChallenge = 'test challenge';
245
293
 
246
294
  sinon.spy(webex.authorization, 'initiateAuthorizationCodeGrant');
247
- sinon.stub(webex.authorization, '_generateCodeChallenge')
248
- .returns(expectedCodeChallenge);
249
-
250
- return webex.authorization.initiateLogin()
251
- .then(() => {
252
- assert.called(webex.authorization.initiateAuthorizationCodeGrant);
253
- const grantOptions = webex.authorization.initiateAuthorizationCodeGrant.getCall(0).args[0];
254
-
255
- assert.equal(grantOptions.code_challenge, expectedCodeChallenge);
256
- assert.equal(grantOptions.code_challenge_method, 'S256');
257
- // eslint-disable-next-line no-underscore-dangle
258
- assert.calledWith(webex.authorization._generateCodeChallenge);
259
- });
295
+ sinon.stub(webex.authorization, '_generateCodeChallenge').returns(expectedCodeChallenge);
296
+
297
+ return webex.authorization.initiateLogin().then(() => {
298
+ assert.called(webex.authorization.initiateAuthorizationCodeGrant);
299
+ const grantOptions =
300
+ webex.authorization.initiateAuthorizationCodeGrant.getCall(0).args[0];
301
+
302
+ assert.equal(grantOptions.code_challenge, expectedCodeChallenge);
303
+ assert.equal(grantOptions.code_challenge_method, 'S256');
304
+ // eslint-disable-next-line no-underscore-dangle
305
+ assert.calledWith(webex.authorization._generateCodeChallenge);
306
+ });
260
307
  });
261
308
 
262
309
  it('adds emailHash', () => {
263
310
  const webex = makeWebex(undefined, undefined, {
264
311
  credentials: {
265
- clientType: 'confidential'
266
- }
312
+ clientType: 'confidential',
313
+ },
267
314
  });
268
315
 
269
- const expectedEmailHash = '73062d872926c2a556f17b36f50e328ddf9bff9d403939bd14b6c3b7f5a33fc2';
316
+ const expectedEmailHash =
317
+ '73062d872926c2a556f17b36f50e328ddf9bff9d403939bd14b6c3b7f5a33fc2';
270
318
 
271
319
  sinon.spy(webex.authorization, 'initiateAuthorizationCodeGrant');
272
320
 
273
- return webex.authorization.initiateLogin({email: 'test@email.com'})
274
- .then(() => {
275
- assert.called(webex.authorization.initiateAuthorizationCodeGrant);
276
- const grantOptions = webex.authorization.initiateAuthorizationCodeGrant.getCall(0).args[0];
321
+ return webex.authorization.initiateLogin({email: 'test@email.com'}).then(() => {
322
+ assert.called(webex.authorization.initiateAuthorizationCodeGrant);
323
+ const grantOptions =
324
+ webex.authorization.initiateAuthorizationCodeGrant.getCall(0).args[0];
277
325
 
278
- assert.equal(grantOptions.emailHash, expectedEmailHash);
279
- assert.isUndefined(grantOptions.email);
280
- });
326
+ assert.equal(grantOptions.emailHash, expectedEmailHash);
327
+ assert.isUndefined(grantOptions.email);
328
+ });
281
329
  });
282
330
 
283
331
  it('sets #isAuthorizing', () => {
284
332
  const webex = makeWebex(undefined, undefined, {
285
333
  credentials: {
286
- clientType: 'confidential'
287
- }
334
+ clientType: 'confidential',
335
+ },
288
336
  });
289
337
 
290
338
  assert.isFalse(webex.authorization.isAuthorizing);
@@ -298,8 +346,8 @@ browserOnly(describe)('plugin-authorization-browser-first-party', () => {
298
346
  it('sets #isAuthenticating', () => {
299
347
  const webex = makeWebex(undefined, undefined, {
300
348
  credentials: {
301
- clientType: 'confidential'
302
- }
349
+ clientType: 'confidential',
350
+ },
303
351
  });
304
352
 
305
353
  assert.isFalse(webex.authorization.isAuthenticating);
@@ -315,17 +363,16 @@ browserOnly(describe)('plugin-authorization-browser-first-party', () => {
315
363
  it('redirects to the login page with response_type=code', () => {
316
364
  const webex = makeWebex(undefined, undefined, {
317
365
  credentials: {
318
- clientType: 'confidential'
319
- }
366
+ clientType: 'confidential',
367
+ },
320
368
  });
321
369
 
322
370
  sinon.spy(webex.authorization, 'initiateAuthorizationCodeGrant');
323
371
 
324
- return webex.authorization.initiateLogin()
325
- .then(() => {
326
- assert.called(webex.authorization.initiateAuthorizationCodeGrant);
327
- assert.include(webex.getWindow().location, 'response_type=code');
328
- });
372
+ return webex.authorization.initiateLogin().then(() => {
373
+ assert.called(webex.authorization.initiateAuthorizationCodeGrant);
374
+ assert.include(webex.getWindow().location, 'response_type=code');
375
+ });
329
376
  });
330
377
  });
331
378
 
@@ -342,7 +389,7 @@ browserOnly(describe)('plugin-authorization-browser-first-party', () => {
342
389
  const toStringStub = sinon.stub().returns(expectedCodeChallenge);
343
390
  const randomStub = sinon.stub(lodash, 'random').returns(0);
344
391
  const sha256Stub = sinon.stub(CryptoJS, 'SHA256').returns({
345
- toString: toStringStub
392
+ toString: toStringStub,
346
393
  });
347
394
 
348
395
  // eslint-disable-next-line no-underscore-dangle
@@ -365,16 +412,16 @@ browserOnly(describe)('plugin-authorization-browser-first-party', () => {
365
412
  it('removes the state parameter when it has no keys', () => {
366
413
  const webex = makeWebex(undefined, undefined, {
367
414
  credentials: {
368
- clientType: 'confidential'
369
- }
415
+ clientType: 'confidential',
416
+ },
370
417
  });
371
418
  const location = {
372
419
  query: {
373
420
  code: 'code',
374
421
  state: {
375
- csrf_token: 'token'
376
- }
377
- }
422
+ csrf_token: 'token',
423
+ },
424
+ },
378
425
  };
379
426
 
380
427
  sinon.spy(webex.authorization, '_cleanUrl');
@@ -386,17 +433,17 @@ browserOnly(describe)('plugin-authorization-browser-first-party', () => {
386
433
  it('keeps the parameter when it has keys', () => {
387
434
  const webex = makeWebex(undefined, undefined, {
388
435
  credentials: {
389
- clientType: 'confidential'
390
- }
436
+ clientType: 'confidential',
437
+ },
391
438
  });
392
439
  const location = {
393
440
  query: {
394
441
  code: 'code',
395
442
  state: {
396
443
  csrf_token: 'token',
397
- key: 'value'
398
- }
399
- }
444
+ key: 'value',
445
+ },
446
+ },
400
447
  };
401
448
 
402
449
  sinon.spy(webex.authorization, '_cleanUrl');