@webex/plugin-authorization-browser-first-party 3.11.0 → 3.12.0-llmrefactor.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +38 -7
- package/dist/authorization.js +170 -15
- package/dist/authorization.js.map +1 -1
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/package.json +13 -13
- package/src/authorization.js +176 -21
- package/src/index.js +1 -1
- package/test/unit/spec/authorization.js +447 -67
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["_webexCore","require","_authorization","_interopRequireWildcard","_config","_interopRequireDefault","e","t","_WeakMap","r","n","__esModule","o","i","f","__proto__","default","_typeof","has","get","set","_t","hasOwnProperty","call","_Object$defineProperty","_Object$getOwnPropertyDescriptor","proxies","registerPlugin","Authorization","config"],"sources":["index.js"],"sourcesContent":["/*!\n * Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.\n */\n\nimport {registerPlugin} from '@webex/webex-core';\n\nimport Authorization from './authorization';\nimport config from './config';\n\nconst proxies = ['isAuthorizing', 'isAuthenticating'];\n\nregisterPlugin('authorization', Authorization, {\n config,\n proxies,\n});\n\nexport {default, Events} from './authorization';\nexport {default as config} from './config';\n"],"mappings":"
|
|
1
|
+
{"version":3,"names":["_webexCore","require","_authorization","_interopRequireWildcard","_config","_interopRequireDefault","e","t","_WeakMap","r","n","__esModule","o","i","f","__proto__","default","_typeof","has","get","set","_t","hasOwnProperty","call","_Object$defineProperty","_Object$getOwnPropertyDescriptor","proxies","registerPlugin","Authorization","config"],"sources":["index.js"],"sourcesContent":["/*!\n * Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.\n */\n\nimport {registerPlugin} from '@webex/webex-core';\n\nimport Authorization from './authorization';\nimport config from './config';\n\nconst proxies = ['isAuthorizing', 'isAuthenticating'];\n\nregisterPlugin('authorization', Authorization, {\n config,\n proxies,\n});\n\nexport {default, Events, InitialAuthorizationCodeGrantOutcomes} from './authorization';\nexport {default as config} from './config';\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAIA,IAAAA,UAAA,GAAAC,OAAA;AAEA,IAAAC,cAAA,GAAAC,uBAAA,CAAAF,OAAA;AACA,IAAAG,OAAA,GAAAC,sBAAA,CAAAJ,OAAA;AAA8B,SAAAE,wBAAAG,CAAA,EAAAC,CAAA,6BAAAC,QAAA,MAAAC,CAAA,OAAAD,QAAA,IAAAE,CAAA,OAAAF,QAAA,YAAAL,uBAAA,YAAAA,wBAAAG,CAAA,EAAAC,CAAA,SAAAA,CAAA,IAAAD,CAAA,IAAAA,CAAA,CAAAK,UAAA,SAAAL,CAAA,MAAAM,CAAA,EAAAC,CAAA,EAAAC,CAAA,KAAAC,SAAA,QAAAC,OAAA,EAAAV,CAAA,iBAAAA,CAAA,gBAAAW,OAAA,CAAAX,CAAA,0BAAAA,CAAA,SAAAQ,CAAA,MAAAF,CAAA,GAAAL,CAAA,GAAAG,CAAA,GAAAD,CAAA,QAAAG,CAAA,CAAAM,GAAA,CAAAZ,CAAA,UAAAM,CAAA,CAAAO,GAAA,CAAAb,CAAA,GAAAM,CAAA,CAAAQ,GAAA,CAAAd,CAAA,EAAAQ,CAAA,cAAAO,EAAA,IAAAf,CAAA,gBAAAe,EAAA,OAAAC,cAAA,CAAAC,IAAA,CAAAjB,CAAA,EAAAe,EAAA,OAAAR,CAAA,IAAAD,CAAA,GAAAY,sBAAA,KAAAC,gCAAA,CAAAnB,CAAA,EAAAe,EAAA,OAAAR,CAAA,CAAAM,GAAA,IAAAN,CAAA,CAAAO,GAAA,IAAAR,CAAA,CAAAE,CAAA,EAAAO,EAAA,EAAAR,CAAA,IAAAC,CAAA,CAAAO,EAAA,IAAAf,CAAA,CAAAe,EAAA,WAAAP,CAAA,KAAAR,CAAA,EAAAC,CAAA;AAP9B;AACA;AACA;;AAOA,IAAMmB,OAAO,GAAG,CAAC,eAAe,EAAE,kBAAkB,CAAC;AAErD,IAAAC,yBAAc,EAAC,eAAe,EAAEC,sBAAa,EAAE;EAC7CC,MAAM,EAANA,eAAM;EACNH,OAAO,EAAPA;AACF,CAAC,CAAC","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.
|
|
30
|
-
"@webex/test-helper-mocha": "3.
|
|
31
|
-
"@webex/test-helper-mock-webex": "3.
|
|
32
|
-
"@webex/test-helper-test-users": "3.
|
|
29
|
+
"@webex/test-helper-chai": "3.12.0-llmrefactor.1",
|
|
30
|
+
"@webex/test-helper-mocha": "3.12.0-llmrefactor.1",
|
|
31
|
+
"@webex/test-helper-mock-webex": "3.12.0-llmrefactor.1",
|
|
32
|
+
"@webex/test-helper-test-users": "3.12.0-llmrefactor.1",
|
|
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.
|
|
39
|
-
"@webex/storage-adapter-local-storage": "3.
|
|
40
|
-
"@webex/test-helper-automation": "3.
|
|
41
|
-
"@webex/test-helper-chai": "3.
|
|
42
|
-
"@webex/test-helper-mocha": "3.
|
|
43
|
-
"@webex/test-helper-mock-webex": "3.
|
|
44
|
-
"@webex/test-helper-test-users": "3.
|
|
45
|
-
"@webex/webex-core": "3.
|
|
38
|
+
"@webex/common": "3.12.0-llmrefactor.1",
|
|
39
|
+
"@webex/storage-adapter-local-storage": "3.12.0-llmrefactor.1",
|
|
40
|
+
"@webex/test-helper-automation": "3.12.0-llmrefactor.1",
|
|
41
|
+
"@webex/test-helper-chai": "3.12.0-llmrefactor.1",
|
|
42
|
+
"@webex/test-helper-mocha": "3.12.0-llmrefactor.1",
|
|
43
|
+
"@webex/test-helper-mock-webex": "3.12.0-llmrefactor.1",
|
|
44
|
+
"@webex/test-helper-test-users": "3.12.0-llmrefactor.1",
|
|
45
|
+
"@webex/webex-core": "3.12.0-llmrefactor.1",
|
|
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.
|
|
59
|
+
"version": "3.12.0-llmrefactor.1"
|
|
60
60
|
}
|
package/src/authorization.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
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 {
|
|
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';
|
|
@@ -39,6 +39,18 @@ export const Events = {
|
|
|
39
39
|
qRCodeLogin: 'qRCodeLogin',
|
|
40
40
|
};
|
|
41
41
|
|
|
42
|
+
/**
|
|
43
|
+
* Terminal outcomes for the automatic authorization-code exchange performed
|
|
44
|
+
* during authorization plugin initialization.
|
|
45
|
+
*
|
|
46
|
+
* @enum {string}
|
|
47
|
+
*/
|
|
48
|
+
export const InitialAuthorizationCodeGrantOutcomes = {
|
|
49
|
+
failure: 'failure',
|
|
50
|
+
notAttempted: 'not_attempted',
|
|
51
|
+
success: 'success',
|
|
52
|
+
};
|
|
53
|
+
|
|
42
54
|
/**
|
|
43
55
|
* Browser support for OAuth2 for first-party (Webex Web Client) usage.
|
|
44
56
|
*
|
|
@@ -69,6 +81,37 @@ export const Events = {
|
|
|
69
81
|
*/
|
|
70
82
|
const Authorization = WebexPlugin.extend({
|
|
71
83
|
derived: {
|
|
84
|
+
/**
|
|
85
|
+
* Retains the terminal outcome of the automatic authorization-code exchange
|
|
86
|
+
* performed during this authorization plugin instance's initialization.
|
|
87
|
+
*
|
|
88
|
+
* This historical value does not represent current authorization state,
|
|
89
|
+
* credentials hydrated from storage, guest authentication, or
|
|
90
|
+
* authorization-code exchanges requested later on the same SDK instance. It
|
|
91
|
+
* is not reset by logout. OAuth redirect errors and CSRF validation failures
|
|
92
|
+
* occur before the exchange, so the value remains not_attempted; these errors
|
|
93
|
+
* can throw before ready becomes true.
|
|
94
|
+
*
|
|
95
|
+
* Calling logout({noRedirect: true}) does not cancel an initialization
|
|
96
|
+
* exchange already in flight. If that exchange later settles, it can still
|
|
97
|
+
* update credentials and this retained outcome.
|
|
98
|
+
*
|
|
99
|
+
* Interpret only after authorization readiness:
|
|
100
|
+
* - not_attempted: initialization did not invoke requestAuthorizationCodeGrant()
|
|
101
|
+
* - success: the initialization exchange fulfilled
|
|
102
|
+
* - failure: the initialization exchange threw or rejected
|
|
103
|
+
*
|
|
104
|
+
* @instance
|
|
105
|
+
* @memberof AuthorizationBrowserFirstParty
|
|
106
|
+
* @readonly
|
|
107
|
+
* @type {string}
|
|
108
|
+
*/
|
|
109
|
+
initialAuthorizationCodeGrantOutcome: {
|
|
110
|
+
deps: ['_initialAuthorizationCodeGrantOutcome'],
|
|
111
|
+
fn() {
|
|
112
|
+
return this._initialAuthorizationCodeGrantOutcome;
|
|
113
|
+
},
|
|
114
|
+
},
|
|
72
115
|
/**
|
|
73
116
|
* Alias of {@link AuthorizationBrowserFirstParty#isAuthorizing}
|
|
74
117
|
* @instance
|
|
@@ -94,6 +137,14 @@ const Authorization = WebexPlugin.extend({
|
|
|
94
137
|
default: false,
|
|
95
138
|
type: 'boolean',
|
|
96
139
|
},
|
|
140
|
+
/**
|
|
141
|
+
* Internal backing state for the initial authorization-code grant outcome.
|
|
142
|
+
* @private
|
|
143
|
+
*/
|
|
144
|
+
_initialAuthorizationCodeGrantOutcome: {
|
|
145
|
+
default: InitialAuthorizationCodeGrantOutcomes.notAttempted,
|
|
146
|
+
type: 'string',
|
|
147
|
+
},
|
|
97
148
|
/**
|
|
98
149
|
* Indicates that the plugin has finished any automatic startup
|
|
99
150
|
* processing (e.g., exchanging a returned authorization code)
|
|
@@ -199,7 +250,7 @@ const Authorization = WebexPlugin.extend({
|
|
|
199
250
|
|
|
200
251
|
// Decode and parse state object (if present)
|
|
201
252
|
if (location.query.state) {
|
|
202
|
-
location.query.state =
|
|
253
|
+
location.query.state = decodeState(location.query.state);
|
|
203
254
|
} else {
|
|
204
255
|
location.query.state = {};
|
|
205
256
|
}
|
|
@@ -233,7 +284,13 @@ const Authorization = WebexPlugin.extend({
|
|
|
233
284
|
.collectPreauthCatalog(preauthCatalogParams)
|
|
234
285
|
.catch(() => Promise.resolve()) // Non-fatal if catalog collection fails
|
|
235
286
|
.then(() => this.requestAuthorizationCodeGrant({code, codeVerifier}))
|
|
287
|
+
.then(() => {
|
|
288
|
+
this._initialAuthorizationCodeGrantOutcome =
|
|
289
|
+
InitialAuthorizationCodeGrantOutcomes.success;
|
|
290
|
+
})
|
|
236
291
|
.catch((error) => {
|
|
292
|
+
this._initialAuthorizationCodeGrantOutcome =
|
|
293
|
+
InitialAuthorizationCodeGrantOutcomes.failure;
|
|
237
294
|
this.logger.warn('authorization: failed initial authorization code grant request', error);
|
|
238
295
|
})
|
|
239
296
|
.then(() => {
|
|
@@ -267,7 +324,7 @@ const Authorization = WebexPlugin.extend({
|
|
|
267
324
|
eventType: 'initiateLogin',
|
|
268
325
|
data: {
|
|
269
326
|
hasEmail: !!options.email,
|
|
270
|
-
hasState: !!options.state
|
|
327
|
+
hasState: !!options.state,
|
|
271
328
|
},
|
|
272
329
|
});
|
|
273
330
|
|
|
@@ -316,7 +373,7 @@ const Authorization = WebexPlugin.extend({
|
|
|
316
373
|
|
|
317
374
|
this.eventEmitter.emit(Events.login, {
|
|
318
375
|
eventType: 'redirectToLoginUrl',
|
|
319
|
-
data: {
|
|
376
|
+
data: {loginUrl},
|
|
320
377
|
});
|
|
321
378
|
|
|
322
379
|
if (options?.separateWindow) {
|
|
@@ -343,6 +400,97 @@ const Authorization = WebexPlugin.extend({
|
|
|
343
400
|
return Promise.resolve();
|
|
344
401
|
},
|
|
345
402
|
|
|
403
|
+
/**
|
|
404
|
+
* Initiates third-party (social provider) login. Generates a CSRF token,
|
|
405
|
+
* embeds it in `options.state.csrf_token`, and delegates to
|
|
406
|
+
* `initiateThirdPartyLoginRedirect` for navigation.
|
|
407
|
+
*
|
|
408
|
+
* @instance
|
|
409
|
+
* @memberof AuthorizationBrowserFirstParty
|
|
410
|
+
* @param {Object} options
|
|
411
|
+
* @param {string} options.oauth2provider
|
|
412
|
+
* @param {string} options.returnURL
|
|
413
|
+
* @param {Object} [options.state] - Caller-supplied state object. Merged
|
|
414
|
+
* with the generated `csrf_token`.
|
|
415
|
+
* @returns {Promise<void>}
|
|
416
|
+
*/
|
|
417
|
+
initiateThirdPartyLogin(options = {}) {
|
|
418
|
+
options = cloneDeep(options);
|
|
419
|
+
if (options.state !== undefined && !isObject(options.state)) {
|
|
420
|
+
throw new Error('if specified, `options.state` must be an object');
|
|
421
|
+
}
|
|
422
|
+
options.state = options.state || {};
|
|
423
|
+
options.state.csrf_token = this._generateSecurityToken();
|
|
424
|
+
|
|
425
|
+
return this.initiateThirdPartyLoginRedirect(options);
|
|
426
|
+
},
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* Performs the navigation step of the third-party login flow. Builds the
|
|
430
|
+
* IdBroker URL via `Credentials#buildThirdPartyLoginUrl` and assigns it
|
|
431
|
+
* to `getWindow().location`.
|
|
432
|
+
*
|
|
433
|
+
* Mirrors `initiateAuthorizationCodeGrant` for the `/authorize` flow.
|
|
434
|
+
* Consumers may override this method for custom navigation handling
|
|
435
|
+
* (e.g. postMessage in iframed contexts).
|
|
436
|
+
*
|
|
437
|
+
* @instance
|
|
438
|
+
* @memberof AuthorizationBrowserFirstParty
|
|
439
|
+
* @param {Object} options
|
|
440
|
+
* @param {string} options.oauth2provider
|
|
441
|
+
* @param {string} options.returnURL
|
|
442
|
+
* @returns {Promise<void>}
|
|
443
|
+
*/
|
|
444
|
+
initiateThirdPartyLoginRedirect(options = {}) {
|
|
445
|
+
this.logger.info('authorization: initiating third-party login redirect');
|
|
446
|
+
|
|
447
|
+
try {
|
|
448
|
+
const url = this.webex.credentials.buildThirdPartyLoginUrl(options);
|
|
449
|
+
|
|
450
|
+
this.webex.getWindow().location = url;
|
|
451
|
+
} catch (err) {
|
|
452
|
+
return Promise.reject(err);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
return Promise.resolve();
|
|
456
|
+
},
|
|
457
|
+
|
|
458
|
+
/**
|
|
459
|
+
* Handles the third-party (social provider) login callback. Reads the
|
|
460
|
+
* current `window.location`, decodes `state`, validates the CSRF token
|
|
461
|
+
* (`state.csrf_token`), scrubs sensitive parameters from the URL via
|
|
462
|
+
* `_cleanUrl`, and returns the parsed payload.
|
|
463
|
+
*
|
|
464
|
+
* Mirrors `initialize()` in always operating on the live
|
|
465
|
+
* `window.location`
|
|
466
|
+
*
|
|
467
|
+
* `idToken` is single-use: it is parsed out of the URL exactly once and
|
|
468
|
+
* the calling client is expected to exchange it (or discard it)
|
|
469
|
+
* immediately. The returned `state` has `csrf_token` removed.
|
|
470
|
+
*
|
|
471
|
+
* @instance
|
|
472
|
+
* @memberof AuthorizationBrowserFirstParty
|
|
473
|
+
* @returns {{idToken: string|undefined, email: string|undefined,
|
|
474
|
+
* error: string|undefined, state: Object}}
|
|
475
|
+
*/
|
|
476
|
+
handleThirdPartyCallback() {
|
|
477
|
+
const location = url.parse(this.webex.getWindow().location.href, true);
|
|
478
|
+
|
|
479
|
+
location.query.state = decodeState(location.query.state || 'e30');
|
|
480
|
+
|
|
481
|
+
this._verifySecurityToken(location.query, {requireMatch: true});
|
|
482
|
+
this._cleanUrl(location);
|
|
483
|
+
|
|
484
|
+
const {
|
|
485
|
+
id_token: idToken,
|
|
486
|
+
email,
|
|
487
|
+
error,
|
|
488
|
+
state: {csrf_token, ...state},
|
|
489
|
+
} = location.query;
|
|
490
|
+
|
|
491
|
+
return {idToken, email, error, state};
|
|
492
|
+
},
|
|
493
|
+
|
|
346
494
|
/**
|
|
347
495
|
* Called by {@link WebexCore#logout()}.
|
|
348
496
|
* Constructs logout URL and (unless suppressed) navigates away to ensure
|
|
@@ -497,10 +645,11 @@ const Authorization = WebexPlugin.extend({
|
|
|
497
645
|
})
|
|
498
646
|
.then((res) => {
|
|
499
647
|
const {user_code, verification_uri, verification_uri_complete} = res.body;
|
|
500
|
-
const verificationUriComplete =
|
|
648
|
+
const verificationUriComplete =
|
|
649
|
+
this._generateQRCodeVerificationUrl(verification_uri_complete);
|
|
501
650
|
this.eventEmitter.emit(Events.qRCodeLogin, {
|
|
502
651
|
eventType: 'getUserCodeSuccess',
|
|
503
|
-
|
|
652
|
+
userData: {
|
|
504
653
|
userCode: user_code,
|
|
505
654
|
verificationUri: verification_uri,
|
|
506
655
|
verificationUriComplete,
|
|
@@ -591,7 +740,7 @@ const Authorization = WebexPlugin.extend({
|
|
|
591
740
|
// If polling canceled (id changed), ignore this response
|
|
592
741
|
if (this.currentPollingId !== this.pollingId) return;
|
|
593
742
|
|
|
594
|
-
|
|
743
|
+
this.eventEmitter.emit(Events.qRCodeLogin, {
|
|
595
744
|
eventType: 'authorizationSuccess',
|
|
596
745
|
data: res.body,
|
|
597
746
|
});
|
|
@@ -703,8 +852,9 @@ const Authorization = WebexPlugin.extend({
|
|
|
703
852
|
* - HTTP referrer headers to third-party content
|
|
704
853
|
*
|
|
705
854
|
* Approach:
|
|
706
|
-
* - Remove 'code'
|
|
707
|
-
*
|
|
855
|
+
* - Remove 'code' (OAuth code-grant), 'id_token', and 'email'
|
|
856
|
+
* (third-party callback).
|
|
857
|
+
* - Remove 'state' entirely if it only contained csrf_token.
|
|
708
858
|
* - Else, re-encode remaining state fields (minus csrf_token).
|
|
709
859
|
* - Replace current history entry (no page reload).
|
|
710
860
|
*
|
|
@@ -718,12 +868,12 @@ const Authorization = WebexPlugin.extend({
|
|
|
718
868
|
location = cloneDeep(location);
|
|
719
869
|
if (this.webex.getWindow().history && this.webex.getWindow().history.replaceState) {
|
|
720
870
|
Reflect.deleteProperty(location.query, 'code');
|
|
871
|
+
Reflect.deleteProperty(location.query, 'id_token');
|
|
872
|
+
Reflect.deleteProperty(location.query, 'email');
|
|
721
873
|
if (isEmpty(omit(location.query.state, 'csrf_token'))) {
|
|
722
874
|
Reflect.deleteProperty(location.query, 'state');
|
|
723
875
|
} else {
|
|
724
|
-
location.query.state =
|
|
725
|
-
JSON.stringify(omit(location.query.state, 'csrf_token'))
|
|
726
|
-
);
|
|
876
|
+
location.query.state = encodeState(omit(location.query.state, 'csrf_token'));
|
|
727
877
|
}
|
|
728
878
|
location.search = querystring.stringify(location.query);
|
|
729
879
|
Reflect.deleteProperty(location, 'query');
|
|
@@ -792,27 +942,32 @@ const Authorization = WebexPlugin.extend({
|
|
|
792
942
|
* - Ensure state + state.csrf_token exist.
|
|
793
943
|
* - Compare values; throw descriptive errors on mismatch / absence.
|
|
794
944
|
*
|
|
795
|
-
* If no stored token (e.g., user navigated directly), silently returns
|
|
945
|
+
* If no stored token (e.g., user navigated directly), silently returns
|
|
946
|
+
* unless `options.requireMatch` is `true`, in which case absence of a
|
|
947
|
+
* stored token is treated as a CSRF failure.
|
|
796
948
|
*
|
|
797
949
|
* @instance
|
|
798
950
|
* @memberof AuthorizationBrowserFirstParty
|
|
799
951
|
* @param {Object} query - Parsed query (location.query)
|
|
952
|
+
* @param {Object} [options]
|
|
953
|
+
* @param {boolean} [options.requireMatch=false] - When true, throws if
|
|
954
|
+
* no stored sessionToken is present.
|
|
800
955
|
* @private
|
|
801
956
|
* @returns {void}
|
|
802
957
|
*/
|
|
803
|
-
_verifySecurityToken(query) {
|
|
958
|
+
_verifySecurityToken(query, options = {}) {
|
|
804
959
|
const sessionToken = this.webex.getWindow().sessionStorage.getItem(OAUTH2_CSRF_TOKEN);
|
|
805
960
|
|
|
806
961
|
this.webex.getWindow().sessionStorage.removeItem(OAUTH2_CSRF_TOKEN);
|
|
807
962
|
if (!sessionToken) {
|
|
808
|
-
|
|
809
|
-
|
|
963
|
+
if (options.requireMatch) {
|
|
964
|
+
throw new Error('CSRF token missing from session storage');
|
|
965
|
+
}
|
|
810
966
|
|
|
811
|
-
|
|
812
|
-
throw new Error(`Expected CSRF token ${sessionToken}, but not found in redirect query`);
|
|
967
|
+
return;
|
|
813
968
|
}
|
|
814
969
|
|
|
815
|
-
if (!query.state
|
|
970
|
+
if (!query.state?.csrf_token) {
|
|
816
971
|
throw new Error(`Expected CSRF token ${sessionToken}, but not found in redirect query`);
|
|
817
972
|
}
|
|
818
973
|
|
package/src/index.js
CHANGED