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