@webex/plugin-authorization-browser-first-party 3.12.0-next.2 → 3.12.0-next.21

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.
@@ -6,18 +6,18 @@ _Object$defineProperty(exports, "__esModule", {
6
6
  value: true
7
7
  });
8
8
  exports.default = exports.Events = void 0;
9
+ var _objectWithoutProperties2 = _interopRequireDefault(require("@babel/runtime-corejs2/helpers/objectWithoutProperties"));
9
10
  var _apply = _interopRequireDefault(require("@babel/runtime-corejs2/core-js/reflect/apply"));
10
11
  var _promise = _interopRequireDefault(require("@babel/runtime-corejs2/core-js/promise"));
11
12
  var _assign = _interopRequireDefault(require("@babel/runtime-corejs2/core-js/object/assign"));
12
13
  var _entries = _interopRequireDefault(require("@babel/runtime-corejs2/core-js/object/entries"));
13
14
  var _deleteProperty = _interopRequireDefault(require("@babel/runtime-corejs2/core-js/reflect/delete-property"));
14
- var _stringify = _interopRequireDefault(require("@babel/runtime-corejs2/core-js/json/stringify"));
15
15
  var _getOwnPropertyDescriptor = _interopRequireDefault(require("@babel/runtime-corejs2/core-js/object/get-own-property-descriptor"));
16
16
  var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime-corejs2/helpers/slicedToArray"));
17
17
  var _typeof2 = _interopRequireDefault(require("@babel/runtime-corejs2/helpers/typeof"));
18
18
  var _applyDecoratedDescriptor2 = _interopRequireDefault(require("@babel/runtime-corejs2/helpers/applyDecoratedDescriptor"));
19
19
  var _querystring = _interopRequireDefault(require("querystring"));
20
- var _url = _interopRequireDefault(require("url"));
20
+ var _url2 = _interopRequireDefault(require("url"));
21
21
  var _events = require("events");
22
22
  var _common = require("@webex/common");
23
23
  var _webexCore = require("@webex/webex-core");
@@ -25,6 +25,7 @@ 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 _excluded = ["csrf_token"];
28
29
  var _dec, _dec2, _obj; // @ts-nocheck
29
30
  /* eslint-disable */
30
31
  /*!
@@ -194,7 +195,7 @@ var Authorization = _webexCore.WebexPlugin.extend((_dec = (0, _common.whileInFli
194
195
  attrs[_key] = arguments[_key];
195
196
  }
196
197
  var ret = (0, _apply.default)(_webexCore.WebexPlugin.prototype.initialize, this, attrs);
197
- var location = _url.default.parse(this.webex.getWindow().location.href, true);
198
+ var location = _url2.default.parse(this.webex.getWindow().location.href, true);
198
199
 
199
200
  // Check if redirect includes error
200
201
  this._checkForErrors(location);
@@ -208,7 +209,7 @@ var Authorization = _webexCore.WebexPlugin.extend((_dec = (0, _common.whileInFli
208
209
 
209
210
  // Decode and parse state object (if present)
210
211
  if (location.query.state) {
211
- location.query.state = JSON.parse(_common.base64.decode(location.query.state));
212
+ location.query.state = (0, _common.decodeState)(location.query.state);
212
213
  } else {
213
214
  location.query.state = {};
214
215
  }
@@ -347,6 +348,96 @@ var Authorization = _webexCore.WebexPlugin.extend((_dec = (0, _common.whileInFli
347
348
  }
348
349
  return _promise.default.resolve();
349
350
  },
351
+ /**
352
+ * Initiates third-party (social provider) login. Generates a CSRF token,
353
+ * embeds it in `options.state.csrf_token`, and delegates to
354
+ * `initiateThirdPartyLoginRedirect` for navigation.
355
+ *
356
+ * @instance
357
+ * @memberof AuthorizationBrowserFirstParty
358
+ * @param {Object} options
359
+ * @param {string} options.oauth2provider
360
+ * @param {string} options.returnURL
361
+ * @param {Object} [options.state] - Caller-supplied state object. Merged
362
+ * with the generated `csrf_token`.
363
+ * @returns {Promise<void>}
364
+ */
365
+ initiateThirdPartyLogin: function initiateThirdPartyLogin() {
366
+ var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
367
+ options = (0, _lodash.cloneDeep)(options);
368
+ if (options.state !== undefined && !(0, _lodash.isObject)(options.state)) {
369
+ throw new Error('if specified, `options.state` must be an object');
370
+ }
371
+ options.state = options.state || {};
372
+ options.state.csrf_token = this._generateSecurityToken();
373
+ return this.initiateThirdPartyLoginRedirect(options);
374
+ },
375
+ /**
376
+ * Performs the navigation step of the third-party login flow. Builds the
377
+ * IdBroker URL via `Credentials#buildThirdPartyLoginUrl` and assigns it
378
+ * to `getWindow().location`.
379
+ *
380
+ * Mirrors `initiateAuthorizationCodeGrant` for the `/authorize` flow.
381
+ * Consumers may override this method for custom navigation handling
382
+ * (e.g. postMessage in iframed contexts).
383
+ *
384
+ * @instance
385
+ * @memberof AuthorizationBrowserFirstParty
386
+ * @param {Object} options
387
+ * @param {string} options.oauth2provider
388
+ * @param {string} options.returnURL
389
+ * @returns {Promise<void>}
390
+ */
391
+ initiateThirdPartyLoginRedirect: function initiateThirdPartyLoginRedirect() {
392
+ var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
393
+ this.logger.info('authorization: initiating third-party login redirect');
394
+ try {
395
+ var _url = this.webex.credentials.buildThirdPartyLoginUrl(options);
396
+ this.webex.getWindow().location = _url;
397
+ } catch (err) {
398
+ return _promise.default.reject(err);
399
+ }
400
+ return _promise.default.resolve();
401
+ },
402
+ /**
403
+ * Handles the third-party (social provider) login callback. Reads the
404
+ * current `window.location`, decodes `state`, validates the CSRF token
405
+ * (`state.csrf_token`), scrubs sensitive parameters from the URL via
406
+ * `_cleanUrl`, and returns the parsed payload.
407
+ *
408
+ * Mirrors `initialize()` in always operating on the live
409
+ * `window.location`
410
+ *
411
+ * `idToken` is single-use: it is parsed out of the URL exactly once and
412
+ * the calling client is expected to exchange it (or discard it)
413
+ * immediately. The returned `state` has `csrf_token` removed.
414
+ *
415
+ * @instance
416
+ * @memberof AuthorizationBrowserFirstParty
417
+ * @returns {{idToken: string|undefined, email: string|undefined,
418
+ * error: string|undefined, state: Object}}
419
+ */
420
+ handleThirdPartyCallback: function handleThirdPartyCallback() {
421
+ var location = _url2.default.parse(this.webex.getWindow().location.href, true);
422
+ location.query.state = (0, _common.decodeState)(location.query.state || 'e30');
423
+ this._verifySecurityToken(location.query, {
424
+ requireMatch: true
425
+ });
426
+ this._cleanUrl(location);
427
+ var _location$query = location.query,
428
+ idToken = _location$query.id_token,
429
+ email = _location$query.email,
430
+ error = _location$query.error,
431
+ _location$query$state = _location$query.state,
432
+ csrf_token = _location$query$state.csrf_token,
433
+ state = (0, _objectWithoutProperties2.default)(_location$query$state, _excluded);
434
+ return {
435
+ idToken: idToken,
436
+ email: email,
437
+ error: error,
438
+ state: state
439
+ };
440
+ },
350
441
  /**
351
442
  * Called by {@link WebexCore#logout()}.
352
443
  * Constructs logout URL and (unless suppressed) navigates away to ensure
@@ -696,8 +787,9 @@ var Authorization = _webexCore.WebexPlugin.extend((_dec = (0, _common.whileInFli
696
787
  * - HTTP referrer headers to third-party content
697
788
  *
698
789
  * Approach:
699
- * - Remove 'code'.
700
- * - Remove 'state' entirely if only contained csrf_token.
790
+ * - Remove 'code' (OAuth code-grant), 'id_token', and 'email'
791
+ * (third-party callback).
792
+ * - Remove 'state' entirely if it only contained csrf_token.
701
793
  * - Else, re-encode remaining state fields (minus csrf_token).
702
794
  * - Replace current history entry (no page reload).
703
795
  *
@@ -711,14 +803,16 @@ var Authorization = _webexCore.WebexPlugin.extend((_dec = (0, _common.whileInFli
711
803
  location = (0, _lodash.cloneDeep)(location);
712
804
  if (this.webex.getWindow().history && this.webex.getWindow().history.replaceState) {
713
805
  (0, _deleteProperty.default)(location.query, 'code');
806
+ (0, _deleteProperty.default)(location.query, 'id_token');
807
+ (0, _deleteProperty.default)(location.query, 'email');
714
808
  if ((0, _lodash.isEmpty)((0, _lodash.omit)(location.query.state, 'csrf_token'))) {
715
809
  (0, _deleteProperty.default)(location.query, 'state');
716
810
  } else {
717
- location.query.state = _common.base64.encode((0, _stringify.default)((0, _lodash.omit)(location.query.state, 'csrf_token')));
811
+ location.query.state = (0, _common.encodeState)((0, _lodash.omit)(location.query.state, 'csrf_token'));
718
812
  }
719
813
  location.search = _querystring.default.stringify(location.query);
720
814
  (0, _deleteProperty.default)(location, 'query');
721
- this.webex.getWindow().history.replaceState({}, null, _url.default.format(location));
815
+ this.webex.getWindow().history.replaceState({}, null, _url2.default.format(location));
722
816
  }
723
817
  },
724
818
  /**
@@ -773,24 +867,31 @@ var Authorization = _webexCore.WebexPlugin.extend((_dec = (0, _common.whileInFli
773
867
  * - Ensure state + state.csrf_token exist.
774
868
  * - Compare values; throw descriptive errors on mismatch / absence.
775
869
  *
776
- * If no stored token (e.g., user navigated directly), silently returns.
870
+ * If no stored token (e.g., user navigated directly), silently returns
871
+ * unless `options.requireMatch` is `true`, in which case absence of a
872
+ * stored token is treated as a CSRF failure.
777
873
  *
778
874
  * @instance
779
875
  * @memberof AuthorizationBrowserFirstParty
780
876
  * @param {Object} query - Parsed query (location.query)
877
+ * @param {Object} [options]
878
+ * @param {boolean} [options.requireMatch=false] - When true, throws if
879
+ * no stored sessionToken is present.
781
880
  * @private
782
881
  * @returns {void}
783
882
  */
