@webex/plugin-authorization-browser-first-party 3.6.0 → 3.7.0-ipv6-multi-turn-urls.2

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.
@@ -5,7 +5,7 @@ var _interopRequireDefault = require("@babel/runtime-corejs2/helpers/interopRequ
5
5
  _Object$defineProperty(exports, "__esModule", {
6
6
  value: true
7
7
  });
8
- exports.default = void 0;
8
+ exports.default = exports.Events = void 0;
9
9
  var _apply = _interopRequireDefault(require("@babel/runtime-corejs2/core-js/reflect/apply"));
10
10
  var _promise = _interopRequireDefault(require("@babel/runtime-corejs2/core-js/promise"));
11
11
  var _assign = _interopRequireDefault(require("@babel/runtime-corejs2/core-js/object/assign"));
@@ -15,6 +15,7 @@ var _getOwnPropertyDescriptor = _interopRequireDefault(require("@babel/runtime-c
15
15
  var _applyDecoratedDescriptor2 = _interopRequireDefault(require("@babel/runtime-corejs2/helpers/applyDecoratedDescriptor"));
16
16
  var _querystring = _interopRequireDefault(require("querystring"));
17
17
  var _url = _interopRequireDefault(require("url"));
18
+ var _events = require("events");
18
19
  var _common = require("@webex/common");
19
20
  var _webexCore = require("@webex/webex-core");
20
21
  var _lodash = require("lodash");
@@ -32,6 +33,16 @@ var lodash = require('lodash');
32
33
  var OAUTH2_CSRF_TOKEN = 'oauth2-csrf-token';
33
34
  var OAUTH2_CODE_VERIFIER = 'oauth2-code-verifier';
34
35
 
36
+ /**
37
+ * Authorization plugin events
38
+ */
39
+ var Events = exports.Events = {
40
+ /**
41
+ * QR code login events
42
+ */
43
+ qRCodeLogin: 'qRCodeLogin'
44
+ };
45
+
35
46
  /**
36
47
  * Browser support for OAuth2. Automatically parses the URL query for an
37
48
  * authorization code
@@ -74,6 +85,46 @@ var Authorization = _webexCore.WebexPlugin.extend((_dec = (0, _common.whileInFli
74
85
  }
75
86
  },
76
87
  namespace: 'Credentials',
88
+ /**
89
+ * EventEmitter for authorization events
90
+ * @instance
91
+ * @memberof AuthorizationBrowserFirstParty
92
+ * @type {EventEmitter}
93
+ * @public
94
+ */
95
+ eventEmitter: new _events.EventEmitter(),
96
+ /**
97
+ * Stores the timer ID for QR code polling
98
+ * @instance
99
+ * @memberof AuthorizationBrowserFirstParty
100
+ * @type {?number}
101
+ * @private
102
+ */
103
+ pollingTimer: null,
104
+ /**
105
+ * Stores the expiration timer ID for QR code polling
106
+ * @instance
107
+ * @memberof AuthorizationBrowserFirstParty
108
+ * @type {?number}
109
+ * @private
110
+ */
111
+ pollingExpirationTimer: null,
112
+ /**
113
+ * Monotonically increasing id to identify the current polling request
114
+ * @instance
115
+ * @memberof AuthorizationBrowserFirstParty
116
+ * @type {number}
117
+ * @private
118
+ */
119
+ pollingId: 0,
120
+ /**
121
+ * Identifier for the current polling request
122
+ * @instance
123
+ * @memberof AuthorizationBrowserFirstParty
124
+ * @type {?number}
125
+ * @private
126
+ */
127
+ currentPollingId: null,
77
128
  /**
78
129
  * Initializer
79
130
  * @instance
@@ -231,6 +282,203 @@ var Authorization = _webexCore.WebexPlugin.extend((_dec = (0, _common.whileInFli
231
282
  return _promise.default.reject(new ErrorConstructor(res._res || res));
232
283
  });
233
284
  },
285
+ /**
286
+ * Generate a QR code URL to launch the Webex app when scanning with the camera
287
+ * @instance
288
+ * @memberof AuthorizationBrowserFirstParty
289
+ * @param {String} verificationUrl
290
+ * @returns {String}
291
+ */
292
+ _generateQRCodeVerificationUrl: function _generateQRCodeVerificationUrl(verificationUrl) {
293
+ var baseUrl = 'https://web.webex.com/deviceAuth';
294
+ var urlParams = new URLSearchParams(new URL(verificationUrl).search);
295
+ var userCode = urlParams.get('userCode');
296
+ if (userCode) {
297
+ var services = this.webex.internal.services;
298
+ var oauthHelperUrl = services.get('oauth-helper');
299
+ var newVerificationUrl = new URL(baseUrl);
300
+ newVerificationUrl.searchParams.set('usercode', userCode);
301
+ newVerificationUrl.searchParams.set('oauthhelper', oauthHelperUrl);
302
+ return newVerificationUrl.toString();
303
+ } else {
304
+ return verificationUrl;
305
+ }
306
+ },
307
+ /**
308
+ * Get an OAuth Login URL for QRCode. Generate QR code based on the returned URL.
309
+ * @instance
310
+ * @memberof AuthorizationBrowserFirstParty
311
+ * @emits #qRCodeLogin
312
+ */
313
+ initQRCodeLogin: function initQRCodeLogin() {
314
+ var _this3 = this;
315
+ if (this.pollingTimer) {
316
+ this.eventEmitter.emit(Events.qRCodeLogin, {
317
+ eventType: 'getUserCodeFailure',
318
+ data: {
319
+ message: 'There is already a polling request'
320
+ }
321
+ });
322
+ return;
323
+ }
324
+ this.webex.request({
325
+ method: 'POST',
326
+ service: 'oauth-helper',
327
+ resource: '/actions/device/authorize',
328
+ form: {
329
+ client_id: this.config.client_id,
330
+ scope: this.config.scope
331
+ },
332
+ auth: {
333
+ user: this.config.client_id,
334
+ pass: this.config.client_secret,
335
+ sendImmediately: true
336
+ }
337
+ }).then(function (res) {
338
+ var _res$body = res.body,
339
+ user_code = _res$body.user_code,
340
+ verification_uri = _res$body.verification_uri,
341
+ verification_uri_complete = _res$body.verification_uri_complete;
342
+ var verificationUriComplete = _this3._generateQRCodeVerificationUrl(verification_uri_complete);
343
+ _this3.eventEmitter.emit(Events.qRCodeLogin, {
344
+ eventType: 'getUserCodeSuccess',
345
+ userData: {
346
+ userCode: user_code,
347
+ verificationUri: verification_uri,
348
+ verificationUriComplete: verificationUriComplete
349
+ }
350
+ });
351
+ // if device authorization success, then start to poll server to check whether the user has completed authorization
352
+ _this3._startQRCodePolling(res.body);
353
+ }).catch(function (res) {
354
+ _this3.eventEmitter.emit(Events.qRCodeLogin, {
355
+ eventType: 'getUserCodeFailure',
356
+ data: res.body
357
+ });
358
+ });
359
+ },
360
+ /**
361
+ * Polling the server to check whether the user has completed authorization
362
+ * @instance
363
+ * @memberof AuthorizationBrowserFirstParty
364
+ * @param {Object} options
365
+ * @emits #qRCodeLogin
366
+ */
367
+ _startQRCodePolling: function _startQRCodePolling() {
368
+ var _options$interval,
369
+ _this4 = this;
370
+ var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
371
+ if (!options.device_code) {
372
+ this.eventEmitter.emit(Events.qRCodeLogin, {
373
+ eventType: 'authorizationFailure',
374
+ data: {
375
+ message: 'A deviceCode is required'
376
+ }
377
+ });
378
+ return;
379
+ }
380
+ if (this.pollingTimer) {
381
+ this.eventEmitter.emit(Events.qRCodeLogin, {
382
+ eventType: 'authorizationFailure',
383
+ data: {
384
+ message: 'There is already a polling request'
385
+ }
386
+ });
387
+ return;
388
+ }
389
+ var deviceCode = options.device_code,
390
+ _options$expires_in = options.expires_in,
391
+ expiresIn = _options$expires_in === void 0 ? 300 : _options$expires_in;
392
+ var interval = (_options$interval = options.interval) !== null && _options$interval !== void 0 ? _options$interval : 2;
393
+ this.pollingExpirationTimer = setTimeout(function () {
394
+ _this4.cancelQRCodePolling(false);
395
+ _this4.eventEmitter.emit(Events.qRCodeLogin, {
396
+ eventType: 'authorizationFailure',
397
+ data: {
398
+ message: 'Authorization timed out'
399
+ }
400
+ });
401
+ }, expiresIn * 1000);
402
+ var polling = function polling() {
403
+ _this4.pollingId += 1;
404
+ _this4.currentPollingId = _this4.pollingId;
405
+ _this4.webex.request({
406
+ method: 'POST',
407
+ service: 'oauth-helper',
408
+ resource: '/actions/device/token',
409
+ form: {
410
+ grant_type: 'urn:ietf:params:oauth:grant-type:device_code',
411
+ device_code: deviceCode,
412
+ client_id: _this4.config.client_id
413
+ },
414
+ auth: {
415
+ user: _this4.config.client_id,
416
+ pass: _this4.config.client_secret,
417
+ sendImmediately: true
418
+ }
419
+ }).then(function (res) {
420
+ // if the pollingId has changed, it means that the polling request has been canceled
421
+ if (_this4.currentPollingId !== _this4.pollingId) return;
422
+ _this4.eventEmitter.emit(Events.qRCodeLogin, {
423
+ eventType: 'authorizationSuccess',
424
+ data: res.body
425
+ });
426
+ _this4.webex.credentials.set({
427
+ supertoken: res.body
428
+ });
429
+ _this4.cancelQRCodePolling();
430
+ }).catch(function (res) {
431
+ // if the pollingId has changed, it means that the polling request has been canceled
432
+ if (_this4.currentPollingId !== _this4.pollingId) return;
433
+
434
+ // When server sends 400 status code with message 'slow_down', it means that last request happened too soon.
435
+ // So, skip one interval and then poll again.
436
+ if (res.statusCode === 400 && res.body.message === 'slow_down') {
437
+ schedulePolling(interval * 2);
438
+ return;
439
+ }
440
+
441
+ // if the statusCode is 428 which means that the authorization request is still pending
442
+ // as the end user hasn't yet completed the user-interaction steps. So keep polling.
443
+ if (res.statusCode === 428) {
444
+ _this4.eventEmitter.emit(Events.qRCodeLogin, {
445
+ eventType: 'authorizationPending',
446
+ data: res.body
447
+ });
448
+ schedulePolling(interval);
449
+ return;
450
+ }
451
+ _this4.cancelQRCodePolling();
452
+ _this4.eventEmitter.emit(Events.qRCodeLogin, {
453
+ eventType: 'authorizationFailure',
454
+ data: res.body
455
+ });
456
+ });
457
+ };
458
+ var schedulePolling = function schedulePolling(interval) {
459
+ return _this4.pollingTimer = setTimeout(polling, interval * 1000);
460
+ };
461
+ schedulePolling(interval);
462
+ },
463
+ /**
464
+ * cancel polling request
465
+ * @instance
466
+ * @memberof AuthorizationBrowserFirstParty
467
+ * @returns {void}
468
+ */
469
+ cancelQRCodePolling: function cancelQRCodePolling() {
470
+ var withCancelEvent = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
471
+ if (this.pollingTimer && withCancelEvent) {
472
+ this.eventEmitter.emit(Events.qRCodeLogin, {
473
+ eventType: 'pollingCanceled'
474
+ });
475
+ }
476
+ this.currentPollingId = null;
477
+ clearTimeout(this.pollingExpirationTimer);
478
+ this.pollingExpirationTimer = null;
479
+ clearTimeout(this.pollingTimer);
480
+ this.pollingTimer = null;
481
+ },
234
482
  /**
235
483
  * Extracts the orgId from the returned code from idbroker
236
484
  * Description of how to parse the code can be found here:
@@ -339,7 +587,7 @@ var Authorization = _webexCore.WebexPlugin.extend((_dec = (0, _common.whileInFli
339
587
  throw new Error("CSRF token ".concat(token, " does not match stored token ").concat(sessionToken));
340
588
  }
341
589
  },
342
- version: "3.6.0"
590
+ version: "3.7.0-ipv6-multi-turn-urls.2"
343
591
  }, ((0, _applyDecoratedDescriptor2.default)(_obj, "initiateAuthorizationCodeGrant", [_dec], (0, _getOwnPropertyDescriptor.default)(_obj, "initiateAuthorizationCodeGrant"), _obj), (0, _applyDecoratedDescriptor2.default)(_obj, "requestAuthorizationCodeGrant", [_dec2, _common.oneFlight], (0, _getOwnPropertyDescriptor.default)(_obj, "requestAuthorizationCodeGrant"), _obj)), _obj)));
344
592
  var _default = exports.default = Authorization;
345
593
  //# sourceMappingURL=authorization.js.map
@@ -1 +1 @@
1
- {"version":3,"names":["_querystring","_interopRequireDefault","require","_url","_common","_webexCore","_lodash","_uuid","_encBase64url","_cryptoJs","_dec","_dec2","_obj","lodash","OAUTH2_CSRF_TOKEN","OAUTH2_CODE_VERIFIER","Authorization","WebexPlugin","extend","whileInFlight","derived","isAuthenticating","deps","fn","isAuthorizing","session","default","type","ready","namespace","initialize","_this","_len","arguments","length","attrs","Array","_key","ret","_apply","prototype","location","url","parse","webex","getWindow","href","_checkForErrors","code","query","state","JSON","base64","decode","codeVerifier","sessionStorage","getItem","removeItem","emailhash","_verifySecurityToken","_cleanUrl","preauthCatalogParams","orgId","_extractOrgIdFromCode","process","nextTick","internal","services","collectPreauthCatalog","catch","_promise","resolve","then","requestAuthorizationCodeGrant","error","logger","warn","initiateLogin","options","undefined","cloneDeep","email","emailHash","CryptoJS","SHA256","toString","csrf_token","_generateSecurityToken","code_challenge","_generateCodeChallenge","code_challenge_method","initiateAuthorizationCodeGrant","info","credentials","buildLoginUrl","_assign","response_type","logout","noRedirect","buildLogoutUrl","_this2","reject","Error","form","grant_type","redirect_uri","config","self_contained_token","code_verifier","request","method","uri","tokenUrl","auth","user","client_id","pass","client_secret","sendImmediately","shouldRefreshAccessToken","res","set","supertoken","body","statusCode","ErrorConstructor","grantErrors","select","_res","split","history","replaceState","_deleteProperty","isEmpty","omit","encode","_stringify","search","querystring","stringify","format","safeCharacterMap","base64url","_safe_map","times","random","join","codeChallenge","setItem","token","uuid","v4","sessionToken","concat","version","_applyDecoratedDescriptor2","_getOwnPropertyDescriptor","oneFlight","_default","exports"],"sources":["authorization.js"],"sourcesContent":["/*!\n * Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.\n */\n\n/* eslint camelcase: [0] */\n\nimport querystring from 'querystring';\nimport url from 'url';\n\nimport {base64, oneFlight, whileInFlight} from '@webex/common';\nimport {grantErrors, WebexPlugin} from '@webex/webex-core';\nimport {cloneDeep, isEmpty, omit} from 'lodash';\nimport uuid from 'uuid';\nimport base64url from 'crypto-js/enc-base64url';\nimport CryptoJS from 'crypto-js';\n\n// Necessary to require lodash this way in order to stub\n// methods in the unit test\nconst lodash = require('lodash');\n\nconst OAUTH2_CSRF_TOKEN = 'oauth2-csrf-token';\nconst OAUTH2_CODE_VERIFIER = 'oauth2-code-verifier';\n\n/**\n * Browser support for OAuth2. Automatically parses the URL query for an\n * authorization code\n *\n * Use of this plugin for anything other than the Webex Web Client is strongly\n * discouraged and may be broken at any time\n * @class\n * @name AuthorizationBrowserFirstParty\n * @private\n */\nconst Authorization = WebexPlugin.extend({\n derived: {\n /**\n * Alias of {@link AuthorizationBrowserFirstParty#isAuthorizing}\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @type {boolean}\n */\n isAuthenticating: {\n deps: ['isAuthorizing'],\n fn() {\n return this.isAuthorizing;\n },\n },\n },\n\n session: {\n /**\n * Indicates if an Authorization Code exchange is inflight\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @type {boolean}\n */\n isAuthorizing: {\n default: false,\n type: 'boolean',\n },\n ready: {\n default: false,\n type: 'boolean',\n },\n },\n\n namespace: 'Credentials',\n\n /**\n * Initializer\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @private\n * @returns {Authorization}\n */\n // eslint-disable-next-line complexity\n initialize(...attrs) {\n const ret = Reflect.apply(WebexPlugin.prototype.initialize, this, attrs);\n const location = url.parse(this.webex.getWindow().location.href, true);\n\n this._checkForErrors(location);\n\n const {code} = location.query;\n\n if (!code) {\n this.ready = true;\n\n return ret;\n }\n\n if (location.query.state) {\n location.query.state = JSON.parse(base64.decode(location.query.state));\n } else {\n location.query.state = {};\n }\n\n const codeVerifier = this.webex.getWindow().sessionStorage.getItem(OAUTH2_CODE_VERIFIER);\n\n this.webex.getWindow().sessionStorage.removeItem(OAUTH2_CODE_VERIFIER);\n\n const {emailhash} = location.query.state;\n\n this._verifySecurityToken(location.query);\n this._cleanUrl(location);\n\n let preauthCatalogParams;\n\n const orgId = this._extractOrgIdFromCode(code);\n\n if (emailhash) {\n preauthCatalogParams = {emailhash};\n } else if (orgId) {\n preauthCatalogParams = {orgId};\n }\n\n // Wait until nextTick in case `credentials` hasn't initialized yet\n process.nextTick(() => {\n this.webex.internal.services\n .collectPreauthCatalog(preauthCatalogParams)\n .catch(() => Promise.resolve())\n .then(() => this.requestAuthorizationCodeGrant({code, codeVerifier}))\n .catch((error) => {\n this.logger.warn('authorization: failed initial authorization code grant request', error);\n })\n .then(() => {\n this.ready = true;\n });\n });\n\n return ret;\n },\n\n /**\n * Kicks off an oauth flow\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @param {Object} options\n * @returns {Promise}\n */\n initiateLogin(options = {}) {\n options = cloneDeep(options);\n if (options.email) {\n options.emailHash = CryptoJS.SHA256(options.email).toString();\n }\n delete options.email;\n options.state = options.state || {};\n options.state.csrf_token = this._generateSecurityToken();\n // catalog uses emailhash and redirectCI uses emailHash\n options.state.emailhash = options.emailHash;\n\n options.code_challenge = this._generateCodeChallenge();\n options.code_challenge_method = 'S256';\n\n return this.initiateAuthorizationCodeGrant(options);\n },\n\n @whileInFlight('isAuthorizing')\n /**\n * Kicks off the Implicit Code grant flow. Typically called via\n * {@link AuthorizationBrowserFirstParty#initiateLogin}\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @param {Object} options\n * @returns {Promise}\n */\n initiateAuthorizationCodeGrant(options) {\n this.logger.info('authorization: initiating authorization code grant flow');\n this.webex.getWindow().location = this.webex.credentials.buildLoginUrl(\n Object.assign({response_type: 'code'}, options)\n );\n\n return Promise.resolve();\n },\n\n /**\n * Called by {@link WebexCore#logout()}. Redirects to the logout page\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @param {Object} options\n * @param {boolean} options.noRedirect if true, does not redirect\n * @returns {Promise}\n */\n logout(options = {}) {\n if (!options.noRedirect) {\n this.webex.getWindow().location = this.webex.credentials.buildLogoutUrl(options);\n }\n },\n\n @whileInFlight('isAuthorizing')\n @oneFlight\n /**\n * Exchanges an authorization code for an access token\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @param {Object} options\n * @param {Object} options.code\n * @returns {Promise}\n */\n requestAuthorizationCodeGrant(options = {}) {\n this.logger.info('credentials: requesting authorization code grant');\n\n if (!options.code) {\n return Promise.reject(new Error('`options.code` is required'));\n }\n\n const form = {\n grant_type: 'authorization_code',\n redirect_uri: this.config.redirect_uri,\n code: options.code,\n self_contained_token: true,\n };\n\n if (options.codeVerifier) {\n form.code_verifier = options.codeVerifier;\n }\n\n return this.webex\n .request({\n method: 'POST',\n uri: this.config.tokenUrl,\n form,\n auth: {\n user: this.config.client_id,\n pass: this.config.client_secret,\n sendImmediately: true,\n },\n shouldRefreshAccessToken: false,\n })\n .then((res) => {\n this.webex.credentials.set({supertoken: res.body});\n })\n .catch((res) => {\n if (res.statusCode !== 400) {\n return Promise.reject(res);\n }\n\n const ErrorConstructor = grantErrors.select(res.body.error);\n\n return Promise.reject(new ErrorConstructor(res._res || res));\n });\n },\n\n /**\n * Extracts the orgId from the returned code from idbroker\n * Description of how to parse the code can be found here:\n * https://wiki.cisco.com/display/IDENTITY/Federated+Token+Validation\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @param {String} code\n * @private\n * @returns {String}\n */\n _extractOrgIdFromCode(code) {\n return code?.split('_')[2] || undefined;\n },\n\n /**\n * Checks if the result of the login redirect contains an error string\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @param {Object} location\n * @private\n * @returns {Promise}\n */\n _checkForErrors(location) {\n const {query} = location;\n\n if (query && query.error) {\n const ErrorConstructor = grantErrors.select(query.error);\n\n throw new ErrorConstructor(query);\n }\n },\n\n /**\n * Removes no-longer needed values from the url (access token, csrf token, etc)\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @param {Object} location\n * @private\n * @returns {Promise}\n */\n _cleanUrl(location) {\n location = cloneDeep(location);\n if (this.webex.getWindow().history && this.webex.getWindow().history.replaceState) {\n Reflect.deleteProperty(location.query, 'code');\n if (isEmpty(omit(location.query.state, 'csrf_token'))) {\n Reflect.deleteProperty(location.query, 'state');\n } else {\n location.query.state = base64.encode(\n JSON.stringify(omit(location.query.state, 'csrf_token'))\n );\n }\n location.search = querystring.stringify(location.query);\n Reflect.deleteProperty(location, 'query');\n this.webex.getWindow().history.replaceState({}, null, url.format(location));\n }\n },\n\n /**\n * Generates PKCE code verifier and code challenge and sets the the code verifier in sessionStorage\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @private\n * @returns {string}\n */\n _generateCodeChallenge() {\n this.logger.info('authorization: generating PKCE code challenge');\n\n // eslint-disable-next-line no-underscore-dangle\n const safeCharacterMap = base64url._safe_map;\n\n const codeVerifier = lodash\n .times(128, () => safeCharacterMap[lodash.random(0, safeCharacterMap.length - 1)])\n .join('');\n\n const codeChallenge = CryptoJS.SHA256(codeVerifier).toString(base64url);\n\n this.webex.getWindow().sessionStorage.setItem(OAUTH2_CODE_VERIFIER, codeVerifier);\n\n return codeChallenge;\n },\n\n /**\n * Generates a CSRF token and sticks in in sessionStorage\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @private\n * @returns {Promise}\n */\n _generateSecurityToken() {\n this.logger.info('authorization: generating csrf token');\n\n const token = uuid.v4();\n\n this.webex.getWindow().sessionStorage.setItem('oauth2-csrf-token', token);\n\n return token;\n },\n\n /**\n * Checks if the CSRF token in sessionStorage is the same as the one returned\n * in the url.\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @param {Object} query\n * @private\n * @returns {Promise}\n */\n _verifySecurityToken(query) {\n const sessionToken = this.webex.getWindow().sessionStorage.getItem(OAUTH2_CSRF_TOKEN);\n\n this.webex.getWindow().sessionStorage.removeItem(OAUTH2_CSRF_TOKEN);\n if (!sessionToken) {\n return;\n }\n\n if (!query.state) {\n throw new Error(`Expected CSRF token ${sessionToken}, but not found in redirect query`);\n }\n\n if (!query.state.csrf_token) {\n throw new Error(`Expected CSRF token ${sessionToken}, but not found in redirect query`);\n }\n\n const token = query.state.csrf_token;\n\n if (token !== sessionToken) {\n throw new Error(`CSRF token ${token} does not match stored token ${sessionToken}`);\n }\n },\n});\n\nexport default Authorization;\n"],"mappings":"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"}
1
+ {"version":3,"names":["_querystring","_interopRequireDefault","require","_url","_events","_common","_webexCore","_lodash","_uuid","_encBase64url","_cryptoJs","_dec","_dec2","_obj","lodash","OAUTH2_CSRF_TOKEN","OAUTH2_CODE_VERIFIER","Events","exports","qRCodeLogin","Authorization","WebexPlugin","extend","whileInFlight","derived","isAuthenticating","deps","fn","isAuthorizing","session","default","type","ready","namespace","eventEmitter","EventEmitter","pollingTimer","pollingExpirationTimer","pollingId","currentPollingId","initialize","_this","_len","arguments","length","attrs","Array","_key","ret","_apply","prototype","location","url","parse","webex","getWindow","href","_checkForErrors","code","query","state","JSON","base64","decode","codeVerifier","sessionStorage","getItem","removeItem","emailhash","_verifySecurityToken","_cleanUrl","preauthCatalogParams","orgId","_extractOrgIdFromCode","process","nextTick","internal","services","collectPreauthCatalog","catch","_promise","resolve","then","requestAuthorizationCodeGrant","error","logger","warn","initiateLogin","options","undefined","cloneDeep","email","emailHash","CryptoJS","SHA256","toString","csrf_token","_generateSecurityToken","code_challenge","_generateCodeChallenge","code_challenge_method","initiateAuthorizationCodeGrant","info","credentials","buildLoginUrl","_assign","response_type","logout","noRedirect","buildLogoutUrl","_this2","reject","Error","form","grant_type","redirect_uri","config","self_contained_token","code_verifier","request","method","uri","tokenUrl","auth","user","client_id","pass","client_secret","sendImmediately","shouldRefreshAccessToken","res","set","supertoken","body","statusCode","ErrorConstructor","grantErrors","select","_res","_generateQRCodeVerificationUrl","verificationUrl","baseUrl","urlParams","URLSearchParams","URL","search","userCode","get","oauthHelperUrl","newVerificationUrl","searchParams","initQRCodeLogin","_this3","emit","eventType","data","message","service","resource","scope","_res$body","user_code","verification_uri","verification_uri_complete","verificationUriComplete","userData","verificationUri","_startQRCodePolling","_options$interval","_this4","device_code","deviceCode","_options$expires_in","expires_in","expiresIn","interval","setTimeout","cancelQRCodePolling","polling","schedulePolling","withCancelEvent","clearTimeout","split","history","replaceState","_deleteProperty","isEmpty","omit","encode","_stringify","querystring","stringify","format","safeCharacterMap","base64url","_safe_map","times","random","join","codeChallenge","setItem","token","uuid","v4","sessionToken","concat","version","_applyDecoratedDescriptor2","_getOwnPropertyDescriptor","oneFlight","_default"],"sources":["authorization.js"],"sourcesContent":["/*!\n * Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.\n */\n\n/* eslint camelcase: [0] */\n\nimport querystring from 'querystring';\nimport url from 'url';\nimport {EventEmitter} from 'events';\n\nimport {base64, oneFlight, whileInFlight} from '@webex/common';\nimport {grantErrors, WebexPlugin} from '@webex/webex-core';\nimport {cloneDeep, isEmpty, omit} from 'lodash';\nimport uuid from 'uuid';\nimport base64url from 'crypto-js/enc-base64url';\nimport CryptoJS from 'crypto-js';\n\n// Necessary to require lodash this way in order to stub\n// methods in the unit test\nconst lodash = require('lodash');\n\nconst OAUTH2_CSRF_TOKEN = 'oauth2-csrf-token';\nconst OAUTH2_CODE_VERIFIER = 'oauth2-code-verifier';\n\n/**\n * Authorization plugin events\n */\nexport const Events = {\n /**\n * QR code login events\n */\n qRCodeLogin: 'qRCodeLogin',\n};\n\n/**\n * Browser support for OAuth2. Automatically parses the URL query for an\n * authorization code\n *\n * Use of this plugin for anything other than the Webex Web Client is strongly\n * discouraged and may be broken at any time\n * @class\n * @name AuthorizationBrowserFirstParty\n * @private\n */\nconst Authorization = WebexPlugin.extend({\n derived: {\n /**\n * Alias of {@link AuthorizationBrowserFirstParty#isAuthorizing}\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @type {boolean}\n */\n isAuthenticating: {\n deps: ['isAuthorizing'],\n fn() {\n return this.isAuthorizing;\n },\n },\n },\n\n session: {\n /**\n * Indicates if an Authorization Code exchange is inflight\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @type {boolean}\n */\n isAuthorizing: {\n default: false,\n type: 'boolean',\n },\n ready: {\n default: false,\n type: 'boolean',\n },\n },\n\n namespace: 'Credentials',\n\n /**\n * EventEmitter for authorization events\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @type {EventEmitter}\n * @public\n */\n eventEmitter: new EventEmitter(),\n\n /**\n * Stores the timer ID for QR code polling\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @type {?number}\n * @private\n */\n pollingTimer: null,\n /**\n * Stores the expiration timer ID for QR code polling\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @type {?number}\n * @private\n */\n pollingExpirationTimer: null,\n\n /**\n * Monotonically increasing id to identify the current polling request\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @type {number}\n * @private\n */\n pollingId: 0,\n\n /**\n * Identifier for the current polling request\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @type {?number}\n * @private\n */\n currentPollingId: null,\n\n /**\n * Initializer\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @private\n * @returns {Authorization}\n */\n // eslint-disable-next-line complexity\n initialize(...attrs) {\n const ret = Reflect.apply(WebexPlugin.prototype.initialize, this, attrs);\n const location = url.parse(this.webex.getWindow().location.href, true);\n\n this._checkForErrors(location);\n\n const {code} = location.query;\n\n if (!code) {\n this.ready = true;\n\n return ret;\n }\n\n if (location.query.state) {\n location.query.state = JSON.parse(base64.decode(location.query.state));\n } else {\n location.query.state = {};\n }\n\n const codeVerifier = this.webex.getWindow().sessionStorage.getItem(OAUTH2_CODE_VERIFIER);\n\n this.webex.getWindow().sessionStorage.removeItem(OAUTH2_CODE_VERIFIER);\n\n const {emailhash} = location.query.state;\n\n this._verifySecurityToken(location.query);\n this._cleanUrl(location);\n\n let preauthCatalogParams;\n\n const orgId = this._extractOrgIdFromCode(code);\n\n if (emailhash) {\n preauthCatalogParams = {emailhash};\n } else if (orgId) {\n preauthCatalogParams = {orgId};\n }\n\n // Wait until nextTick in case `credentials` hasn't initialized yet\n process.nextTick(() => {\n this.webex.internal.services\n .collectPreauthCatalog(preauthCatalogParams)\n .catch(() => Promise.resolve())\n .then(() => this.requestAuthorizationCodeGrant({code, codeVerifier}))\n .catch((error) => {\n this.logger.warn('authorization: failed initial authorization code grant request', error);\n })\n .then(() => {\n this.ready = true;\n });\n });\n\n return ret;\n },\n\n /**\n * Kicks off an oauth flow\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @param {Object} options\n * @returns {Promise}\n */\n initiateLogin(options = {}) {\n options = cloneDeep(options);\n if (options.email) {\n options.emailHash = CryptoJS.SHA256(options.email).toString();\n }\n delete options.email;\n options.state = options.state || {};\n options.state.csrf_token = this._generateSecurityToken();\n // catalog uses emailhash and redirectCI uses emailHash\n options.state.emailhash = options.emailHash;\n\n options.code_challenge = this._generateCodeChallenge();\n options.code_challenge_method = 'S256';\n\n return this.initiateAuthorizationCodeGrant(options);\n },\n\n @whileInFlight('isAuthorizing')\n /**\n * Kicks off the Implicit Code grant flow. Typically called via\n * {@link AuthorizationBrowserFirstParty#initiateLogin}\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @param {Object} options\n * @returns {Promise}\n */\n initiateAuthorizationCodeGrant(options) {\n this.logger.info('authorization: initiating authorization code grant flow');\n this.webex.getWindow().location = this.webex.credentials.buildLoginUrl(\n Object.assign({response_type: 'code'}, options)\n );\n\n return Promise.resolve();\n },\n\n /**\n * Called by {@link WebexCore#logout()}. Redirects to the logout page\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @param {Object} options\n * @param {boolean} options.noRedirect if true, does not redirect\n * @returns {Promise}\n */\n logout(options = {}) {\n if (!options.noRedirect) {\n this.webex.getWindow().location = this.webex.credentials.buildLogoutUrl(options);\n }\n },\n\n @whileInFlight('isAuthorizing')\n @oneFlight\n /**\n * Exchanges an authorization code for an access token\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @param {Object} options\n * @param {Object} options.code\n * @returns {Promise}\n */\n requestAuthorizationCodeGrant(options = {}) {\n this.logger.info('credentials: requesting authorization code grant');\n\n if (!options.code) {\n return Promise.reject(new Error('`options.code` is required'));\n }\n\n const form = {\n grant_type: 'authorization_code',\n redirect_uri: this.config.redirect_uri,\n code: options.code,\n self_contained_token: true,\n };\n\n if (options.codeVerifier) {\n form.code_verifier = options.codeVerifier;\n }\n\n return this.webex\n .request({\n method: 'POST',\n uri: this.config.tokenUrl,\n form,\n auth: {\n user: this.config.client_id,\n pass: this.config.client_secret,\n sendImmediately: true,\n },\n shouldRefreshAccessToken: false,\n })\n .then((res) => {\n this.webex.credentials.set({supertoken: res.body});\n })\n .catch((res) => {\n if (res.statusCode !== 400) {\n return Promise.reject(res);\n }\n\n const ErrorConstructor = grantErrors.select(res.body.error);\n\n return Promise.reject(new ErrorConstructor(res._res || res));\n });\n },\n\n /**\n * Generate a QR code URL to launch the Webex app when scanning with the camera\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @param {String} verificationUrl\n * @returns {String}\n */\n _generateQRCodeVerificationUrl(verificationUrl) {\n const baseUrl = 'https://web.webex.com/deviceAuth';\n const urlParams = new URLSearchParams(new URL(verificationUrl).search);\n const userCode = urlParams.get('userCode');\n\n if (userCode) {\n const {services} = this.webex.internal;\n const oauthHelperUrl = services.get('oauth-helper');\n const newVerificationUrl = new URL(baseUrl);\n newVerificationUrl.searchParams.set('usercode', userCode);\n newVerificationUrl.searchParams.set('oauthhelper', oauthHelperUrl);\n return newVerificationUrl.toString();\n } else {\n return verificationUrl;\n }\n },\n\n /**\n * Get an OAuth Login URL for QRCode. Generate QR code based on the returned URL.\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @emits #qRCodeLogin\n */\n initQRCodeLogin() {\n if (this.pollingTimer) {\n this.eventEmitter.emit(Events.qRCodeLogin, {\n eventType: 'getUserCodeFailure',\n data: {message: 'There is already a polling request'},\n });\n return;\n }\n\n this.webex\n .request({\n method: 'POST',\n service: 'oauth-helper',\n resource: '/actions/device/authorize',\n form: {\n client_id: this.config.client_id,\n scope: this.config.scope,\n },\n auth: {\n user: this.config.client_id,\n pass: this.config.client_secret,\n sendImmediately: true,\n },\n })\n .then((res) => {\n const {user_code, verification_uri, verification_uri_complete} = res.body;\n const verificationUriComplete = this._generateQRCodeVerificationUrl(verification_uri_complete);\n this.eventEmitter.emit(Events.qRCodeLogin, {\n eventType: 'getUserCodeSuccess',\n userData: {\n userCode: user_code,\n verificationUri: verification_uri,\n verificationUriComplete,\n },\n });\n // if device authorization success, then start to poll server to check whether the user has completed authorization\n this._startQRCodePolling(res.body);\n })\n .catch((res) => {\n this.eventEmitter.emit(Events.qRCodeLogin, {\n eventType: 'getUserCodeFailure',\n data: res.body,\n });\n });\n },\n\n /**\n * Polling the server to check whether the user has completed authorization\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @param {Object} options\n * @emits #qRCodeLogin\n */\n _startQRCodePolling(options = {}) {\n if (!options.device_code) {\n this.eventEmitter.emit(Events.qRCodeLogin, {\n eventType: 'authorizationFailure',\n data: {message: 'A deviceCode is required'},\n });\n return;\n }\n\n if (this.pollingTimer) {\n this.eventEmitter.emit(Events.qRCodeLogin, {\n eventType: 'authorizationFailure',\n data: {message: 'There is already a polling request'},\n });\n return;\n }\n\n const {device_code: deviceCode, expires_in: expiresIn = 300} = options;\n let interval = options.interval ?? 2;\n\n this.pollingExpirationTimer = setTimeout(() => {\n this.cancelQRCodePolling(false);\n this.eventEmitter.emit(Events.qRCodeLogin, {\n eventType: 'authorizationFailure',\n data: {message: 'Authorization timed out'},\n });\n }, expiresIn * 1000);\n\n const polling = () => {\n this.pollingId += 1;\n this.currentPollingId = this.pollingId;\n\n this.webex\n .request({\n method: 'POST',\n service: 'oauth-helper',\n resource: '/actions/device/token',\n form: {\n grant_type: 'urn:ietf:params:oauth:grant-type:device_code',\n device_code: deviceCode,\n client_id: this.config.client_id,\n },\n auth: {\n user: this.config.client_id,\n pass: this.config.client_secret,\n sendImmediately: true,\n },\n })\n .then((res) => {\n // if the pollingId has changed, it means that the polling request has been canceled\n if (this.currentPollingId !== this.pollingId) return;\n\n this.eventEmitter.emit(Events.qRCodeLogin, {\n eventType: 'authorizationSuccess',\n data: res.body,\n });\n this.webex.credentials.set({supertoken: res.body});\n this.cancelQRCodePolling();\n })\n .catch((res) => {\n // if the pollingId has changed, it means that the polling request has been canceled\n if (this.currentPollingId !== this.pollingId) return;\n\n // When server sends 400 status code with message 'slow_down', it means that last request happened too soon.\n // So, skip one interval and then poll again.\n if (res.statusCode === 400 && res.body.message === 'slow_down') {\n schedulePolling(interval * 2);\n return;\n }\n\n // if the statusCode is 428 which means that the authorization request is still pending\n // as the end user hasn't yet completed the user-interaction steps. So keep polling.\n if (res.statusCode === 428) {\n this.eventEmitter.emit(Events.qRCodeLogin, {\n eventType: 'authorizationPending',\n data: res.body,\n });\n schedulePolling(interval);\n return;\n }\n\n this.cancelQRCodePolling();\n\n this.eventEmitter.emit(Events.qRCodeLogin, {\n eventType: 'authorizationFailure',\n data: res.body,\n });\n });\n };\n\n const schedulePolling = (interval) =>\n (this.pollingTimer = setTimeout(polling, interval * 1000));\n\n schedulePolling(interval);\n },\n\n /**\n * cancel polling request\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @returns {void}\n */\n cancelQRCodePolling(withCancelEvent = true) {\n if (this.pollingTimer && withCancelEvent) {\n this.eventEmitter.emit(Events.qRCodeLogin, {\n eventType: 'pollingCanceled',\n });\n }\n\n this.currentPollingId = null;\n\n clearTimeout(this.pollingExpirationTimer);\n this.pollingExpirationTimer = null;\n clearTimeout(this.pollingTimer);\n this.pollingTimer = null;\n },\n\n /**\n * Extracts the orgId from the returned code from idbroker\n * Description of how to parse the code can be found here:\n * https://wiki.cisco.com/display/IDENTITY/Federated+Token+Validation\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @param {String} code\n * @private\n * @returns {String}\n */\n _extractOrgIdFromCode(code) {\n return code?.split('_')[2] || undefined;\n },\n\n /**\n * Checks if the result of the login redirect contains an error string\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @param {Object} location\n * @private\n * @returns {Promise}\n */\n _checkForErrors(location) {\n const {query} = location;\n\n if (query && query.error) {\n const ErrorConstructor = grantErrors.select(query.error);\n\n throw new ErrorConstructor(query);\n }\n },\n\n /**\n * Removes no-longer needed values from the url (access token, csrf token, etc)\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @param {Object} location\n * @private\n * @returns {Promise}\n */\n _cleanUrl(location) {\n location = cloneDeep(location);\n if (this.webex.getWindow().history && this.webex.getWindow().history.replaceState) {\n Reflect.deleteProperty(location.query, 'code');\n if (isEmpty(omit(location.query.state, 'csrf_token'))) {\n Reflect.deleteProperty(location.query, 'state');\n } else {\n location.query.state = base64.encode(\n JSON.stringify(omit(location.query.state, 'csrf_token'))\n );\n }\n location.search = querystring.stringify(location.query);\n Reflect.deleteProperty(location, 'query');\n this.webex.getWindow().history.replaceState({}, null, url.format(location));\n }\n },\n\n /**\n * Generates PKCE code verifier and code challenge and sets the the code verifier in sessionStorage\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @private\n * @returns {string}\n */\n _generateCodeChallenge() {\n this.logger.info('authorization: generating PKCE code challenge');\n\n // eslint-disable-next-line no-underscore-dangle\n const safeCharacterMap = base64url._safe_map;\n\n const codeVerifier = lodash\n .times(128, () => safeCharacterMap[lodash.random(0, safeCharacterMap.length - 1)])\n .join('');\n\n const codeChallenge = CryptoJS.SHA256(codeVerifier).toString(base64url);\n\n this.webex.getWindow().sessionStorage.setItem(OAUTH2_CODE_VERIFIER, codeVerifier);\n\n return codeChallenge;\n },\n\n /**\n * Generates a CSRF token and sticks in in sessionStorage\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @private\n * @returns {Promise}\n */\n _generateSecurityToken() {\n this.logger.info('authorization: generating csrf token');\n\n const token = uuid.v4();\n\n this.webex.getWindow().sessionStorage.setItem('oauth2-csrf-token', token);\n\n return token;\n },\n\n /**\n * Checks if the CSRF token in sessionStorage is the same as the one returned\n * in the url.\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @param {Object} query\n * @private\n * @returns {Promise}\n */\n _verifySecurityToken(query) {\n const sessionToken = this.webex.getWindow().sessionStorage.getItem(OAUTH2_CSRF_TOKEN);\n\n this.webex.getWindow().sessionStorage.removeItem(OAUTH2_CSRF_TOKEN);\n if (!sessionToken) {\n return;\n }\n\n if (!query.state) {\n throw new Error(`Expected CSRF token ${sessionToken}, but not found in redirect query`);\n }\n\n if (!query.state.csrf_token) {\n throw new Error(`Expected CSRF token ${sessionToken}, but not found in redirect query`);\n }\n\n const token = query.state.csrf_token;\n\n if (token !== sessionToken) {\n throw new Error(`CSRF token ${token} does not match stored token ${sessionToken}`);\n }\n },\n});\n\nexport default Authorization;\n"],"mappings":"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"}
package/dist/index.js CHANGED
@@ -1,10 +1,19 @@
1
1
  "use strict";
2
2
 
3
+ var _typeof = require("@babel/runtime-corejs2/helpers/typeof");
4
+ var _WeakMap = require("@babel/runtime-corejs2/core-js/weak-map");
3
5
  var _Object$defineProperty = require("@babel/runtime-corejs2/core-js/object/define-property");
6
+ var _Object$getOwnPropertyDescriptor = require("@babel/runtime-corejs2/core-js/object/get-own-property-descriptor");
4
7
  var _interopRequireDefault = require("@babel/runtime-corejs2/helpers/interopRequireDefault");
5
8
  _Object$defineProperty(exports, "__esModule", {
6
9
  value: true
7
10
  });
11
+ _Object$defineProperty(exports, "Events", {
12
+ enumerable: true,
13
+ get: function get() {
14
+ return _authorization.Events;
15
+ }
16
+ });
8
17
  _Object$defineProperty(exports, "config", {
9
18
  enumerable: true,
10
19
  get: function get() {
@@ -18,8 +27,10 @@ _Object$defineProperty(exports, "default", {
18
27
  }
19
28
  });
20
29
  var _webexCore = require("@webex/webex-core");
21
- var _authorization = _interopRequireDefault(require("./authorization"));
30
+ var _authorization = _interopRequireWildcard(require("./authorization"));
22
31
  var _config = _interopRequireDefault(require("./config"));
32
+ function _getRequireWildcardCache(e) { if ("function" != typeof _WeakMap) return null; var r = new _WeakMap(), t = new _WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(e) { return e ? t : r; })(e); }
33
+ function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != _typeof(e) && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = _Object$defineProperty && _Object$getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? _Object$getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? _Object$defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
23
34
  /*!
24
35
  * Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.
25
36
  */
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"names":["_webexCore","require","_authorization","_interopRequireDefault","_config","proxies","registerPlugin","Authorization","config"],"sources":["index.js"],"sourcesContent":["/*!\n * Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.\n */\n\nimport {registerPlugin} from '@webex/webex-core';\n\nimport Authorization from './authorization';\nimport config from './config';\n\nconst proxies = ['isAuthorizing', 'isAuthenticating'];\n\nregisterPlugin('authorization', Authorization, {\n config,\n proxies,\n});\n\nexport {default} from './authorization';\nexport {default as config} from './config';\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAIA,IAAAA,UAAA,GAAAC,OAAA;AAEA,IAAAC,cAAA,GAAAC,sBAAA,CAAAF,OAAA;AACA,IAAAG,OAAA,GAAAD,sBAAA,CAAAF,OAAA;AAPA;AACA;AACA;;AAOA,IAAMI,OAAO,GAAG,CAAC,eAAe,EAAE,kBAAkB,CAAC;AAErD,IAAAC,yBAAc,EAAC,eAAe,EAAEC,sBAAa,EAAE;EAC7CC,MAAM,EAANA,eAAM;EACNH,OAAO,EAAPA;AACF,CAAC,CAAC"}
1
+ {"version":3,"names":["_webexCore","require","_authorization","_interopRequireWildcard","_config","_interopRequireDefault","_getRequireWildcardCache","e","_WeakMap","r","t","__esModule","_typeof","default","has","get","n","__proto__","a","_Object$defineProperty","_Object$getOwnPropertyDescriptor","u","Object","prototype","hasOwnProperty","call","i","set","proxies","registerPlugin","Authorization","config"],"sources":["index.js"],"sourcesContent":["/*!\n * Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.\n */\n\nimport {registerPlugin} from '@webex/webex-core';\n\nimport Authorization from './authorization';\nimport config from './config';\n\nconst proxies = ['isAuthorizing', 'isAuthenticating'];\n\nregisterPlugin('authorization', Authorization, {\n config,\n proxies,\n});\n\nexport {default, Events} from './authorization';\nexport {default as config} from './config';\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAIA,IAAAA,UAAA,GAAAC,OAAA;AAEA,IAAAC,cAAA,GAAAC,uBAAA,CAAAF,OAAA;AACA,IAAAG,OAAA,GAAAC,sBAAA,CAAAJ,OAAA;AAA8B,SAAAK,yBAAAC,CAAA,6BAAAC,QAAA,mBAAAC,CAAA,OAAAD,QAAA,IAAAE,CAAA,OAAAF,QAAA,YAAAF,wBAAA,YAAAA,yBAAAC,CAAA,WAAAA,CAAA,GAAAG,CAAA,GAAAD,CAAA,KAAAF,CAAA;AAAA,SAAAJ,wBAAAI,CAAA,EAAAE,CAAA,SAAAA,CAAA,IAAAF,CAAA,IAAAA,CAAA,CAAAI,UAAA,SAAAJ,CAAA,eAAAA,CAAA,gBAAAK,OAAA,CAAAL,CAAA,0BAAAA,CAAA,WAAAM,OAAA,EAAAN,CAAA,QAAAG,CAAA,GAAAJ,wBAAA,CAAAG,CAAA,OAAAC,CAAA,IAAAA,CAAA,CAAAI,GAAA,CAAAP,CAAA,UAAAG,CAAA,CAAAK,GAAA,CAAAR,CAAA,OAAAS,CAAA,KAAAC,SAAA,UAAAC,CAAA,GAAAC,sBAAA,IAAAC,gCAAA,WAAAC,CAAA,IAAAd,CAAA,oBAAAc,CAAA,IAAAC,MAAA,CAAAC,SAAA,CAAAC,cAAA,CAAAC,IAAA,CAAAlB,CAAA,EAAAc,CAAA,SAAAK,CAAA,GAAAR,CAAA,GAAAE,gCAAA,CAAAb,CAAA,EAAAc,CAAA,UAAAK,CAAA,KAAAA,CAAA,CAAAX,GAAA,IAAAW,CAAA,CAAAC,GAAA,IAAAR,sBAAA,CAAAH,CAAA,EAAAK,CAAA,EAAAK,CAAA,IAAAV,CAAA,CAAAK,CAAA,IAAAd,CAAA,CAAAc,CAAA,YAAAL,CAAA,CAAAH,OAAA,GAAAN,CAAA,EAAAG,CAAA,IAAAA,CAAA,CAAAiB,GAAA,CAAApB,CAAA,EAAAS,CAAA,GAAAA,CAAA;AAP9B;AACA;AACA;;AAOA,IAAMY,OAAO,GAAG,CAAC,eAAe,EAAE,kBAAkB,CAAC;AAErD,IAAAC,yBAAc,EAAC,eAAe,EAAEC,sBAAa,EAAE;EAC7CC,MAAM,EAANA,eAAM;EACNH,OAAO,EAAPA;AACF,CAAC,CAAC"}
package/package.json CHANGED
@@ -26,23 +26,23 @@
26
26
  "@webex/eslint-config-legacy": "0.0.0",
27
27
  "@webex/jest-config-legacy": "0.0.0",
28
28
  "@webex/legacy-tools": "0.0.0",
29
- "@webex/test-helper-chai": "3.6.0",
30
- "@webex/test-helper-mocha": "3.6.0",
31
- "@webex/test-helper-mock-webex": "3.6.0",
32
- "@webex/test-helper-test-users": "3.6.0",
29
+ "@webex/test-helper-chai": "3.7.0-ipv6-multi-turn-urls.2",
30
+ "@webex/test-helper-mocha": "3.7.0-ipv6-multi-turn-urls.2",
31
+ "@webex/test-helper-mock-webex": "3.7.0-ipv6-multi-turn-urls.2",
32
+ "@webex/test-helper-test-users": "3.7.0-ipv6-multi-turn-urls.2",
33
33
  "eslint": "^8.24.0",
34
34
  "prettier": "^2.7.1",
35
35
  "sinon": "^9.2.4"
36
36
  },
37
37
  "dependencies": {
38
- "@webex/common": "3.6.0",
39
- "@webex/storage-adapter-local-storage": "3.6.0",
40
- "@webex/test-helper-automation": "3.6.0",
41
- "@webex/test-helper-chai": "3.6.0",
42
- "@webex/test-helper-mocha": "3.6.0",
43
- "@webex/test-helper-mock-webex": "3.6.0",
44
- "@webex/test-helper-test-users": "3.6.0",
45
- "@webex/webex-core": "3.6.0",
38
+ "@webex/common": "3.7.0-ipv6-multi-turn-urls.2",
39
+ "@webex/storage-adapter-local-storage": "3.7.0-ipv6-multi-turn-urls.2",
40
+ "@webex/test-helper-automation": "3.7.0-ipv6-multi-turn-urls.2",
41
+ "@webex/test-helper-chai": "3.7.0-ipv6-multi-turn-urls.2",
42
+ "@webex/test-helper-mocha": "3.7.0-ipv6-multi-turn-urls.2",
43
+ "@webex/test-helper-mock-webex": "3.7.0-ipv6-multi-turn-urls.2",
44
+ "@webex/test-helper-test-users": "3.7.0-ipv6-multi-turn-urls.2",
45
+ "@webex/webex-core": "3.7.0-ipv6-multi-turn-urls.2",
46
46
  "crypto-js": "^4.1.1",
47
47
  "lodash": "^4.17.21",
48
48
  "uuid": "^3.3.2"
@@ -56,5 +56,5 @@
56
56
  "test:style": "eslint ./src/**/*.*",
57
57
  "test:unit": "webex-legacy-tools test --unit --runner jest"
58
58
  },
59
- "version": "3.6.0"
59
+ "version": "3.7.0-ipv6-multi-turn-urls.2"
60
60
  }
@@ -6,6 +6,7 @@
6
6
 
7
7
  import querystring from 'querystring';
8
8
  import url from 'url';
9
+ import {EventEmitter} from 'events';
9
10
 
10
11
  import {base64, oneFlight, whileInFlight} from '@webex/common';
11
12
  import {grantErrors, WebexPlugin} from '@webex/webex-core';
@@ -21,6 +22,16 @@ const lodash = require('lodash');
21
22
  const OAUTH2_CSRF_TOKEN = 'oauth2-csrf-token';
22
23
  const OAUTH2_CODE_VERIFIER = 'oauth2-code-verifier';
23
24
 
25
+ /**
26
+ * Authorization plugin events
27
+ */
28
+ export const Events = {
29
+ /**
30
+ * QR code login events
31
+ */
32
+ qRCodeLogin: 'qRCodeLogin',
33
+ };
34
+
24
35
  /**
25
36
  * Browser support for OAuth2. Automatically parses the URL query for an
26
37
  * authorization code
@@ -66,6 +77,50 @@ const Authorization = WebexPlugin.extend({
66
77
 
67
78
  namespace: 'Credentials',
68
79
 
80
+ /**
81
+ * EventEmitter for authorization events
82
+ * @instance
83
+ * @memberof AuthorizationBrowserFirstParty
84
+ * @type {EventEmitter}
85
+ * @public
86
+ */
87
+ eventEmitter: new EventEmitter(),
88
+
89
+ /**
90
+ * Stores the timer ID for QR code polling
91
+ * @instance
92
+ * @memberof AuthorizationBrowserFirstParty
93
+ * @type {?number}
94
+ * @private
95
+ */
96
+ pollingTimer: null,
97
+ /**
98
+ * Stores the expiration timer ID for QR code polling
99
+ * @instance
100
+ * @memberof AuthorizationBrowserFirstParty
101
+ * @type {?number}
102
+ * @private
103
+ */
104
+ pollingExpirationTimer: null,
105
+
106
+ /**
107
+ * Monotonically increasing id to identify the current polling request
108
+ * @instance
109
+ * @memberof AuthorizationBrowserFirstParty
110
+ * @type {number}
111
+ * @private
112
+ */
113
+ pollingId: 0,
114
+
115
+ /**
116
+ * Identifier for the current polling request
117
+ * @instance
118
+ * @memberof AuthorizationBrowserFirstParty
119
+ * @type {?number}
120
+ * @private
121
+ */
122
+ currentPollingId: null,
123
+
69
124
  /**
70
125
  * Initializer
71
126
  * @instance
@@ -240,6 +295,206 @@ const Authorization = WebexPlugin.extend({
240
295
  });
241
296
  },
242
297
 
298
+ /**
299
+ * Generate a QR code URL to launch the Webex app when scanning with the camera
300
+ * @instance
301
+ * @memberof AuthorizationBrowserFirstParty
302
+ * @param {String} verificationUrl
303
+ * @returns {String}
304
+ */
305
+ _generateQRCodeVerificationUrl(verificationUrl) {
306
+ const baseUrl = 'https://web.webex.com/deviceAuth';
307
+ const urlParams = new URLSearchParams(new URL(verificationUrl).search);
308
+ const userCode = urlParams.get('userCode');
309
+
310
+ if (userCode) {
311
+ const {services} = this.webex.internal;
312
+ const oauthHelperUrl = services.get('oauth-helper');
313
+ const newVerificationUrl = new URL(baseUrl);
314
+ newVerificationUrl.searchParams.set('usercode', userCode);
315
+ newVerificationUrl.searchParams.set('oauthhelper', oauthHelperUrl);
316
+ return newVerificationUrl.toString();
317
+ } else {
318
+ return verificationUrl;
319
+ }
320
+ },
321
+
322
+ /**
323
+ * Get an OAuth Login URL for QRCode. Generate QR code based on the returned URL.
324
+ * @instance
325
+ * @memberof AuthorizationBrowserFirstParty
326
+ * @emits #qRCodeLogin
327
+ */
328
+ initQRCodeLogin() {
329
+ if (this.pollingTimer) {
330
+ this.eventEmitter.emit(Events.qRCodeLogin, {
331
+ eventType: 'getUserCodeFailure',
332
+ data: {message: 'There is already a polling request'},
333
+ });
334
+ return;
335
+ }
336
+
337
+ this.webex
338
+ .request({
339
+ method: 'POST',
340
+ service: 'oauth-helper',
341
+ resource: '/actions/device/authorize',
342
+ form: {
343
+ client_id: this.config.client_id,
344
+ scope: this.config.scope,
345
+ },
346
+ auth: {
347
+ user: this.config.client_id,
348
+ pass: this.config.client_secret,
349
+ sendImmediately: true,
350
+ },
351
+ })
352
+ .then((res) => {
353
+ const {user_code, verification_uri, verification_uri_complete} = res.body;
354
+ const verificationUriComplete = this._generateQRCodeVerificationUrl(verification_uri_complete);
355
+ this.eventEmitter.emit(Events.qRCodeLogin, {
356
+ eventType: 'getUserCodeSuccess',
357
+ userData: {
358
+ userCode: user_code,
359
+ verificationUri: verification_uri,
360
+ verificationUriComplete,
361
+ },
362
+ });
363
+ // if device authorization success, then start to poll server to check whether the user has completed authorization
364
+ this._startQRCodePolling(res.body);
365
+ })
366
+ .catch((res) => {
367
+ this.eventEmitter.emit(Events.qRCodeLogin, {
368
+ eventType: 'getUserCodeFailure',
369
+ data: res.body,
370
+ });
371
+ });
372
+ },
373
+
374
+ /**
375
+ * Polling the server to check whether the user has completed authorization
376
+ * @instance
377
+ * @memberof AuthorizationBrowserFirstParty
378
+ * @param {Object} options
379
+ * @emits #qRCodeLogin
380
+ */
381
+ _startQRCodePolling(options = {}) {
382
+ if (!options.device_code) {
383
+ this.eventEmitter.emit(Events.qRCodeLogin, {
384
+ eventType: 'authorizationFailure',
385
+ data: {message: 'A deviceCode is required'},
386
+ });
387
+ return;
388
+ }
389
+
390
+ if (this.pollingTimer) {
391
+ this.eventEmitter.emit(Events.qRCodeLogin, {
392
+ eventType: 'authorizationFailure',
393
+ data: {message: 'There is already a polling request'},
394
+ });
395
+ return;
396
+ }
397
+
398
+ const {device_code: deviceCode, expires_in: expiresIn = 300} = options;
399
+ let interval = options.interval ?? 2;
400
+
401
+ this.pollingExpirationTimer = setTimeout(() => {
402
+ this.cancelQRCodePolling(false);
403
+ this.eventEmitter.emit(Events.qRCodeLogin, {
404
+ eventType: 'authorizationFailure',
405
+ data: {message: 'Authorization timed out'},
406
+ });
407
+ }, expiresIn * 1000);
408
+
409
+ const polling = () => {
410
+ this.pollingId += 1;
411
+ this.currentPollingId = this.pollingId;
412
+
413
+ this.webex
414
+ .request({
415
+ method: 'POST',
416
+ service: 'oauth-helper',
417
+ resource: '/actions/device/token',
418
+ form: {
419
+ grant_type: 'urn:ietf:params:oauth:grant-type:device_code',
420
+ device_code: deviceCode,
421
+ client_id: this.config.client_id,
422
+ },
423
+ auth: {
424
+ user: this.config.client_id,
425
+ pass: this.config.client_secret,
426
+ sendImmediately: true,
427
+ },
428
+ })
429
+ .then((res) => {
430
+ // if the pollingId has changed, it means that the polling request has been canceled
431
+ if (this.currentPollingId !== this.pollingId) return;
432
+
433
+ this.eventEmitter.emit(Events.qRCodeLogin, {
434
+ eventType: 'authorizationSuccess',
435
+ data: res.body,
436
+ });
437
+ this.webex.credentials.set({supertoken: res.body});
438
+ this.cancelQRCodePolling();
439
+ })
440
+ .catch((res) => {
441
+ // if the pollingId has changed, it means that the polling request has been canceled
442
+ if (this.currentPollingId !== this.pollingId) return;
443
+
444
+ // When server sends 400 status code with message 'slow_down', it means that last request happened too soon.
445
+ // So, skip one interval and then poll again.
446
+ if (res.statusCode === 400 && res.body.message === 'slow_down') {
447
+ schedulePolling(interval * 2);
448
+ return;
449
+ }
450
+
451
+ // if the statusCode is 428 which means that the authorization request is still pending
452
+ // as the end user hasn't yet completed the user-interaction steps. So keep polling.
453
+ if (res.statusCode === 428) {
454
+ this.eventEmitter.emit(Events.qRCodeLogin, {
455
+ eventType: 'authorizationPending',
456
+ data: res.body,
457
+ });
458
+ schedulePolling(interval);
459
+ return;
460
+ }
461
+
462
+ this.cancelQRCodePolling();
463
+
464
+ this.eventEmitter.emit(Events.qRCodeLogin, {
465
+ eventType: 'authorizationFailure',
466
+ data: res.body,
467
+ });
468
+ });
469
+ };
470
+
471
+ const schedulePolling = (interval) =>
472
+ (this.pollingTimer = setTimeout(polling, interval * 1000));
473
+
474
+ schedulePolling(interval);
475
+ },
476
+
477
+ /**
478
+ * cancel polling request
479
+ * @instance
480
+ * @memberof AuthorizationBrowserFirstParty
481
+ * @returns {void}
482
+ */
483
+ cancelQRCodePolling(withCancelEvent = true) {
484
+ if (this.pollingTimer && withCancelEvent) {
485
+ this.eventEmitter.emit(Events.qRCodeLogin, {
486
+ eventType: 'pollingCanceled',
487
+ });
488
+ }
489
+
490
+ this.currentPollingId = null;
491
+
492
+ clearTimeout(this.pollingExpirationTimer);
493
+ this.pollingExpirationTimer = null;
494
+ clearTimeout(this.pollingTimer);
495
+ this.pollingTimer = null;
496
+ },
497
+
243
498
  /**
244
499
  * Extracts the orgId from the returned code from idbroker
245
500
  * Description of how to parse the code can be found here:
package/src/index.js CHANGED
@@ -14,5 +14,5 @@ registerPlugin('authorization', Authorization, {
14
14
  proxies,
15
15
  });
16
16
 
17
- export {default} from './authorization';
17
+ export {default, Events} from './authorization';
18
18
  export {default as config} from './config';
@@ -18,7 +18,6 @@ import Authorization from '@webex/plugin-authorization-browser-first-party';
18
18
  // Necessary to require lodash this way in order to stub the method
19
19
  const lodash = require('lodash');
20
20
 
21
-
22
21
  describe('plugin-authorization-browser-first-party', () => {
23
22
  describe('Authorization', () => {
24
23
  function makeWebex(
@@ -187,14 +186,16 @@ describe('plugin-authorization-browser-first-party', () => {
187
186
  const webex = makeWebex(
188
187
  `http://example.com/?code=${code}&state=${base64.encode(
189
188
  JSON.stringify({emailhash: 'someemailhash'})
190
- )}`,
189
+ )}`
191
190
  );
192
191
 
193
192
  const requestAuthorizationCodeGrantStub = sinon.stub(
194
193
  Authorization.prototype,
195
194
  'requestAuthorizationCodeGrant'
196
195
  );
197
- const collectPreauthCatalogStub = sinon.stub(Services.prototype, 'collectPreauthCatalog').resolves();
196
+ const collectPreauthCatalogStub = sinon
197
+ .stub(Services.prototype, 'collectPreauthCatalog')
198
+ .resolves();
198
199
 
199
200
  await webex.authorization.when('change:ready');
200
201
 
@@ -206,9 +207,7 @@ describe('plugin-authorization-browser-first-party', () => {
206
207
 
207
208
  it('collects the preauth catalog no emailhash is present in the state', async () => {
208
209
  const code = 'authcode_clusterid_theOrgId';
209
- const webex = makeWebex(
210
- `http://example.com/?code=${code}`
211
- );
210
+ const webex = makeWebex(`http://example.com/?code=${code}`);
212
211
 
213
212
  const requestAuthorizationCodeGrantStub = sinon.stub(
214
213
  Authorization.prototype,
@@ -271,12 +270,13 @@ describe('plugin-authorization-browser-first-party', () => {
271
270
  it('throws a grant error', () => {
272
271
  let err = null;
273
272
  try {
274
- makeWebex('http://127.0.0.1:8000/?error=invalid_scope&error_description=The%20requested%20scope%20is%20invalid.');
275
- }
276
- catch (e) {
273
+ makeWebex(
274
+ 'http://127.0.0.1:8000/?error=invalid_scope&error_description=The%20requested%20scope%20is%20invalid.'
275
+ );
276
+ } catch (e) {
277
277
  err = e;
278
278
  }
279
- expect(err?.message).toBe('Cannot convert object to primitive value')
279
+ expect(err?.message).toBe('Cannot convert object to primitive value');
280
280
  });
281
281
  });
282
282
 
@@ -443,6 +443,396 @@ describe('plugin-authorization-browser-first-party', () => {
443
443
  });
444
444
  });
445
445
 
446
+ describe('#_generateQRCodeVerificationUrl()', () => {
447
+ it('should generate a QR code URL when a userCode is present', () => {
448
+ const verificationUrl = 'https://example.com/verify?userCode=123456';
449
+ const oauthHelperUrl = 'https://oauth-helper-a.wbx2.com/helperservice/v1';
450
+ const expectedUrl = 'https://web.webex.com/deviceAuth?usercode=123456&oauthhelper=https%3A%2F%2Foauth-helper-a.wbx2.com%2Fhelperservice%2Fv1';
451
+
452
+ const webex = makeWebex('http://example.com');
453
+
454
+ const oauthHelperSpy = sinon.stub(webex.internal.services, 'get').returns(oauthHelperUrl);
455
+ const result = webex.authorization._generateQRCodeVerificationUrl(verificationUrl);
456
+
457
+ assert.calledOnce(oauthHelperSpy);
458
+ assert.calledWithExactly(oauthHelperSpy, 'oauth-helper');
459
+ assert.equal(result, expectedUrl);
460
+
461
+ oauthHelperSpy.restore();
462
+ });
463
+
464
+ it('should return the original verificationUrl when userCode is not present', () => {
465
+ const verificationUrl = 'https://example.com/verify';
466
+ const webex = makeWebex('http://example.com');
467
+
468
+ const oauthHelperSpy = sinon.stub(webex.internal.services, 'get');
469
+ const result = webex.authorization._generateQRCodeVerificationUrl(verificationUrl);
470
+
471
+ assert.notCalled(oauthHelperSpy);
472
+ assert.equal(result, verificationUrl);
473
+
474
+ oauthHelperSpy.restore();
475
+ });
476
+ });
477
+
478
+ describe('#initQRCodeLogin()', () => {
479
+ it('should prevent concurrent request if there is already a polling request', async () => {
480
+ const webex = makeWebex('http://example.com');
481
+
482
+ webex.authorization.pollingTimer = 1;
483
+ const emitSpy = sinon.spy(webex.authorization.eventEmitter, 'emit');
484
+ webex.authorization.initQRCodeLogin();
485
+
486
+ assert.calledOnce(emitSpy);
487
+ assert.equal(emitSpy.getCall(0).args[1].eventType, 'getUserCodeFailure');
488
+ webex.authorization.pollingTimer = null;
489
+ });
490
+
491
+ it('should send correct request parameters to the API', async () => {
492
+ const clock = sinon.useFakeTimers();
493
+ const testClientId = 'test-client-id';
494
+ const testScope = 'test-scope';
495
+ const sampleData = {
496
+ device_code: 'test123',
497
+ expires_in: 300,
498
+ user_code: '421175',
499
+ verification_uri: 'http://example.com',
500
+ verification_uri_complete: 'http://example.com',
501
+ interval: 2,
502
+ };
503
+
504
+ const webex = makeWebex('http://example.com', undefined, undefined, {
505
+ credentials: {
506
+ client_id: testClientId,
507
+ scope: testScope,
508
+ },
509
+ });
510
+ webex.request.onFirstCall().resolves({statusCode: 200, body: sampleData});
511
+ sinon.spy(webex.authorization, '_startQRCodePolling');
512
+ sinon.spy(webex.authorization, '_generateQRCodeVerificationUrl');
513
+ const emitSpy = sinon.spy(webex.authorization.eventEmitter, 'emit');
514
+
515
+ webex.authorization.initQRCodeLogin();
516
+ clock.tick(2000);
517
+ await clock.runAllAsync();
518
+
519
+ assert.calledTwice(webex.request);
520
+ assert.calledOnce(webex.authorization._startQRCodePolling);
521
+ assert.calledOnce(webex.authorization._generateQRCodeVerificationUrl);
522
+ assert.equal(emitSpy.getCall(0).args[1].eventType, 'getUserCodeSuccess');
523
+
524
+ const request = webex.request.getCall(0);
525
+
526
+ assert.equal(request.args[0].form.client_id, testClientId);
527
+ assert.equal(request.args[0].form.scope, testScope);
528
+ clock.restore();
529
+ });
530
+
531
+ it('should use POST method and correct endpoint', async () => {
532
+ const clock = sinon.useFakeTimers();
533
+ const webex = makeWebex('http://example.com');
534
+ const sampleData = {
535
+ device_code: 'test123',
536
+ expires_in: 300,
537
+ user_code: '421175',
538
+ verification_uri: 'http://example.com',
539
+ verification_uri_complete: 'http://example.com',
540
+ interval: 2,
541
+ };
542
+ webex.request.resolves().resolves({statusCode: 200, body: sampleData});
543
+
544
+ webex.authorization.initQRCodeLogin();
545
+ clock.tick(2000);
546
+ await clock.runAllAsync();
547
+
548
+ const request = webex.request.getCall(0);
549
+ assert.equal(request.args[0].method, 'POST');
550
+ assert.equal(request.args[0].service, 'oauth-helper');
551
+ assert.equal(request.args[0].resource, '/actions/device/authorize');
552
+ clock.restore();
553
+ });
554
+
555
+ it('should emit getUserCodeFailure event', async () => {
556
+ const clock = sinon.useFakeTimers();
557
+ const webex = makeWebex('http://example.com');
558
+ webex.request.rejects(new Error('API Error'));
559
+ const emitSpy = sinon.spy(webex.authorization.eventEmitter, 'emit');
560
+
561
+ webex.authorization.initQRCodeLogin();
562
+
563
+ await clock.runAllAsync();
564
+
565
+ assert.calledOnce(emitSpy);
566
+ assert.equal(emitSpy.getCall(0).args[1].eventType, 'getUserCodeFailure');
567
+ clock.restore();
568
+ });
569
+ });
570
+
571
+ describe('#_startQRCodePolling()', () => {
572
+ it('requires a deviceCode', () => {
573
+ const webex = makeWebex('http://example.com');
574
+
575
+ const emitSpy = sinon.spy(webex.authorization.eventEmitter, 'emit');
576
+
577
+ webex.authorization._startQRCodePolling({});
578
+
579
+ assert.calledOnce(emitSpy);
580
+ assert.equal(emitSpy.getCall(0).args[1].eventType, 'authorizationFailure');
581
+ });
582
+
583
+ it('should send correct request parameters to the API', async () => {
584
+ const clock = sinon.useFakeTimers();
585
+ const testClientId = 'test-client-id';
586
+ const testDeviceCode = 'test-device-code';
587
+
588
+ const options = {
589
+ device_code: testDeviceCode,
590
+ interval: 2,
591
+ expires_in: 300,
592
+ };
593
+
594
+ const webex = makeWebex('http://example.com', undefined, undefined, {
595
+ credentials: {
596
+ client_id: testClientId,
597
+ },
598
+ });
599
+
600
+ webex.request.onFirstCall().resolves({statusCode: 200, body: {access_token: 'token'}});
601
+ const emitSpy = sinon.spy(webex.authorization.eventEmitter, 'emit');
602
+ const credentialsSetSpy = sinon.spy(webex.credentials, 'set');
603
+ sinon.spy(webex.authorization, 'cancelQRCodePolling');
604
+
605
+ webex.authorization._startQRCodePolling(options);
606
+ clock.tick(2000);
607
+ await clock.runAllAsync();
608
+
609
+ assert.calledOnce(webex.request);
610
+
611
+ const request = webex.request.getCall(0);
612
+
613
+ assert.equal(request.args[0].form.client_id, testClientId);
614
+ assert.equal(request.args[0].form.device_code, testDeviceCode);
615
+ assert.equal(
616
+ request.args[0].form.grant_type,
617
+ 'urn:ietf:params:oauth:grant-type:device_code'
618
+ );
619
+
620
+ assert.calledOnce(webex.authorization.cancelQRCodePolling);
621
+ assert.calledOnce(credentialsSetSpy);
622
+ assert.calledTwice(emitSpy);
623
+ assert.equal(emitSpy.getCall(0).args[1].eventType, 'authorizationSuccess');
624
+ assert.equal(emitSpy.getCall(1).args[1].eventType, 'pollingCanceled');
625
+
626
+ clock.restore();
627
+ });
628
+
629
+ it('should respect polling interval', async () => {
630
+ const clock = sinon.useFakeTimers();
631
+ const webex = makeWebex('http://example.com');
632
+ const options = {
633
+ device_code: 'test-device-code',
634
+ interval: 2,
635
+ expires_in: 300,
636
+ };
637
+
638
+ webex.request
639
+ .onFirstCall()
640
+ .rejects({statusCode: 428, body: {message: 'authorization_pending'}});
641
+ webex.request.onSecondCall().resolves({statusCode: 200, body: {access_token: 'token'}});
642
+ sinon.spy(webex.authorization, 'cancelQRCodePolling');
643
+ const emitSpy = sinon.spy(webex.authorization.eventEmitter, 'emit');
644
+
645
+ webex.authorization._startQRCodePolling(options);
646
+ await clock.tickAsync(4000);
647
+ //await clock.runAllAsync()
648
+
649
+ assert.calledTwice(webex.request);
650
+ assert.calledOnce(webex.authorization.cancelQRCodePolling);
651
+ assert.calledThrice(emitSpy);
652
+ assert.equal(emitSpy.getCall(0).args[1].eventType, 'authorizationPending');
653
+ assert.equal(emitSpy.getCall(1).args[1].eventType, 'authorizationSuccess');
654
+ assert.equal(emitSpy.getCall(2).args[1].eventType, 'pollingCanceled');
655
+ clock.restore();
656
+ });
657
+
658
+ it('should timeout after expires_in seconds', async () => {
659
+ const clock = sinon.useFakeTimers();
660
+ const webex = makeWebex('http://example.com');
661
+ const options = {
662
+ device_code: 'test-device-code',
663
+ interval: 5,
664
+ expires_in: 9,
665
+ };
666
+
667
+ webex.request.rejects({statusCode: 428, body: {message: 'authorizationPending'}});
668
+ sinon.spy(webex.authorization, 'cancelQRCodePolling');
669
+ const emitSpy = sinon.spy(webex.authorization.eventEmitter, 'emit');
670
+
671
+ webex.authorization._startQRCodePolling(options);
672
+ await clock.tickAsync(10_000);
673
+
674
+ assert.calledOnce(webex.request);
675
+ assert.calledOnce(webex.authorization.cancelQRCodePolling);
676
+ assert.calledTwice(emitSpy);
677
+ assert.equal(emitSpy.getCall(0).args[1].eventType, 'authorizationPending');
678
+ assert.equal(emitSpy.getCall(1).args[1].eventType, 'authorizationFailure');
679
+ clock.restore();
680
+ });
681
+
682
+ it('should prevent concurrent polling attempts if this is already a polling request', async () => {
683
+ const webex = makeWebex('http://example.com');
684
+ const options = {
685
+ device_code: 'test-device-code',
686
+ interval: 2,
687
+ expires_in: 300,
688
+ };
689
+
690
+ webex.authorization.pollingTimer = 1;
691
+
692
+ const emitSpy = sinon.spy(webex.authorization.eventEmitter, 'emit');
693
+ webex.authorization._startQRCodePolling(options);
694
+
695
+ assert.calledOnce(emitSpy);
696
+ assert.equal(emitSpy.getCall(0).args[1].eventType, 'authorizationFailure');
697
+ webex.authorization.pollingTimer = null;
698
+ });
699
+
700
+ it('should skip a interval when server ask for slow_down', async () => {
701
+ const clock = sinon.useFakeTimers();
702
+ const webex = makeWebex('http://example.com');
703
+ const options = {
704
+ device_code: 'test-device-code',
705
+ interval: 2,
706
+ expires_in: 300,
707
+ };
708
+
709
+ webex.request.onFirstCall().rejects({statusCode: 400, body: {message: 'slow_down'}});
710
+ webex.request.onSecondCall().resolves({statusCode: 200, body: {access_token: 'token'}});
711
+ sinon.spy(webex.authorization, 'cancelQRCodePolling');
712
+ const emitSpy = sinon.spy(webex.authorization.eventEmitter, 'emit');
713
+ const credentialsSetSpy = sinon.spy(webex.credentials, 'set');
714
+
715
+ webex.authorization._startQRCodePolling(options);
716
+ await clock.tickAsync(4000);
717
+
718
+ // Request only once because of slow_down
719
+ assert.calledOnce(webex.request);
720
+
721
+ // Wait for next interval
722
+ await clock.tickAsync(2000);
723
+
724
+ assert.calledTwice(webex.request);
725
+ assert.calledOnce(webex.authorization.cancelQRCodePolling);
726
+ assert.calledOnce(credentialsSetSpy);
727
+ assert.calledTwice(emitSpy);
728
+ assert.equal(emitSpy.getCall(0).args[1].eventType, 'authorizationSuccess');
729
+ assert.equal(emitSpy.getCall(1).args[1].eventType, 'pollingCanceled');
730
+ clock.restore();
731
+ });
732
+
733
+ it('should ignore the response from the previous polling', async () => {
734
+ const clock = sinon.useFakeTimers();
735
+ const webex = makeWebex('http://example.com');
736
+ const options = {
737
+ device_code: 'test-device-code',
738
+ interval: 2,
739
+ expires_in: 300,
740
+ };
741
+
742
+ webex.request.onFirstCall().callsFake(() => {
743
+ return new Promise((resolve) => {
744
+ setTimeout(() => {
745
+ resolve({statusCode: 200, body: {access_token: 'token'}});
746
+ }, 1000);
747
+ });
748
+ });
749
+
750
+ webex.request
751
+ .onSecondCall()
752
+ .rejects({statusCode: 428, body: {message: 'authorizationPending'}});
753
+ sinon.spy(webex.authorization, 'cancelQRCodePolling');
754
+ const emitSpy = sinon.spy(webex.authorization.eventEmitter, 'emit');
755
+
756
+ webex.authorization._startQRCodePolling(options);
757
+ await clock.tickAsync(2500);
758
+
759
+ webex.authorization.cancelQRCodePolling();
760
+
761
+ // Start new polling
762
+
763
+ webex.authorization._startQRCodePolling(options);
764
+
765
+ // Wait for next interval
766
+ await clock.tickAsync(3000);
767
+
768
+ assert.calledTwice(webex.request);
769
+ assert.calledOnce(webex.authorization.cancelQRCodePolling);
770
+ assert.calledTwice(emitSpy);
771
+ // authorizationSuccess event should not be emitted
772
+ assert.equal(emitSpy.getCall(0).args[1].eventType, 'pollingCanceled');
773
+ assert.equal(emitSpy.getCall(1).args[1].eventType, 'authorizationPending');
774
+ clock.restore();
775
+ });
776
+ });
777
+
778
+ describe('#cancelQRCodePolling()', () => {
779
+ it('should stop polling after cancellation', async () => {
780
+ const clock = sinon.useFakeTimers();
781
+ const webex = makeWebex('http://example.com');
782
+ const options = {
783
+ device_code: 'test-device-code',
784
+ interval: 2,
785
+ expires_in: 300,
786
+ };
787
+
788
+ webex.request.rejects({statusCode: 428, body: {message: 'authorizationPending'}});
789
+ const emitSpy = sinon.spy(webex.authorization.eventEmitter, 'emit');
790
+
791
+ webex.authorization._startQRCodePolling(options);
792
+ // First poll
793
+ clock.tick(2000);
794
+ assert.calledOnce(webex.request);
795
+
796
+ webex.authorization.cancelQRCodePolling();
797
+ // Wait for next interval
798
+ clock.tick(2000);
799
+
800
+ const eventArgs = emitSpy.getCall(0).args;
801
+
802
+ // Verify no additional requests were made
803
+ assert.calledOnce(webex.request);
804
+ assert.calledOnce(emitSpy);
805
+ assert.equal(eventArgs[1].eventType, 'pollingCanceled');
806
+ clock.restore();
807
+ });
808
+ it('should clear interval and reset polling request', () => {
809
+ const clock = sinon.useFakeTimers();
810
+ const webex = makeWebex('http://example.com');
811
+
812
+ const options = {
813
+ device_code: 'test_device_code',
814
+ interval: 2,
815
+ expires_in: 300,
816
+ };
817
+
818
+ webex.authorization._startQRCodePolling(options);
819
+ assert.isDefined(webex.authorization.pollingTimer);
820
+
821
+ webex.authorization.cancelQRCodePolling();
822
+ assert.isNull(webex.authorization.pollingTimer);
823
+
824
+ clock.restore();
825
+ });
826
+
827
+ it('should handle cancellation when no polling is in progress', () => {
828
+ const webex = makeWebex('http://example.com');
829
+ assert.isNull(webex.authorization.pollingTimer);
830
+
831
+ webex.authorization.cancelQRCodePolling();
832
+ assert.isNull(webex.authorization.pollingTimer);
833
+ });
834
+ });
835
+
446
836
  describe('#_generateCodeChallenge', () => {
447
837
  const expectedCodeChallenge = 'code challenge';
448
838
  // eslint-disable-next-line no-underscore-dangle
@@ -562,7 +952,7 @@ describe('plugin-authorization-browser-first-party', () => {
562
952
  const orgId = webex.authorization._extractOrgIdFromCode(code);
563
953
 
564
954
  assert.isUndefined(orgId);
565
- })
955
+ });
566
956
  });
567
957
  });
568
958
  });