@webex/webex-core 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.
Files changed (188) hide show
  1. package/dist/config.js +1 -11
  2. package/dist/config.js.map +1 -1
  3. package/dist/credentials-config.js +44 -64
  4. package/dist/credentials-config.js.map +1 -1
  5. package/dist/index.js +0 -76
  6. package/dist/index.js.map +1 -1
  7. package/dist/interceptors/auth.js +22 -55
  8. package/dist/interceptors/auth.js.map +1 -1
  9. package/dist/interceptors/default-options.js +0 -20
  10. package/dist/interceptors/default-options.js.map +1 -1
  11. package/dist/interceptors/embargo.js +0 -21
  12. package/dist/interceptors/embargo.js.map +1 -1
  13. package/dist/interceptors/network-timing.js +2 -21
  14. package/dist/interceptors/network-timing.js.map +1 -1
  15. package/dist/interceptors/payload-transformer.js +2 -22
  16. package/dist/interceptors/payload-transformer.js.map +1 -1
  17. package/dist/interceptors/rate-limit.js +25 -57
  18. package/dist/interceptors/rate-limit.js.map +1 -1
  19. package/dist/interceptors/redirect.js +4 -33
  20. package/dist/interceptors/redirect.js.map +1 -1
  21. package/dist/interceptors/request-event.js +3 -30
  22. package/dist/interceptors/request-event.js.map +1 -1
  23. package/dist/interceptors/request-logger.js +1 -30
  24. package/dist/interceptors/request-logger.js.map +1 -1
  25. package/dist/interceptors/request-timing.js +3 -22
  26. package/dist/interceptors/request-timing.js.map +1 -1
  27. package/dist/interceptors/response-logger.js +2 -31
  28. package/dist/interceptors/response-logger.js.map +1 -1
  29. package/dist/interceptors/user-agent.js +2 -29
  30. package/dist/interceptors/user-agent.js.map +1 -1
  31. package/dist/interceptors/webex-tracking-id.js +5 -28
  32. package/dist/interceptors/webex-tracking-id.js.map +1 -1
  33. package/dist/interceptors/webex-user-agent.js +5 -38
  34. package/dist/interceptors/webex-user-agent.js.map +1 -1
  35. package/dist/lib/batcher.js +3 -51
  36. package/dist/lib/batcher.js.map +1 -1
  37. package/dist/lib/constants.js +14 -0
  38. package/dist/lib/constants.js.map +1 -0
  39. package/dist/lib/credentials/credentials.js +98 -139
  40. package/dist/lib/credentials/credentials.js.map +1 -1
  41. package/dist/lib/credentials/grant-errors.js +0 -49
  42. package/dist/lib/credentials/grant-errors.js.map +1 -1
  43. package/dist/lib/credentials/index.js +1 -13
  44. package/dist/lib/credentials/index.js.map +1 -1
  45. package/dist/lib/credentials/scope.js +25 -14
  46. package/dist/lib/credentials/scope.js.map +1 -1
  47. package/dist/lib/credentials/token-collection.js +1 -7
  48. package/dist/lib/credentials/token-collection.js.map +1 -1
  49. package/dist/lib/credentials/token.js +42 -118
  50. package/dist/lib/credentials/token.js.map +1 -1
  51. package/dist/lib/page.js +13 -26
  52. package/dist/lib/page.js.map +1 -1
  53. package/dist/lib/services/constants.js +0 -2
  54. package/dist/lib/services/constants.js.map +1 -1
  55. package/dist/lib/services/index.js +1 -28
  56. package/dist/lib/services/index.js.map +1 -1
  57. package/dist/lib/services/interceptors/server-error.js +2 -23
  58. package/dist/lib/services/interceptors/server-error.js.map +1 -1
  59. package/dist/lib/services/interceptors/service.js +15 -35
  60. package/dist/lib/services/interceptors/service.js.map +1 -1
  61. package/dist/lib/services/metrics.js +0 -2
  62. package/dist/lib/services/metrics.js.map +1 -1
  63. package/dist/lib/services/service-catalog.js +12 -91
  64. package/dist/lib/services/service-catalog.js.map +1 -1
  65. package/dist/lib/services/service-fed-ramp.js +0 -2
  66. package/dist/lib/services/service-fed-ramp.js.map +1 -1
  67. package/dist/lib/services/service-host.js +47 -62
  68. package/dist/lib/services/service-host.js.map +1 -1
  69. package/dist/lib/services/service-registry.js +78 -90
  70. package/dist/lib/services/service-registry.js.map +1 -1
  71. package/dist/lib/services/service-state.js +3 -15
  72. package/dist/lib/services/service-state.js.map +1 -1
  73. package/dist/lib/services/service-url.js +4 -25
  74. package/dist/lib/services/service-url.js.map +1 -1
  75. package/dist/lib/services/services.js +135 -239
  76. package/dist/lib/services/services.js.map +1 -1
  77. package/dist/lib/stateless-webex-plugin.js +5 -28
  78. package/dist/lib/stateless-webex-plugin.js.map +1 -1
  79. package/dist/lib/storage/decorators.js +19 -62
  80. package/dist/lib/storage/decorators.js.map +1 -1
  81. package/dist/lib/storage/errors.js +0 -23
  82. package/dist/lib/storage/errors.js.map +1 -1
  83. package/dist/lib/storage/index.js +2 -16
  84. package/dist/lib/storage/index.js.map +1 -1
  85. package/dist/lib/storage/make-webex-plugin-store.js +11 -41
  86. package/dist/lib/storage/make-webex-plugin-store.js.map +1 -1
  87. package/dist/lib/storage/make-webex-store.js +8 -30
  88. package/dist/lib/storage/make-webex-store.js.map +1 -1
  89. package/dist/lib/storage/memory-store-adapter.js +1 -19
  90. package/dist/lib/storage/memory-store-adapter.js.map +1 -1
  91. package/dist/lib/webex-core-plugin-mixin.js +9 -29
  92. package/dist/lib/webex-core-plugin-mixin.js.map +1 -1
  93. package/dist/lib/webex-http-error.js +1 -31
  94. package/dist/lib/webex-http-error.js.map +1 -1
  95. package/dist/lib/webex-internal-core-plugin-mixin.js +9 -29
  96. package/dist/lib/webex-internal-core-plugin-mixin.js.map +1 -1
  97. package/dist/lib/webex-plugin.js +6 -40
  98. package/dist/lib/webex-plugin.js.map +1 -1
  99. package/dist/plugins/logger.js +3 -17
  100. package/dist/plugins/logger.js.map +1 -1
  101. package/dist/webex-core.js +84 -203
  102. package/dist/webex-core.js.map +1 -1
  103. package/dist/webex-internal-core.js +0 -10
  104. package/dist/webex-internal-core.js.map +1 -1
  105. package/package.json +14 -14
  106. package/src/config.js +9 -11
  107. package/src/credentials-config.js +110 -72
  108. package/src/index.js +4 -14
  109. package/src/interceptors/auth.js +36 -37
  110. package/src/interceptors/default-options.js +0 -1
  111. package/src/interceptors/embargo.js +1 -1
  112. package/src/interceptors/payload-transformer.js +1 -2
  113. package/src/interceptors/rate-limit.js +8 -5
  114. package/src/interceptors/redirect.js +14 -8
  115. package/src/interceptors/request-event.js +4 -8
  116. package/src/interceptors/request-logger.js +8 -5
  117. package/src/interceptors/response-logger.js +11 -8
  118. package/src/interceptors/user-agent.js +1 -2
  119. package/src/interceptors/webex-user-agent.js +3 -9
  120. package/src/lib/batcher.js +70 -69
  121. package/src/lib/constants.js +6 -0
  122. package/src/lib/credentials/credentials.js +173 -141
  123. package/src/lib/credentials/grant-errors.js +6 -7
  124. package/src/lib/credentials/index.js +1 -4
  125. package/src/lib/credentials/scope.js +24 -8
  126. package/src/lib/credentials/token-collection.js +1 -1
  127. package/src/lib/credentials/token.js +95 -81
  128. package/src/lib/page.js +10 -11
  129. package/src/lib/services/constants.js +3 -13
  130. package/src/lib/services/index.js +2 -2
  131. package/src/lib/services/interceptors/server-error.js +12 -7
  132. package/src/lib/services/interceptors/service.js +7 -6
  133. package/src/lib/services/metrics.js +1 -1
  134. package/src/lib/services/service-catalog.js +112 -100
  135. package/src/lib/services/service-fed-ramp.js +1 -2
  136. package/src/lib/services/service-host.js +10 -17
  137. package/src/lib/services/service-registry.js +69 -96
  138. package/src/lib/services/service-state.js +4 -6
  139. package/src/lib/services/service-url.js +24 -23
  140. package/src/lib/services/services.js +272 -249
  141. package/src/lib/stateless-webex-plugin.js +4 -2
  142. package/src/lib/storage/decorators.js +68 -66
  143. package/src/lib/storage/index.js +4 -6
  144. package/src/lib/storage/make-webex-plugin-store.js +34 -21
  145. package/src/lib/storage/make-webex-store.js +6 -7
  146. package/src/lib/storage/memory-store-adapter.js +3 -3
  147. package/src/lib/webex-core-plugin-mixin.js +10 -7
  148. package/src/lib/webex-http-error.js +7 -8
  149. package/src/lib/webex-internal-core-plugin-mixin.js +9 -6
  150. package/src/lib/webex-plugin.js +41 -34
  151. package/src/plugins/logger.js +8 -3
  152. package/src/webex-core.js +198 -117
  153. package/src/webex-internal-core.js +15 -9
  154. package/test/integration/spec/credentials/credentials.js +26 -30
  155. package/test/integration/spec/credentials/token.js +36 -33
  156. package/test/integration/spec/services/service-catalog.js +177 -156
  157. package/test/integration/spec/services/services.js +313 -304
  158. package/test/integration/spec/webex-core.js +98 -86
  159. package/test/unit/spec/_setup.js +26 -18
  160. package/test/unit/spec/credentials/credentials.js +352 -162
  161. package/test/unit/spec/credentials/scope.js +80 -0
  162. package/test/unit/spec/credentials/token.js +105 -77
  163. package/test/unit/spec/interceptors/auth.js +294 -243
  164. package/test/unit/spec/interceptors/default-options.js +36 -24
  165. package/test/unit/spec/interceptors/embargo.js +32 -27
  166. package/test/unit/spec/interceptors/network-timing.js +2 -2
  167. package/test/unit/spec/interceptors/payload-transformer.js +61 -52
  168. package/test/unit/spec/interceptors/rate-limit.js +104 -75
  169. package/test/unit/spec/interceptors/redirect.js +22 -20
  170. package/test/unit/spec/interceptors/request-timing.js +18 -22
  171. package/test/unit/spec/interceptors/user-agent.js +28 -16
  172. package/test/unit/spec/interceptors/webex-tracking-id.js +14 -8
  173. package/test/unit/spec/interceptors/webex-user-agent.js +83 -37
  174. package/test/unit/spec/lib/batcher.js +36 -32
  175. package/test/unit/spec/lib/page.js +36 -32
  176. package/test/unit/spec/lib/webex-plugin.js +1 -1
  177. package/test/unit/spec/services/interceptors/server-error.js +67 -90
  178. package/test/unit/spec/services/interceptors/service.js +23 -28
  179. package/test/unit/spec/services/service-catalog.js +19 -27
  180. package/test/unit/spec/services/service-host.js +29 -26
  181. package/test/unit/spec/services/service-registry.js +128 -170
  182. package/test/unit/spec/services/service-state.js +13 -22
  183. package/test/unit/spec/services/service-url.js +24 -43
  184. package/test/unit/spec/services/services.js +147 -41
  185. package/test/unit/spec/storage/persist.js +6 -9
  186. package/test/unit/spec/storage/wait-for-value.js +22 -21
  187. package/test/unit/spec/webex-core.js +90 -57
  188. package/test/unit/spec/webex-internal-core.js +56 -31
