@webex/plugin-authorization-browser-first-party 3.8.1 → 3.9.0-multiple-llm.1

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.
@@ -25,11 +25,16 @@ var _lodash = require("lodash");
25
25
  var _uuid = _interopRequireDefault(require("uuid"));
26
26
  var _encBase64url = _interopRequireDefault(require("crypto-js/enc-base64url"));
27
27
  var _cryptoJs = _interopRequireDefault(require("crypto-js"));
28
- var _dec, _dec2, _obj;
28
+ var _dec, _dec2, _obj; // @ts-nocheck
29
+ /* eslint-disable */
29
30
  /*!
30
31
  * Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.
31
32
  */
32
33
  /* eslint camelcase: [0] */
34
+ /**
35
+ * TS checking disabled: file uses legacy decorator syntax inside an object literal
36
+ * transformed by Babel. Safe to ignore for now.
37
+ */
33
38
  // Necessary to require lodash this way in order to stub
34
39
  // methods in the unit test
35
40
  var lodash = require('lodash');
@@ -47,11 +52,29 @@ var Events = exports.Events = {
47
52
  };
48
53
 
49
54
  /**
50
- * Browser support for OAuth2. Automatically parses the URL query for an
51
- * authorization code
55
+ * Browser support for OAuth2 for first-party (Webex Web Client) usage.
56
+ *
57
+ * High-level flow handled by this module:
58
+ * 1. initiateLogin() constructs authorization request (adds CSRF + PKCE).
59
+ * 2. Browser navigates to IdBroker (login).
60
+ * 3. IdBroker redirects back with ?code=... (&state=...).
61
+ * 4. initialize() detects code, validates state/CSRF, cleans URL, optionally
62
+ * pre-fetches a preauth catalog, then exchanges the code via
63
+ * requestAuthorizationCodeGrant().
64
+ * 5. Sets resulting supertoken (access/refresh token bundle) on credentials.
65
+ *
66
+ * Additional supported flow:
67
+ * - Device Authorization (QR Code login):
68
+ * initQRCodeLogin() obtains device + user codes and begins polling
69
+ * _startQRCodePolling() until tokens are issued or timeout/cancel occurs.
70
+ *
71
+ * Security considerations implemented:
72
+ * - CSRF token (state.csrf_token) generation + verification.
73
+ * - PKCE (S256) code verifier + challenge generation and consumption.
74
+ * - URL cleanup after redirect (removes code & CSRF to prevent leakage).
75
+ *
76
+ * Use of this plugin for anything other than the Webex Web Client is discouraged.
52
77
  *
53
- * Use of this plugin for anything other than the Webex Web Client is strongly
54
- * discouraged and may be broken at any time
55
78
  * @class
56
79
  * @name AuthorizationBrowserFirstParty
57
80
  * @private
@@ -82,6 +105,10 @@ var Authorization = _webexCore.WebexPlugin.extend((_dec = (0, _common.whileInFli
82
105
  default: false,
83
106
  type: 'boolean'
84
107
  },
108
+ /**
109
+ * Indicates that the plugin has finished any automatic startup
110
+ * processing (e.g., exchanging a returned authorization code)
111
+ */
85
112
  ready: {
86
113
  default: false,
87
114
  type: 'boolean'
@@ -89,7 +116,7 @@ var Authorization = _webexCore.WebexPlugin.extend((_dec = (0, _common.whileInFli
89
116
  },
90
117
  namespace: 'Credentials',
91
118
  /**
92
- * EventEmitter for authorization events
119
+ * EventEmitter for authorization events such as QR code login progress
93
120
  * @instance
94
121
  * @memberof AuthorizationBrowserFirstParty
95
122
  * @type {EventEmitter}
@@ -97,7 +124,7 @@ var Authorization = _webexCore.WebexPlugin.extend((_dec = (0, _common.whileInFli
97
124
  */
98
125
  eventEmitter: new _events.EventEmitter(),
99
126
  /**
100
- * Stores the timer ID for QR code polling
127
+ * Stores the timer ID for QR code polling (device authorization)
101
128
  * @instance
102
129
  * @memberof AuthorizationBrowserFirstParty
103
130
  * @type {?number}
@@ -105,7 +132,7 @@ var Authorization = _webexCore.WebexPlugin.extend((_dec = (0, _common.whileInFli
105
132
  */
106
133
  pollingTimer: null,
107
134
  /**
108
- * Stores the expiration timer ID for QR code polling
135
+ * Stores the expiration timer ID for QR code polling (overall timeout)
109
136
  * @instance
110
137
  * @memberof AuthorizationBrowserFirstParty
111
138
  * @type {?number}
@@ -113,7 +140,8 @@ var Authorization = _webexCore.WebexPlugin.extend((_dec = (0, _common.whileInFli
113
140
  */
114
141
  pollingExpirationTimer: null,
115
142
  /**
116
- * Monotonically increasing id to identify the current polling request
143
+ * Monotonically increasing id to identify the current polling request.
144
+ * Used to safely ignore late poll responses after a cancel/reset.
117
145
  * @instance
118
146
  * @memberof AuthorizationBrowserFirstParty
119
147
  * @type {number}
@@ -121,7 +149,7 @@ var Authorization = _webexCore.WebexPlugin.extend((_dec = (0, _common.whileInFli
121
149
  */
122
150
  pollingId: 0,
123
151
  /**
124
- * Identifier for the current polling request
152
+ * Identifier for the current polling request (snapshot of pollingId)
125
153
  * @instance
126
154
  * @memberof AuthorizationBrowserFirstParty
127
155
  * @type {?number}
@@ -129,11 +157,34 @@ var Authorization = _webexCore.WebexPlugin.extend((_dec = (0, _common.whileInFli
129
157
  */
130
158
  currentPollingId: null,
131
159
  /**
132
- * Initializer
133
- * @instance
134
- * @memberof AuthorizationBrowserFirstParty
135
- * @private
136
- * @returns {Authorization}
160
+ * Auto executes during Webex.init() – you do NOT call this yourself.
161
+ *
162
+ * Purpose: Seamless "redirect completion" of the OAuth Authorization Code (+ PKCE) flow.
163
+ *
164
+ * Simple summary:
165
+ * - You call initiateLogin() which redirects user to IdBroker.
166
+ * - User signs in; IdBroker redirects back to your redirect_uri with ?code=... (&state=...).
167
+ * - During SDK startup this initialize() runs automatically, sees the code, and
168
+ * silently finishes the login (validates state/CSRF + PKCE, scrubs URL, exchanges code).
169
+ * - When done, webex.credentials.supertoken holds access+refresh and ready=true.
170
+ *
171
+ * Step-by-step:
172
+ * 1. Inspect current window.location for ?code= (& state=).
173
+ * 2. If no code: set ready=true immediately (nothing to complete).
174
+ * 3. If code present:
175
+ * - Decode base64 state JSON.
176
+ * - Verify CSRF token matches sessionStorage value.
177
+ * - Retrieve then delete PKCE code_verifier (single use).
178
+ * - Optionally derive preauth hint (emailhash in state OR orgId parsed from code).
179
+ * - Clean the URL (history.replaceState) to remove code & csrf token data.
180
+ * - nextTick:
181
+ * a. Best‑effort preauth catalog fetch (non-blocking).
182
+ * b. Exchange authorization code (with code_verifier if any) for supertoken
183
+ * and store on webex.credentials.
184
+ * 4. Set ready=true after the async sequence finishes (or immediately if step 2).
185
+ *
186
+ * Result: If the redirect included a valid code the token exchange is completed
187
+ * automatically—no extra API call needed after Webex.init().
137
188
  */
138
189
  // eslint-disable-next-line complexity
139
190
  initialize: function initialize() {
@@ -143,23 +194,37 @@ var Authorization = _webexCore.WebexPlugin.extend((_dec = (0, _common.whileInFli
143
194
  }
144
195
  var ret = (0, _apply.default)(_webexCore.WebexPlugin.prototype.initialize, this, attrs);
145
196
  var location = _url.default.parse(this.webex.getWindow().location.href, true);
197
+
198
+ // Check if redirect includes error
146
199
  this._checkForErrors(location);
147
200
  var code = location.query.code;
201
+
202
+ // If no authorization code returned, nothing to do
148
203
  if (!code) {
149
204
  this.ready = true;
150
205
  return ret;
151
206
  }
207
+
208
+ // Decode and parse state object (if present)
152
209
  if (location.query.state) {
153
210
  location.query.state = JSON.parse(_common.base64.decode(location.query.state));
154
211
  } else {
155
212
  location.query.state = {};
156
213
  }
214
+
215
+ // Retrieve PKCE code verifier (if a PKCE flow was initiated)
157
216
  var codeVerifier = this.webex.getWindow().sessionStorage.getItem(OAUTH2_CODE_VERIFIER);
217
+ // Immediately remove code verifier to minimize exposure
158
218
  this.webex.getWindow().sessionStorage.removeItem(OAUTH2_CODE_VERIFIER);
159
219
  var emailhash = location.query.state.emailhash;
220
+
221
+ // Validate CSRF token included in state
160
222
  this._verifySecurityToken(location.query);
223
+ // Remove code + CSRF token remnants from URL (history replace)
161
224
  this._cleanUrl(location);
162
225
  var preauthCatalogParams;
226
+
227
+ // Attempt to extract orgId from structured authorization code (if present)
163
228
  var orgId = this._extractOrgIdFromCode(code);
164
229
  if (emailhash) {
165
230
  preauthCatalogParams = {
@@ -171,11 +236,12 @@ var Authorization = _webexCore.WebexPlugin.extend((_dec = (0, _common.whileInFli
171
236
  };
172
237
  }
173
238
 
174
- // Wait until nextTick in case `credentials` hasn't initialized yet
239
+ // Defer token exchange until next tick in case credentials plugin not ready yet
175
240
  process.nextTick(function () {
176
241
  _this.webex.internal.services.collectPreauthCatalog(preauthCatalogParams).catch(function () {
177
242
  return _promise.default.resolve();
178
- }).then(function () {
243
+ }) // Non-fatal if catalog collection fails
244
+ .then(function () {
179
245
  return _this.requestAuthorizationCodeGrant({
180
246
  code: code,
181
247
  codeVerifier: codeVerifier
@@ -183,13 +249,24 @@ var Authorization = _webexCore.WebexPlugin.extend((_dec = (0, _common.whileInFli
183
249
  }).catch(function (error) {
184
250
  _this.logger.warn('authorization: failed initial authorization code grant request', error);
185
251
  }).then(function () {
252
+ // Mark plugin ready regardless of success/failure of token exchange
186
253
  _this.ready = true;
187
254
  });
188
255
  });
189
256
  return ret;
190
257
  },
191
258
  /**
192
- * Kicks off an oauth flow
259
+ * Kicks off an OAuth authorization code flow (first party).
260
+ *
261
+ * Adds security + PKCE properties:
262
+ * - SHA256(email) (emailHash & emailhash) for preauth and redirect flows
263
+ * - state.csrf_token for CSRF protection
264
+ * - PKCE code_challenge (S256)
265
+ *
266
+ * NOTE: This does not itself perform the redirect; it calls
267
+ * initiateAuthorizationCodeGrant() which changes window location or opens
268
+ * a separate window as configured.
269
+ *
193
270
  * @instance
194
271
  * @memberof AuthorizationBrowserFirstParty
195
272
  * @param {Object} options
@@ -198,25 +275,38 @@ var Authorization = _webexCore.WebexPlugin.extend((_dec = (0, _common.whileInFli
198
275
  initiateLogin: function initiateLogin() {
199
276
  var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
200
277
  options = (0, _lodash.cloneDeep)(options);
278
+
279
+ // Optionally compute heuristic email hash for preauth usage
201
280
  if (options.email) {
202
281
  options.emailHash = _cryptoJs.default.SHA256(options.email).toString();
203
282
  }
204
- delete options.email;
283
+ delete options.email; // Ensure raw email not propagated further
284
+
205
285
  options.state = options.state || {};
286
+ // Embed CSRF token
206
287
  options.state.csrf_token = this._generateSecurityToken();
207
- // catalog uses emailhash and redirectCI uses emailHash
288
+ // Provide email hash in lower-case key used by catalog service
289
+ // (Note: catalog uses emailhash and redirectCI uses emailHash)
208
290
  options.state.emailhash = options.emailHash;
291
+
292
+ // PKCE - produce code_challenge (S256) and persist code_verifier
209
293
  options.code_challenge = this._generateCodeChallenge();
210
294
  options.code_challenge_method = 'S256';
211
295
  return this.initiateAuthorizationCodeGrant(options);
212
296
  },
213
297
  /**
214
- * Kicks off the Implicit Code grant flow. Typically called via
215
- * {@link AuthorizationBrowserFirstParty#initiateLogin}
298
+ * Performs the navigation step of the Authorization Code flow.
299
+ * Builds login URL and either:
300
+ * - Replaces current window location (default), or
301
+ * - Opens a separate window (popup) if options.separateWindow supplied.
302
+ *
303
+ * Decorated with whileInFlight('isAuthorizing') to set isAuthorizing=true
304
+ * during execution to prevent concurrent overlapping attempts.
305
+ *
216
306
  * @instance
217
307
  * @memberof AuthorizationBrowserFirstParty
218
- * @param {Object} options
219
- * @returns {Promise}
308
+ * @param {Object} options - Already augmented with state + PKCE info
309
+ * @returns {Promise<void>}
220
310
  */
221
311
  initiateAuthorizationCodeGrant: function initiateAuthorizationCodeGrant(options) {
222
312
  this.logger.info('authorization: initiating authorization code grant flow');
@@ -224,15 +314,12 @@ var Authorization = _webexCore.WebexPlugin.extend((_dec = (0, _common.whileInFli
224
314
  response_type: 'code'
225
315
  }, options));
226
316
  if (options !== null && options !== void 0 && options.separateWindow) {
227
- // Default window settings
317
+ // If a separate popup window is requested, combine user supplied window features
228
318
  var defaultWindowSettings = {
229
319
  width: 600,
230
320
  height: 800
231
321
  };
232
-
233
- // Merge user provided settings with defaults
234
322
  var windowSettings = (0, _assign.default)(defaultWindowSettings, (0, _typeof2.default)(options.separateWindow) === 'object' ? options.separateWindow : {});
235
- // Convert settings object to window.open features string
236
323
  var windowFeatures = (0, _entries.default)(windowSettings).map(function (_ref) {
237
324
  var _ref2 = (0, _slicedToArray2.default)(_ref, 2),
238
325
  key = _ref2[0],
@@ -241,18 +328,21 @@ var Authorization = _webexCore.WebexPlugin.extend((_dec = (0, _common.whileInFli
241
328
  }).join(',');
242
329
  this.webex.getWindow().open(loginUrl, '_blank', windowFeatures);
243
330
  } else {
244
- // Default behavior - open in same window
331
+ // Normal (in-tab) redirect
245
332
  this.webex.getWindow().location = loginUrl;
246
333
  }
247
334
  return _promise.default.resolve();
248
335
  },
249
336
  /**
250
- * Called by {@link WebexCore#logout()}. Redirects to the logout page
337
+ * Called by {@link WebexCore#logout()}.
338
+ * Constructs logout URL and (unless suppressed) navigates away to ensure
339
+ * server-side session termination.
340
+ *
251
341
  * @instance
252
342
  * @memberof AuthorizationBrowserFirstParty
253
343
  * @param {Object} options
254
344
  * @param {boolean} options.noRedirect if true, does not redirect
255
- * @returns {Promise}
345
+ * @returns {Promise<void>}
256
346
  */
257
347
  logout: function logout() {
258
348
  var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
@@ -261,11 +351,23 @@ var Authorization = _webexCore.WebexPlugin.extend((_dec = (0, _common.whileInFli
261
351
  }
262
352
  },
263
353
  /**
264
- * Exchanges an authorization code for an access token
354
+ * Exchanges an authorization code for an access (super) token bundle.
355
+ *
356
+ * Decorators:
357
+ * - @whileInFlight('isAuthorizing'): prevents overlapping exchanges.
358
+ * - @oneFlight: collapses simultaneous calls into one network request.
359
+ *
360
+ * Includes PKCE code_verifier if present from earlier login initiation.
361
+ *
362
+ * Error Handling:
363
+ * - Non-400 responses are propagated.
364
+ * - 400 responses map to OAuth-specific grantErrors.
365
+ *
265
366
  * @instance
266
367
  * @memberof AuthorizationBrowserFirstParty
267
368
  * @param {Object} options
268
- * @param {Object} options.code
369
+ * @param {string} options.code - Authorization code from redirect
370
+ * @param {string} [options.codeVerifier] - PKCE code verifier if used
269
371
  * @returns {Promise}
270
372
  */
271
373
  requestAuthorizationCodeGrant: function requestAuthorizationCodeGrant() {
@@ -279,8 +381,9 @@ var Authorization = _webexCore.WebexPlugin.extend((_dec = (0, _common.whileInFli
279
381
  grant_type: 'authorization_code',
280
382
  redirect_uri: this.config.redirect_uri,
281
383
  code: options.code,
282
- self_contained_token: true
384
+ self_contained_token: true // Request combined access/refresh response
283
385
  };
386
+
284
387
  if (options.codeVerifier) {
285
388
  form.code_verifier = options.codeVerifier;
286
389
  }
@@ -293,8 +396,9 @@ var Authorization = _webexCore.WebexPlugin.extend((_dec = (0, _common.whileInFli
293
396
  pass: this.config.client_secret,
294
397
  sendImmediately: true
295
398
  },
296
- shouldRefreshAccessToken: false
399
+ shouldRefreshAccessToken: false // This is the token acquisition call itself
297
400
  }).then(function (res) {
401
+ // Store supertoken into credentials (includes refresh token)
298
402
  _this2.webex.credentials.set({
299
403
  supertoken: res.body
300
404
  });
@@ -302,16 +406,22 @@ var Authorization = _webexCore.WebexPlugin.extend((_dec = (0, _common.whileInFli
302
406
  if (res.statusCode !== 400) {
303
407
  return _promise.default.reject(res);
304
408
  }
409
+
410
+ // Map standard OAuth error to strongly typed error class
305
411
  var ErrorConstructor = _webexCore.grantErrors.select(res.body.error);
306
412
  return _promise.default.reject(new ErrorConstructor(res._res || res));
307
413
  });
308
414
  },
309
415
  /**
310
- * Generate a QR code URL to launch the Webex app when scanning with the camera
416
+ * Generate a QR code verification URL for device authorization flow.
417
+ * When a user scans the QR code with a mobile device, this deep-links into
418
+ * Webex (web) to continue login, including passing along userCode and the
419
+ * helper service base URL.
420
+ *
311
421
  * @instance
312
422
  * @memberof AuthorizationBrowserFirstParty
313
- * @param {String} verificationUrl
314
- * @returns {String}
423
+ * @param {String} verificationUrl - Original verification URI (complete)
424
+ * @returns {String} Possibly rewritten verification URL
315
425
  */
316
426
  _generateQRCodeVerificationUrl: function _generateQRCodeVerificationUrl(verificationUrl) {
317
427
  var baseUrl = 'https://web.webex.com/deviceAuth';
@@ -329,7 +439,15 @@ var Authorization = _webexCore.WebexPlugin.extend((_dec = (0, _common.whileInFli
329
439
  }
330
440
  },
331
441
  /**
332
- * Get an OAuth Login URL for QRCode. Generate QR code based on the returned URL.
442
+ * Initiates Device Authorization (QR Code) flow.
443
+ *
444
+ * Steps:
445
+ * 1. Obtain device_code, user_code, verification URLs from oauth-helper.
446
+ * 2. Emit getUserCodeSuccess (provides data for generating QR code).
447
+ * 3. Start polling token endpoint with device_code.
448
+ *
449
+ * Emits qRCodeLogin events for UI to react (success, failure, pending, etc.).
450
+ *
333
451
  * @instance
334
452
  * @memberof AuthorizationBrowserFirstParty
335
453
  * @emits #qRCodeLogin
@@ -337,6 +455,7 @@ var Authorization = _webexCore.WebexPlugin.extend((_dec = (0, _common.whileInFli
337
455
  initQRCodeLogin: function initQRCodeLogin() {
338
456
  var _this3 = this;
339
457
  if (this.pollingTimer) {
458
+ // Prevent concurrent device authorization attempts
340
459
  this.eventEmitter.emit(Events.qRCodeLogin, {
341
460
  eventType: 'getUserCodeFailure',
342
461
  data: {
@@ -372,7 +491,7 @@ var Authorization = _webexCore.WebexPlugin.extend((_dec = (0, _common.whileInFli
372
491
  verificationUriComplete: verificationUriComplete
373
492
  }
374
493
  });
375
- // if device authorization success, then start to poll server to check whether the user has completed authorization
494
+ // Begin polling for authorization completion
376
495
  _this3._startQRCodePolling(res.body);
377
496
  }).catch(function (res) {
378
497
  _this3.eventEmitter.emit(Events.qRCodeLogin, {
@@ -382,10 +501,21 @@ var Authorization = _webexCore.WebexPlugin.extend((_dec = (0, _common.whileInFli
382
501
  });
383
502
  },
384
503
  /**
385
- * Polling the server to check whether the user has completed authorization
504
+ * Poll the device token endpoint until user authorizes, an error occurs,
505
+ * or timeout happens.
506
+ *
507
+ * Polling behavior:
508
+ * - Interval provided by server (default 2s). 'slow_down' doubles interval once.
509
+ * - 428 status => pending (continue).
510
+ * - Success => set credentials + emit authorizationSuccess + stop polling.
511
+ * - Any other error => emit authorizationFailure + stop polling.
512
+ *
513
+ * Cancellation:
514
+ * - cancelQRCodePolling() resets timers and polling ids so late responses are ignored.
515
+ *
386
516
  * @instance
387
517
  * @memberof AuthorizationBrowserFirstParty
388
- * @param {Object} options
518
+ * @param {Object} options - Must include device_code, may include interval/expires_in
389
519
  * @emits #qRCodeLogin
390
520
  */
391
521
  _startQRCodePolling: function _startQRCodePolling() {
@@ -402,6 +532,7 @@ var Authorization = _webexCore.WebexPlugin.extend((_dec = (0, _common.whileInFli
402
532
  return;
403
533
  }
404
534
  if (this.pollingTimer) {
535
+ // Already polling; avoid starting a duplicate cycle
405
536
  this.eventEmitter.emit(Events.qRCodeLogin, {
406
537
  eventType: 'authorizationFailure',
407
538
  data: {
@@ -413,7 +544,10 @@ var Authorization = _webexCore.WebexPlugin.extend((_dec = (0, _common.whileInFli
413
544
  var deviceCode = options.device_code,
414
545
  _options$expires_in = options.expires_in,
415
546
  expiresIn = _options$expires_in === void 0 ? 300 : _options$expires_in;
547
+ // Server recommended polling interval (seconds)
416
548
  var interval = (_options$interval = options.interval) !== null && _options$interval !== void 0 ? _options$interval : 2;
549
+
550
+ // Global timeout for entire device authorization attempt
417
551
  this.pollingExpirationTimer = setTimeout(function () {
418
552
  _this4.cancelQRCodePolling(false);
419
553
  _this4.eventEmitter.emit(Events.qRCodeLogin, {
@@ -424,6 +558,7 @@ var Authorization = _webexCore.WebexPlugin.extend((_dec = (0, _common.whileInFli
424
558
  });
425
559
  }, expiresIn * 1000);
426
560
  var polling = function polling() {
561
+ // Increment id so any previous poll loops can be invalidated
427
562
  _this4.pollingId += 1;
428
563
  _this4.currentPollingId = _this4.pollingId;
429
564
  _this4.webex.request({
@@ -441,7 +576,7 @@ var Authorization = _webexCore.WebexPlugin.extend((_dec = (0, _common.whileInFli
441
576
  sendImmediately: true
442
577
  }
443
578
  }).then(function (res) {
444
- // if the pollingId has changed, it means that the polling request has been canceled
579
+ // If polling canceled (id changed), ignore this response
445
580
  if (_this4.currentPollingId !== _this4.pollingId) return;
446
581
  _this4.eventEmitter.emit(Events.qRCodeLogin, {
447
582
  eventType: 'authorizationSuccess',
@@ -452,18 +587,15 @@ var Authorization = _webexCore.WebexPlugin.extend((_dec = (0, _common.whileInFli
452
587
  });
453
588
  _this4.cancelQRCodePolling();
454
589
  }).catch(function (res) {
455
- // if the pollingId has changed, it means that the polling request has been canceled
456
590
  if (_this4.currentPollingId !== _this4.pollingId) return;
457
591
 
458
- // When server sends 400 status code with message 'slow_down', it means that last request happened too soon.
459
- // So, skip one interval and then poll again.
592
+ // Backoff signal from server; increase interval just once for next cycle
460
593
  if (res.statusCode === 400 && res.body.message === 'slow_down') {
461
594
  schedulePolling(interval * 2);
462
595
  return;
463
596
  }
464
597
 
465
- // if the statusCode is 428 which means that the authorization request is still pending
466
- // as the end user hasn't yet completed the user-interaction steps. So keep polling.
598
+ // Pending: keep polling
467
599
  if (res.statusCode === 428) {
468
600
  _this4.eventEmitter.emit(Events.qRCodeLogin, {
469
601
  eventType: 'authorizationPending',
@@ -472,6 +604,8 @@ var Authorization = _webexCore.WebexPlugin.extend((_dec = (0, _common.whileInFli
472
604
  schedulePolling(interval);
473
605
  return;
474
606
  }
607
+
608
+ // Terminal error
475
609
  _this4.cancelQRCodePolling();
476
610
  _this4.eventEmitter.emit(Events.qRCodeLogin, {
477
611
  eventType: 'authorizationFailure',
@@ -479,13 +613,17 @@ var Authorization = _webexCore.WebexPlugin.extend((_dec = (0, _common.whileInFli
479
613
  });
480
614
  });
481
615
  };
616
+
617
+ // Schedules next poll invocation
482
618
  var schedulePolling = function schedulePolling(interval) {
483
619
  return _this4.pollingTimer = setTimeout(polling, interval * 1000);
484
620
  };
485
621
  schedulePolling(interval);
486
622
  },
487
623
  /**
488
- * cancel polling request
624
+ * Cancel active device authorization polling loop.
625
+ *
626
+ * @param {boolean} withCancelEvent emit a pollingCanceled event (default true)
489
627
  * @instance
490
628
  * @memberof AuthorizationBrowserFirstParty
491
629
  * @returns {void}
@@ -504,25 +642,31 @@ var Authorization = _webexCore.WebexPlugin.extend((_dec = (0, _common.whileInFli
504
642
  this.pollingTimer = null;
505
643
  },
506
644
  /**
507
- * Extracts the orgId from the returned code from idbroker
508
- * Description of how to parse the code can be found here:
509
- * https://wiki.cisco.com/display/IDENTITY/Federated+Token+Validation
645
+ * Extracts the orgId from the returned code from idbroker.
646
+ *
647
+ * Certain authorization codes encode organization info in a structured
648
+ * underscore-delimited format. This method parses out the 3rd segment.
649
+ *
650
+ * For undocumented formats or unexpected code shapes, returns undefined.
651
+ *
510
652
  * @instance
511
653
  * @memberof AuthorizationBrowserFirstParty
512
654
  * @param {String} code
513
655
  * @private
514
- * @returns {String}
656
+ * @returns {String|undefined}
515
657
  */
516
658
  _extractOrgIdFromCode: function _extractOrgIdFromCode(code) {
517
659
  return (code === null || code === void 0 ? void 0 : code.split('_')[2]) || undefined;
518
660
  },
519
661
  /**
520
- * Checks if the result of the login redirect contains an error string
662
+ * Checks if the result of the login redirect contains an OAuth error.
663
+ * Throws a mapped grant error if encountered.
664
+ *
521
665
  * @instance
522
666
  * @memberof AuthorizationBrowserFirstParty
523
667
  * @param {Object} location
524
668
  * @private
525
- * @returns {Promise}
669
+ * @returns {void}
526
670
  */
527
671
  _checkForErrors: function _checkForErrors(location) {
528
672
  var query = location.query;
@@ -532,12 +676,23 @@ var Authorization = _webexCore.WebexPlugin.extend((_dec = (0, _common.whileInFli
532
676
  }
533
677
  },
534
678
  /**
535
- * Removes no-longer needed values from the url (access token, csrf token, etc)
679
+ * Removes no-longer needed values from the URL (authorization code, CSRF token).
680
+ * This is important to avoid leaking sensitive parameters via:
681
+ * - Browser history
682
+ * - Copy/paste of URL
683
+ * - HTTP referrer headers to third-party content
684
+ *
685
+ * Approach:
686
+ * - Remove 'code'.
687
+ * - Remove 'state' entirely if only contained csrf_token.
688
+ * - Else, re-encode remaining state fields (minus csrf_token).
689
+ * - Replace current history entry (no page reload).
690
+ *
536
691
  * @instance
537
692
  * @memberof AuthorizationBrowserFirstParty
538
693
  * @param {Object} location
539
694
  * @private
540
- * @returns {Promise}
695
+ * @returns {void}
541
696
  */
542
697
  _cleanUrl: function _cleanUrl(location) {
543
698
  location = (0, _lodash.cloneDeep)(location);
@@ -554,11 +709,18 @@ var Authorization = _webexCore.WebexPlugin.extend((_dec = (0, _common.whileInFli
554
709
  }
555
710
  },
556
711
  /**
557
- * Generates PKCE code verifier and code challenge and sets the the code verifier in sessionStorage
712
+ * Generates a PKCE (RFC 7636) code verifier and corresponding S256 code challenge.
713
+ * Persists the verifier in sessionStorage (single-use) for later retrieval
714
+ * during authorization code exchange; removes it once consumed.
715
+ *
716
+ * Implementation details:
717
+ * - Creates a 128 character string using base64url safe alphabet.
718
+ * - Computes SHA256 hash, encodes to base64url (no padding).
719
+ *
558
720
  * @instance
559
721
  * @memberof AuthorizationBrowserFirstParty
560
722
  * @private
561
- * @returns {string}
723
+ * @returns {string} code_challenge
562
724
  */
563
725
  _generateCodeChallenge: function _generateCodeChallenge() {
564
726
  this.logger.info('authorization: generating PKCE code challenge');
@@ -573,11 +735,15 @@ var Authorization = _webexCore.WebexPlugin.extend((_dec = (0, _common.whileInFli
573
735
  return codeChallenge;
574
736
  },
575
737
  /**
576
- * Generates a CSRF token and sticks in in sessionStorage
738
+ * Generates a CSRF token and stores it in sessionStorage.
739
+ * Token is embedded in 'state' and validated upon redirect return.
740
+ *
741
+ * Uses UUID v4 for randomness.
742
+ *
577
743
  * @instance
578
744
  * @memberof AuthorizationBrowserFirstParty
579
745
  * @private
580
- * @returns {Promise}
746
+ * @returns {string} token
581
747
  */
582
748
  _generateSecurityToken: function _generateSecurityToken() {
583
749
  this.logger.info('authorization: generating csrf token');
@@ -586,13 +752,21 @@ var Authorization = _webexCore.WebexPlugin.extend((_dec = (0, _common.whileInFli
586
752
  return token;
587
753
  },
588
754
  /**
589
- * Checks if the CSRF token in sessionStorage is the same as the one returned
590
- * in the url.
755
+ * Verifies that the CSRF token returned in the 'state' matches the one
756
+ * previously stored in sessionStorage.
757
+ *
758
+ * Steps:
759
+ * - Retrieve and immediately remove stored token (one-time use).
760
+ * - Ensure state + state.csrf_token exist.
761
+ * - Compare values; throw descriptive errors on mismatch / absence.
762
+ *
763
+ * If no stored token (e.g., user navigated directly), silently returns.
764
+ *
591
765
  * @instance
592
766
  * @memberof AuthorizationBrowserFirstParty
593
- * @param {Object} query
767
+ * @param {Object} query - Parsed query (location.query)
594
768
  * @private
595
- * @returns {Promise}
769
+ * @returns {void}
596
770
  */
597
771
  _verifySecurityToken: function _verifySecurityToken(query) {
598
772
  var sessionToken = this.webex.getWindow().sessionStorage.getItem(OAUTH2_CSRF_TOKEN);
@@ -611,7 +785,7 @@ var Authorization = _webexCore.WebexPlugin.extend((_dec = (0, _common.whileInFli
611
785
  throw new Error("CSRF token ".concat(token, " does not match stored token ").concat(sessionToken));
612
786
  }
613
787
  },
614
- version: "3.8.1"
788
+ version: "3.9.0-multiple-llm.1"
615
789
  }, ((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)));
616
790
  var _default = exports.default = Authorization;
617
791
  //# sourceMappingURL=authorization.js.map