784
883
  _verifySecurityToken: function _verifySecurityToken(query) {
884
+ var _query$state;
885
+ var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
785
886
  var sessionToken = this.webex.getWindow().sessionStorage.getItem(OAUTH2_CSRF_TOKEN);
786
887
  this.webex.getWindow().sessionStorage.removeItem(OAUTH2_CSRF_TOKEN);
787
888
  if (!sessionToken) {
889
+ if (options.requireMatch) {
890
+ throw new Error('CSRF token missing from session storage');
891
+ }
788
892
  return;
789
893
  }
790
- if (!query.state) {
791
- throw new Error("Expected CSRF token ".concat(sessionToken, ", but not found in redirect query"));
792
- }
793
- if (!query.state.csrf_token) {
894
+ if (!((_query$state = query.state) !== null && _query$state !== void 0 && _query$state.csrf_token)) {
794
895
  throw new Error("Expected CSRF token ".concat(sessionToken, ", but not found in redirect query"));
795
896
  }
796
897
  var token = query.state.csrf_token;
@@ -798,7 +899,7 @@ var Authorization = _webexCore.WebexPlugin.extend((_dec = (0, _common.whileInFli
798
899
  throw new Error("CSRF token ".concat(token, " does not match stored token ").concat(sessionToken));
799
900
  }
800
901
  },
801
- version: "3.12.0-next.2"
902
+ version: "3.12.0-next.21"
802
903
  }, (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));
803
904
  var _default = exports.default = Authorization;
804
905
  //# sourceMappingURL=authorization.js.map
@@ -1 +1 @@
1
- {"version":3,"names":["_querystring","_interopRequireDefault","require","_url","_events","_common","_webexCore","_lodash","_uuid","_encBase64url","_cryptoJs","_dec","_dec2","_obj","lodash","OAUTH2_CSRF_TOKEN","OAUTH2_CODE_VERIFIER","Events","exports","login","qRCodeLogin","Authorization","WebexPlugin","extend","whileInFlight","derived","isAuthenticating","deps","fn","isAuthorizing","session","default","type","ready","namespace","eventEmitter","EventEmitter","pollingTimer","pollingExpirationTimer","pollingId","currentPollingId","initialize","_this","_len","arguments","length","attrs","Array","_key","ret","_apply","prototype","location","url","parse","webex","getWindow","href","_checkForErrors","code","query","state","JSON","base64","decode","codeVerifier","sessionStorage","getItem","removeItem","emailhash","_verifySecurityToken","_cleanUrl","preauthCatalogParams","orgId","_extractOrgIdFromCode","process","nextTick","internal","services","collectPreauthCatalog","catch","_promise","resolve","then","requestAuthorizationCodeGrant","error","logger","warn","initiateLogin","options","undefined","emit","eventType","data","hasEmail","email","hasState","cloneDeep","emailHash","CryptoJS","SHA256","toString","csrf_token","_generateSecurityToken","code_challenge","_generateCodeChallenge","code_challenge_method","initiateAuthorizationCodeGrant","info","loginUrl","credentials","buildLoginUrl","_assign","response_type","separateWindow","defaultWindowSettings","width","height","windowSettings","_typeof2","windowFeatures","_entries","map","_ref","_ref2","_slicedToArray2","key","value","concat","join","open","logout","noRedirect","buildLogoutUrl","_this2","reject","Error","form","grant_type","redirect_uri","config","self_contained_token","code_verifier","request","method","uri","tokenUrl","auth","user","client_id","pass","client_secret","sendImmediately","shouldRefreshAccessToken","res","set","supertoken","body","statusCode","ErrorConstructor","grantErrors","select","_res","_generateQRCodeVerificationUrl","verificationUrl","baseUrl","urlParams","URLSearchParams","URL","search","userCode","get","oauthHelperUrl","newVerificationUrl","searchParams","initQRCodeLogin","_this3","message","service","resource","scope","_res$body","user_code","verification_uri","verification_uri_complete","verificationUriComplete","userData","verificationUri","_startQRCodePolling","_options$interval","_this4","device_code","deviceCode","_options$expires_in","expires_in","expiresIn","interval","setTimeout","cancelQRCodePolling","polling","schedulePolling","withCancelEvent","clearTimeout","split","history","replaceState","_deleteProperty","isEmpty","omit","encode","_stringify","querystring","stringify","format","safeCharacterMap","base64url","_safe_map","times","random","codeChallenge","setItem","token","uuid","v4","sessionToken","version","_applyDecoratedDescriptor2","_getOwnPropertyDescriptor","oneFlight","_default"],"sources":["authorization.js"],"sourcesContent":[" // @ts-nocheck\n/* eslint-disable */\n/*!\n * Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.\n */\n\n/* eslint camelcase: [0] */\n/**\n * TS checking disabled: file uses legacy decorator syntax inside an object literal\n * transformed by Babel. Safe to ignore for now.\n */\n\nimport querystring from 'querystring';\nimport url from 'url';\nimport {EventEmitter} from 'events';\n\nimport {base64, oneFlight, whileInFlight} from '@webex/common';\nimport {grantErrors, WebexPlugin} from '@webex/webex-core';\nimport {cloneDeep, isEmpty, omit} from 'lodash';\nimport uuid from 'uuid';\nimport base64url from 'crypto-js/enc-base64url';\nimport CryptoJS from 'crypto-js';\n\n// Necessary to require lodash this way in order to stub\n// methods in the unit test\nconst lodash = require('lodash');\n\nconst OAUTH2_CSRF_TOKEN = 'oauth2-csrf-token';\nconst OAUTH2_CODE_VERIFIER = 'oauth2-code-verifier';\n\n/**\n * Authorization plugin events\n */\nexport const Events = {\n login: 'login',\n /**\n * QR code login events\n */\n qRCodeLogin: 'qRCodeLogin',\n};\n\n/**\n * Browser support for OAuth2 for first-party (Webex Web Client) usage.\n *\n * High-level flow handled by this module:\n * 1. initiateLogin() constructs authorization request (adds CSRF + PKCE).\n * 2. Browser navigates to IdBroker (login).\n * 3. IdBroker redirects back with ?code=... (&state=...).\n * 4. initialize() detects code, validates state/CSRF, cleans URL, optionally\n * pre-fetches a preauth catalog, then exchanges the code via\n * requestAuthorizationCodeGrant().\n * 5. Sets resulting supertoken (access/refresh token bundle) on credentials.\n *\n * Additional supported flow:\n * - Device Authorization (QR Code login):\n * initQRCodeLogin() obtains device + user codes and begins polling\n * _startQRCodePolling() until tokens are issued or timeout/cancel occurs.\n *\n * Security considerations implemented:\n * - CSRF token (state.csrf_token) generation + verification.\n * - PKCE (S256) code verifier + challenge generation and consumption.\n * - URL cleanup after redirect (removes code & CSRF to prevent leakage).\n *\n * Use of this plugin for anything other than the Webex Web Client is discouraged.\n *\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 /**\n * Indicates that the plugin has finished any automatic startup\n * processing (e.g., exchanging a returned authorization code)\n */\n ready: {\n default: false,\n type: 'boolean',\n },\n },\n\n namespace: 'Credentials',\n\n /**\n * EventEmitter for authorization events such as QR code login progress\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @type {EventEmitter}\n * @public\n */\n eventEmitter: new EventEmitter(),\n\n /**\n * Stores the timer ID for QR code polling (device authorization)\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @type {?number}\n * @private\n */\n pollingTimer: null,\n /**\n * Stores the expiration timer ID for QR code polling (overall timeout)\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @type {?number}\n * @private\n */\n pollingExpirationTimer: null,\n\n /**\n * Monotonically increasing id to identify the current polling request.\n * Used to safely ignore late poll responses after a cancel/reset.\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @type {number}\n * @private\n */\n pollingId: 0,\n\n /**\n * Identifier for the current polling request (snapshot of pollingId)\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @type {?number}\n * @private\n */\n currentPollingId: null,\n\n /**\n * Auto executes during Webex.init() – you do NOT call this yourself.\n *\n * Purpose: Seamless \"redirect completion\" of the OAuth Authorization Code (+ PKCE) flow.\n *\n * Simple summary:\n * - You call initiateLogin() which redirects user to IdBroker.\n * - User signs in; IdBroker redirects back to your redirect_uri with ?code=... (&state=...).\n * - During SDK startup this initialize() runs automatically, sees the code, and\n * silently finishes the login (validates state/CSRF + PKCE, scrubs URL, exchanges code).\n * - When done, webex.credentials.supertoken holds access+refresh and ready=true.\n *\n * Step-by-step:\n * 1. Inspect current window.location for ?code= (& state=).\n * 2. If no code: set ready=true immediately (nothing to complete).\n * 3. If code present:\n * - Decode base64 state JSON.\n * - Verify CSRF token matches sessionStorage value.\n * - Retrieve then delete PKCE code_verifier (single use).\n * - Optionally derive preauth hint (emailhash in state OR orgId parsed from code).\n * - Clean the URL (history.replaceState) to remove code & csrf token data.\n * - nextTick:\n * a. Best‑effort preauth catalog fetch (non-blocking).\n * b. Exchange authorization code (with code_verifier if any) for supertoken\n * and store on webex.credentials.\n * 4. Set ready=true after the async sequence finishes (or immediately if step 2).\n *\n * Result: If the redirect included a valid code the token exchange is completed\n * automatically—no extra API call needed after Webex.init().\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 // Check if redirect includes error\n this._checkForErrors(location);\n\n const {code} = location.query;\n\n // If no authorization code returned, nothing to do\n if (!code) {\n this.ready = true;\n return ret;\n }\n\n // Decode and parse state object (if present)\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 // Retrieve PKCE code verifier (if a PKCE flow was initiated)\n const codeVerifier = this.webex.getWindow().sessionStorage.getItem(OAUTH2_CODE_VERIFIER);\n // Immediately remove code verifier to minimize exposure\n this.webex.getWindow().sessionStorage.removeItem(OAUTH2_CODE_VERIFIER);\n\n const {emailhash} = location.query.state;\n\n // Validate CSRF token included in state\n this._verifySecurityToken(location.query);\n // Remove code + CSRF token remnants from URL (history replace)\n this._cleanUrl(location);\n\n let preauthCatalogParams;\n\n // Attempt to extract orgId from structured authorization code (if present)\n const orgId = this._extractOrgIdFromCode(code);\n\n if (emailhash) {\n preauthCatalogParams = {emailhash};\n } else if (orgId) {\n preauthCatalogParams = {orgId};\n }\n\n // Defer token exchange until next tick in case credentials plugin not ready yet\n process.nextTick(() => {\n this.webex.internal.services\n .collectPreauthCatalog(preauthCatalogParams)\n .catch(() => Promise.resolve()) // Non-fatal if catalog collection fails\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 // Mark plugin ready regardless of success/failure of token exchange\n this.ready = true;\n });\n });\n\n return ret;\n },\n\n /**\n * Kicks off an OAuth authorization code flow (first party).\n *\n * Adds security + PKCE properties:\n * - SHA256(email) (emailHash & emailhash) for preauth and redirect flows\n * - state.csrf_token for CSRF protection\n * - PKCE code_challenge (S256)\n *\n * NOTE: This does not itself perform the redirect; it calls\n * initiateAuthorizationCodeGrant() which changes window location or opens\n * a separate window as configured.\n *\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @param {Object} options\n * @returns {Promise}\n */\n initiateLogin(options = {}) {\n this.eventEmitter.emit(Events.login, {\n eventType: 'initiateLogin',\n data: {\n hasEmail: !!options.email,\n hasState: !!options.state\n },\n });\n\n options = cloneDeep(options);\n\n // Optionally compute heuristic email hash for preauth usage\n if (options.email) {\n options.emailHash = CryptoJS.SHA256(options.email).toString();\n }\n delete options.email; // Ensure raw email not propagated further\n\n options.state = options.state || {};\n // Embed CSRF token\n options.state.csrf_token = this._generateSecurityToken();\n // Provide email hash in lower-case key used by catalog service\n // (Note: catalog uses emailhash and redirectCI uses emailHash)\n options.state.emailhash = options.emailHash;\n\n // PKCE - produce code_challenge (S256) and persist code_verifier\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 * Performs the navigation step of the Authorization Code flow.\n * Builds login URL and either:\n * - Replaces current window location (default), or\n * - Opens a separate window (popup) if options.separateWindow supplied.\n *\n * Decorated with whileInFlight('isAuthorizing') to set isAuthorizing=true\n * during execution to prevent concurrent overlapping attempts.\n *\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @param {Object} options - Already augmented with state + PKCE info\n * @returns {Promise<void>}\n */\n initiateAuthorizationCodeGrant(options) {\n this.logger.info('authorization: initiating authorization code grant flow');\n const loginUrl = this.webex.credentials.buildLoginUrl(\n Object.assign({response_type: 'code'}, options)\n );\n\n this.eventEmitter.emit(Events.login, {\n eventType: 'redirectToLoginUrl',\n data: { loginUrl },\n });\n\n if (options?.separateWindow) {\n // If a separate popup window is requested, combine user supplied window features\n const defaultWindowSettings = {\n width: 600,\n height: 800,\n };\n\n const windowSettings = Object.assign(\n defaultWindowSettings,\n typeof options.separateWindow === 'object' ? options.separateWindow : {}\n );\n\n const windowFeatures = Object.entries(windowSettings)\n .map(([key, value]) => `${key}=${value}`)\n .join(',');\n this.webex.getWindow().open(loginUrl, '_blank', windowFeatures);\n } else {\n // Normal (in-tab) redirect\n this.webex.getWindow().location = loginUrl;\n }\n\n return Promise.resolve();\n },\n\n /**\n * Called by {@link WebexCore#logout()}.\n * Constructs logout URL and (unless suppressed) navigates away to ensure\n * server-side session termination.\n *\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @param {Object} options\n * @param {boolean} options.noRedirect if true, does not redirect\n * @returns {Promise<void>}\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 (super) token bundle.\n *\n * Decorators:\n * - @whileInFlight('isAuthorizing'): prevents overlapping exchanges.\n * - @oneFlight: collapses simultaneous calls into one network request.\n *\n * Includes PKCE code_verifier if present from earlier login initiation.\n *\n * Error Handling:\n * - Non-400 responses are propagated.\n * - 400 responses map to OAuth-specific grantErrors.\n *\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @param {Object} options\n * @param {string} options.code - Authorization code from redirect\n * @param {string} [options.codeVerifier] - PKCE code verifier if used\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, // Request combined access/refresh response\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, // This is the token acquisition call itself\n })\n .then((res) => {\n // Store supertoken into credentials (includes refresh token)\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 // Map standard OAuth error to strongly typed error class\n const ErrorConstructor = grantErrors.select(res.body.error);\n\n return Promise.reject(new ErrorConstructor(res._res || res));\n });\n },\n\n /**\n * Generate a QR code verification URL for device authorization flow.\n * When a user scans the QR code with a mobile device, this deep-links into\n * Webex (web) to continue login, including passing along userCode and the\n * helper service base URL.\n *\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @param {String} verificationUrl - Original verification URI (complete)\n * @returns {String} Possibly rewritten verification URL\n */\n _generateQRCodeVerificationUrl(verificationUrl) {\n const baseUrl = 'https://web.webex.com/deviceAuth';\n const urlParams = new URLSearchParams(new URL(verificationUrl).search);\n const userCode = urlParams.get('userCode');\n\n if (userCode) {\n const {services} = this.webex.internal;\n const oauthHelperUrl = services.get('oauth-helper');\n const newVerificationUrl = new URL(baseUrl);\n newVerificationUrl.searchParams.set('usercode', userCode);\n newVerificationUrl.searchParams.set('oauthhelper', oauthHelperUrl);\n return newVerificationUrl.toString();\n } else {\n return verificationUrl;\n }\n },\n\n /**\n * Initiates Device Authorization (QR Code) flow.\n *\n * Steps:\n * 1. Obtain device_code, user_code, verification URLs from oauth-helper.\n * 2. Emit getUserCodeSuccess (provides data for generating QR code).\n * 3. Start polling token endpoint with device_code.\n *\n * Emits qRCodeLogin events for UI to react (success, failure, pending, etc.).\n *\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @emits #qRCodeLogin\n */\n initQRCodeLogin() {\n if (this.pollingTimer) {\n // Prevent concurrent device authorization attempts\n this.eventEmitter.emit(Events.qRCodeLogin, {\n eventType: 'getUserCodeFailure',\n data: {message: 'There is already a polling request'},\n });\n return;\n }\n\n this.webex\n .request({\n method: 'POST',\n service: 'oauth-helper',\n resource: '/actions/device/authorize',\n form: {\n client_id: this.config.client_id,\n scope: this.config.scope,\n },\n auth: {\n user: this.config.client_id,\n pass: this.config.client_secret,\n sendImmediately: true,\n },\n })\n .then((res) => {\n const {user_code, verification_uri, verification_uri_complete} = res.body;\n const verificationUriComplete = this._generateQRCodeVerificationUrl(verification_uri_complete);\n this.eventEmitter.emit(Events.qRCodeLogin, {\n eventType: 'getUserCodeSuccess',\n userData: {\n userCode: user_code,\n verificationUri: verification_uri,\n verificationUriComplete,\n },\n });\n // Begin polling for authorization completion\n this._startQRCodePolling(res.body);\n })\n .catch((res) => {\n this.eventEmitter.emit(Events.qRCodeLogin, {\n eventType: 'getUserCodeFailure',\n data: res.body,\n });\n });\n },\n\n /**\n * Poll the device token endpoint until user authorizes, an error occurs,\n * or timeout happens.\n *\n * Polling behavior:\n * - Interval provided by server (default 2s). 'slow_down' doubles interval once.\n * - 428 status => pending (continue).\n * - Success => set credentials + emit authorizationSuccess + stop polling.\n * - Any other error => emit authorizationFailure + stop polling.\n *\n * Cancellation:\n * - cancelQRCodePolling() resets timers and polling ids so late responses are ignored.\n *\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @param {Object} options - Must include device_code, may include interval/expires_in\n * @emits #qRCodeLogin\n */\n _startQRCodePolling(options = {}) {\n if (!options.device_code) {\n this.eventEmitter.emit(Events.qRCodeLogin, {\n eventType: 'authorizationFailure',\n data: {message: 'A deviceCode is required'},\n });\n return;\n }\n\n if (this.pollingTimer) {\n // Already polling; avoid starting a duplicate cycle\n this.eventEmitter.emit(Events.qRCodeLogin, {\n eventType: 'authorizationFailure',\n data: {message: 'There is already a polling request'},\n });\n return;\n }\n\n const {device_code: deviceCode, expires_in: expiresIn = 300} = options;\n // Server recommended polling interval (seconds)\n let interval = options.interval ?? 2;\n\n // Global timeout for entire device authorization attempt\n this.pollingExpirationTimer = setTimeout(() => {\n this.cancelQRCodePolling(false);\n this.eventEmitter.emit(Events.qRCodeLogin, {\n eventType: 'authorizationFailure',\n data: {message: 'Authorization timed out'},\n });\n }, expiresIn * 1000);\n\n const polling = () => {\n // Increment id so any previous poll loops can be invalidated\n this.pollingId += 1;\n this.currentPollingId = this.pollingId;\n\n this.webex\n .request({\n method: 'POST',\n service: 'oauth-helper',\n resource: '/actions/device/token',\n form: {\n grant_type: 'urn:ietf:params:oauth:grant-type:device_code',\n device_code: deviceCode,\n client_id: this.config.client_id,\n },\n auth: {\n user: this.config.client_id,\n pass: this.config.client_secret,\n sendImmediately: true,\n },\n })\n .then((res) => {\n // If polling canceled (id changed), ignore this response\n if (this.currentPollingId !== this.pollingId) return;\n\n this.eventEmitter.emit(Events.qRCodeLogin, {\n eventType: 'authorizationSuccess',\n data: res.body,\n });\n this.webex.credentials.set({supertoken: res.body});\n this.cancelQRCodePolling();\n })\n .catch((res) => {\n if (this.currentPollingId !== this.pollingId) return;\n\n // Backoff signal from server; increase interval just once for next cycle\n if (res.statusCode === 400 && res.body.message === 'slow_down') {\n schedulePolling(interval * 2);\n return;\n }\n\n // Pending: keep polling\n if (res.statusCode === 428) {\n this.eventEmitter.emit(Events.qRCodeLogin, {\n eventType: 'authorizationPending',\n data: res.body,\n });\n schedulePolling(interval);\n return;\n }\n\n // Terminal error\n this.cancelQRCodePolling();\n\n this.eventEmitter.emit(Events.qRCodeLogin, {\n eventType: 'authorizationFailure',\n data: res.body,\n });\n });\n };\n\n // Schedules next poll invocation\n const schedulePolling = (interval) =>\n (this.pollingTimer = setTimeout(polling, interval * 1000));\n\n schedulePolling(interval);\n },\n\n /**\n * Cancel active device authorization polling loop.\n *\n * @param {boolean} withCancelEvent emit a pollingCanceled event (default true)\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @returns {void}\n */\n cancelQRCodePolling(withCancelEvent = true) {\n if (this.pollingTimer && withCancelEvent) {\n this.eventEmitter.emit(Events.qRCodeLogin, {\n eventType: 'pollingCanceled',\n });\n }\n\n this.currentPollingId = null;\n\n clearTimeout(this.pollingExpirationTimer);\n this.pollingExpirationTimer = null;\n clearTimeout(this.pollingTimer);\n this.pollingTimer = null;\n },\n\n /**\n * Extracts the orgId from the returned code from idbroker.\n *\n * Certain authorization codes encode organization info in a structured\n * underscore-delimited format. This method parses out the 3rd segment.\n *\n * For undocumented formats or unexpected code shapes, returns undefined.\n *\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @param {String} code\n * @private\n * @returns {String|undefined}\n */\n _extractOrgIdFromCode(code) {\n return code?.split('_')[2] || undefined;\n },\n\n /**\n * Checks if the result of the login redirect contains an OAuth error.\n * Throws a mapped grant error if encountered.\n *\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @param {Object} location\n * @private\n * @returns {void}\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 (authorization code, CSRF token).\n * This is important to avoid leaking sensitive parameters via:\n * - Browser history\n * - Copy/paste of URL\n * - HTTP referrer headers to third-party content\n *\n * Approach:\n * - Remove 'code'.\n * - Remove 'state' entirely if only contained csrf_token.\n * - Else, re-encode remaining state fields (minus csrf_token).\n * - Replace current history entry (no page reload).\n *\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @param {Object} location\n * @private\n * @returns {void}\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 a PKCE (RFC 7636) code verifier and corresponding S256 code challenge.\n * Persists the verifier in sessionStorage (single-use) for later retrieval\n * during authorization code exchange; removes it once consumed.\n *\n * Implementation details:\n * - Creates a 128 character string using base64url safe alphabet.\n * - Computes SHA256 hash, encodes to base64url (no padding).\n *\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @private\n * @returns {string} code_challenge\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 stores it in sessionStorage.\n * Token is embedded in 'state' and validated upon redirect return.\n *\n * Uses UUID v4 for randomness.\n *\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @private\n * @returns {string} token\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 * Verifies that the CSRF token returned in the 'state' matches the one\n * previously stored in sessionStorage.\n *\n * Steps:\n * - Retrieve and immediately remove stored token (one-time use).\n * - Ensure state + state.csrf_token exist.\n * - Compare values; throw descriptive errors on mismatch / absence.\n *\n * If no stored token (e.g., user navigated directly), silently returns.\n *\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @param {Object} query - Parsed query (location.query)\n * @private\n * @returns {void}\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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","ignoreList":[]}
1
+ {"version":3,"names":["_querystring","_interopRequireDefault","require","_url2","_events","_common","_webexCore","_lodash","_uuid","_encBase64url","_cryptoJs","_excluded","_dec","_dec2","_obj","lodash","OAUTH2_CSRF_TOKEN","OAUTH2_CODE_VERIFIER","Events","exports","login","qRCodeLogin","Authorization","WebexPlugin","extend","whileInFlight","derived","isAuthenticating","deps","fn","isAuthorizing","session","default","type","ready","namespace","eventEmitter","EventEmitter","pollingTimer","pollingExpirationTimer","pollingId","currentPollingId","initialize","_this","_len","arguments","length","attrs","Array","_key","ret","_apply","prototype","location","url","parse","webex","getWindow","href","_checkForErrors","code","query","state","decodeState","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","emit","eventType","data","hasEmail","email","hasState","cloneDeep","emailHash","CryptoJS","SHA256","toString","csrf_token","_generateSecurityToken","code_challenge","_generateCodeChallenge","code_challenge_method","initiateAuthorizationCodeGrant","info","loginUrl","credentials","buildLoginUrl","_assign","response_type","separateWindow","defaultWindowSettings","width","height","windowSettings","_typeof2","windowFeatures","_entries","map","_ref","_ref2","_slicedToArray2","key","value","concat","join","open","initiateThirdPartyLogin","isObject","Error","initiateThirdPartyLoginRedirect","buildThirdPartyLoginUrl","err","reject","handleThirdPartyCallback","requireMatch","_location$query","idToken","id_token","_location$query$state","_objectWithoutProperties2","logout","noRedirect","buildLogoutUrl","_this2","form","grant_type","redirect_uri","config","self_contained_token","code_verifier","request","method","uri","tokenUrl","auth","user","client_id","pass","client_secret","sendImmediately","shouldRefreshAccessToken","res","set","supertoken","body","statusCode","ErrorConstructor","grantErrors","select","_res","_generateQRCodeVerificationUrl","verificationUrl","baseUrl","urlParams","URLSearchParams","URL","search","userCode","get","oauthHelperUrl","newVerificationUrl","searchParams","initQRCodeLogin","_this3","message","service","resource","scope","_res$body","user_code","verification_uri","verification_uri_complete","verificationUriComplete","userData","verificationUri","_startQRCodePolling","_options$interval","_this4","device_code","deviceCode","_options$expires_in","expires_in","expiresIn","interval","setTimeout","cancelQRCodePolling","polling","schedulePolling","withCancelEvent","clearTimeout","split","history","replaceState","_deleteProperty","isEmpty","omit","encodeState","querystring","stringify","format","safeCharacterMap","base64url","_safe_map","times","random","codeChallenge","setItem","token","uuid","v4","_query$state","sessionToken","version","_applyDecoratedDescriptor2","_getOwnPropertyDescriptor","oneFlight","_default"],"sources":["authorization.js"],"sourcesContent":["// @ts-nocheck\n/* eslint-disable */\n/*!\n * Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.\n */\n\n/* eslint camelcase: [0] */\n/**\n * TS checking disabled: file uses legacy decorator syntax inside an object literal\n * transformed by Babel. Safe to ignore for now.\n */\n\nimport querystring from 'querystring';\nimport url from 'url';\nimport {EventEmitter} from 'events';\n\nimport {decodeState, encodeState, oneFlight, whileInFlight} from '@webex/common';\nimport {grantErrors, WebexPlugin} from '@webex/webex-core';\nimport {cloneDeep, isEmpty, omit, isObject} from 'lodash';\nimport uuid from 'uuid';\nimport base64url from 'crypto-js/enc-base64url';\nimport CryptoJS from 'crypto-js';\n\n// Necessary to require lodash this way in order to stub\n// methods in the unit test\nconst lodash = require('lodash');\n\nconst OAUTH2_CSRF_TOKEN = 'oauth2-csrf-token';\nconst OAUTH2_CODE_VERIFIER = 'oauth2-code-verifier';\n\n/**\n * Authorization plugin events\n */\nexport const Events = {\n login: 'login',\n /**\n * QR code login events\n */\n qRCodeLogin: 'qRCodeLogin',\n};\n\n/**\n * Browser support for OAuth2 for first-party (Webex Web Client) usage.\n *\n * High-level flow handled by this module:\n * 1. initiateLogin() constructs authorization request (adds CSRF + PKCE).\n * 2. Browser navigates to IdBroker (login).\n * 3. IdBroker redirects back with ?code=... (&state=...).\n * 4. initialize() detects code, validates state/CSRF, cleans URL, optionally\n * pre-fetches a preauth catalog, then exchanges the code via\n * requestAuthorizationCodeGrant().\n * 5. Sets resulting supertoken (access/refresh token bundle) on credentials.\n *\n * Additional supported flow:\n * - Device Authorization (QR Code login):\n * initQRCodeLogin() obtains device + user codes and begins polling\n * _startQRCodePolling() until tokens are issued or timeout/cancel occurs.\n *\n * Security considerations implemented:\n * - CSRF token (state.csrf_token) generation + verification.\n * - PKCE (S256) code verifier + challenge generation and consumption.\n * - URL cleanup after redirect (removes code & CSRF to prevent leakage).\n *\n * Use of this plugin for anything other than the Webex Web Client is discouraged.\n *\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 /**\n * Indicates that the plugin has finished any automatic startup\n * processing (e.g., exchanging a returned authorization code)\n */\n ready: {\n default: false,\n type: 'boolean',\n },\n },\n\n namespace: 'Credentials',\n\n /**\n * EventEmitter for authorization events such as QR code login progress\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @type {EventEmitter}\n * @public\n */\n eventEmitter: new EventEmitter(),\n\n /**\n * Stores the timer ID for QR code polling (device authorization)\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @type {?number}\n * @private\n */\n pollingTimer: null,\n /**\n * Stores the expiration timer ID for QR code polling (overall timeout)\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @type {?number}\n * @private\n */\n pollingExpirationTimer: null,\n\n /**\n * Monotonically increasing id to identify the current polling request.\n * Used to safely ignore late poll responses after a cancel/reset.\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @type {number}\n * @private\n */\n pollingId: 0,\n\n /**\n * Identifier for the current polling request (snapshot of pollingId)\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @type {?number}\n * @private\n */\n currentPollingId: null,\n\n /**\n * Auto executes during Webex.init() – you do NOT call this yourself.\n *\n * Purpose: Seamless \"redirect completion\" of the OAuth Authorization Code (+ PKCE) flow.\n *\n * Simple summary:\n * - You call initiateLogin() which redirects user to IdBroker.\n * - User signs in; IdBroker redirects back to your redirect_uri with ?code=... (&state=...).\n * - During SDK startup this initialize() runs automatically, sees the code, and\n * silently finishes the login (validates state/CSRF + PKCE, scrubs URL, exchanges code).\n * - When done, webex.credentials.supertoken holds access+refresh and ready=true.\n *\n * Step-by-step:\n * 1. Inspect current window.location for ?code= (& state=).\n * 2. If no code: set ready=true immediately (nothing to complete).\n * 3. If code present:\n * - Decode base64 state JSON.\n * - Verify CSRF token matches sessionStorage value.\n * - Retrieve then delete PKCE code_verifier (single use).\n * - Optionally derive preauth hint (emailhash in state OR orgId parsed from code).\n * - Clean the URL (history.replaceState) to remove code & csrf token data.\n * - nextTick:\n * a. Best‑effort preauth catalog fetch (non-blocking).\n * b. Exchange authorization code (with code_verifier if any) for supertoken\n * and store on webex.credentials.\n * 4. Set ready=true after the async sequence finishes (or immediately if step 2).\n *\n * Result: If the redirect included a valid code the token exchange is completed\n * automatically—no extra API call needed after Webex.init().\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 // Check if redirect includes error\n this._checkForErrors(location);\n\n const {code} = location.query;\n\n // If no authorization code returned, nothing to do\n if (!code) {\n this.ready = true;\n return ret;\n }\n\n // Decode and parse state object (if present)\n if (location.query.state) {\n location.query.state = decodeState(location.query.state);\n } else {\n location.query.state = {};\n }\n\n // Retrieve PKCE code verifier (if a PKCE flow was initiated)\n const codeVerifier = this.webex.getWindow().sessionStorage.getItem(OAUTH2_CODE_VERIFIER);\n // Immediately remove code verifier to minimize exposure\n this.webex.getWindow().sessionStorage.removeItem(OAUTH2_CODE_VERIFIER);\n\n const {emailhash} = location.query.state;\n\n // Validate CSRF token included in state\n this._verifySecurityToken(location.query);\n // Remove code + CSRF token remnants from URL (history replace)\n this._cleanUrl(location);\n\n let preauthCatalogParams;\n\n // Attempt to extract orgId from structured authorization code (if present)\n const orgId = this._extractOrgIdFromCode(code);\n\n if (emailhash) {\n preauthCatalogParams = {emailhash};\n } else if (orgId) {\n preauthCatalogParams = {orgId};\n }\n\n // Defer token exchange until next tick in case credentials plugin not ready yet\n process.nextTick(() => {\n this.webex.internal.services\n .collectPreauthCatalog(preauthCatalogParams)\n .catch(() => Promise.resolve()) // Non-fatal if catalog collection fails\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 // Mark plugin ready regardless of success/failure of token exchange\n this.ready = true;\n });\n });\n\n return ret;\n },\n\n /**\n * Kicks off an OAuth authorization code flow (first party).\n *\n * Adds security + PKCE properties:\n * - SHA256(email) (emailHash & emailhash) for preauth and redirect flows\n * - state.csrf_token for CSRF protection\n * - PKCE code_challenge (S256)\n *\n * NOTE: This does not itself perform the redirect; it calls\n * initiateAuthorizationCodeGrant() which changes window location or opens\n * a separate window as configured.\n *\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @param {Object} options\n * @returns {Promise}\n */\n initiateLogin(options = {}) {\n this.eventEmitter.emit(Events.login, {\n eventType: 'initiateLogin',\n data: {\n hasEmail: !!options.email,\n hasState: !!options.state,\n },\n });\n\n options = cloneDeep(options);\n\n // Optionally compute heuristic email hash for preauth usage\n if (options.email) {\n options.emailHash = CryptoJS.SHA256(options.email).toString();\n }\n delete options.email; // Ensure raw email not propagated further\n\n options.state = options.state || {};\n // Embed CSRF token\n options.state.csrf_token = this._generateSecurityToken();\n // Provide email hash in lower-case key used by catalog service\n // (Note: catalog uses emailhash and redirectCI uses emailHash)\n options.state.emailhash = options.emailHash;\n\n // PKCE - produce code_challenge (S256) and persist code_verifier\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 * Performs the navigation step of the Authorization Code flow.\n * Builds login URL and either:\n * - Replaces current window location (default), or\n * - Opens a separate window (popup) if options.separateWindow supplied.\n *\n * Decorated with whileInFlight('isAuthorizing') to set isAuthorizing=true\n * during execution to prevent concurrent overlapping attempts.\n *\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @param {Object} options - Already augmented with state + PKCE info\n * @returns {Promise<void>}\n */\n initiateAuthorizationCodeGrant(options) {\n this.logger.info('authorization: initiating authorization code grant flow');\n const loginUrl = this.webex.credentials.buildLoginUrl(\n Object.assign({response_type: 'code'}, options)\n );\n\n this.eventEmitter.emit(Events.login, {\n eventType: 'redirectToLoginUrl',\n data: {loginUrl},\n });\n\n if (options?.separateWindow) {\n // If a separate popup window is requested, combine user supplied window features\n const defaultWindowSettings = {\n width: 600,\n height: 800,\n };\n\n const windowSettings = Object.assign(\n defaultWindowSettings,\n typeof options.separateWindow === 'object' ? options.separateWindow : {}\n );\n\n const windowFeatures = Object.entries(windowSettings)\n .map(([key, value]) => `${key}=${value}`)\n .join(',');\n this.webex.getWindow().open(loginUrl, '_blank', windowFeatures);\n } else {\n // Normal (in-tab) redirect\n this.webex.getWindow().location = loginUrl;\n }\n\n return Promise.resolve();\n },\n\n /**\n * Initiates third-party (social provider) login. Generates a CSRF token,\n * embeds it in `options.state.csrf_token`, and delegates to\n * `initiateThirdPartyLoginRedirect` for navigation.\n *\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @param {Object} options\n * @param {string} options.oauth2provider\n * @param {string} options.returnURL\n * @param {Object} [options.state] - Caller-supplied state object. Merged\n * with the generated `csrf_token`.\n * @returns {Promise<void>}\n */\n initiateThirdPartyLogin(options = {}) {\n options = cloneDeep(options);\n if (options.state !== undefined && !isObject(options.state)) {\n throw new Error('if specified, `options.state` must be an object');\n }\n options.state = options.state || {};\n options.state.csrf_token = this._generateSecurityToken();\n\n return this.initiateThirdPartyLoginRedirect(options);\n },\n\n /**\n * Performs the navigation step of the third-party login flow. Builds the\n * IdBroker URL via `Credentials#buildThirdPartyLoginUrl` and assigns it\n * to `getWindow().location`.\n *\n * Mirrors `initiateAuthorizationCodeGrant` for the `/authorize` flow.\n * Consumers may override this method for custom navigation handling\n * (e.g. postMessage in iframed contexts).\n *\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @param {Object} options\n * @param {string} options.oauth2provider\n * @param {string} options.returnURL\n * @returns {Promise<void>}\n */\n initiateThirdPartyLoginRedirect(options = {}) {\n this.logger.info('authorization: initiating third-party login redirect');\n\n try {\n const url = this.webex.credentials.buildThirdPartyLoginUrl(options);\n\n this.webex.getWindow().location = url;\n } catch (err) {\n return Promise.reject(err);\n }\n\n return Promise.resolve();\n },\n\n /**\n * Handles the third-party (social provider) login callback. Reads the\n * current `window.location`, decodes `state`, validates the CSRF token\n * (`state.csrf_token`), scrubs sensitive parameters from the URL via\n * `_cleanUrl`, and returns the parsed payload.\n *\n * Mirrors `initialize()` in always operating on the live\n * `window.location`\n *\n * `idToken` is single-use: it is parsed out of the URL exactly once and\n * the calling client is expected to exchange it (or discard it)\n * immediately. The returned `state` has `csrf_token` removed.\n *\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @returns {{idToken: string|undefined, email: string|undefined,\n * error: string|undefined, state: Object}}\n */\n handleThirdPartyCallback() {\n const location = url.parse(this.webex.getWindow().location.href, true);\n\n location.query.state = decodeState(location.query.state || 'e30');\n\n this._verifySecurityToken(location.query, {requireMatch: true});\n this._cleanUrl(location);\n\n const {id_token: idToken, email, error, state: {csrf_token, ...state}} = location.query;\n\n return {idToken, email, error, state};\n },\n\n /**\n * Called by {@link WebexCore#logout()}.\n * Constructs logout URL and (unless suppressed) navigates away to ensure\n * server-side session termination.\n *\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @param {Object} options\n * @param {boolean} options.noRedirect if true, does not redirect\n * @returns {Promise<void>}\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 (super) token bundle.\n *\n * Decorators:\n * - @whileInFlight('isAuthorizing'): prevents overlapping exchanges.\n * - @oneFlight: collapses simultaneous calls into one network request.\n *\n * Includes PKCE code_verifier if present from earlier login initiation.\n *\n * Error Handling:\n * - Non-400 responses are propagated.\n * - 400 responses map to OAuth-specific grantErrors.\n *\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @param {Object} options\n * @param {string} options.code - Authorization code from redirect\n * @param {string} [options.codeVerifier] - PKCE code verifier if used\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, // Request combined access/refresh response\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, // This is the token acquisition call itself\n })\n .then((res) => {\n // Store supertoken into credentials (includes refresh token)\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 // Map standard OAuth error to strongly typed error class\n const ErrorConstructor = grantErrors.select(res.body.error);\n\n return Promise.reject(new ErrorConstructor(res._res || res));\n });\n },\n\n /**\n * Generate a QR code verification URL for device authorization flow.\n * When a user scans the QR code with a mobile device, this deep-links into\n * Webex (web) to continue login, including passing along userCode and the\n * helper service base URL.\n *\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @param {String} verificationUrl - Original verification URI (complete)\n * @returns {String} Possibly rewritten verification URL\n */\n _generateQRCodeVerificationUrl(verificationUrl) {\n const baseUrl = 'https://web.webex.com/deviceAuth';\n const urlParams = new URLSearchParams(new URL(verificationUrl).search);\n const userCode = urlParams.get('userCode');\n\n if (userCode) {\n const {services} = this.webex.internal;\n const oauthHelperUrl = services.get('oauth-helper');\n const newVerificationUrl = new URL(baseUrl);\n newVerificationUrl.searchParams.set('usercode', userCode);\n newVerificationUrl.searchParams.set('oauthhelper', oauthHelperUrl);\n return newVerificationUrl.toString();\n } else {\n return verificationUrl;\n }\n },\n\n /**\n * Initiates Device Authorization (QR Code) flow.\n *\n * Steps:\n * 1. Obtain device_code, user_code, verification URLs from oauth-helper.\n * 2. Emit getUserCodeSuccess (provides data for generating QR code).\n * 3. Start polling token endpoint with device_code.\n *\n * Emits qRCodeLogin events for UI to react (success, failure, pending, etc.).\n *\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @emits #qRCodeLogin\n */\n initQRCodeLogin() {\n if (this.pollingTimer) {\n // Prevent concurrent device authorization attempts\n this.eventEmitter.emit(Events.qRCodeLogin, {\n eventType: 'getUserCodeFailure',\n data: {message: 'There is already a polling request'},\n });\n return;\n }\n\n this.webex\n .request({\n method: 'POST',\n service: 'oauth-helper',\n resource: '/actions/device/authorize',\n form: {\n client_id: this.config.client_id,\n scope: this.config.scope,\n },\n auth: {\n user: this.config.client_id,\n pass: this.config.client_secret,\n sendImmediately: true,\n },\n })\n .then((res) => {\n const {user_code, verification_uri, verification_uri_complete} = res.body;\n const verificationUriComplete =\n this._generateQRCodeVerificationUrl(verification_uri_complete);\n this.eventEmitter.emit(Events.qRCodeLogin, {\n eventType: 'getUserCodeSuccess',\n userData: {\n userCode: user_code,\n verificationUri: verification_uri,\n verificationUriComplete,\n },\n });\n // Begin polling for authorization completion\n this._startQRCodePolling(res.body);\n })\n .catch((res) => {\n this.eventEmitter.emit(Events.qRCodeLogin, {\n eventType: 'getUserCodeFailure',\n data: res.body,\n });\n });\n },\n\n /**\n * Poll the device token endpoint until user authorizes, an error occurs,\n * or timeout happens.\n *\n * Polling behavior:\n * - Interval provided by server (default 2s). 'slow_down' doubles interval once.\n * - 428 status => pending (continue).\n * - Success => set credentials + emit authorizationSuccess + stop polling.\n * - Any other error => emit authorizationFailure + stop polling.\n *\n * Cancellation:\n * - cancelQRCodePolling() resets timers and polling ids so late responses are ignored.\n *\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @param {Object} options - Must include device_code, may include interval/expires_in\n * @emits #qRCodeLogin\n */\n _startQRCodePolling(options = {}) {\n if (!options.device_code) {\n this.eventEmitter.emit(Events.qRCodeLogin, {\n eventType: 'authorizationFailure',\n data: {message: 'A deviceCode is required'},\n });\n return;\n }\n\n if (this.pollingTimer) {\n // Already polling; avoid starting a duplicate cycle\n this.eventEmitter.emit(Events.qRCodeLogin, {\n eventType: 'authorizationFailure',\n data: {message: 'There is already a polling request'},\n });\n return;\n }\n\n const {device_code: deviceCode, expires_in: expiresIn = 300} = options;\n // Server recommended polling interval (seconds)\n let interval = options.interval ?? 2;\n\n // Global timeout for entire device authorization attempt\n this.pollingExpirationTimer = setTimeout(() => {\n this.cancelQRCodePolling(false);\n this.eventEmitter.emit(Events.qRCodeLogin, {\n eventType: 'authorizationFailure',\n data: {message: 'Authorization timed out'},\n });\n }, expiresIn * 1000);\n\n const polling = () => {\n // Increment id so any previous poll loops can be invalidated\n this.pollingId += 1;\n this.currentPollingId = this.pollingId;\n\n this.webex\n .request({\n method: 'POST',\n service: 'oauth-helper',\n resource: '/actions/device/token',\n form: {\n grant_type: 'urn:ietf:params:oauth:grant-type:device_code',\n device_code: deviceCode,\n client_id: this.config.client_id,\n },\n auth: {\n user: this.config.client_id,\n pass: this.config.client_secret,\n sendImmediately: true,\n },\n })\n .then((res) => {\n // If polling canceled (id changed), ignore this response\n if (this.currentPollingId !== this.pollingId) return;\n\n this.eventEmitter.emit(Events.qRCodeLogin, {\n eventType: 'authorizationSuccess',\n data: res.body,\n });\n this.webex.credentials.set({supertoken: res.body});\n this.cancelQRCodePolling();\n })\n .catch((res) => {\n if (this.currentPollingId !== this.pollingId) return;\n\n // Backoff signal from server; increase interval just once for next cycle\n if (res.statusCode === 400 && res.body.message === 'slow_down') {\n schedulePolling(interval * 2);\n return;\n }\n\n // Pending: keep polling\n if (res.statusCode === 428) {\n this.eventEmitter.emit(Events.qRCodeLogin, {\n eventType: 'authorizationPending',\n data: res.body,\n });\n schedulePolling(interval);\n return;\n }\n\n // Terminal error\n this.cancelQRCodePolling();\n\n this.eventEmitter.emit(Events.qRCodeLogin, {\n eventType: 'authorizationFailure',\n data: res.body,\n });\n });\n };\n\n // Schedules next poll invocation\n const schedulePolling = (interval) =>\n (this.pollingTimer = setTimeout(polling, interval * 1000));\n\n schedulePolling(interval);\n },\n\n /**\n * Cancel active device authorization polling loop.\n *\n * @param {boolean} withCancelEvent emit a pollingCanceled event (default true)\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @returns {void}\n */\n cancelQRCodePolling(withCancelEvent = true) {\n if (this.pollingTimer && withCancelEvent) {\n this.eventEmitter.emit(Events.qRCodeLogin, {\n eventType: 'pollingCanceled',\n });\n }\n\n this.currentPollingId = null;\n\n clearTimeout(this.pollingExpirationTimer);\n this.pollingExpirationTimer = null;\n clearTimeout(this.pollingTimer);\n this.pollingTimer = null;\n },\n\n /**\n * Extracts the orgId from the returned code from idbroker.\n *\n * Certain authorization codes encode organization info in a structured\n * underscore-delimited format. This method parses out the 3rd segment.\n *\n * For undocumented formats or unexpected code shapes, returns undefined.\n *\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @param {String} code\n * @private\n * @returns {String|undefined}\n */\n _extractOrgIdFromCode(code) {\n return code?.split('_')[2] || undefined;\n },\n\n /**\n * Checks if the result of the login redirect contains an OAuth error.\n * Throws a mapped grant error if encountered.\n *\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @param {Object} location\n * @private\n * @returns {void}\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 (authorization code, CSRF token).\n * This is important to avoid leaking sensitive parameters via:\n * - Browser history\n * - Copy/paste of URL\n * - HTTP referrer headers to third-party content\n *\n * Approach:\n * - Remove 'code' (OAuth code-grant), 'id_token', and 'email'\n * (third-party callback).\n * - Remove 'state' entirely if it only contained csrf_token.\n * - Else, re-encode remaining state fields (minus csrf_token).\n * - Replace current history entry (no page reload).\n *\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @param {Object} location\n * @private\n * @returns {void}\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 Reflect.deleteProperty(location.query, 'id_token');\n Reflect.deleteProperty(location.query, 'email');\n if (isEmpty(omit(location.query.state, 'csrf_token'))) {\n Reflect.deleteProperty(location.query, 'state');\n } else {\n location.query.state = encodeState(omit(location.query.state, 'csrf_token'));\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 a PKCE (RFC 7636) code verifier and corresponding S256 code challenge.\n * Persists the verifier in sessionStorage (single-use) for later retrieval\n * during authorization code exchange; removes it once consumed.\n *\n * Implementation details:\n * - Creates a 128 character string using base64url safe alphabet.\n * - Computes SHA256 hash, encodes to base64url (no padding).\n *\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @private\n * @returns {string} code_challenge\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 stores it in sessionStorage.\n * Token is embedded in 'state' and validated upon redirect return.\n *\n * Uses UUID v4 for randomness.\n *\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @private\n * @returns {string} token\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 * Verifies that the CSRF token returned in the 'state' matches the one\n * previously stored in sessionStorage.\n *\n * Steps:\n * - Retrieve and immediately remove stored token (one-time use).\n * - Ensure state + state.csrf_token exist.\n * - Compare values; throw descriptive errors on mismatch / absence.\n *\n * If no stored token (e.g., user navigated directly), silently returns\n * unless `options.requireMatch` is `true`, in which case absence of a\n * stored token is treated as a CSRF failure.\n *\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @param {Object} query - Parsed query (location.query)\n * @param {Object} [options]\n * @param {boolean} [options.requireMatch=false] - When true, throws if\n * no stored sessionToken is present.\n * @private\n * @returns {void}\n */\n _verifySecurityToken(query, options = {}) {\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 if (options.requireMatch) {\n throw new Error('CSRF token missing from session storage');\n }\n\n return;\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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","ignoreList":[]}
package/package.json CHANGED
@@ -26,23 +26,23 @@
26
26
  "@webex/eslint-config-legacy": "0.0.0",
27
27
  "@webex/jest-config-legacy": "0.0.0",
28
28
  "@webex/legacy-tools": "0.0.0",
29
- "@webex/test-helper-chai": "3.11.0-next.1",
30
- "@webex/test-helper-mocha": "3.11.0-next.1",
31
- "@webex/test-helper-mock-webex": "3.11.0-next.1",
32
- "@webex/test-helper-test-users": "3.11.0-next.1",
29
+ "@webex/test-helper-chai": "3.12.0-next.3",
30
+ "@webex/test-helper-mocha": "3.12.0-next.3",
31
+ "@webex/test-helper-mock-webex": "3.12.0-next.3",
32
+ "@webex/test-helper-test-users": "3.12.0-next.3",
33
33
  "eslint": "^8.24.0",
34
34
  "prettier": "^2.7.1",
35
35
  "sinon": "^9.2.4"
36
36
  },
37
37
  "dependencies": {
38
- "@webex/common": "3.11.0-next.1",
39
- "@webex/storage-adapter-local-storage": "3.12.0-next.2",
40
- "@webex/test-helper-automation": "3.11.0-next.1",
41
- "@webex/test-helper-chai": "3.11.0-next.1",
42
- "@webex/test-helper-mocha": "3.11.0-next.1",
43
- "@webex/test-helper-mock-webex": "3.11.0-next.1",
44
- "@webex/test-helper-test-users": "3.11.0-next.1",
45
- "@webex/webex-core": "3.12.0-next.2",
38
+ "@webex/common": "3.12.0-next.3",
39
+ "@webex/storage-adapter-local-storage": "3.12.0-next.21",
40
+ "@webex/test-helper-automation": "3.12.0-next.3",
41
+ "@webex/test-helper-chai": "3.12.0-next.3",
42
+ "@webex/test-helper-mocha": "3.12.0-next.3",
43
+ "@webex/test-helper-mock-webex": "3.12.0-next.3",
44
+ "@webex/test-helper-test-users": "3.12.0-next.3",
45
+ "@webex/webex-core": "3.12.0-next.21",
46
46
  "crypto-js": "^4.1.1",
47
47
  "lodash": "^4.17.21",
48
48
  "uuid": "^3.3.2"
@@ -56,5 +56,5 @@
56
56
  "test:style": "eslint ./src/**/*.*",
57
57
  "test:unit": "webex-legacy-tools test --unit --runner jest"
58
58
  },
59
- "version": "3.12.0-next.2"
59
+ "version": "3.12.0-next.21"
60
60
  }
@@ -1,4 +1,4 @@
1
- // @ts-nocheck
1
+ // @ts-nocheck
2
2
  /* eslint-disable */
3
3
  /*!
4
4
  * Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.
@@ -14,9 +14,9 @@ import querystring from 'querystring';
14
14
  import url from 'url';
15
15
  import {EventEmitter} from 'events';
16
16
 
17
- import {base64, oneFlight, whileInFlight} from '@webex/common';
17
+ import {decodeState, encodeState, oneFlight, whileInFlight} from '@webex/common';
18
18
  import {grantErrors, WebexPlugin} from '@webex/webex-core';
19
- import {cloneDeep, isEmpty, omit} from 'lodash';
19
+ import {cloneDeep, isEmpty, omit, isObject} from 'lodash';
20
20
  import uuid from 'uuid';
21
21
  import base64url from 'crypto-js/enc-base64url';
22
22
  import CryptoJS from 'crypto-js';
@@ -199,7 +199,7 @@ const Authorization = WebexPlugin.extend({
199
199
 
200
200
  // Decode and parse state object (if present)
201
201
  if (location.query.state) {
202
- location.query.state = JSON.parse(base64.decode(location.query.state));
202
+ location.query.state = decodeState(location.query.state);
203
203
  } else {
204
204
  location.query.state = {};
205
205
  }
@@ -267,7 +267,7 @@ const Authorization = WebexPlugin.extend({
267
267
  eventType: 'initiateLogin',
268
268
  data: {
269
269
  hasEmail: !!options.email,
270
- hasState: !!options.state
270
+ hasState: !!options.state,
271
271
  },
272
272
  });
273
273
 
@@ -316,7 +316,7 @@ const Authorization = WebexPlugin.extend({
316
316
 
317
317
  this.eventEmitter.emit(Events.login, {
318
318
  eventType: 'redirectToLoginUrl',
319
- data: { loginUrl },
319
+ data: {loginUrl},
320
320
  });
321
321
 
322
322
  if (options?.separateWindow) {
@@ -343,6 +343,92 @@ const Authorization = WebexPlugin.extend({
343
343
  return Promise.resolve();
344
344
  },
345
345
 
346
+ /**
347
+ * Initiates third-party (social provider) login. Generates a CSRF token,
348
+ * embeds it in `options.state.csrf_token`, and delegates to
349
+ * `initiateThirdPartyLoginRedirect` for navigation.
350
+ *
351
+ * @instance
352
+ * @memberof AuthorizationBrowserFirstParty
353
+ * @param {Object} options
354
+ * @param {string} options.oauth2provider
355
+ * @param {string} options.returnURL
356
+ * @param {Object} [options.state] - Caller-supplied state object. Merged
357
+ * with the generated `csrf_token`.
358
+ * @returns {Promise<void>}
359
+ */
360
+ initiateThirdPartyLogin(options = {}) {
361
+ options = cloneDeep(options);
362
+ if (options.state !== undefined && !isObject(options.state)) {
363
+ throw new Error('if specified, `options.state` must be an object');
364
+ }
365
+ options.state = options.state || {};
366
+ options.state.csrf_token = this._generateSecurityToken();
367
+
368
+ return this.initiateThirdPartyLoginRedirect(options);
369
+ },
370
+
371
+ /**
372
+ * Performs the navigation step of the third-party login flow. Builds the
373
+ * IdBroker URL via `Credentials#buildThirdPartyLoginUrl` and assigns it
374
+ * to `getWindow().location`.
375
+ *
376
+ * Mirrors `initiateAuthorizationCodeGrant` for the `/authorize` flow.
377
+ * Consumers may override this method for custom navigation handling
378
+ * (e.g. postMessage in iframed contexts).
379
+ *
380
+ * @instance
381
+ * @memberof AuthorizationBrowserFirstParty
382
+ * @param {Object} options
383
+ * @param {string} options.oauth2provider
384
+ * @param {string} options.returnURL
385
+ * @returns {Promise<void>}
386
+ */
387
+ initiateThirdPartyLoginRedirect(options = {}) {
388
+ this.logger.info('authorization: initiating third-party login redirect');
389
+
390
+ try {
391
+ const url = this.webex.credentials.buildThirdPartyLoginUrl(options);
392
+
393
+ this.webex.getWindow().location = url;
394
+ } catch (err) {
395
+ return Promise.reject(err);
396
+ }
397
+
398
+ return Promise.resolve();
399
+ },
400
+
401
+ /**
402
+ * Handles the third-party (social provider) login callback. Reads the
403
+ * current `window.location`, decodes `state`, validates the CSRF token
404
+ * (`state.csrf_token`), scrubs sensitive parameters from the URL via
405
+ * `_cleanUrl`, and returns the parsed payload.
406
+ *
407
+ * Mirrors `initialize()` in always operating on the live
408
+ * `window.location`
409
+ *
410
+ * `idToken` is single-use: it is parsed out of the URL exactly once and
411
+ * the calling client is expected to exchange it (or discard it)
412
+ * immediately. The returned `state` has `csrf_token` removed.
413
+ *
414
+ * @instance
415
+ * @memberof AuthorizationBrowserFirstParty
416
+ * @returns {{idToken: string|undefined, email: string|undefined,
417
+ * error: string|undefined, state: Object}}
418
+ */
419
+ handleThirdPartyCallback() {
420
+ const location = url.parse(this.webex.getWindow().location.href, true);
421
+
422
+ location.query.state = decodeState(location.query.state || 'e30');
423
+
424
+ this._verifySecurityToken(location.query, {requireMatch: true});
425
+ this._cleanUrl(location);
426
+
427
+ const {id_token: idToken, email, error, state: {csrf_token, ...state}} = location.query;
428
+
429
+ return {idToken, email, error, state};
430
+ },
431
+
346
432
  /**
347
433
  * Called by {@link WebexCore#logout()}.
348
434
  * Constructs logout URL and (unless suppressed) navigates away to ensure
@@ -497,10 +583,11 @@ const Authorization = WebexPlugin.extend({
497
583
  })
498
584
  .then((res) => {
499
585
  const {user_code, verification_uri, verification_uri_complete} = res.body;
500
- const verificationUriComplete = this._generateQRCodeVerificationUrl(verification_uri_complete);
586
+ const verificationUriComplete =
587
+ this._generateQRCodeVerificationUrl(verification_uri_complete);
501
588
  this.eventEmitter.emit(Events.qRCodeLogin, {
502
589
  eventType: 'getUserCodeSuccess',
503
- userData: {
590
+ userData: {
504
591
  userCode: user_code,
505
592
  verificationUri: verification_uri,
506
593
  verificationUriComplete,
@@ -591,7 +678,7 @@ const Authorization = WebexPlugin.extend({
591
678
  // If polling canceled (id changed), ignore this response
592
679
  if (this.currentPollingId !== this.pollingId) return;
593
680
 
594
- this.eventEmitter.emit(Events.qRCodeLogin, {
681
+ this.eventEmitter.emit(Events.qRCodeLogin, {
595
682
  eventType: 'authorizationSuccess',
596
683
  data: res.body,
597
684
  });
@@ -703,8 +790,9 @@ const Authorization = WebexPlugin.extend({
703
790
  * - HTTP referrer headers to third-party content
704
791
  *
705
792
  * Approach:
706
- * - Remove 'code'.
707
- * - Remove 'state' entirely if only contained csrf_token.
793
+ * - Remove 'code' (OAuth code-grant), 'id_token', and 'email'
794
+ * (third-party callback).
795
+ * - Remove 'state' entirely if it only contained csrf_token.
708
796
  * - Else, re-encode remaining state fields (minus csrf_token).
709
797
  * - Replace current history entry (no page reload).
710
798
  *
@@ -718,12 +806,12 @@ const Authorization = WebexPlugin.extend({
718
806
  location = cloneDeep(location);
719
807
  if (this.webex.getWindow().history && this.webex.getWindow().history.replaceState) {
720
808
  Reflect.deleteProperty(location.query, 'code');
809
+ Reflect.deleteProperty(location.query, 'id_token');
810
+ Reflect.deleteProperty(location.query, 'email');
721
811
  if (isEmpty(omit(location.query.state, 'csrf_token'))) {
722
812
  Reflect.deleteProperty(location.query, 'state');
723
813
  } else {
724
- location.query.state = base64.encode(
725
- JSON.stringify(omit(location.query.state, 'csrf_token'))
726
- );
814
+ location.query.state = encodeState(omit(location.query.state, 'csrf_token'));
727
815
  }
728
816
  location.search = querystring.stringify(location.query);
729
817
  Reflect.deleteProperty(location, 'query');
@@ -792,27 +880,32 @@ const Authorization = WebexPlugin.extend({
792
880
  * - Ensure state + state.csrf_token exist.
793
881
  * - Compare values; throw descriptive errors on mismatch / absence.
794
882
  *
795
- * If no stored token (e.g., user navigated directly), silently returns.
883
+ * If no stored token (e.g., user navigated directly), silently returns
884
+ * unless `options.requireMatch` is `true`, in which case absence of a
885
+ * stored token is treated as a CSRF failure.
796
886
  *
797
887
  * @instance
798
888
  * @memberof AuthorizationBrowserFirstParty
799
889
  * @param {Object} query - Parsed query (location.query)
890
+ * @param {Object} [options]
891
+ * @param {boolean} [options.requireMatch=false] - When true, throws if
892
+ * no stored sessionToken is present.
800
893
  * @private
801
894
  * @returns {void}
802
895
  */
803
- _verifySecurityToken(query) {
896
+ _verifySecurityToken(query, options = {}) {
804
897
  const sessionToken = this.webex.getWindow().sessionStorage.getItem(OAUTH2_CSRF_TOKEN);
805
898
 
806
899
  this.webex.getWindow().sessionStorage.removeItem(OAUTH2_CSRF_TOKEN);
807
900
  if (!sessionToken) {
808
- return;
809
- }
901
+ if (options.requireMatch) {
902
+ throw new Error('CSRF token missing from session storage');
903
+ }
810
904
 
811
- if (!query.state) {
812
- throw new Error(`Expected CSRF token ${sessionToken}, but not found in redirect query`);
905
+ return;
813
906
  }
814
907
 
815
- if (!query.state.csrf_token) {
908
+ if (!query.state?.csrf_token) {
816
909
  throw new Error(`Expected CSRF token ${sessionToken}, but not found in redirect query`);
817
910
  }
818
911
 
@@ -430,12 +430,14 @@ describe('plugin-authorization-browser-first-party', () => {
430
430
  const webex = makeWebex();
431
431
 
432
432
  const emitSpy = sinon.spy(webex.authorization.eventEmitter, 'emit');
433
- sinon.stub(webex.authorization, 'initiateAuthorizationCodeGrant').returns(Promise.resolve());
433
+ sinon
434
+ .stub(webex.authorization, 'initiateAuthorizationCodeGrant')
435
+ .returns(Promise.resolve());
434
436
 
435
437
  return webex.authorization.initiateLogin().then(() => {
436
438
  assert.calledOnceWithExactly(emitSpy, Events.login, {
437
439
  eventType: 'initiateLogin',
438
- data: { hasEmail: false, hasState: false },
440
+ data: {hasEmail: false, hasState: false},
439
441
  });
440
442
  });
441
443
  });
@@ -444,12 +446,14 @@ describe('plugin-authorization-browser-first-party', () => {
444
446
  const webex = makeWebex();
445
447
 
446
448
  const emitSpy = sinon.spy(webex.authorization.eventEmitter, 'emit');
447
- sinon.stub(webex.authorization, 'initiateAuthorizationCodeGrant').returns(Promise.resolve());
449
+ sinon
450
+ .stub(webex.authorization, 'initiateAuthorizationCodeGrant')
451
+ .returns(Promise.resolve());
448
452
 
449
- return webex.authorization.initiateLogin({ email: 'test@abc.xyz' }).then(() => {
453
+ return webex.authorization.initiateLogin({email: 'test@abc.xyz'}).then(() => {
450
454
  assert.calledOnceWithExactly(emitSpy, Events.login, {
451
455
  eventType: 'initiateLogin',
452
- data: { hasEmail: true, hasState: false },
456
+ data: {hasEmail: true, hasState: false},
453
457
  });
454
458
  });
455
459
  });
@@ -458,12 +462,14 @@ describe('plugin-authorization-browser-first-party', () => {
458
462
  const webex = makeWebex();
459
463
 
460
464
  const emitSpy = sinon.spy(webex.authorization.eventEmitter, 'emit');
461
- sinon.stub(webex.authorization, 'initiateAuthorizationCodeGrant').returns(Promise.resolve());
465
+ sinon
466
+ .stub(webex.authorization, 'initiateAuthorizationCodeGrant')
467
+ .returns(Promise.resolve());
462
468
 
463
- return webex.authorization.initiateLogin({ state: {} }).then(() => {
469
+ return webex.authorization.initiateLogin({state: {}}).then(() => {
464
470
  assert.calledOnceWithExactly(emitSpy, Events.login, {
465
471
  eventType: 'initiateLogin',
466
- data: { hasEmail: false, hasState: true },
472
+ data: {hasEmail: false, hasState: true},
467
473
  });
468
474
  });
469
475
  });
@@ -471,81 +477,87 @@ describe('plugin-authorization-browser-first-party', () => {
471
477
 
472
478
  describe('#initiateAuthorizationCodeGrant()', () => {
473
479
  it('redirects to the login page with response_type=code', () => {
474
- const webex = makeWebex(undefined, undefined, {
475
- credentials: {
476
- clientType: 'confidential',
477
- },
478
- });
480
+ const webex = makeWebex(undefined, undefined, {
481
+ credentials: {
482
+ clientType: 'confidential',
483
+ },
484
+ });
479
485
 
480
- sinon.spy(webex.authorization, 'initiateAuthorizationCodeGrant');
486
+ sinon.spy(webex.authorization, 'initiateAuthorizationCodeGrant');
481
487
 
482
- return webex.authorization.initiateLogin().then(() => {
483
- assert.called(webex.authorization.initiateAuthorizationCodeGrant);
484
- assert.include(webex.getWindow().location, 'response_type=code');
485
- });
488
+ return webex.authorization.initiateLogin().then(() => {
489
+ assert.called(webex.authorization.initiateAuthorizationCodeGrant);
490
+ assert.include(webex.getWindow().location, 'response_type=code');
491
+ });
486
492
  });
487
493
 
488
494
  it('redirects to the login page in the same window by default', () => {
489
- const webex = makeWebex();
495
+ const webex = makeWebex();
490
496
 
491
- return webex.authorization.initiateAuthorizationCodeGrant().then(() => {
492
- assert.isDefined(webex.getWindow().location);
493
- assert.isUndefined(webex.getWindow().open);
494
- });
497
+ return webex.authorization.initiateAuthorizationCodeGrant().then(() => {
498
+ assert.isDefined(webex.getWindow().location);
499
+ assert.isUndefined(webex.getWindow().open);
500
+ });
495
501
  });
496
502
 
497
503
  it('opens login page in a new window when separateWindow is true', () => {
498
- const webex = makeWebex();
499
- webex.getWindow().open = sinon.spy();
500
-
501
- return webex.authorization.initiateAuthorizationCodeGrant({ separateWindow: true }).then(() => {
502
- assert.called(webex.getWindow().open);
503
- const openCall = webex.getWindow().open.getCall(0);
504
- assert.equal(openCall.args[1], '_blank');
505
- assert.equal(openCall.args[2], 'width=600,height=800');
506
- });
504
+ const webex = makeWebex();
505
+ webex.getWindow().open = sinon.spy();
506
+
507
+ return webex.authorization
508
+ .initiateAuthorizationCodeGrant({separateWindow: true})
509
+ .then(() => {
510
+ assert.called(webex.getWindow().open);
511
+ const openCall = webex.getWindow().open.getCall(0);
512
+ assert.equal(openCall.args[1], '_blank');
513
+ assert.equal(openCall.args[2], 'width=600,height=800');
514
+ });
507
515
  });
508
516
 
509
517
  it('opens login page in a new window with custom dimensions', () => {
510
- const webex = makeWebex();
511
- webex.getWindow().open = sinon.spy();
512
-
513
- const customWindow = {
514
- width: 800,
515
- height: 600,
516
- menubar: 'no',
517
- toolbar: 'no'
518
- };
518
+ const webex = makeWebex();
519
+ webex.getWindow().open = sinon.spy();
519
520
 
520
- return webex.authorization.initiateAuthorizationCodeGrant({
521
- separateWindow: customWindow
522
- }).then(() => {
523
- assert.called(webex.getWindow().open);
524
- const openCall = webex.getWindow().open.getCall(0);
525
- assert.equal(openCall.args[1], '_blank');
526
- assert.equal(
527
- openCall.args[2],
528
- 'width=800,height=600,menubar=no,toolbar=no'
529
- );
530
- });
521
+ const customWindow = {
522
+ width: 800,
523
+ height: 600,
524
+ menubar: 'no',
525
+ toolbar: 'no',
526
+ };
527
+
528
+ return webex.authorization
529
+ .initiateAuthorizationCodeGrant({
530
+ separateWindow: customWindow,
531
+ })
532
+ .then(() => {
533
+ assert.called(webex.getWindow().open);
534
+ const openCall = webex.getWindow().open.getCall(0);
535
+ assert.equal(openCall.args[1], '_blank');
536
+ assert.equal(openCall.args[2], 'width=800,height=600,menubar=no,toolbar=no');
537
+ });
531
538
  });
532
539
 
533
540
  it('preserves other options when using separateWindow', () => {
534
- const webex = makeWebex();
535
- webex.getWindow().open = sinon.spy();
536
-
537
- return webex.authorization.initiateAuthorizationCodeGrant({
538
- separateWindow: true,
539
- state: {}
540
- }).then(() => {
541
- assert.called(webex.getWindow().open);
542
- const url = webex.getWindow().open.getCall(0).args[0];
543
- assert.include(url, "https://idbrokerbts.webex.com/idb/oauth2/v1/authorize?response_type=code&separateWindow=true&client_id=fake&redirect_uri=http%3A%2F%2Fexample.com&scope=scope%3Aone");
544
- });
541
+ const webex = makeWebex();
542
+ webex.getWindow().open = sinon.spy();
543
+
544
+ return webex.authorization
545
+ .initiateAuthorizationCodeGrant({
546
+ separateWindow: true,
547
+ state: {},
548
+ })
549
+ .then(() => {
550
+ assert.called(webex.getWindow().open);
551
+ const url = webex.getWindow().open.getCall(0).args[0];
552
+ assert.include(
553
+ url,
554
+ 'https://idbrokerbts.webex.com/idb/oauth2/v1/authorize?response_type=code&separateWindow=true&client_id=fake&redirect_uri=http%3A%2F%2Fexample.com&scope=scope%3Aone'
555
+ );
556
+ });
545
557
  });
546
558
 
547
559
  it('Emits an event containing the login url', () => {
548
- const testLoginUrl = "https://test.example.com";
560
+ const testLoginUrl = 'https://test.example.com';
549
561
  const webex = makeWebex();
550
562
 
551
563
  sinon.stub(webex.credentials, 'buildLoginUrl').returns(testLoginUrl);
@@ -554,17 +566,275 @@ describe('plugin-authorization-browser-first-party', () => {
554
566
  return webex.authorization.initiateAuthorizationCodeGrant().then(() => {
555
567
  assert.calledOnceWithExactly(emitSpy, Events.login, {
556
568
  eventType: 'redirectToLoginUrl',
557
- data: { loginUrl: testLoginUrl },
569
+ data: {loginUrl: testLoginUrl},
558
570
  });
559
571
  });
560
572
  });
561
573
  });
562
574
 
575
+ describe('#initiateThirdPartyLogin()', () => {
576
+ it('generates a csrf_token, embeds it in state, and delegates to #initiateThirdPartyLoginRedirect', () => {
577
+ const webex = makeWebex();
578
+ const expected = Promise.resolve();
579
+ const stub = sinon
580
+ .stub(webex.authorization, 'initiateThirdPartyLoginRedirect')
581
+ .returns(expected);
582
+ sinon.stub(webex.authorization, '_generateSecurityToken').returns('csrf-1234');
583
+ const options = {
584
+ oauth2provider: 'google',
585
+ returnURL: 'https://web.webex.com',
586
+ };
587
+
588
+ const result = webex.authorization.initiateThirdPartyLogin(options);
589
+
590
+ assert.equal(result, expected);
591
+ assert.calledOnce(webex.authorization._generateSecurityToken);
592
+ assert.calledOnce(stub);
593
+ const passed = stub.getCall(0).args[0];
594
+
595
+ assert.deepEqual(passed, {
596
+ oauth2provider: 'google',
597
+ returnURL: 'https://web.webex.com',
598
+ state: {csrf_token: 'csrf-1234'},
599
+ });
600
+ // Caller's options object is not mutated
601
+ assert.notProperty(options, 'state');
602
+
603
+ return result;
604
+ });
605
+
606
+ it('merges generated csrf_token into caller-supplied state without mutating the caller object', () => {
607
+ const webex = makeWebex();
608
+ sinon
609
+ .stub(webex.authorization, 'initiateThirdPartyLoginRedirect')
610
+ .returns(Promise.resolve());
611
+ sinon.stub(webex.authorization, '_generateSecurityToken').returns('csrf-1234');
612
+ const options = {
613
+ oauth2provider: 'google',
614
+ returnURL: 'https://web.webex.com',
615
+ state: {popUpSignIn: true, mode: 'meeting'},
616
+ };
617
+
618
+ return webex.authorization.initiateThirdPartyLogin(options).then(() => {
619
+ const passed = webex.authorization.initiateThirdPartyLoginRedirect.getCall(0).args[0];
620
+
621
+ assert.deepEqual(passed.state, {
622
+ popUpSignIn: true,
623
+ mode: 'meeting',
624
+ csrf_token: 'csrf-1234',
625
+ });
626
+ // Caller-supplied state is not mutated
627
+ assert.deepEqual(options.state, {popUpSignIn: true, mode: 'meeting'});
628
+ });
629
+ });
630
+
631
+ it('throws (before generating a csrf_token) when state is supplied but is not an object', () => {
632
+ const webex = makeWebex();
633
+ const redirectStub = sinon.stub(webex.authorization, 'initiateThirdPartyLoginRedirect');
634
+ const tokenStub = sinon.stub(webex.authorization, '_generateSecurityToken');
635
+
636
+ assert.throws(
637
+ () =>
638
+ webex.authorization.initiateThirdPartyLogin({
639
+ oauth2provider: 'google',
640
+ returnURL: 'https://web.webex.com',
641
+ state: 'not-an-object',
642
+ }),
643
+ /`options.state` must be an object/
644
+ );
645
+
646
+ assert.notCalled(tokenStub);
647
+ assert.notCalled(redirectStub);
648
+ });
649
+ });
650
+
651
+ describe('#initiateThirdPartyLoginRedirect()', () => {
652
+ it('builds the third-party login URL and assigns it to getWindow().location', () => {
653
+ const webex = makeWebex();
654
+ const builtUrl =
655
+ 'https://idbroker.webex.com/idb/ThirdPartyLogin?oauth2provider=google&returnURL=https%3A%2F%2Fweb.webex.com';
656
+ sinon.stub(webex.credentials, 'buildThirdPartyLoginUrl').returns(builtUrl);
657
+
658
+ return webex.authorization
659
+ .initiateThirdPartyLoginRedirect({
660
+ oauth2provider: 'google',
661
+ returnURL: 'https://web.webex.com',
662
+ })
663
+ .then(() => {
664
+ assert.calledOnceWithExactly(webex.credentials.buildThirdPartyLoginUrl, {
665
+ oauth2provider: 'google',
666
+ returnURL: 'https://web.webex.com',
667
+ });
668
+ assert.equal(webex.getWindow().location, builtUrl);
669
+ });
670
+ });
671
+
672
+ it('returns a rejected promise if buildThirdPartyLoginUrl throws', () => {
673
+ const webex = makeWebex();
674
+ sinon
675
+ .stub(webex.credentials, 'buildThirdPartyLoginUrl')
676
+ .throws(new Error('`options.oauth2provider` is required'));
677
+
678
+ return assert.isRejected(
679
+ webex.authorization.initiateThirdPartyLoginRedirect({}),
680
+ /`options.oauth2provider` is required/
681
+ );
682
+ });
683
+ });
684
+
685
+ describe('#_verifySecurityToken() requireMatch', () => {
686
+ it('silently returns undefined when no stored token and requireMatch is false', () => {
687
+ const webex = makeWebex();
688
+ webex.getWindow().sessionStorage.getItem = sinon.stub().returns(null);
689
+
690
+ const result = webex.authorization._verifySecurityToken({});
691
+
692
+ assert.isUndefined(result);
693
+ });
694
+
695
+ it('throws when no stored token and requireMatch is true', () => {
696
+ const webex = makeWebex();
697
+ webex.getWindow().sessionStorage.getItem = sinon.stub().returns(null);
698
+
699
+ assert.throws(() => {
700
+ webex.authorization._verifySecurityToken({}, {requireMatch: true});
701
+ }, /CSRF token missing from session storage/);
702
+ });
703
+ });
704
+
705
+ describe('#handleThirdPartyCallback()', () => {
706
+ const buildState = (state) => base64.toBase64Url(JSON.stringify(state));
707
+
708
+ // Mirror the SDK behaviour: replaceState writes the cleaned URL to
709
+ // location.href, so we can re-parse it and assert against named query
710
+ // params instead of substring-matching the raw URL.
711
+ const parseLocationQuery = (webex) => url.parse(webex.getWindow().location.href, true).query;
712
+
713
+ it('CSRF round-trip succeeds, scrubs sensitive params, and resolves with payload', () => {
714
+ const storedToken = 'csrf-abc';
715
+ const state = {csrf_token: storedToken, popUpSignIn: true, mode: 'meeting'};
716
+ const search = `?id_token=id-1&email=user%40example.com&state=${buildState(state)}`;
717
+ const webex = makeWebex(`http://example.com/${search}`, storedToken);
718
+ webex.getWindow().sessionStorage.getItem = sinon.stub().returns(storedToken);
719
+
720
+ const result = webex.authorization.handleThirdPartyCallback();
721
+
722
+ assert.deepEqual(result, {
723
+ idToken: 'id-1',
724
+ email: 'user@example.com',
725
+ error: undefined,
726
+ state: {popUpSignIn: true, mode: 'meeting'},
727
+ });
728
+ // Stored token consumed
729
+ assert.calledWith(webex.getWindow().sessionStorage.removeItem, 'oauth2-csrf-token');
730
+ // URL scrubbed: no id_token/email/csrf_token left, residual state re-encoded.
731
+ assert.called(webex.getWindow().history.replaceState);
732
+ const cleanedQuery = parseLocationQuery(webex);
733
+
734
+ assert.notProperty(cleanedQuery, 'id_token');
735
+ assert.notProperty(cleanedQuery, 'email');
736
+ assert.deepEqual(JSON.parse(base64.decode(cleanedQuery.state)), {
737
+ popUpSignIn: true,
738
+ mode: 'meeting',
739
+ });
740
+ });
741
+
742
+ it('throws when no stored CSRF token (treated as CSRF failure)', () => {
743
+ const search = `?id_token=id-1&state=${buildState({csrf_token: 'csrf-abc'})}`;
744
+ const webex = makeWebex(`http://example.com/${search}`);
745
+ webex.getWindow().sessionStorage.getItem = sinon.stub().returns(null);
746
+
747
+ assert.throws(
748
+ () => webex.authorization.handleThirdPartyCallback(),
749
+ /CSRF token missing from session storage/
750
+ );
751
+ });
752
+
753
+ it('throws when state.csrf_token does not match stored token', () => {
754
+ const search = `?id_token=id-1&state=${buildState({csrf_token: 'attacker'})}`;
755
+ const webex = makeWebex(`http://example.com/${search}`, 'real-token');
756
+ webex.getWindow().sessionStorage.getItem = sinon.stub().returns('real-token');
757
+
758
+ assert.throws(
759
+ () => webex.authorization.handleThirdPartyCallback(),
760
+ /CSRF token attacker does not match stored token real-token/
761
+ );
762
+ });
763
+
764
+ it('throws the native decode error (not a wrapped message) when state is malformed', () => {
765
+ // base64('not json') decodes to 'not json' which is not valid JSON
766
+ const search = `?id_token=id-1&state=bm90IGpzb24%3D`;
767
+ const webex = makeWebex(`http://example.com/${search}`, 'csrf-abc');
768
+ webex.getWindow().sessionStorage.getItem = sinon.stub().returns('csrf-abc');
769
+
770
+ // _verifySecurityToken is never reached because decode throws first;
771
+ // surface the underlying SyntaxError untouched.
772
+ assert.throws(() => webex.authorization.handleThirdPartyCallback(), SyntaxError);
773
+ });
774
+
775
+ it('throws on error responses that lack a valid state (still verifies CSRF)', () => {
776
+ // Social provider returned ?error=... without echoing state. We must
777
+ // not silently treat this as a successful callback.
778
+ const search = `?error=FailedToCallOAuthProvider`;
779
+ const webex = makeWebex('http://example.com', 'csrf-abc');
780
+ webex.getWindow().location.href = `http://example.com/${search}`;
781
+ webex.getWindow().sessionStorage.getItem = sinon.stub().returns('csrf-abc');
782
+
783
+ assert.throws(
784
+ () => webex.authorization.handleThirdPartyCallback(),
785
+ /Expected CSRF token csrf-abc, but not found in redirect query/
786
+ );
787
+ });
788
+
789
+ it('throws CSRF error when location has no query params at all', () => {
790
+ const webex = makeWebex('http://example.com/', 'csrf-abc');
791
+ webex.getWindow().sessionStorage.getItem = sinon.stub().returns('csrf-abc');
792
+
793
+ assert.throws(
794
+ () => webex.authorization.handleThirdPartyCallback(),
795
+ /Expected CSRF token csrf-abc, but not found in redirect query/
796
+ );
797
+ });
798
+
799
+ it('throws when state is absent even if no stored CSRF token exists (requireMatch)', () => {
800
+ const webex = makeWebex('http://example.com/?id_token=id-1');
801
+ webex.getWindow().sessionStorage.getItem = sinon.stub().returns(null);
802
+
803
+ assert.throws(
804
+ () => webex.authorization.handleThirdPartyCallback(),
805
+ /CSRF token missing from session storage/
806
+ );
807
+ });
808
+
809
+ it('returns error value in the result and preserves non-csrf state', () => {
810
+ const storedToken = 'csrf-abc';
811
+ const search = `?error=FailedToCallOAuthProvider&state=${buildState({
812
+ csrf_token: storedToken,
813
+ popUpSignIn: true,
814
+ })}`;
815
+ const webex = makeWebex('http://example.com', storedToken);
816
+ webex.getWindow().location.href = `http://example.com/${search}`;
817
+ webex.getWindow().sessionStorage.getItem = sinon.stub().returns(storedToken);
818
+
819
+ const result = webex.authorization.handleThirdPartyCallback();
820
+
821
+ assert.deepEqual(result, {
822
+ idToken: undefined,
823
+ email: undefined,
824
+ error: 'FailedToCallOAuthProvider',
825
+ state: {popUpSignIn: true},
826
+ });
827
+ // `error` is non-sensitive; not scrubbed from the URL.
828
+ assert.equal(parseLocationQuery(webex).error, 'FailedToCallOAuthProvider');
829
+ });
830
+ });
831
+
563
832
  describe('#_generateQRCodeVerificationUrl()', () => {
564
833
  it('should generate a QR code URL when a userCode is present', () => {
565
834
  const verificationUrl = 'https://example.com/verify?userCode=123456';
566
835
  const oauthHelperUrl = 'https://oauth-helper-a.wbx2.com/helperservice/v1';
567
- const expectedUrl = 'https://web.webex.com/deviceAuth?usercode=123456&oauthhelper=https%3A%2F%2Foauth-helper-a.wbx2.com%2Fhelperservice%2Fv1';
836
+ const expectedUrl =
837
+ 'https://web.webex.com/deviceAuth?usercode=123456&oauthhelper=https%3A%2F%2Foauth-helper-a.wbx2.com%2Fhelperservice%2Fv1';
568
838
 
569
839
  const webex = makeWebex('http://example.com');
570
840
 
@@ -1027,7 +1297,33 @@ describe('plugin-authorization-browser-first-party', () => {
1027
1297
 
1028
1298
  assert.isDefined(href);
1029
1299
  assert.equal(href, `?state=${base64.encode(JSON.stringify({key: 'value'}))}`);
1030
- assert.notInclude(href, 'csrf_token');
1300
+ });
1301
+
1302
+ it('strips id_token and email from the query string', () => {
1303
+ const webex = makeWebex(undefined, undefined, {
1304
+ credentials: {
1305
+ clientType: 'confidential',
1306
+ },
1307
+ });
1308
+ const location = {
1309
+ query: {
1310
+ code: 'code',
1311
+ id_token: 'id-token-value',
1312
+ email: 'user@example.com',
1313
+ state: {
1314
+ csrf_token: 'token',
1315
+ key: 'value',
1316
+ },
1317
+ },
1318
+ };
1319
+
1320
+ webex.authorization._cleanUrl(location);
1321
+ assert.called(webex.getWindow().history.replaceState);
1322
+ const {href} = webex.getWindow().location;
1323
+
1324
+ assert.notInclude(href, 'id_token');
1325
+ assert.notInclude(href, 'email');
1326
+ assert.notInclude(href, 'code');
1031
1327
  });
1032
1328
  });
1033
1329