@webex/plugin-authorization-browser-first-party 1.144.2 → 1.145.0
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/dist/authorization.js +49 -8
- package/dist/authorization.js.map +1 -1
- package/package.json +4 -3
- package/src/authorization.js +55 -7
- package/test/unit/spec/authorization.js +103 -5
package/dist/authorization.js
CHANGED
|
@@ -40,9 +40,18 @@ var _webexCore = require("@webex/webex-core");
|
|
|
40
40
|
|
|
41
41
|
var _uuid = _interopRequireDefault(require("uuid"));
|
|
42
42
|
|
|
43
|
+
var _encBase64url = _interopRequireDefault(require("crypto-js/enc-base64url"));
|
|
44
|
+
|
|
45
|
+
var _cryptoJs = _interopRequireDefault(require("crypto-js"));
|
|
46
|
+
|
|
43
47
|
var _dec, _dec2, _obj;
|
|
44
48
|
|
|
49
|
+
// Necessary to require lodash this way in order to stub
|
|
50
|
+
// methods in the unit test
|
|
51
|
+
var lodash = require('lodash');
|
|
52
|
+
|
|
45
53
|
var OAUTH2_CSRF_TOKEN = 'oauth2-csrf-token';
|
|
54
|
+
var OAUTH2_CODE_VERIFIER = 'oauth2-code-verifier';
|
|
46
55
|
/**
|
|
47
56
|
* Browser support for OAuth2. Automatically parses the URL query for an
|
|
48
57
|
* authorization code
|
|
@@ -121,6 +130,8 @@ var Authorization = _webexCore.WebexPlugin.extend((_dec = (0, _common.whileInFli
|
|
|
121
130
|
location.query.state = {};
|
|
122
131
|
}
|
|
123
132
|
|
|
133
|
+
var codeVerifier = this.webex.getWindow().sessionStorage.getItem(OAUTH2_CODE_VERIFIER);
|
|
134
|
+
this.webex.getWindow().sessionStorage.removeItem(OAUTH2_CODE_VERIFIER);
|
|
124
135
|
var email = location.query.state.email;
|
|
125
136
|
|
|
126
137
|
this._verifySecurityToken(location.query);
|
|
@@ -135,7 +146,8 @@ var Authorization = _webexCore.WebexPlugin.extend((_dec = (0, _common.whileInFli
|
|
|
135
146
|
return _promise.default.resolve();
|
|
136
147
|
}).then(function () {
|
|
137
148
|
return _this.requestAuthorizationCodeGrant({
|
|
138
|
-
code: code
|
|
149
|
+
code: code,
|
|
150
|
+
codeVerifier: codeVerifier
|
|
139
151
|
});
|
|
140
152
|
}).then(function () {
|
|
141
153
|
_this.ready = true;
|
|
@@ -156,6 +168,8 @@ var Authorization = _webexCore.WebexPlugin.extend((_dec = (0, _common.whileInFli
|
|
|
156
168
|
options.state = options.state || {};
|
|
157
169
|
options.state.csrf_token = this._generateSecurityToken();
|
|
158
170
|
options.state.email = options.email;
|
|
171
|
+
options.code_challenge = this._generateCodeChallenge();
|
|
172
|
+
options.code_challenge_method = 'S256';
|
|
159
173
|
return this.initiateAuthorizationCodeGrant(options);
|
|
160
174
|
},
|
|
161
175
|
|
|
@@ -209,15 +223,21 @@ var Authorization = _webexCore.WebexPlugin.extend((_dec = (0, _common.whileInFli
|
|
|
209
223
|
return _promise.default.reject(new Error('`options.code` is required'));
|
|
210
224
|
}
|
|
211
225
|
|
|
226
|
+
var form = {
|
|
227
|
+
grant_type: 'authorization_code',
|
|
228
|
+
redirect_uri: this.config.redirect_uri,
|
|
229
|
+
code: options.code,
|
|
230
|
+
self_contained_token: true
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
if (options.codeVerifier) {
|
|
234
|
+
form.code_verifier = options.codeVerifier;
|
|
235
|
+
}
|
|
236
|
+
|
|
212
237
|
return this.webex.request({
|
|
213
238
|
method: 'POST',
|
|
214
239
|
uri: this.config.tokenUrl,
|
|
215
|
-
form:
|
|
216
|
-
grant_type: 'authorization_code',
|
|
217
|
-
redirect_uri: this.config.redirect_uri,
|
|
218
|
-
code: options.code,
|
|
219
|
-
self_contained_token: true
|
|
220
|
-
},
|
|
240
|
+
form: form,
|
|
221
241
|
auth: {
|
|
222
242
|
user: this.config.client_id,
|
|
223
243
|
pass: this.config.client_secret,
|
|
@@ -283,6 +303,27 @@ var Authorization = _webexCore.WebexPlugin.extend((_dec = (0, _common.whileInFli
|
|
|
283
303
|
}
|
|
284
304
|
},
|
|
285
305
|
|
|
306
|
+
/**
|
|
307
|
+
* Generates PKCE code verifier and code challenge and sets the the code verifier in sessionStorage
|
|
308
|
+
* @instance
|
|
309
|
+
* @memberof AuthorizationBrowserFirstParty
|
|
310
|
+
* @private
|
|
311
|
+
* @returns {string}
|
|
312
|
+
*/
|
|
313
|
+
_generateCodeChallenge: function _generateCodeChallenge() {
|
|
314
|
+
this.logger.info('authorization: generating PKCE code challenge'); // eslint-disable-next-line no-underscore-dangle
|
|
315
|
+
|
|
316
|
+
var safeCharacterMap = _encBase64url.default._safe_map;
|
|
317
|
+
var codeVerifier = lodash.times(128, function () {
|
|
318
|
+
return safeCharacterMap[lodash.random(0, safeCharacterMap.length - 1)];
|
|
319
|
+
}).join('');
|
|
320
|
+
|
|
321
|
+
var codeChallenge = _cryptoJs.default.SHA256(codeVerifier).toString(_encBase64url.default);
|
|
322
|
+
|
|
323
|
+
this.webex.getWindow().sessionStorage.setItem(OAUTH2_CODE_VERIFIER, codeVerifier);
|
|
324
|
+
return codeChallenge;
|
|
325
|
+
},
|
|
326
|
+
|
|
286
327
|
/**
|
|
287
328
|
* Generates a CSRF token and sticks in in sessionStorage
|
|
288
329
|
* @instance
|
|
@@ -330,7 +371,7 @@ var Authorization = _webexCore.WebexPlugin.extend((_dec = (0, _common.whileInFli
|
|
|
330
371
|
throw new Error("CSRF token ".concat(token, " does not match stored token ").concat(sessionToken));
|
|
331
372
|
}
|
|
332
373
|
},
|
|
333
|
-
version: "1.
|
|
374
|
+
version: "1.145.0"
|
|
334
375
|
}, ((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)));
|
|
335
376
|
|
|
336
377
|
var _default = Authorization;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["authorization.js"],"names":["OAUTH2_CSRF_TOKEN","Authorization","WebexPlugin","extend","derived","isAuthenticating","deps","fn","isAuthorizing","session","default","type","ready","namespace","initialize","attrs","ret","prototype","location","url","parse","webex","getWindow","href","_checkForErrors","code","query","state","JSON","base64","decode","email","_verifySecurityToken","_cleanUrl","process","nextTick","internal","services","collectPreauthCatalog","catch","resolve","then","requestAuthorizationCodeGrant","initiateLogin","options","csrf_token","_generateSecurityToken","initiateAuthorizationCodeGrant","logger","info","credentials","buildLoginUrl","response_type","logout","noRedirect","buildLogoutUrl","reject","Error","request","method","uri","config","tokenUrl","form","grant_type","redirect_uri","self_contained_token","auth","user","client_id","pass","client_secret","sendImmediately","shouldRefreshAccessToken","res","set","supertoken","body","statusCode","ErrorConstructor","grantErrors","select","error","_res","history","replaceState","encode","search","querystring","stringify","format","token","uuid","v4","sessionStorage","setItem","sessionToken","getItem","removeItem","oneFlight"],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.\n */\n\n/* eslint camelcase: [0] */\n\nimport querystring from 'querystring';\nimport url from 'url';\n\nimport {base64, oneFlight, whileInFlight} from '@webex/common';\nimport {grantErrors, WebexPlugin} from '@webex/webex-core';\nimport {cloneDeep, isEmpty, omit} from 'lodash';\nimport uuid from 'uuid';\n\nconst OAUTH2_CSRF_TOKEN = 'oauth2-csrf-token';\n\n/**\n * Browser support for OAuth2. Automatically parses the URL query for an\n * authorization code\n *\n * Use of this plugin for anything other than the Webex Web Client is strongly\n * discouraged and may be broken at any time\n * @class\n * @name AuthorizationBrowserFirstParty\n * @private\n */\nconst Authorization = WebexPlugin.extend({\n derived: {\n /**\n * Alias of {@link AuthorizationBrowserFirstParty#isAuthorizing}\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @type {boolean}\n */\n isAuthenticating: {\n deps: ['isAuthorizing'],\n fn() {\n return this.isAuthorizing;\n }\n }\n },\n\n session: {\n /**\n * Indicates if an Authorization Code exchange is inflight\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @type {boolean}\n */\n isAuthorizing: {\n default: false,\n type: 'boolean'\n },\n ready: {\n default: false,\n type: 'boolean'\n }\n },\n\n namespace: 'Credentials',\n\n /**\n * Initializer\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @private\n * @returns {Authorization}\n */\n // eslint-disable-next-line complexity\n initialize(...attrs) {\n const ret = Reflect.apply(WebexPlugin.prototype.initialize, this, attrs);\n const location = url.parse(this.webex.getWindow().location.href, true);\n\n this._checkForErrors(location);\n\n const {code} = location.query;\n\n if (!code) {\n this.ready = true;\n\n return ret;\n }\n\n if (location.query.state) {\n location.query.state = JSON.parse(base64.decode(location.query.state));\n }\n else {\n location.query.state = {};\n }\n\n const {email} = location.query.state;\n\n this._verifySecurityToken(location.query);\n this._cleanUrl(location);\n\n // Wait until nextTick in case `credentials` hasn't initialized yet\n process.nextTick(() => {\n this.webex.internal.services.collectPreauthCatalog({email})\n .catch(() => Promise.resolve())\n .then(() => this.requestAuthorizationCodeGrant({code}))\n .then(() => {\n this.ready = true;\n });\n });\n\n\n return ret;\n },\n\n /**\n * Kicks off an oauth flow\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @param {Object} options\n * @returns {Promise}\n */\n initiateLogin(options = {}) {\n options.state = options.state || {};\n options.state.csrf_token = this._generateSecurityToken();\n options.state.email = options.email;\n\n\n return this.initiateAuthorizationCodeGrant(options);\n },\n\n @whileInFlight('isAuthorizing')\n /**\n * Kicks off the Implicit Code grant flow. Typically called via\n * {@link AuthorizationBrowserFirstParty#initiateLogin}\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @param {Object} options\n * @returns {Promise}\n */\n initiateAuthorizationCodeGrant(options) {\n this.logger.info('authorization: initiating authorization code grant flow');\n this.webex.getWindow().location = this.webex.credentials.buildLoginUrl(Object.assign({response_type: 'code'}, options));\n\n return Promise.resolve();\n },\n\n /**\n * Called by {@link WebexCore#logout()}. Redirects to the logout page\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @param {Object} options\n * @param {boolean} options.noRedirect if true, does not redirect\n * @returns {Promise}\n */\n logout(options = {}) {\n if (!options.noRedirect) {\n this.webex.getWindow().location = this.webex.credentials.buildLogoutUrl(options);\n }\n },\n\n\n @whileInFlight('isAuthorizing')\n @oneFlight\n /**\n * Exchanges an authorization code for an access token\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @param {Object} options\n * @param {Object} options.code\n * @returns {Promise}\n */\n requestAuthorizationCodeGrant(options = {}) {\n this.logger.info('credentials: requesting authorization code grant');\n\n if (!options.code) {\n return Promise.reject(new Error('`options.code` is required'));\n }\n\n return this.webex.request({\n method: 'POST',\n uri: this.config.tokenUrl,\n form: {\n grant_type: 'authorization_code',\n redirect_uri: this.config.redirect_uri,\n code: options.code,\n self_contained_token: true\n },\n auth: {\n user: this.config.client_id,\n pass: this.config.client_secret,\n sendImmediately: true\n },\n shouldRefreshAccessToken: false\n })\n .then((res) => {\n this.webex.credentials.set({supertoken: res.body});\n })\n .catch((res) => {\n if (res.statusCode !== 400) {\n return Promise.reject(res);\n }\n\n const ErrorConstructor = grantErrors.select(res.body.error);\n\n return Promise.reject(new ErrorConstructor(res._res || res));\n });\n },\n\n /**\n * Checks if the result of the login redirect contains an error string\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @param {Object} location\n * @private\n * @returns {Promise}\n */\n _checkForErrors(location) {\n const {query} = location;\n\n if (query && query.error) {\n const ErrorConstructor = grantErrors.select(query.error);\n\n throw new ErrorConstructor(query);\n }\n },\n\n /**\n * Removes no-longer needed values from the url (access token, csrf token, etc)\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @param {Object} location\n * @private\n * @returns {Promise}\n */\n _cleanUrl(location) {\n location = cloneDeep(location);\n if (this.webex.getWindow().history && this.webex.getWindow().history.replaceState) {\n Reflect.deleteProperty(location.query, 'code');\n if (isEmpty(omit(location.query.state, 'csrf_token'))) {\n Reflect.deleteProperty(location.query, 'state');\n }\n else {\n location.query.state = base64.encode(JSON.stringify(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 CSRF token and sticks in in sessionStorage\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @private\n * @returns {Promise}\n */\n _generateSecurityToken() {\n this.logger.info('authorization: generating csrf token');\n\n const token = uuid.v4();\n\n this.webex.getWindow().sessionStorage.setItem('oauth2-csrf-token', token);\n\n return token;\n },\n\n /**\n * Checks if the CSRF token in sessionStorage is the same as the one returned\n * in the url.\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @param {Object} query\n * @private\n * @returns {Promise}\n */\n _verifySecurityToken(query) {\n const sessionToken = this.webex.getWindow().sessionStorage.getItem(OAUTH2_CSRF_TOKEN);\n\n this.webex.getWindow().sessionStorage.removeItem(OAUTH2_CSRF_TOKEN);\n if (!sessionToken) {\n return;\n }\n\n if (!query.state) {\n throw new Error(`Expected CSRF token ${sessionToken}, but not found in redirect query`);\n }\n\n if (!query.state.csrf_token) {\n throw new Error(`Expected CSRF token ${sessionToken}, but not found in redirect query`);\n }\n\n const token = query.state.csrf_token;\n\n if (token !== sessionToken) {\n throw new Error(`CSRF token ${token} does not match stored token ${sessionToken}`);\n }\n }\n});\n\nexport default Authorization;\n"]}
|
|
1
|
+
{"version":3,"sources":["authorization.js"],"names":["lodash","require","OAUTH2_CSRF_TOKEN","OAUTH2_CODE_VERIFIER","Authorization","WebexPlugin","extend","derived","isAuthenticating","deps","fn","isAuthorizing","session","default","type","ready","namespace","initialize","attrs","ret","prototype","location","url","parse","webex","getWindow","href","_checkForErrors","code","query","state","JSON","base64","decode","codeVerifier","sessionStorage","getItem","removeItem","email","_verifySecurityToken","_cleanUrl","process","nextTick","internal","services","collectPreauthCatalog","catch","resolve","then","requestAuthorizationCodeGrant","initiateLogin","options","csrf_token","_generateSecurityToken","code_challenge","_generateCodeChallenge","code_challenge_method","initiateAuthorizationCodeGrant","logger","info","credentials","buildLoginUrl","response_type","logout","noRedirect","buildLogoutUrl","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","error","_res","history","replaceState","encode","search","querystring","stringify","format","safeCharacterMap","base64url","_safe_map","times","random","length","join","codeChallenge","CryptoJS","SHA256","toString","setItem","token","uuid","v4","sessionToken","oneFlight"],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.\n */\n\n/* eslint camelcase: [0] */\n\nimport querystring from 'querystring';\nimport url from 'url';\n\nimport {base64, oneFlight, whileInFlight} from '@webex/common';\nimport {grantErrors, WebexPlugin} from '@webex/webex-core';\nimport {cloneDeep, isEmpty, omit} from 'lodash';\nimport uuid from 'uuid';\nimport base64url from 'crypto-js/enc-base64url';\nimport CryptoJS from 'crypto-js';\n\n// Necessary to require lodash this way in order to stub\n// methods in the unit test\nconst lodash = require('lodash');\n\nconst OAUTH2_CSRF_TOKEN = 'oauth2-csrf-token';\nconst OAUTH2_CODE_VERIFIER = 'oauth2-code-verifier';\n\n/**\n * Browser support for OAuth2. Automatically parses the URL query for an\n * authorization code\n *\n * Use of this plugin for anything other than the Webex Web Client is strongly\n * discouraged and may be broken at any time\n * @class\n * @name AuthorizationBrowserFirstParty\n * @private\n */\nconst Authorization = WebexPlugin.extend({\n derived: {\n /**\n * Alias of {@link AuthorizationBrowserFirstParty#isAuthorizing}\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @type {boolean}\n */\n isAuthenticating: {\n deps: ['isAuthorizing'],\n fn() {\n return this.isAuthorizing;\n }\n }\n },\n\n session: {\n /**\n * Indicates if an Authorization Code exchange is inflight\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @type {boolean}\n */\n isAuthorizing: {\n default: false,\n type: 'boolean'\n },\n ready: {\n default: false,\n type: 'boolean'\n }\n },\n\n namespace: 'Credentials',\n\n /**\n * Initializer\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @private\n * @returns {Authorization}\n */\n // eslint-disable-next-line complexity\n initialize(...attrs) {\n const ret = Reflect.apply(WebexPlugin.prototype.initialize, this, attrs);\n const location = url.parse(this.webex.getWindow().location.href, true);\n\n this._checkForErrors(location);\n\n const {code} = location.query;\n\n if (!code) {\n this.ready = true;\n\n return ret;\n }\n\n if (location.query.state) {\n location.query.state = JSON.parse(base64.decode(location.query.state));\n }\n else {\n location.query.state = {};\n }\n\n const codeVerifier = this.webex.getWindow().sessionStorage.getItem(OAUTH2_CODE_VERIFIER);\n\n this.webex.getWindow().sessionStorage.removeItem(OAUTH2_CODE_VERIFIER);\n\n\n const {email} = location.query.state;\n\n this._verifySecurityToken(location.query);\n this._cleanUrl(location);\n\n // Wait until nextTick in case `credentials` hasn't initialized yet\n process.nextTick(() => {\n this.webex.internal.services.collectPreauthCatalog({email})\n .catch(() => Promise.resolve())\n .then(() => this.requestAuthorizationCodeGrant({code, codeVerifier}))\n .then(() => {\n this.ready = true;\n });\n });\n\n\n return ret;\n },\n\n /**\n * Kicks off an oauth flow\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @param {Object} options\n * @returns {Promise}\n */\n initiateLogin(options = {}) {\n options.state = options.state || {};\n options.state.csrf_token = this._generateSecurityToken();\n options.state.email = options.email;\n\n options.code_challenge = this._generateCodeChallenge();\n options.code_challenge_method = 'S256';\n\n\n return this.initiateAuthorizationCodeGrant(options);\n },\n\n @whileInFlight('isAuthorizing')\n /**\n * Kicks off the Implicit Code grant flow. Typically called via\n * {@link AuthorizationBrowserFirstParty#initiateLogin}\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @param {Object} options\n * @returns {Promise}\n */\n initiateAuthorizationCodeGrant(options) {\n this.logger.info('authorization: initiating authorization code grant flow');\n this.webex.getWindow().location = this.webex.credentials.buildLoginUrl(Object.assign({response_type: 'code'}, options));\n\n return Promise.resolve();\n },\n\n /**\n * Called by {@link WebexCore#logout()}. Redirects to the logout page\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @param {Object} options\n * @param {boolean} options.noRedirect if true, does not redirect\n * @returns {Promise}\n */\n logout(options = {}) {\n if (!options.noRedirect) {\n this.webex.getWindow().location = this.webex.credentials.buildLogoutUrl(options);\n }\n },\n\n\n @whileInFlight('isAuthorizing')\n @oneFlight\n /**\n * Exchanges an authorization code for an access token\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @param {Object} options\n * @param {Object} options.code\n * @returns {Promise}\n */\n requestAuthorizationCodeGrant(options = {}) {\n this.logger.info('credentials: requesting authorization code grant');\n\n if (!options.code) {\n return Promise.reject(new Error('`options.code` is required'));\n }\n\n const form = {\n grant_type: 'authorization_code',\n redirect_uri: this.config.redirect_uri,\n code: options.code,\n self_contained_token: true\n };\n\n if (options.codeVerifier) {\n form.code_verifier = options.codeVerifier;\n }\n\n return this.webex.request({\n method: 'POST',\n uri: this.config.tokenUrl,\n form,\n auth: {\n user: this.config.client_id,\n pass: this.config.client_secret,\n sendImmediately: true\n },\n shouldRefreshAccessToken: false\n })\n .then((res) => {\n this.webex.credentials.set({supertoken: res.body});\n })\n .catch((res) => {\n if (res.statusCode !== 400) {\n return Promise.reject(res);\n }\n\n const ErrorConstructor = grantErrors.select(res.body.error);\n\n return Promise.reject(new ErrorConstructor(res._res || res));\n });\n },\n\n /**\n * Checks if the result of the login redirect contains an error string\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @param {Object} location\n * @private\n * @returns {Promise}\n */\n _checkForErrors(location) {\n const {query} = location;\n\n if (query && query.error) {\n const ErrorConstructor = grantErrors.select(query.error);\n\n throw new ErrorConstructor(query);\n }\n },\n\n /**\n * Removes no-longer needed values from the url (access token, csrf token, etc)\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @param {Object} location\n * @private\n * @returns {Promise}\n */\n _cleanUrl(location) {\n location = cloneDeep(location);\n if (this.webex.getWindow().history && this.webex.getWindow().history.replaceState) {\n Reflect.deleteProperty(location.query, 'code');\n if (isEmpty(omit(location.query.state, 'csrf_token'))) {\n Reflect.deleteProperty(location.query, 'state');\n }\n else {\n location.query.state = base64.encode(JSON.stringify(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 PKCE code verifier and code challenge and sets the the code verifier in sessionStorage\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @private\n * @returns {string}\n */\n _generateCodeChallenge() {\n this.logger.info('authorization: generating PKCE code challenge');\n\n // eslint-disable-next-line no-underscore-dangle\n const safeCharacterMap = base64url._safe_map;\n\n const codeVerifier = lodash.times(\n 128,\n () => 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(\n OAUTH2_CODE_VERIFIER, codeVerifier\n );\n\n return codeChallenge;\n },\n\n /**\n * Generates a CSRF token and sticks in in sessionStorage\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @private\n * @returns {Promise}\n */\n _generateSecurityToken() {\n this.logger.info('authorization: generating csrf token');\n\n const token = uuid.v4();\n\n this.webex.getWindow().sessionStorage.setItem('oauth2-csrf-token', token);\n\n return token;\n },\n\n /**\n * Checks if the CSRF token in sessionStorage is the same as the one returned\n * in the url.\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @param {Object} query\n * @private\n * @returns {Promise}\n */\n _verifySecurityToken(query) {\n const sessionToken = this.webex.getWindow().sessionStorage.getItem(OAUTH2_CSRF_TOKEN);\n\n this.webex.getWindow().sessionStorage.removeItem(OAUTH2_CSRF_TOKEN);\n if (!sessionToken) {\n return;\n }\n\n if (!query.state) {\n throw new Error(`Expected CSRF token ${sessionToken}, but not found in redirect query`);\n }\n\n if (!query.state.csrf_token) {\n throw new Error(`Expected CSRF token ${sessionToken}, but not found in redirect query`);\n }\n\n const token = query.state.csrf_token;\n\n if (token !== sessionToken) {\n throw new Error(`CSRF token ${token} does not match stored token ${sessionToken}`);\n }\n }\n});\n\nexport default Authorization;\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webex/plugin-authorization-browser-first-party",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.145.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Ian W. Remmel <iremmel@cisco.com>",
|
|
@@ -17,10 +17,11 @@
|
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
19
|
"@babel/runtime-corejs2": "^7.14.8",
|
|
20
|
-
"@webex/webex-core": "1.
|
|
20
|
+
"@webex/webex-core": "1.145.0",
|
|
21
21
|
"lodash": "^4.17.21",
|
|
22
|
-
"@webex/common": "1.
|
|
22
|
+
"@webex/common": "1.145.0",
|
|
23
23
|
"uuid": "^3.3.2",
|
|
24
|
+
"crypto-js": "^4.1.1",
|
|
24
25
|
"envify": "^4.1.0"
|
|
25
26
|
}
|
|
26
27
|
}
|
package/src/authorization.js
CHANGED
|
@@ -11,8 +11,15 @@ import {base64, oneFlight, whileInFlight} from '@webex/common';
|
|
|
11
11
|
import {grantErrors, WebexPlugin} from '@webex/webex-core';
|
|
12
12
|
import {cloneDeep, isEmpty, omit} from 'lodash';
|
|
13
13
|
import uuid from 'uuid';
|
|
14
|
+
import base64url from 'crypto-js/enc-base64url';
|
|
15
|
+
import CryptoJS from 'crypto-js';
|
|
16
|
+
|
|
17
|
+
// Necessary to require lodash this way in order to stub
|
|
18
|
+
// methods in the unit test
|
|
19
|
+
const lodash = require('lodash');
|
|
14
20
|
|
|
15
21
|
const OAUTH2_CSRF_TOKEN = 'oauth2-csrf-token';
|
|
22
|
+
const OAUTH2_CODE_VERIFIER = 'oauth2-code-verifier';
|
|
16
23
|
|
|
17
24
|
/**
|
|
18
25
|
* Browser support for OAuth2. Automatically parses the URL query for an
|
|
@@ -88,6 +95,11 @@ const Authorization = WebexPlugin.extend({
|
|
|
88
95
|
location.query.state = {};
|
|
89
96
|
}
|
|
90
97
|
|
|
98
|
+
const codeVerifier = this.webex.getWindow().sessionStorage.getItem(OAUTH2_CODE_VERIFIER);
|
|
99
|
+
|
|
100
|
+
this.webex.getWindow().sessionStorage.removeItem(OAUTH2_CODE_VERIFIER);
|
|
101
|
+
|
|
102
|
+
|
|
91
103
|
const {email} = location.query.state;
|
|
92
104
|
|
|
93
105
|
this._verifySecurityToken(location.query);
|
|
@@ -97,7 +109,7 @@ const Authorization = WebexPlugin.extend({
|
|
|
97
109
|
process.nextTick(() => {
|
|
98
110
|
this.webex.internal.services.collectPreauthCatalog({email})
|
|
99
111
|
.catch(() => Promise.resolve())
|
|
100
|
-
.then(() => this.requestAuthorizationCodeGrant({code}))
|
|
112
|
+
.then(() => this.requestAuthorizationCodeGrant({code, codeVerifier}))
|
|
101
113
|
.then(() => {
|
|
102
114
|
this.ready = true;
|
|
103
115
|
});
|
|
@@ -119,6 +131,9 @@ const Authorization = WebexPlugin.extend({
|
|
|
119
131
|
options.state.csrf_token = this._generateSecurityToken();
|
|
120
132
|
options.state.email = options.email;
|
|
121
133
|
|
|
134
|
+
options.code_challenge = this._generateCodeChallenge();
|
|
135
|
+
options.code_challenge_method = 'S256';
|
|
136
|
+
|
|
122
137
|
|
|
123
138
|
return this.initiateAuthorizationCodeGrant(options);
|
|
124
139
|
},
|
|
@@ -171,15 +186,21 @@ const Authorization = WebexPlugin.extend({
|
|
|
171
186
|
return Promise.reject(new Error('`options.code` is required'));
|
|
172
187
|
}
|
|
173
188
|
|
|
189
|
+
const form = {
|
|
190
|
+
grant_type: 'authorization_code',
|
|
191
|
+
redirect_uri: this.config.redirect_uri,
|
|
192
|
+
code: options.code,
|
|
193
|
+
self_contained_token: true
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
if (options.codeVerifier) {
|
|
197
|
+
form.code_verifier = options.codeVerifier;
|
|
198
|
+
}
|
|
199
|
+
|
|
174
200
|
return this.webex.request({
|
|
175
201
|
method: 'POST',
|
|
176
202
|
uri: this.config.tokenUrl,
|
|
177
|
-
form
|
|
178
|
-
grant_type: 'authorization_code',
|
|
179
|
-
redirect_uri: this.config.redirect_uri,
|
|
180
|
-
code: options.code,
|
|
181
|
-
self_contained_token: true
|
|
182
|
-
},
|
|
203
|
+
form,
|
|
183
204
|
auth: {
|
|
184
205
|
user: this.config.client_id,
|
|
185
206
|
pass: this.config.client_secret,
|
|
@@ -243,6 +264,33 @@ const Authorization = WebexPlugin.extend({
|
|
|
243
264
|
}
|
|
244
265
|
},
|
|
245
266
|
|
|
267
|
+
/**
|
|
268
|
+
* Generates PKCE code verifier and code challenge and sets the the code verifier in sessionStorage
|
|
269
|
+
* @instance
|
|
270
|
+
* @memberof AuthorizationBrowserFirstParty
|
|
271
|
+
* @private
|
|
272
|
+
* @returns {string}
|
|
273
|
+
*/
|
|
274
|
+
_generateCodeChallenge() {
|
|
275
|
+
this.logger.info('authorization: generating PKCE code challenge');
|
|
276
|
+
|
|
277
|
+
// eslint-disable-next-line no-underscore-dangle
|
|
278
|
+
const safeCharacterMap = base64url._safe_map;
|
|
279
|
+
|
|
280
|
+
const codeVerifier = lodash.times(
|
|
281
|
+
128,
|
|
282
|
+
() => safeCharacterMap[lodash.random(0, safeCharacterMap.length - 1)]
|
|
283
|
+
).join('');
|
|
284
|
+
|
|
285
|
+
const codeChallenge = CryptoJS.SHA256(codeVerifier).toString(base64url);
|
|
286
|
+
|
|
287
|
+
this.webex.getWindow().sessionStorage.setItem(
|
|
288
|
+
OAUTH2_CODE_VERIFIER, codeVerifier
|
|
289
|
+
);
|
|
290
|
+
|
|
291
|
+
return codeChallenge;
|
|
292
|
+
},
|
|
293
|
+
|
|
246
294
|
/**
|
|
247
295
|
* Generates a CSRF token and sticks in in sessionStorage
|
|
248
296
|
* @instance
|
|
@@ -11,13 +11,18 @@ import {browserOnly} from '@webex/test-helper-mocha';
|
|
|
11
11
|
import sinon from 'sinon';
|
|
12
12
|
import MockWebex from '@webex/test-helper-mock-webex';
|
|
13
13
|
import {Credentials, Services} from '@webex/webex-core';
|
|
14
|
-
import Authorization from '@webex/plugin-authorization-browser-first-party';
|
|
15
14
|
import {base64, patterns} from '@webex/common';
|
|
16
|
-
import {merge} from 'lodash';
|
|
15
|
+
import {merge, times} from 'lodash';
|
|
16
|
+
import CryptoJS from 'crypto-js';
|
|
17
|
+
import Authorization from '@webex/plugin-authorization-browser-first-party';
|
|
18
|
+
|
|
19
|
+
// Necessary to require lodash this way in order to stub the method
|
|
20
|
+
const lodash = require('lodash');
|
|
21
|
+
|
|
17
22
|
|
|
18
23
|
browserOnly(describe)('plugin-authorization-browser-first-party', () => {
|
|
19
24
|
describe('Authorization', () => {
|
|
20
|
-
function makeWebex(href = 'https://example.com', csrfToken = undefined, config = {}) {
|
|
25
|
+
function makeWebex(href = 'https://example.com', csrfToken = undefined, pkceVerifier = undefined, config = {}) {
|
|
21
26
|
const mockWindow = {
|
|
22
27
|
history: {
|
|
23
28
|
replaceState(a, b, location) {
|
|
@@ -28,7 +33,11 @@ browserOnly(describe)('plugin-authorization-browser-first-party', () => {
|
|
|
28
33
|
href
|
|
29
34
|
},
|
|
30
35
|
sessionStorage: {
|
|
31
|
-
getItem: sinon.stub()
|
|
36
|
+
getItem: sinon.stub()
|
|
37
|
+
.onCall(0)
|
|
38
|
+
.returns(pkceVerifier)
|
|
39
|
+
.onCall(1)
|
|
40
|
+
.returns(csrfToken),
|
|
32
41
|
removeItem: sinon.spy(),
|
|
33
42
|
setItem: sinon.spy()
|
|
34
43
|
}
|
|
@@ -86,7 +95,10 @@ browserOnly(describe)('plugin-authorization-browser-first-party', () => {
|
|
|
86
95
|
|
|
87
96
|
return webex.authorization.when('change:ready')
|
|
88
97
|
.then(() => {
|
|
89
|
-
|
|
98
|
+
// Webex request gets called twice:
|
|
99
|
+
// once for the pre-auth catalog
|
|
100
|
+
// once for auth token exchange
|
|
101
|
+
assert.calledTwice(webex.request);
|
|
90
102
|
assert.isTrue(webex.authorization.ready);
|
|
91
103
|
assert.isTrue(webex.credentials.canAuthorize);
|
|
92
104
|
});
|
|
@@ -149,6 +161,35 @@ browserOnly(describe)('plugin-authorization-browser-first-party', () => {
|
|
|
149
161
|
assert.isFalse(webex.credentials.canAuthorize);
|
|
150
162
|
});
|
|
151
163
|
});
|
|
164
|
+
|
|
165
|
+
describe('when code verifier is present in session storage', () => {
|
|
166
|
+
it('passes codeVerifier to requestAuthorizationCodeGrant', () => {
|
|
167
|
+
const expectedVerifier = 'test verifier';
|
|
168
|
+
|
|
169
|
+
const webex = makeWebex(
|
|
170
|
+
'http://example.com?code=5',
|
|
171
|
+
undefined,
|
|
172
|
+
expectedVerifier
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
return webex.authorization.when('change:ready')
|
|
176
|
+
.then(() => {
|
|
177
|
+
assert.calledTwice(webex.request);
|
|
178
|
+
assert.calledWith(
|
|
179
|
+
webex.getWindow().sessionStorage.getItem,
|
|
180
|
+
'oauth2-code-verifier'
|
|
181
|
+
);
|
|
182
|
+
assert.calledWith(
|
|
183
|
+
webex.getWindow().sessionStorage.removeItem,
|
|
184
|
+
'oauth2-code-verifier'
|
|
185
|
+
);
|
|
186
|
+
assert.equal(
|
|
187
|
+
webex.request.getCall(1).args[0].form.code_verifier,
|
|
188
|
+
expectedVerifier
|
|
189
|
+
);
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
});
|
|
152
193
|
});
|
|
153
194
|
|
|
154
195
|
describe('#initiateLogin()', () => {
|
|
@@ -193,6 +234,31 @@ browserOnly(describe)('plugin-authorization-browser-first-party', () => {
|
|
|
193
234
|
});
|
|
194
235
|
});
|
|
195
236
|
|
|
237
|
+
it('adds a pkce code challenge', () => {
|
|
238
|
+
const webex = makeWebex(undefined, undefined, {
|
|
239
|
+
credentials: {
|
|
240
|
+
clientType: 'confidential'
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
const expectedCodeChallenge = 'test challenge';
|
|
245
|
+
|
|
246
|
+
sinon.spy(webex.authorization, 'initiateAuthorizationCodeGrant');
|
|
247
|
+
sinon.stub(webex.authorization, '_generateCodeChallenge')
|
|
248
|
+
.returns(expectedCodeChallenge);
|
|
249
|
+
|
|
250
|
+
return webex.authorization.initiateLogin()
|
|
251
|
+
.then(() => {
|
|
252
|
+
assert.called(webex.authorization.initiateAuthorizationCodeGrant);
|
|
253
|
+
const grantOptions = webex.authorization.initiateAuthorizationCodeGrant.getCall(0).args[0];
|
|
254
|
+
|
|
255
|
+
assert.equal(grantOptions.code_challenge, expectedCodeChallenge);
|
|
256
|
+
assert.equal(grantOptions.code_challenge_method, 'S256');
|
|
257
|
+
// eslint-disable-next-line no-underscore-dangle
|
|
258
|
+
assert.calledWith(webex.authorization._generateCodeChallenge);
|
|
259
|
+
});
|
|
260
|
+
});
|
|
261
|
+
|
|
196
262
|
it('sets #isAuthorizing', () => {
|
|
197
263
|
const webex = makeWebex(undefined, undefined, {
|
|
198
264
|
credentials: {
|
|
@@ -242,6 +308,38 @@ browserOnly(describe)('plugin-authorization-browser-first-party', () => {
|
|
|
242
308
|
});
|
|
243
309
|
});
|
|
244
310
|
|
|
311
|
+
describe('#_generateCodeChallenge', () => {
|
|
312
|
+
const expectedCodeChallenge = 'code challenge';
|
|
313
|
+
// eslint-disable-next-line no-underscore-dangle
|
|
314
|
+
const safeCharacterMap = CryptoJS.enc.Base64url._safe_map;
|
|
315
|
+
|
|
316
|
+
const expectedVerifier = times(128, () => safeCharacterMap[0]).join('');
|
|
317
|
+
|
|
318
|
+
it('generates a challenge code and stores it in session storage', () => {
|
|
319
|
+
const webex = makeWebex('http://example.com');
|
|
320
|
+
|
|
321
|
+
const toStringStub = sinon.stub().returns(expectedCodeChallenge);
|
|
322
|
+
const randomStub = sinon.stub(lodash, 'random').returns(0);
|
|
323
|
+
const sha256Stub = sinon.stub(CryptoJS, 'SHA256').returns({
|
|
324
|
+
toString: toStringStub
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
// eslint-disable-next-line no-underscore-dangle
|
|
328
|
+
const codeChallenge = webex.authorization._generateCodeChallenge();
|
|
329
|
+
|
|
330
|
+
assert.equal(codeChallenge, expectedCodeChallenge);
|
|
331
|
+
assert.calledWith(sha256Stub, expectedVerifier);
|
|
332
|
+
assert.calledWith(toStringStub, CryptoJS.enc.Base64url);
|
|
333
|
+
assert.callCount(randomStub, 128);
|
|
334
|
+
assert.calledWith(randomStub, 0, safeCharacterMap.length - 1);
|
|
335
|
+
assert.calledWith(
|
|
336
|
+
webex.getWindow().sessionStorage.setItem,
|
|
337
|
+
'oauth2-code-verifier',
|
|
338
|
+
expectedVerifier
|
|
339
|
+
);
|
|
340
|
+
});
|
|
341
|
+
});
|
|
342
|
+
|
|
245
343
|
describe('#_cleanUrl()', () => {
|
|
246
344
|
it('removes the state parameter when it has no keys', () => {
|
|
247
345
|
const webex = makeWebex(undefined, undefined, {
|