@webex/plugin-authorization-browser-first-party 3.6.0-next.2 → 3.6.0-next.3

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.
@@ -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");
@@ -74,6 +75,15 @@ var Authorization = _webexCore.WebexPlugin.extend((_dec = (0, _common.whileInFli
74
75
  }
75
76
  },
76
77
  namespace: 'Credentials',
78
+ /**
79
+ * Stores the interval ID for QR code polling
80
+ * @instance
81
+ * @memberof AuthorizationBrowserFirstParty
82
+ * @type {?number}
83
+ * @private
84
+ */
85
+ pollingRequest: null,
86
+ eventEmitter: new _events.EventEmitter(),
77
87
  /**
78
88
  * Initializer
79
89
  * @instance
@@ -231,6 +241,161 @@ var Authorization = _webexCore.WebexPlugin.extend((_dec = (0, _common.whileInFli
231
241
  return _promise.default.reject(new ErrorConstructor(res._res || res));
232
242
  });
233
243
  },
244
+ /**
245
+ * Get an OAuth Login URL for QRCode. Generate QR code based on the returned URL.
246
+ * @instance
247
+ * @memberof AuthorizationBrowserFirstParty
248
+ * @emits #qRCodeLogin
249
+ */
250
+ initQRCodeLogin: function initQRCodeLogin() {
251
+ var _this3 = this;
252
+ if (this.pollingRequest) {
253
+ this.eventEmitter.emit('qRCodeLogin', {
254
+ eventType: 'getUserCodeFailure',
255
+ data: {
256
+ message: 'There is already a polling request'
257
+ }
258
+ });
259
+ return;
260
+ }
261
+ this.webex.request({
262
+ method: 'POST',
263
+ service: 'oauth-helper',
264
+ resource: '/actions/device/authorize',
265
+ form: {
266
+ client_id: this.config.client_id,
267
+ scope: this.config.scope
268
+ },
269
+ auth: {
270
+ user: this.config.client_id,
271
+ pass: this.config.client_secret,
272
+ sendImmediately: true
273
+ }
274
+ }).then(function (res) {
275
+ var _res$body = res.body,
276
+ user_code = _res$body.user_code,
277
+ verification_uri = _res$body.verification_uri,
278
+ verification_uri_complete = _res$body.verification_uri_complete;
279
+ _this3.eventEmitter.emit('qRCodeLogin', {
280
+ eventType: 'getUserCodeSuccess',
281
+ userData: {
282
+ userCode: user_code,
283
+ verificationUri: verification_uri,
284
+ verificationUriComplete: verification_uri_complete
285
+ }
286
+ });
287
+ // if device authorization success, then start to poll server to check whether the user has completed authorization
288
+ _this3._startQRCodePolling(res.body);
289
+ }).catch(function (res) {
290
+ _this3.eventEmitter.emit('qRCodeLogin', {
291
+ eventType: 'getUserCodeFailure',
292
+ data: res.body
293
+ });
294
+ });
295
+ },
296
+ /**
297
+ * Polling the server to check whether the user has completed authorization
298
+ * @instance
299
+ * @memberof AuthorizationBrowserFirstParty
300
+ * @param {Object} options
301
+ * @emits #qRCodeLogin
302
+ */
303
+ _startQRCodePolling: function _startQRCodePolling() {
304
+ var _this4 = this;
305
+ var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
306
+ if (!options.device_code) {
307
+ this.eventEmitter.emit('qRCodeLogin', {
308
+ eventType: 'authorizationFailure',
309
+ data: {
310
+ message: 'A deviceCode is required'
311
+ }
312
+ });
313
+ return;
314
+ }
315
+ if (this.pollingRequest) {
316
+ this.eventEmitter.emit('qRCodeLogin', {
317
+ eventType: 'authorizationFailure',
318
+ data: {
319
+ message: 'There is already a polling request'
320
+ }
321
+ });
322
+ return;
323
+ }
324
+ var deviceCode = options.device_code,
325
+ _options$interval = options.interval,
326
+ interval = _options$interval === void 0 ? 2 : _options$interval,
327
+ _options$expires_in = options.expires_in,
328
+ expiresIn = _options$expires_in === void 0 ? 300 : _options$expires_in;
329
+ var attempts = 0;
330
+ var maxAttempts = expiresIn / interval;
331
+ this.pollingRequest = setInterval(function () {
332
+ attempts += 1;
333
+ var currentAttempts = attempts;
334
+ _this4.webex.request({
335
+ method: 'POST',
336
+ service: 'oauth-helper',
337
+ resource: '/actions/device/token',
338
+ form: {
339
+ grant_type: 'urn:ietf:params:oauth:grant-type:device_code',
340
+ device_code: deviceCode,
341
+ client_id: _this4.config.client_id
342
+ },
343
+ auth: {
344
+ user: _this4.config.client_id,
345
+ pass: _this4.config.client_secret,
346
+ sendImmediately: true
347
+ }
348
+ }).then(function (res) {
349
+ if (_this4.pollingRequest === null) return;
350
+ _this4.eventEmitter.emit('qRCodeLogin', {
351
+ eventType: 'authorizationSuccess',
352
+ data: res.body
353
+ });
354
+ _this4.cancelQRCodePolling();
355
+ }).catch(function (res) {
356
+ if (_this4.pollingRequest === null) return;
357
+ if (currentAttempts >= maxAttempts) {
358
+ _this4.eventEmitter.emit('qRCodeLogin', {
359
+ eventType: 'authorizationFailure',
360
+ data: {
361
+ message: 'Authorization timed out'
362
+ }
363
+ });
364
+ _this4.cancelQRCodePolling();
365
+ return;
366
+ }
367
+ // if the statusCode is 428 which means that the authorization request is still pending
368
+ // as the end user hasn't yet completed the user-interaction steps. So keep polling.
369
+ if (res.statusCode === 428) {
370
+ _this4.eventEmitter.emit('qRCodeLogin', {
371
+ eventType: 'authorizationPending',
372
+ data: res.body
373
+ });
374
+ return;
375
+ }
376
+ _this4.cancelQRCodePolling();
377
+ _this4.eventEmitter.emit('qRCodeLogin', {
378
+ eventType: 'authorizationFailure',
379
+ data: res.body
380
+ });
381
+ });
382
+ }, interval * 1000);
383
+ },
384
+ /**
385
+ * cancel polling request
386
+ * @instance
387
+ * @memberof AuthorizationBrowserFirstParty
388
+ * @returns {void}
389
+ */
390
+ cancelQRCodePolling: function cancelQRCodePolling() {
391
+ if (this.pollingRequest) {
392
+ clearInterval(this.pollingRequest);
393
+ this.eventEmitter.emit('qRCodeLogin', {
394
+ eventType: 'pollingCanceled'
395
+ });
396
+ this.pollingRequest = null;
397
+ }
398
+ },
234
399
  /**
235
400
  * Extracts the orgId from the returned code from idbroker
236
401
  * Description of how to parse the code can be found here:
@@ -339,7 +504,7 @@ var Authorization = _webexCore.WebexPlugin.extend((_dec = (0, _common.whileInFli
339
504
  throw new Error("CSRF token ".concat(token, " does not match stored token ").concat(sessionToken));
340
505
  }
341
506
  },
342
- version: "3.6.0-next.2"
507
+ version: "3.6.0-next.3"
343
508
  }, ((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
509
  var _default = exports.default = Authorization;
345
510
  //# 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","Authorization","WebexPlugin","extend","whileInFlight","derived","isAuthenticating","deps","fn","isAuthorizing","session","default","type","ready","namespace","pollingRequest","eventEmitter","EventEmitter","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","initQRCodeLogin","_this3","emit","eventType","data","message","service","resource","scope","_res$body","user_code","verification_uri","verification_uri_complete","userData","userCode","verificationUri","verificationUriComplete","_startQRCodePolling","_this4","device_code","deviceCode","_options$interval","interval","_options$expires_in","expires_in","expiresIn","attempts","maxAttempts","setInterval","currentAttempts","cancelQRCodePolling","clearInterval","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';\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 * 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 /**\n * Stores the interval ID for QR code polling\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @type {?number}\n * @private\n */\n pollingRequest: null,\n\n eventEmitter: new EventEmitter(),\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 * 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.pollingRequest) {\n this.eventEmitter.emit('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 this.eventEmitter.emit('qRCodeLogin', {\n eventType: 'getUserCodeSuccess',\n userData: {\n userCode: user_code,\n verificationUri: verification_uri,\n verificationUriComplete: verification_uri_complete,\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('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('qRCodeLogin', {\n eventType: 'authorizationFailure',\n data: {message: 'A deviceCode is required'},\n });\n return;\n }\n\n if (this.pollingRequest) {\n this.eventEmitter.emit('qRCodeLogin', {\n eventType: 'authorizationFailure',\n data: {message: 'There is already a polling request'},\n });\n return;\n }\n\n const {device_code: deviceCode, interval = 2, expires_in: expiresIn = 300} = options;\n\n let attempts = 0;\n const maxAttempts = expiresIn / interval;\n\n this.pollingRequest = setInterval(() => {\n attempts += 1;\n\n const currentAttempts = attempts;\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 (this.pollingRequest === null) return;\n\n this.eventEmitter.emit('qRCodeLogin', {\n eventType: 'authorizationSuccess',\n data: res.body,\n });\n this.cancelQRCodePolling();\n })\n .catch((res) => {\n if (this.pollingRequest === null) return;\n\n if (currentAttempts >= maxAttempts) {\n this.eventEmitter.emit('qRCodeLogin', {\n eventType: 'authorizationFailure',\n data: {message: 'Authorization timed out'}\n });\n this.cancelQRCodePolling();\n return;\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('qRCodeLogin', {\n eventType: 'authorizationPending',\n data: res.body\n });\n return;\n }\n\n this.cancelQRCodePolling();\n\n this.eventEmitter.emit('qRCodeLogin', {\n eventType: 'authorizationFailure',\n data: res.body\n });\n });\n }, interval * 1000);\n },\n\n /**\n * cancel polling request\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @returns {void}\n */\n cancelQRCodePolling() {\n if (this.pollingRequest) {\n clearInterval(this.pollingRequest);\n this.eventEmitter.emit('qRCodeLogin', {\n eventType: 'pollingCanceled',\n });\n this.pollingRequest = null;\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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"}
package/package.json CHANGED
@@ -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-next.2"
59
+ "version": "3.6.0-next.3"
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';
@@ -66,6 +67,18 @@ const Authorization = WebexPlugin.extend({
66
67
 
67
68
  namespace: 'Credentials',
68
69
 
70
+
71
+ /**
72
+ * Stores the interval ID for QR code polling
73
+ * @instance
74
+ * @memberof AuthorizationBrowserFirstParty
75
+ * @type {?number}
76
+ * @private
77
+ */
78
+ pollingRequest: null,
79
+
80
+ eventEmitter: new EventEmitter(),
81
+
69
82
  /**
70
83
  * Initializer
71
84
  * @instance
@@ -240,6 +253,162 @@ const Authorization = WebexPlugin.extend({
240
253
  });
241
254
  },
242
255
 
256
+ /**
257
+ * Get an OAuth Login URL for QRCode. Generate QR code based on the returned URL.
258
+ * @instance
259
+ * @memberof AuthorizationBrowserFirstParty
260
+ * @emits #qRCodeLogin
261
+ */
262
+ initQRCodeLogin() {
263
+ if (this.pollingRequest) {
264
+ this.eventEmitter.emit('qRCodeLogin', {
265
+ eventType: 'getUserCodeFailure',
266
+ data: {message: 'There is already a polling request'},
267
+ });
268
+ return;
269
+ }
270
+
271
+ this.webex
272
+ .request({
273
+ method: 'POST',
274
+ service: 'oauth-helper',
275
+ resource: '/actions/device/authorize',
276
+ form: {
277
+ client_id: this.config.client_id,
278
+ scope: this.config.scope,
279
+ },
280
+ auth: {
281
+ user: this.config.client_id,
282
+ pass: this.config.client_secret,
283
+ sendImmediately: true,
284
+ },
285
+ })
286
+ .then((res) => {
287
+ const {user_code, verification_uri, verification_uri_complete} = res.body;
288
+ this.eventEmitter.emit('qRCodeLogin', {
289
+ eventType: 'getUserCodeSuccess',
290
+ userData: {
291
+ userCode: user_code,
292
+ verificationUri: verification_uri,
293
+ verificationUriComplete: verification_uri_complete,
294
+ }
295
+ });
296
+ // if device authorization success, then start to poll server to check whether the user has completed authorization
297
+ this._startQRCodePolling(res.body);
298
+ })
299
+ .catch((res) => {
300
+ this.eventEmitter.emit('qRCodeLogin', {
301
+ eventType: 'getUserCodeFailure',
302
+ data: res.body,
303
+ });
304
+ });
305
+ },
306
+
307
+ /**
308
+ * Polling the server to check whether the user has completed authorization
309
+ * @instance
310
+ * @memberof AuthorizationBrowserFirstParty
311
+ * @param {Object} options
312
+ * @emits #qRCodeLogin
313
+ */
314
+ _startQRCodePolling(options = {}) {
315
+ if (!options.device_code) {
316
+ this.eventEmitter.emit('qRCodeLogin', {
317
+ eventType: 'authorizationFailure',
318
+ data: {message: 'A deviceCode is required'},
319
+ });
320
+ return;
321
+ }
322
+
323
+ if (this.pollingRequest) {
324
+ this.eventEmitter.emit('qRCodeLogin', {
325
+ eventType: 'authorizationFailure',
326
+ data: {message: 'There is already a polling request'},
327
+ });
328
+ return;
329
+ }
330
+
331
+ const {device_code: deviceCode, interval = 2, expires_in: expiresIn = 300} = options;
332
+
333
+ let attempts = 0;
334
+ const maxAttempts = expiresIn / interval;
335
+
336
+ this.pollingRequest = setInterval(() => {
337
+ attempts += 1;
338
+
339
+ const currentAttempts = attempts;
340
+ this.webex
341
+ .request({
342
+ method: 'POST',
343
+ service: 'oauth-helper',
344
+ resource: '/actions/device/token',
345
+ form: {
346
+ grant_type: 'urn:ietf:params:oauth:grant-type:device_code',
347
+ device_code: deviceCode,
348
+ client_id: this.config.client_id,
349
+ },
350
+ auth: {
351
+ user: this.config.client_id,
352
+ pass: this.config.client_secret,
353
+ sendImmediately: true,
354
+ },
355
+ })
356
+ .then((res) => {
357
+ if (this.pollingRequest === null) return;
358
+
359
+ this.eventEmitter.emit('qRCodeLogin', {
360
+ eventType: 'authorizationSuccess',
361
+ data: res.body,
362
+ });
363
+ this.cancelQRCodePolling();
364
+ })
365
+ .catch((res) => {
366
+ if (this.pollingRequest === null) return;
367
+
368
+ if (currentAttempts >= maxAttempts) {
369
+ this.eventEmitter.emit('qRCodeLogin', {
370
+ eventType: 'authorizationFailure',
371
+ data: {message: 'Authorization timed out'}
372
+ });
373
+ this.cancelQRCodePolling();
374
+ return;
375
+ }
376
+ // if the statusCode is 428 which means that the authorization request is still pending
377
+ // as the end user hasn't yet completed the user-interaction steps. So keep polling.
378
+ if (res.statusCode === 428) {
379
+ this.eventEmitter.emit('qRCodeLogin', {
380
+ eventType: 'authorizationPending',
381
+ data: res.body
382
+ });
383
+ return;
384
+ }
385
+
386
+ this.cancelQRCodePolling();
387
+
388
+ this.eventEmitter.emit('qRCodeLogin', {
389
+ eventType: 'authorizationFailure',
390
+ data: res.body
391
+ });
392
+ });
393
+ }, interval * 1000);
394
+ },
395
+
396
+ /**
397
+ * cancel polling request
398
+ * @instance
399
+ * @memberof AuthorizationBrowserFirstParty
400
+ * @returns {void}
401
+ */
402
+ cancelQRCodePolling() {
403
+ if (this.pollingRequest) {
404
+ clearInterval(this.pollingRequest);
405
+ this.eventEmitter.emit('qRCodeLogin', {
406
+ eventType: 'pollingCanceled',
407
+ });
408
+ this.pollingRequest = null;
409
+ }
410
+ },
411
+
243
412
  /**
244
413
  * Extracts the orgId from the returned code from idbroker
245
414
  * Description of how to parse the code can be found here:
@@ -443,6 +443,280 @@ describe('plugin-authorization-browser-first-party', () => {
443
443
  });
444
444
  });
445
445
 
446
+ describe('#initQRCodeLogin()', () => {
447
+ it('should prevent concurrent request if there is already a polling request', async () => {
448
+ const webex = makeWebex('http://example.com');
449
+
450
+ webex.authorization.pollingRequest = 1;
451
+ const emitSpy = sinon.spy(webex.authorization.eventEmitter, 'emit');
452
+ webex.authorization.initQRCodeLogin();
453
+
454
+ assert.calledOnce(emitSpy);
455
+ assert.equal(emitSpy.getCall(0).args[1].eventType, 'getUserCodeFailure');
456
+ webex.authorization.pollingRequest = null;
457
+ });
458
+
459
+ it('should send correct request parameters to the API', async () => {
460
+ const clock = sinon.useFakeTimers();
461
+ const testClientId = 'test-client-id';
462
+ const testScope = 'test-scope';
463
+ const sampleData = {
464
+ device_code: "test123",
465
+ expires_in: 300,
466
+ user_code: "421175",
467
+ verification_uri: "http://example.com",
468
+ verification_uri_complete: "http://example.com",
469
+ interval: 2
470
+ };
471
+
472
+ const webex = makeWebex('http://example.com', undefined, undefined, {
473
+ credentials: {
474
+ client_id: testClientId,
475
+ scope: testScope,
476
+ }
477
+ });
478
+ webex.request.onFirstCall().resolves({statusCode: 200, body: sampleData});
479
+ sinon.spy(webex.authorization, '_startQRCodePolling');
480
+ const emitSpy = sinon.spy(webex.authorization.eventEmitter, 'emit');
481
+
482
+ webex.authorization.initQRCodeLogin();
483
+ clock.tick(2000);
484
+ await clock.runAllAsync()
485
+
486
+ assert.calledTwice(webex.request);
487
+ assert.calledOnce(webex.authorization._startQRCodePolling);
488
+ assert.equal(emitSpy.getCall(0).args[1].eventType, 'getUserCodeSuccess');
489
+
490
+ const request = webex.request.getCall(0);
491
+
492
+ assert.equal(request.args[0].form.client_id, testClientId);
493
+ assert.equal(request.args[0].form.scope, testScope);
494
+ clock.restore();
495
+ });
496
+
497
+ it('should use POST method and correct endpoint', async () => {
498
+ const clock = sinon.useFakeTimers();
499
+ const webex = makeWebex('http://example.com');
500
+ const sampleData = {
501
+ device_code: "test123",
502
+ expires_in: 300,
503
+ user_code: "421175",
504
+ verification_uri: "http://example.com",
505
+ verification_uri_complete: "http://example.com",
506
+ interval: 2
507
+ };
508
+ webex.request.resolves().resolves({statusCode: 200, body: sampleData});
509
+
510
+ webex.authorization.initQRCodeLogin();
511
+ clock.tick(2000);
512
+ await clock.runAllAsync()
513
+
514
+ const request = webex.request.getCall(0);
515
+ assert.equal(request.args[0].method, 'POST');
516
+ assert.equal(request.args[0].service, 'oauth-helper');
517
+ assert.equal(request.args[0].resource, '/actions/device/authorize');
518
+ clock.restore();
519
+ });
520
+
521
+ it('should emit getUserCodeFailure event', async () => {
522
+ const clock = sinon.useFakeTimers();
523
+ const webex = makeWebex('http://example.com');
524
+ webex.request.rejects(new Error('API Error'));
525
+ const emitSpy = sinon.spy(webex.authorization.eventEmitter, 'emit');
526
+
527
+ webex.authorization.initQRCodeLogin();
528
+
529
+ await clock.runAllAsync()
530
+
531
+ assert.calledOnce(emitSpy);
532
+ assert.equal(emitSpy.getCall(0).args[1].eventType, 'getUserCodeFailure');
533
+ clock.restore();
534
+ });
535
+ });
536
+
537
+ describe('#_startQRCodePolling()', () => {
538
+ it('requires a deviceCode', () => {
539
+ const webex = makeWebex('http://example.com');
540
+
541
+ const emitSpy = sinon.spy(webex.authorization.eventEmitter, 'emit');
542
+
543
+ webex.authorization._startQRCodePolling({});
544
+
545
+ assert.calledOnce(emitSpy);
546
+ assert.equal(emitSpy.getCall(0).args[1].eventType, 'authorizationFailure');
547
+ });
548
+
549
+ it('should send correct request parameters to the API', async () => {
550
+ const clock = sinon.useFakeTimers();
551
+ const testClientId = 'test-client-id';
552
+ const testDeviceCode = 'test-device-code';
553
+
554
+ const options = {
555
+ device_code: testDeviceCode,
556
+ interval: 2,
557
+ expires_in: 300,
558
+ };
559
+
560
+ const webex = makeWebex('http://example.com', undefined, undefined, {
561
+ credentials: {
562
+ client_id: testClientId,
563
+ }
564
+ });
565
+
566
+ webex.request.onFirstCall().resolves({statusCode: 200, body: {access_token: 'token'}});
567
+ const emitSpy = sinon.spy(webex.authorization.eventEmitter, 'emit');
568
+ sinon.spy(webex.authorization, 'cancelQRCodePolling');
569
+
570
+ webex.authorization._startQRCodePolling(options);
571
+ clock.tick(2000);
572
+ await clock.runAllAsync()
573
+
574
+ assert.calledOnce(webex.request);
575
+
576
+ const request = webex.request.getCall(0);
577
+
578
+ assert.equal(request.args[0].form.client_id, testClientId);
579
+ assert.equal(request.args[0].form.device_code, testDeviceCode);
580
+ assert.equal(request.args[0].form.grant_type, 'urn:ietf:params:oauth:grant-type:device_code');
581
+
582
+ assert.calledOnce(webex.authorization.cancelQRCodePolling);
583
+ assert.calledTwice(emitSpy);
584
+ assert.equal(emitSpy.getCall(0).args[1].eventType, 'authorizationSuccess');
585
+ assert.equal(emitSpy.getCall(1).args[1].eventType, 'pollingCanceled');
586
+
587
+ clock.restore();
588
+ });
589
+
590
+ it('should respect polling interval', async () => {
591
+ const clock = sinon.useFakeTimers();
592
+ const webex = makeWebex('http://example.com');
593
+ const options = {
594
+ device_code: 'test-device-code',
595
+ interval: 2,
596
+ expires_in: 300
597
+ };
598
+
599
+ webex.request.onFirstCall().rejects({statusCode: 428, body: {message: 'authorization_pending'}});
600
+ webex.request.onSecondCall().resolves({statusCode: 200, body: {access_token: 'token'}});
601
+ sinon.spy(webex.authorization, 'cancelQRCodePolling');
602
+ const emitSpy = sinon.spy(webex.authorization.eventEmitter, 'emit');
603
+
604
+ webex.authorization._startQRCodePolling(options);
605
+ clock.tick(4000);
606
+ await clock.runAllAsync()
607
+
608
+ assert.calledTwice(webex.request);
609
+ assert.calledOnce(webex.authorization.cancelQRCodePolling);
610
+ assert.calledTwice(emitSpy);
611
+ assert.equal(emitSpy.getCall(0).args[1].eventType, 'authorizationSuccess');
612
+ assert.equal(emitSpy.getCall(1).args[1].eventType, 'pollingCanceled');
613
+ clock.restore();
614
+ });
615
+
616
+ it('should timeout after expires_in seconds', async () => {
617
+ const clock = sinon.useFakeTimers();
618
+ const webex = makeWebex('http://example.com');
619
+ const options = {
620
+ device_code: 'test-device-code',
621
+ interval: 5,
622
+ expires_in: 10
623
+ };
624
+
625
+ webex.request.rejects({statusCode: 428, body: {message: 'authorizationPending'}});
626
+ sinon.spy(webex.authorization, 'cancelQRCodePolling');
627
+ const emitSpy = sinon.spy(webex.authorization.eventEmitter, 'emit');
628
+
629
+ webex.authorization._startQRCodePolling(options);
630
+ clock.tick(10000);
631
+ await clock.runAllAsync()
632
+
633
+ assert.calledTwice(webex.request);
634
+ assert.calledOnce(webex.authorization.cancelQRCodePolling);
635
+ assert.calledThrice(emitSpy);
636
+ assert.equal(emitSpy.getCall(0).args[1].eventType, 'authorizationPending');
637
+ assert.equal(emitSpy.getCall(1).args[1].eventType, 'authorizationFailure');
638
+ assert.equal(emitSpy.getCall(2).args[1].eventType, 'pollingCanceled');
639
+ clock.restore();
640
+ });
641
+
642
+ it('should prevent concurrent polling attempts if this is already a polling request', async () => {
643
+ const webex = makeWebex('http://example.com');
644
+ const options = {
645
+ device_code: 'test-device-code',
646
+ interval: 2,
647
+ expires_in: 300
648
+ };
649
+
650
+ webex.authorization.pollingRequest = 1;
651
+
652
+ const emitSpy = sinon.spy(webex.authorization.eventEmitter, 'emit');
653
+ webex.authorization._startQRCodePolling(options);
654
+
655
+ assert.calledOnce(emitSpy);
656
+ assert.equal(emitSpy.getCall(0).args[1].eventType, 'authorizationFailure');
657
+ webex.authorization.pollingRequest = null;
658
+ });
659
+ });
660
+
661
+ describe('#cancelQRCodePolling()', () => {
662
+ it('should stop polling after cancellation', async () => {
663
+ const clock = sinon.useFakeTimers();
664
+ const webex = makeWebex('http://example.com');
665
+ const options = {
666
+ device_code: 'test-device-code',
667
+ interval: 2,
668
+ expires_in: 300
669
+ };
670
+
671
+ webex.request.rejects({statusCode: 428, body: {message: 'authorizationPending'}});
672
+ const emitSpy = sinon.spy(webex.authorization.eventEmitter, 'emit');
673
+
674
+ webex.authorization._startQRCodePolling(options);
675
+ // First poll
676
+ clock.tick(2000);
677
+ assert.calledOnce(webex.request);
678
+
679
+ webex.authorization.cancelQRCodePolling();
680
+ // Wait for next interval
681
+ clock.tick(2000);
682
+
683
+ const eventArgs = emitSpy.getCall(0).args;
684
+
685
+ // Verify no additional requests were made
686
+ assert.calledOnce(webex.request);
687
+ assert.calledOnce(emitSpy);
688
+ assert.equal(eventArgs[1].eventType, 'pollingCanceled');
689
+ clock.restore();
690
+ });
691
+ it('should clear interval and reset polling request', () => {
692
+ const clock = sinon.useFakeTimers();
693
+ const webex = makeWebex('http://example.com');
694
+
695
+ const options = {
696
+ device_code: 'test_device_code',
697
+ interval: 2,
698
+ expires_in: 300,
699
+ };
700
+
701
+ webex.authorization._startQRCodePolling(options);
702
+ assert.isDefined(webex.authorization.pollingRequest);
703
+
704
+ webex.authorization.cancelQRCodePolling();
705
+ assert.isNull(webex.authorization.pollingRequest);
706
+
707
+ clock.restore();
708
+ });
709
+
710
+ it('should handle cancellation when no polling is in progress', () => {
711
+ const webex = makeWebex('http://example.com');
712
+ assert.isNull(webex.authorization.pollingRequest);
713
+
714
+ webex.authorization.cancelQRCodePolling();
715
+ assert.isNull(webex.authorization.pollingRequest);
716
+ });
717
+
718
+ });
719
+
446
720
  describe('#_generateCodeChallenge', () => {
447
721
  const expectedCodeChallenge = 'code challenge';
448
722
  // eslint-disable-next-line no-underscore-dangle