@@ -6,52 +6,40 @@ import querystring from 'querystring';
6
6
  import url from 'url';
7
7
 
8
8
  import jwt from 'jsonwebtoken';
9
- import {
10
- base64,
11
- makeStateDataType,
12
- oneFlight,
13
- tap,
14
- whileInFlight
15
- } from '@webex/common';
9
+ import {base64, makeStateDataType, oneFlight, tap, whileInFlight} from '@webex/common';
16
10
  import {safeSetTimeout} from '@webex/common-timers';
17
11
  import {clone, cloneDeep, isObject, isEmpty} from 'lodash';
18
12
 
19
13
  import WebexPlugin from '../webex-plugin';
20
14
  import {persist, waitForValue} from '../storage/decorators';
21
15
 
22
- import grantErrors from './grant-errors';
23
- import {filterScope, sortScope} from './scope';
16
+ import grantErrors, {OAuthError} from './grant-errors';
17
+ import {filterScope, diffScopes, sortScope} from './scope';
24
18
  import Token from './token';
25
19
  import TokenCollection from './token-collection';
20
+ import {METRICS} from '../constants';
26
21
 
27
22
  /**
28
23
  * @class
29
24
  */
30
25
  const Credentials = WebexPlugin.extend({
31
26
  collections: {
32
- userTokens: TokenCollection
27
+ userTokens: TokenCollection,
33
28
  },
34
29
 
35
30
  dataTypes: {
36
- token: makeStateDataType(Token, 'token').dataType
31
+ token: makeStateDataType(Token, 'token').dataType,
37
32
  },
38
33
 
39
34
  derived: {
40
35
  canAuthorize: {
41
- deps: [
42
- 'supertoken',
43
- 'supertoken.canAuthorize',
44
- 'canRefresh'
45
- ],
36
+ deps: ['supertoken', 'supertoken.canAuthorize', 'canRefresh'],
46
37
  fn() {
47
- return Boolean(this.supertoken && this.supertoken.canAuthorize || this.canRefresh);
48
- }
38
+ return Boolean((this.supertoken && this.supertoken.canAuthorize) || this.canRefresh);
39
+ },
49
40
  },
50
41
  canRefresh: {
51
- deps: [
52
- 'supertoken',
53
- 'supertoken.canRefresh'
54
- ],
42
+ deps: ['supertoken', 'supertoken.canRefresh'],
55
43
  fn() {
56
44
  // If we're operating in JWT mode, we have to delegate to the consumer
57
45
  if (this.config.jwtRefreshCallback) {
@@ -59,12 +47,31 @@ const Credentials = WebexPlugin.extend({
59
47
  }
60
48
 
61
49
  return Boolean(this.supertoken && this.supertoken.canRefresh);
62
- }
63
- }
50
+ },
51
+ },
52
+ isUnverifiedGuest: {
53
+ deps: ['supertoken'],
54
+ /**
55
+ * Returns true if the user is an unverified guest
56
+ * @returns {boolean}
57
+ */
58
+ fn() {
59
+ let isGuest = false;
60
+ try {
61
+ isGuest =
62
+ JSON.parse(base64.decode(this.supertoken.access_token.split('.')[1])).user_type ===
63
+ 'guest';
64
+ } catch {
65
+ /* the non-guest token is formatted differently so catch is expected */
66
+ }
67
+
68
+ return isGuest;
69
+ },
70
+ },
64
71
  },
65
72
 
66
73
  props: {
67
- supertoken: makeStateDataType(Token, 'token').prop
74
+ supertoken: makeStateDataType(Token, 'token').prop,
68
75
  },
69
76
 
70
77
  namespace: 'Credentials',
@@ -72,7 +79,7 @@ const Credentials = WebexPlugin.extend({
72
79
  session: {
73
80
  isRefreshing: {
74
81
  default: false,
75
- type: 'boolean'
82
+ type: 'boolean',
76
83
  },
77
84
  /**
78
85
  * Becomes `true` once the {@link loaded} event fires.
@@ -83,12 +90,12 @@ const Credentials = WebexPlugin.extend({
83
90
  */
84
91
  ready: {
85
92
  default: false,
86
- type: 'boolean'
93
+ type: 'boolean',
87
94
  },
88
95
  refreshTimer: {
89
96
  default: undefined,
90
- type: 'any'
91
- }
97
+ type: 'any',
98
+ },
92
99
  },
93
100
 
94
101
  /**
@@ -120,8 +127,7 @@ const Credentials = WebexPlugin.extend({
120
127
  if (options.state) {
121
128
  if (!isEmpty(options.state)) {
122
129
  options.state = base64.toBase64Url(JSON.stringify(options.state));
123
- }
124
- else {
130
+ } else {
125
131
  delete options.state;
126
132
  }
127
133
  }
@@ -137,27 +143,21 @@ const Credentials = WebexPlugin.extend({
137
143
  * @returns {string} - The OrgId.
138
144
  */
139
145
  getOrgId() {
140
- this.logger.info(
141
- 'credentials: attempting to retrieve the OrgId from token'
142
- );
146
+ this.logger.info('credentials: attempting to retrieve the OrgId from token');
143
147
 
144
148
  try {
145
149
  // Attempt to extract a client-authenticated token's OrgId.
146
150
  this.logger.info('credentials: trying to extract OrgId from JWT');
147
151
 
148
152
  return this.extractOrgIdFromJWT(this.supertoken.access_token);
149
- }
150
- catch (e) {
153
+ } catch (e) {
151
154
  // Attempt to extract a user token's OrgId.
152
155
  this.logger.info('credentials: could not extract OrgId from JWT');
153
- this.logger.info(
154
- 'credentials: attempting to extract OrgId from user token'
155
- );
156
+ this.logger.info('credentials: attempting to extract OrgId from user token');
156
157
 
157
158
  try {
158
159
  return this.extractOrgIdFromUserToken(this.supertoken?.access_token);
159
- }
160
- catch (f) {
160
+ } catch (f) {
161
161
  this.logger.info('credentials: could not extract OrgId from user token');
162
162
  throw f;
163
163
  }
@@ -218,10 +218,11 @@ const Credentials = WebexPlugin.extend({
218
218
  * @returns {[type]}
219
219
  */
220
220
  buildLogoutUrl(options = {}) {
221
- return `${this.config.logoutUrl}?${querystring.stringify(Object.assign({
221
+ return `${this.config.logoutUrl}?${querystring.stringify({
222
222
  cisService: this.config.service,
223
- goto: this.config.redirect_uri
224
- }, options))}`;
223
+ goto: this.config.redirect_uri,
224
+ ...options,
225
+ })}`;
225
226
  },
226
227
 
227
228
  /**
@@ -233,7 +234,7 @@ const Credentials = WebexPlugin.extend({
233
234
  * @returns {number}
234
235
  */
235
236
  calcRefreshTimeout(expiration) {
236
- return Math.floor((Math.floor(Math.random() * 4) + 6) / 10 * expiration);
237
+ return Math.floor(((Math.floor(Math.random() * 4) + 6) / 10) * expiration);
237
238
  },
238
239
 
239
240
  constructor(...args) {
@@ -258,13 +259,21 @@ const Credentials = WebexPlugin.extend({
258
259
  * @returns {Promise<Token>}
259
260
  */
260
261
  downscope(scope) {
261
- return this.supertoken.downscope(scope)
262
- .catch((reason) => {
263
- this.logger.trace(`credentials: failed to downscope supertoken to ${scope}`, reason);
264
- this.logger.trace(`credentials: falling back to supertoken for ${scope}`);
262
+ return this.supertoken.downscope(scope).catch((reason) => {
263
+ const failReason = reason?.body ?? reason;
264
+ this.logger.warn(`credentials: failed to downscope supertoken to "${scope}"`, failReason);
265
+ this.logger.trace(`credentials: falling back to supertoken for ${scope}`);
266
+ this.webex.internal.metrics.submitClientMetrics(METRICS.JS_SDK_CREDENTIALS_DOWNSCOPE_FAILED, {
267
+ fields: {
268
+ requestedScope: scope,
269
+ failReason,
270
+ },
271
+ });
265
272
 
266
- return Promise.resolve(new Token(Object.assign({scope}, this.supertoken.serialize())), {parent: this});
273
+ return Promise.resolve(new Token({scope, ...this.supertoken.serialize()}), {
274
+ parent: this,
267
275
  });
276
+ });
268
277
  },
269
278
 
270
279
  /**
@@ -279,23 +288,24 @@ const Credentials = WebexPlugin.extend({
279
288
  getClientToken(options = {}) {
280
289
  this.logger.info('credentials: requesting client credentials grant');
281
290
 
282
- return this.webex.request({
283
- /* eslint-disable camelcase */
284
- method: 'POST',
285
- uri: options.uri || this.config.tokenUrl,
286
- form: {
287
- grant_type: 'client_credentials',
288
- scope: options.scope || 'webexsquare:admin',
289
- self_contained_token: true
290
- },
291
- auth: {
292
- user: this.config.client_id,
293
- pass: this.config.client_secret,
294
- sendImmediately: true
295
- },
296
- shouldRefreshAccessToken: false
297
- /* eslint-enable camelcase */
298
- })
291
+ return this.webex
292
+ .request({
293
+ /* eslint-disable camelcase */
294
+ method: 'POST',
295
+ uri: options.uri || this.config.tokenUrl,
296
+ form: {
297
+ grant_type: 'client_credentials',
298
+ scope: options.scope || 'webexsquare:admin',
299
+ self_contained_token: true,
300
+ },
301
+ auth: {
302
+ user: this.config.client_id,
303
+ pass: this.config.client_secret,
304
+ sendImmediately: true,
305
+ },
306
+ shouldRefreshAccessToken: false,
307
+ /* eslint-enable camelcase */
308
+ })
299
309
  .then((res) => new Token(res.body, {parent: this}))
300
310
  .catch((res) => {
301
311
  if (res.statusCode !== 400) {
@@ -320,41 +330,44 @@ const Credentials = WebexPlugin.extend({
320
330
  * @returns {Promise<Token>}
321
331
  */
322
332
  getUserToken(scope) {
323
- return Promise.resolve(!this.isRefreshing || new Promise((resolve) => {
324
- this.logger.info('credentials: token refresh inflight; delaying getUserToken until refresh completes');
325
- this.once('change:isRefreshing', () => {
326
- this.logger.info('credentials: token refresh complete; reinvoking getUserToken');
327
- resolve();
328
- });
329
- }))
330
- .then(() => {
331
- if (!this.canAuthorize) {
332
- this.logger.info('credentials: cannot produce an access token from current state');
333
-
334
- return Promise.reject(new Error('Current state cannot produce an access token'));
335
- }
333
+ return Promise.resolve(
334
+ !this.isRefreshing ||
335
+ new Promise((resolve) => {
336
+ this.logger.info(
337
+ 'credentials: token refresh inflight; delaying getUserToken until refresh completes'
338
+ );
339
+ this.once('change:isRefreshing', () => {
340
+ this.logger.info('credentials: token refresh complete; reinvoking getUserToken');
341
+ resolve();
342
+ });
343
+ })
344
+ ).then(() => {
345
+ if (!this.canAuthorize) {
346
+ this.logger.info('credentials: cannot produce an access token from current state');
347
+
348
+ return Promise.reject(new Error('Current state cannot produce an access token'));
349
+ }
336
350
 
337
- if (!scope) {
338
- scope = filterScope('spark:kms', this.config.scope);
339
- }
351
+ if (!scope) {
352
+ scope = filterScope('spark:kms', this.supertoken.scope);
353
+ }
340
354
 
341
- scope = sortScope(scope);
355
+ scope = sortScope(scope);
342
356
 
343
- if (scope === sortScope(this.config.scope)) {
344
- return Promise.resolve(this.supertoken);
345
- }
357
+ if (scope === sortScope(this.supertoken.scope)) {
358
+ return Promise.resolve(this.supertoken);
359
+ }
346
360
 
347
- const token = this.userTokens.get(scope);
361
+ const token = this.userTokens.get(scope);
348
362
 
349
- // we should also check for the token.access_token since token object does
350
- // not get cleared on unsetting while logging out.
351
- if (!token || !token.access_token) {
352
- return this.downscope(scope)
353
- .then(tap((t) => this.userTokens.add(t)));
354
- }
363
+ // we should also check for the token.access_token since token object does
364
+ // not get cleared on unsetting while logging out.
365
+ if (!token || !token.access_token) {
366
+ return this.downscope(scope).then(tap((t) => this.userTokens.add(t)));
367
+ }
355
368
 
356
- return Promise.resolve(token);
357
- });
369
+ return Promise.resolve(token);
370
+ });
358
371
  },
359
372
 
360
373
  @persist('@')
@@ -380,8 +393,7 @@ const Credentials = WebexPlugin.extend({
380
393
  if (attrs.authorization) {
381
394
  if (attrs.authorization.supertoken) {
382
395
  this.supertoken = attrs.authorization.supertoken;
383
- }
384
- else {
396
+ } else {
385
397
  this.supertoken = attrs.authorization;
386
398
  }
387
399
  }
@@ -434,16 +446,14 @@ const Credentials = WebexPlugin.extend({
434
446
 
435
447
  try {
436
448
  this.unset('supertoken');
437
- }
438
- catch (err) {
449
+ } catch (err) {
439
450
  this.logger.warn('credentials: failed to clear supertoken', err);
440
451
  }
441
452
 
442
453
  while (this.userTokens.models.length) {
443
454
  try {
444
455
  this.userTokens.remove(this.userTokens.models[0]);
445
- }
446
- catch (err) {
456
+ } catch (err) {
447
457
  this.logger.warn('credentials: failed to remove user token', err);
448
458
  }
449
459
  }
@@ -480,52 +490,29 @@ const Credentials = WebexPlugin.extend({
480
490
  // while I like #2 from a code simplicity standpoint, the third-party DX
481
491
  // isn't great
482
492
  if (this.config.jwtRefreshCallback) {
483
- return this.config.jwtRefreshCallback(this.webex)
484
- .then((jwt) => this.webex.authorization.requestAccessTokenFromJwt({jwt}));
493
+ return (
494
+ this.config
495
+ .jwtRefreshCallback(this.webex)
496
+ // eslint-disable-next-line no-shadow
497
+ .then((jwt) => this.webex.authorization.requestAccessTokenFromJwt({jwt}))
498
+ );
485
499
  }
486
500
 
487
501
  if (this.webex.internal.services) {
488
502
  this.webex.internal.services.updateCredentialsConfig();
489
503
  }
490
504
 
491
- return supertoken.refresh()
492
- .then((st) => {
493
- // clear refresh timer
494
- if (this.refreshTimer) {
495
- clearTimeout(this.refreshTimer);
496
- this.unset('refreshTimer');
497
- }
498
- this.supertoken = st;
499
-
500
- return Promise.all(tokens.map((token) => this.downscope(token.scope)
501
- // eslint-disable-next-line max-nested-callbacks
502
- .then((t) => {
503
- this.logger.info(`credentials: revoking token for ${token.scope}`);
504
-
505
- return token.revoke()
506
- .catch((err) => {
507
- this.logger.warn('credentials: failed to revoke user token', err);
508
- })
509
- .then(() => {
510
- this.userTokens.remove(token.scope);
511
- this.userTokens.add(t);
512
- });
513
- })));
514
- })
515
- .then(() => {
516
- this.scheduleRefresh(this.supertoken.expires);
517
- })
505
+ return supertoken
506
+ .refresh()
518
507
  .catch((error) => {
519
- const {InvalidRequestError} = grantErrors;
520
-
521
- if (error instanceof InvalidRequestError) {
522
- // Error: The refresh token provided is expired, revoked, malformed, or invalid. Hence emit an event to the client, an opportunity to logout.
508
+ if (error instanceof OAuthError) {
509
+ // Error: super token refresh failed with 400 status code.
510
+ // Hence emit an event to the client, an opportunity to logout.
523
511
  this.unset('supertoken');
524
512
  while (this.userTokens.models.length) {
525
513
  try {
526
514
  this.userTokens.remove(this.userTokens.models[0]);
527
- }
528
- catch (err) {
515
+ } catch (err) {
529
516
  this.logger.warn('credentials: failed to remove user token', err);
530
517
  }
531
518
  }
@@ -533,6 +520,53 @@ const Credentials = WebexPlugin.extend({
533
520
  }
534
521
 
535
522
  return Promise.reject(error);
523
+ })
524
+ .then((st) => {
525
+ // clear refresh timer
526
+ if (this.refreshTimer) {
527
+ clearTimeout(this.refreshTimer);
528
+ this.unset('refreshTimer');
529
+ }
530
+ this.supertoken = st;
531
+
532
+ const invalidScopes = diffScopes(this.config.scope, st.scope);
533
+
534
+ if (invalidScopes !== '') {
535
+ this.logger.warn(
536
+ `credentials: "${invalidScopes}" scope(s) are invalid because not listed in the supertoken, they will be excluded from user token requests.`
537
+ );
538
+ this.webex.internal.metrics.submitClientMetrics(
539
+ METRICS.JS_SDK_CREDENTIALS_TOKEN_REFRESH_SCOPE_MISMATCH,
540
+ {fields: {invalidScopes}}
541
+ );
542
+ }
543
+
544
+ return Promise.all(
545
+ tokens.map((token) => {
546
+ const tokenScope = filterScope(diffScopes(token.scope, st.scope), token.scope);
547
+
548
+ return (
549
+ this.downscope(tokenScope)
550
+ // eslint-disable-next-line max-nested-callbacks
551
+ .then((t) => {
552
+ this.logger.info(`credentials: revoking token for ${token.scope}`);
553
+
554
+ return token
555
+ .revoke()
556
+ .catch((err) => {
557
+ this.logger.warn('credentials: failed to revoke user token', err);
558
+ })
559
+ .then(() => {
560
+ this.userTokens.remove(token.scope);
561
+ this.userTokens.add(t);
562
+ });
563
+ })
564
+ );
565
+ })
566
+ );
567
+ })
568
+ .then(() => {
569
+ this.scheduleRefresh(this.supertoken.expires);
536
570
  });
537
571
  },
538
572
 
@@ -551,12 +585,10 @@ const Credentials = WebexPlugin.extend({
551
585
  const timeoutLength = this.calcRefreshTimeout(expiresIn);
552
586
 
553
587
  this.refreshTimer = safeSetTimeout(() => this.refresh(), timeoutLength);
554
- }
555
- else {
588
+ } else {
556
589
  this.refresh();
557
590
  }
558
- }
559
-
591
+ },
560
592
  });
561
593
 
562
594
  export default Credentials;
@@ -21,20 +21,20 @@ export class OAuthError extends Exception {
21
21
  Object.defineProperties(this, {
22
22
  error: {
23
23
  enumerable: true,
24
- value: body.error
24
+ value: body.error,
25
25
  },
26
26
  errorDescription: {
27
27
  enumerable: true,
28
- value: body.error_description
28
+ value: body.error_description,
29
29
  },
30
30
  errorUri: {
31
31
  enumerable: true,
32
- value: body.error_uri
32
+ value: body.error_uri,
33
33
  },
34
34
  res: {
35
35
  enumerable: false,
36
- value: res
37
- }
36
+ value: res,
37
+ },
38
38
  });
39
39
 
40
40
  return this.errorDescription;
@@ -70,7 +70,6 @@ class UnsupportGrantTypeError extends OAuthError {}
70
70
  */
71
71
  class InvalidScopeError extends OAuthError {}
72
72
 
73
-
74
73
  const errors = {
75
74
  OAuthError,
76
75
  InvalidRequestError,
@@ -87,7 +86,7 @@ const errors = {
87
86
  invalid_scope: InvalidScopeError,
88
87
  select(errorString) {
89
88
  return errors[errorString] || OAuthError;
90
- }
89
+ },
91
90
  };
92
91
 
93
92
  export default errors;
@@ -7,10 +7,7 @@ import {registerPlugin} from '../../webex-core';
7
7
  import Credentials from './credentials';
8
8
 
9
9
  registerPlugin('credentials', Credentials, {
10
- proxies: [
11
- 'canAuthorize',
12
- 'canRefresh'
13
- ]
10
+ proxies: ['canAuthorize', 'canRefresh'],
14
11
  });
15
12
 
16
13
  export {default as Credentials} from './credentials';
@@ -2,6 +2,10 @@
2
2
  * Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.
3
3
  */
4
4
 
5
+ import {difference} from 'lodash';
6
+
7
+ const SCOPE_SEPARATOR = ' ';
8
+
5
9
  /**
6
10
  * sorts a list of scopes
7
11
  * @param {string} scope
@@ -12,15 +16,12 @@ export function sortScope(scope) {
12
16
  return '';
13
17
  }
14
18
 
15
- return scope
16
- .split(' ')
17
- .sort()
18
- .join(' ');
19
+ return scope.split(SCOPE_SEPARATOR).sort().join(SCOPE_SEPARATOR);
19
20
  }
20
21
 
21
22
  /**
22
23
  * sorts a list of scopes and filters the specified scope
23
- * @param {string} toFilter
24
+ * @param {string|string[]} toFilter
24
25
  * @param {string} scope
25
26
  * @returns {string}
26
27
  */
@@ -28,10 +29,25 @@ export function filterScope(toFilter, scope) {
28
29
  if (!scope) {
29
30
  return '';
30
31
  }
32
+ const toFilterArr = Array.isArray(toFilter) ? toFilter : [toFilter];
31
33
 
32
34
  return scope
33
- .split(' ')
34
- .filter((item) => item !== toFilter)
35
+ .split(SCOPE_SEPARATOR)
36
+ .filter((item) => !toFilterArr.includes(item))
35
37
  .sort()
36
- .join(' ');
38
+ .join(SCOPE_SEPARATOR);
39
+ }
40
+
41
+ /**
42
+ * Returns a string containing all items in scopeA that are not in scopeB, or an empty string if there are none.
43
+ *
44
+ * @param {string} scopeA
45
+ * @param {string} scopeB
46
+ * @returns {string}
47
+ */
48
+ export function diffScopes(scopeA, scopeB) {
49
+ const a = scopeA?.split(SCOPE_SEPARATOR) ?? [];
50
+ const b = scopeB?.split(SCOPE_SEPARATOR) ?? [];
51
+
52
+ return difference(a, b).sort().join(SCOPE_SEPARATOR);
37
53
  }
@@ -11,7 +11,7 @@ const TokenCollection = AmpCollection.extend({
11
11
 
12
12
  model: Token,
13
13
 
14
- namespace: 'Credentials'
14
+ namespace: 'Credentials',
15
15
  });
16
16
 
17
17
  export default TokenCollection;