@webex/plugin-authorization-browser 2.59.2 → 2.59.3-next.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,394 +1,394 @@
1
- /*!
2
- * Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.
3
- */
4
-
5
- /* eslint camelcase: [0] */
6
-
7
- import querystring from 'querystring';
8
- import url from 'url';
9
-
10
- import {base64, oneFlight, whileInFlight} from '@webex/common';
11
- import {grantErrors, WebexPlugin} from '@webex/webex-core';
12
- import {cloneDeep, isEmpty, omit} from 'lodash';
13
- import uuid from 'uuid';
14
- import * as jose from 'jose'
15
-
16
- const OAUTH2_CSRF_TOKEN = 'oauth2-csrf-token';
17
- const EMPTY_OBJECT_STRING = base64.encode(JSON.stringify({}));
18
-
19
- /**
20
- * Browser support for OAuth2. Automatically parses the URL hash for an access
21
- * token
22
- * @class
23
- * @name AuthorizationBrowser
24
- */
25
- const Authorization = WebexPlugin.extend({
26
- derived: {
27
- /**
28
- * Alias of {@link AuthorizationBrowser#isAuthorizing}
29
- * @instance
30
- * @memberof AuthorizationBrowser
31
- * @type {boolean}
32
- */
33
- isAuthenticating: {
34
- deps: ['isAuthorizing'],
35
- fn() {
36
- return this.isAuthorizing;
37
- },
38
- },
39
- },
40
-
41
- session: {
42
- /**
43
- * Indicates if an Authorization Code exchange is inflight
44
- * @instance
45
- * @memberof AuthorizationBrowser
46
- * @type {boolean}
47
- */
48
- isAuthorizing: {
49
- default: false,
50
- type: 'boolean',
51
- },
52
- ready: {
53
- default: false,
54
- type: 'boolean',
55
- },
56
- },
57
-
58
- namespace: 'Credentials',
59
-
60
- /**
61
- * Initializer
62
- * @instance
63
- * @memberof AuthorizationBrowser
64
- * @param {Object} attrs {@link AmpersandState}
65
- * @param {boolean} attrs.parse Controls whether or not the the url should get
66
- * parsed for an access token
67
- * @private
68
- * @returns {Authorization}
69
- */
70
- // eslint-disable-next-line complexity
71
- initialize(attrs, options) {
72
- const ret = Reflect.apply(WebexPlugin.prototype.initialize, this, [attrs, options]);
73
-
74
- // Reminder, we can't do parse based on config, because config is not
75
- // available until nextTick and we want to be able to throw errors found in
76
- // the url.
77
- if (attrs.parse === false) {
78
- this.ready = true;
79
-
80
- return ret;
81
- }
82
- const location = url.parse(this.webex.getWindow().location.href, true);
83
-
84
- this._checkForErrors(location);
85
-
86
- let {hash} = location;
87
-
88
- if (!hash) {
89
- this.ready = true;
90
-
91
- return ret;
92
- }
93
- if (hash.includes('#')) {
94
- hash = hash.substr(1);
95
- }
96
- location.hash = querystring.parse(hash);
97
- if (location.hash.state) {
98
- location.hash.state = JSON.parse(base64.decode(location.hash.state));
99
- }
100
- const tokenData = this._parseHash(location);
101
-
102
- if (!tokenData) {
103
- return ret;
104
- }
105
- this._cleanUrl(location);
106
-
107
- // Wait until nextTick in case `credentials` hasn't initialized yet
108
- process.nextTick(() => {
109
- this.webex.credentials.set({supertoken: tokenData});
110
- this.ready = true;
111
- });
112
-
113
- return ret;
114
- },
115
-
116
- /**
117
- * Kicks off an oauth flow
118
- * @instance
119
- * @memberof AuthorizationBrowser
120
- * @param {Object} options
121
- * @returns {Promise}
122
- */
123
- initiateLogin(options = {}) {
124
- options.state = options.state || {};
125
- options.state.csrf_token = this._generateSecurityToken();
126
-
127
- // If we're not explicitly a confidential client, assume we're a public
128
- // client
129
- if (this.config.clientType === 'confidential') {
130
- return this.initiateAuthorizationCodeGrant(options);
131
- }
132
-
133
- return this.initiateImplicitGrant(options);
134
- },
135
-
136
- @whileInFlight('isAuthorizing')
137
- /**
138
- * Kicks off the Authorization Code grant flow. Typically called via
139
- * {@link AuthorizationBrowser#initiateLogin}
140
- * @instance
141
- * @memberof AuthorizationBrowser
142
- * @param {Object} options
143
- * @returns {Promise}
144
- */
145
- initiateImplicitGrant(options) {
146
- this.logger.info('authorization: initiating implicit grant flow');
147
- this.webex.getWindow().location = this.webex.credentials.buildLoginUrl(
148
- Object.assign({response_type: 'token'}, options)
149
- );
150
-
151
- return Promise.resolve();
152
- },
153
-
154
- @whileInFlight('isAuthorizing')
155
- /**
156
- * Kicks off the Implicit Code grant flow. Typically called via
157
- * {@link AuthorizationBrowser#initiateLogin}
158
- * @instance
159
- * @memberof AuthorizationBrowser
160
- * @param {Object} options
161
- * @returns {Promise}
162
- */
163
- initiateAuthorizationCodeGrant(options) {
164
- this.logger.info('authorization: initiating authorization code grant flow');
165
- this.webex.getWindow().location = this.webex.credentials.buildLoginUrl(
166
- Object.assign({response_type: 'code'}, options)
167
- );
168
-
169
- return Promise.resolve();
170
- },
171
-
172
- @oneFlight
173
- /**
174
- * Requests a Webex access token for a user already authenticated into
175
- * your product.
176
- *
177
- * Note: You'll need to supply a jwtRefreshCallback of the form
178
- * `Promise<jwt> = jwtRefreshCallback(webex)` for automatic token refresh to
179
- * work.
180
- *
181
- * @instance
182
- * @memberof AuthorizationBrowser
183
- * @param {Object} options
184
- * @param {Object} options.jwt This is a jwt generated by your backend that
185
- * identifies a user in your system
186
- * @returns {Promise}
187
- */
188
- requestAccessTokenFromJwt({jwt}) {
189
- let hydraUri = this.webex.internal.services.get('hydra', true);
190
-
191
- if (hydraUri && hydraUri.slice(-1) !== '/') {
192
- // add a `/` to hydra's uri from the services catalog so that
193
- // it matches the current env service format.
194
- hydraUri += '/';
195
- }
196
-
197
- hydraUri = hydraUri || process.env.HYDRA_SERVICE_URL || 'https://api.ciscospark.com/v1/';
198
-
199
- return this.webex
200
- .request({
201
- method: 'POST',
202
- uri: `${hydraUri}jwt/login`,
203
- headers: {
204
- authorization: jwt,
205
- },
206
- })
207
- .then(({body}) => ({
208
- access_token: body.token,
209
- token_type: 'Bearer',
210
- expires_in: body.expiresIn,
211
- }))
212
- .then((token) => {
213
- this.webex.credentials.set({
214
- supertoken: token,
215
- });
216
- })
217
- .then(() => this.webex.internal.services.initServiceCatalogs());
218
- },
219
-
220
- /**
221
- * Called by {@link WebexCore#logout()}. Redirects to the logout page
222
- * @instance
223
- * @memberof AuthorizationBrowser
224
- * @param {Object} options
225
- * @param {boolean} options.noRedirect if true, does not redirect
226
- * @returns {Promise}
227
- */
228
- logout(options = {}) {
229
- if (!options.noRedirect) {
230
- this.webex.getWindow().location = this.webex.credentials.buildLogoutUrl(options);
231
- }
232
- },
233
-
234
- /**
235
- * Creates a jwt user token
236
- * @param {object} options
237
- * @param {String} options.issuer Guest Issuer ID
238
- * @param {String} options.secretId Guest Secret ID
239
- * @param {String} options.displayName Guest Display Name | optional
240
- * @param {String} options.expiresIn
241
- * @returns {Promise<object>}
242
- */
243
- async createJwt({issuer, secretId, displayName, expiresIn}) {
244
- const secret = Buffer.from(secretId, 'base64');
245
- const payload = {
246
- "sub": `guest-user-${uuid()}`,
247
- "iss": issuer,
248
- "name": displayName || `Guest User - ${uuid()}`
249
- };
250
- const alg = 'HS256';
251
-
252
- try {
253
- const jwtToken = await new jose.SignJWT(payload)
254
- .setProtectedHeader({alg})
255
- .setExpirationTime(expiresIn)
256
- .sign(secret);
257
-
258
- return Promise.resolve({jwt: jwtToken});
259
- } catch (e) {
260
- return Promise.reject(e);
261
- }
262
- },
263
-
264
- /**
265
- * Checks if the result of the login redirect contains an error string
266
- * @instance
267
- * @memberof AuthorizationBrowser
268
- * @param {Object} location
269
- * @private
270
- * @returns {Promise}
271
- */
272
- _checkForErrors(location) {
273
- const {query} = location;
274
-
275
- if (query && query.error) {
276
- const ErrorConstructor = grantErrors.select(query.error);
277
-
278
- throw new ErrorConstructor(query);
279
- }
280
- },
281
-
282
- /**
283
- * Removes no-longer needed values from the url (access token, csrf token, etc)
284
- * @instance
285
- * @memberof AuthorizationBrowser
286
- * @param {Object} location
287
- * @private
288
- * @returns {Promise}
289
- */
290
- _cleanUrl(location) {
291
- location = cloneDeep(location);
292
- if (this.webex.getWindow().history && this.webex.getWindow().history.replaceState) {
293
- [
294
- 'access_token',
295
- 'token_type',
296
- 'expires_in',
297
- 'refresh_token',
298
- 'refresh_token_expires_in',
299
- ].forEach((key) => Reflect.deleteProperty(location.hash, key));
300
- if (!isEmpty(location.hash.state)) {
301
- location.hash.state = base64.encode(
302
- JSON.stringify(omit(location.hash.state, 'csrf_token'))
303
- );
304
- if (location.hash.state === EMPTY_OBJECT_STRING) {
305
- Reflect.deleteProperty(location.hash, 'state');
306
- }
307
- } else {
308
- Reflect.deleteProperty(location.hash, 'state');
309
- }
310
- location.hash = querystring.stringify(location.hash);
311
- this.webex.getWindow().history.replaceState({}, null, url.format(location));
312
- }
313
- },
314
-
315
- /**
316
- * Generates a CSRF token and sticks in in sessionStorage
317
- * @instance
318
- * @memberof AuthorizationBrowser
319
- * @private
320
- * @returns {Promise}
321
- */
322
- _generateSecurityToken() {
323
- this.logger.info('authorization: generating csrf token');
324
-
325
- const token = uuid.v4();
326
-
327
- this.webex.getWindow().sessionStorage.setItem('oauth2-csrf-token', token);
328
-
329
- return token;
330
- },
331
-
332
- /**
333
- * Parses the url hash into an access token object
334
- * @instance
335
- * @memberof AuthorizationBrowser
336
- * @param {Object} location
337
- * @private
338
- * @returns {Object}
339
- */
340
- _parseHash(location) {
341
- const hash = cloneDeep(location.hash);
342
-
343
- if (hash) {
344
- this._verifySecurityToken(hash);
345
- }
346
- if (!hash.access_token) {
347
- this.ready = true;
348
-
349
- return undefined;
350
- }
351
- if (hash.expires_in) {
352
- hash.expires_in = parseInt(hash.expires_in, 10);
353
- }
354
- if (hash.refresh_token_expires_in) {
355
- hash.refresh_token_expires_in = parseInt(hash.refresh_token_expires_in, 10);
356
- }
357
-
358
- return hash;
359
- },
360
-
361
- /**
362
- * Checks if the CSRF token in sessionStorage is the same as the one returned
363
- * in the url.
364
- * @instance
365
- * @memberof AuthorizationBrowser
366
- * @param {Object} hash
367
- * @private
368
- * @returns {Promise}
369
- */
370
- _verifySecurityToken(hash) {
371
- const sessionToken = this.webex.getWindow().sessionStorage.getItem(OAUTH2_CSRF_TOKEN);
372
-
373
- this.webex.getWindow().sessionStorage.removeItem(OAUTH2_CSRF_TOKEN);
374
- if (!sessionToken) {
375
- return;
376
- }
377
-
378
- if (!hash.state) {
379
- throw new Error(`Expected CSRF token ${sessionToken}, but not found in redirect hash`);
380
- }
381
-
382
- if (!hash.state.csrf_token) {
383
- throw new Error(`Expected CSRF token ${sessionToken}, but not found in redirect hash`);
384
- }
385
-
386
- const token = hash.state.csrf_token;
387
-
388
- if (token !== sessionToken) {
389
- throw new Error(`CSRF token ${token} does not match stored token ${sessionToken}`);
390
- }
391
- },
392
- });
393
-
394
- export default Authorization;
1
+ /*!
2
+ * Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.
3
+ */
4
+
5
+ /* eslint camelcase: [0] */
6
+
7
+ import querystring from 'querystring';
8
+ import url from 'url';
9
+
10
+ import {base64, oneFlight, whileInFlight} from '@webex/common';
11
+ import {grantErrors, WebexPlugin} from '@webex/webex-core';
12
+ import {cloneDeep, isEmpty, omit} from 'lodash';
13
+ import uuid from 'uuid';
14
+ import * as jose from 'jose'
15
+
16
+ const OAUTH2_CSRF_TOKEN = 'oauth2-csrf-token';
17
+ const EMPTY_OBJECT_STRING = base64.encode(JSON.stringify({}));
18
+
19
+ /**
20
+ * Browser support for OAuth2. Automatically parses the URL hash for an access
21
+ * token
22
+ * @class
23
+ * @name AuthorizationBrowser
24
+ */
25
+ const Authorization = WebexPlugin.extend({
26
+ derived: {
27
+ /**
28
+ * Alias of {@link AuthorizationBrowser#isAuthorizing}
29
+ * @instance
30
+ * @memberof AuthorizationBrowser
31
+ * @type {boolean}
32
+ */
33
+ isAuthenticating: {
34
+ deps: ['isAuthorizing'],
35
+ fn() {
36
+ return this.isAuthorizing;
37
+ },
38
+ },
39
+ },
40
+
41
+ session: {
42
+ /**
43
+ * Indicates if an Authorization Code exchange is inflight
44
+ * @instance
45
+ * @memberof AuthorizationBrowser
46
+ * @type {boolean}
47
+ */
48
+ isAuthorizing: {
49
+ default: false,
50
+ type: 'boolean',
51
+ },
52
+ ready: {
53
+ default: false,
54
+ type: 'boolean',
55
+ },
56
+ },
57
+
58
+ namespace: 'Credentials',
59
+
60
+ /**
61
+ * Initializer
62
+ * @instance
63
+ * @memberof AuthorizationBrowser
64
+ * @param {Object} attrs {@link AmpersandState}
65
+ * @param {boolean} attrs.parse Controls whether or not the the url should get
66
+ * parsed for an access token
67
+ * @private
68
+ * @returns {Authorization}
69
+ */
70
+ // eslint-disable-next-line complexity
71
+ initialize(attrs, options) {
72
+ const ret = Reflect.apply(WebexPlugin.prototype.initialize, this, [attrs, options]);
73
+
74
+ // Reminder, we can't do parse based on config, because config is not
75
+ // available until nextTick and we want to be able to throw errors found in
76
+ // the url.
77
+ if (attrs.parse === false) {
78
+ this.ready = true;
79
+
80
+ return ret;
81
+ }
82
+ const location = url.parse(this.webex.getWindow().location.href, true);
83
+
84
+ this._checkForErrors(location);
85
+
86
+ let {hash} = location;
87
+
88
+ if (!hash) {
89
+ this.ready = true;
90
+
91
+ return ret;
92
+ }
93
+ if (hash.includes('#')) {
94
+ hash = hash.substr(1);
95
+ }
96
+ location.hash = querystring.parse(hash);
97
+ if (location.hash.state) {
98
+ location.hash.state = JSON.parse(base64.decode(location.hash.state));
99
+ }
100
+ const tokenData = this._parseHash(location);
101
+
102
+ if (!tokenData) {
103
+ return ret;
104
+ }
105
+ this._cleanUrl(location);
106
+
107
+ // Wait until nextTick in case `credentials` hasn't initialized yet
108
+ process.nextTick(() => {
109
+ this.webex.credentials.set({supertoken: tokenData});
110
+ this.ready = true;
111
+ });
112
+
113
+ return ret;
114
+ },
115
+
116
+ /**
117
+ * Kicks off an oauth flow
118
+ * @instance
119
+ * @memberof AuthorizationBrowser
120
+ * @param {Object} options
121
+ * @returns {Promise}
122
+ */
123
+ initiateLogin(options = {}) {
124
+ options.state = options.state || {};
125
+ options.state.csrf_token = this._generateSecurityToken();
126
+
127
+ // If we're not explicitly a confidential client, assume we're a public
128
+ // client
129
+ if (this.config.clientType === 'confidential') {
130
+ return this.initiateAuthorizationCodeGrant(options);
131
+ }
132
+
133
+ return this.initiateImplicitGrant(options);
134
+ },
135
+
136
+ @whileInFlight('isAuthorizing')
137
+ /**
138
+ * Kicks off the Authorization Code grant flow. Typically called via
139
+ * {@link AuthorizationBrowser#initiateLogin}
140
+ * @instance
141
+ * @memberof AuthorizationBrowser
142
+ * @param {Object} options
143
+ * @returns {Promise}
144
+ */
145
+ initiateImplicitGrant(options) {
146
+ this.logger.info('authorization: initiating implicit grant flow');
147
+ this.webex.getWindow().location = this.webex.credentials.buildLoginUrl(
148
+ Object.assign({response_type: 'token'}, options)
149
+ );
150
+
151
+ return Promise.resolve();
152
+ },
153
+
154
+ @whileInFlight('isAuthorizing')
155
+ /**
156
+ * Kicks off the Implicit Code grant flow. Typically called via
157
+ * {@link AuthorizationBrowser#initiateLogin}
158
+ * @instance
159
+ * @memberof AuthorizationBrowser
160
+ * @param {Object} options
161
+ * @returns {Promise}
162
+ */
163
+ initiateAuthorizationCodeGrant(options) {
164
+ this.logger.info('authorization: initiating authorization code grant flow');
165
+ this.webex.getWindow().location = this.webex.credentials.buildLoginUrl(
166
+ Object.assign({response_type: 'code'}, options)
167
+ );
168
+
169
+ return Promise.resolve();
170
+ },
171
+
172
+ @oneFlight
173
+ /**
174
+ * Requests a Webex access token for a user already authenticated into
175
+ * your product.
176
+ *
177
+ * Note: You'll need to supply a jwtRefreshCallback of the form
178
+ * `Promise<jwt> = jwtRefreshCallback(webex)` for automatic token refresh to
179
+ * work.
180
+ *
181
+ * @instance
182
+ * @memberof AuthorizationBrowser
183
+ * @param {Object} options
184
+ * @param {Object} options.jwt This is a jwt generated by your backend that
185
+ * identifies a user in your system
186
+ * @returns {Promise}
187
+ */
188
+ requestAccessTokenFromJwt({jwt}) {
189
+ let hydraUri = this.webex.internal.services.get('hydra', true);
190
+
191
+ if (hydraUri && hydraUri.slice(-1) !== '/') {
192
+ // add a `/` to hydra's uri from the services catalog so that
193
+ // it matches the current env service format.
194
+ hydraUri += '/';
195
+ }
196
+
197
+ hydraUri = hydraUri || process.env.HYDRA_SERVICE_URL || 'https://api.ciscospark.com/v1/';
198
+
199
+ return this.webex
200
+ .request({
201
+ method: 'POST',
202
+ uri: `${hydraUri}jwt/login`,
203
+ headers: {
204
+ authorization: jwt,
205
+ },
206
+ })
207
+ .then(({body}) => ({
208
+ access_token: body.token,
209
+ token_type: 'Bearer',
210
+ expires_in: body.expiresIn,
211
+ }))
212
+ .then((token) => {
213
+ this.webex.credentials.set({
214
+ supertoken: token,
215
+ });
216
+ })
217
+ .then(() => this.webex.internal.services.initServiceCatalogs());
218
+ },
219
+
220
+ /**
221
+ * Called by {@link WebexCore#logout()}. Redirects to the logout page
222
+ * @instance
223
+ * @memberof AuthorizationBrowser
224
+ * @param {Object} options
225
+ * @param {boolean} options.noRedirect if true, does not redirect
226
+ * @returns {Promise}
227
+ */
228
+ logout(options = {}) {
229
+ if (!options.noRedirect) {
230
+ this.webex.getWindow().location = this.webex.credentials.buildLogoutUrl(options);
231
+ }
232
+ },
233
+
234
+ /**
235
+ * Creates a jwt user token
236
+ * @param {object} options
237
+ * @param {String} options.issuer Guest Issuer ID
238
+ * @param {String} options.secretId Guest Secret ID
239
+ * @param {String} options.displayName Guest Display Name | optional
240
+ * @param {String} options.expiresIn
241
+ * @returns {Promise<object>}
242
+ */
243
+ async createJwt({issuer, secretId, displayName, expiresIn}) {
244
+ const secret = Buffer.from(secretId, 'base64');
245
+ const payload = {
246
+ "sub": `guest-user-${uuid()}`,
247
+ "iss": issuer,
248
+ "name": displayName || `Guest User - ${uuid()}`
249
+ };
250
+ const alg = 'HS256';
251
+
252
+ try {
253
+ const jwtToken = await new jose.SignJWT(payload)
254
+ .setProtectedHeader({alg})
255
+ .setExpirationTime(expiresIn)
256
+ .sign(secret);
257
+
258
+ return Promise.resolve({jwt: jwtToken});
259
+ } catch (e) {
260
+ return Promise.reject(e);
261
+ }
262
+ },
263
+
264
+ /**
265
+ * Checks if the result of the login redirect contains an error string
266
+ * @instance
267
+ * @memberof AuthorizationBrowser
268
+ * @param {Object} location
269
+ * @private
270
+ * @returns {Promise}
271
+ */
272
+ _checkForErrors(location) {
273
+ const {query} = location;
274
+
275
+ if (query && query.error) {
276
+ const ErrorConstructor = grantErrors.select(query.error);
277
+
278
+ throw new ErrorConstructor(query);
279
+ }
280
+ },
281
+
282
+ /**
283
+ * Removes no-longer needed values from the url (access token, csrf token, etc)
284
+ * @instance
285
+ * @memberof AuthorizationBrowser
286
+ * @param {Object} location
287
+ * @private
288
+ * @returns {Promise}
289
+ */
290
+ _cleanUrl(location) {
291
+ location = cloneDeep(location);
292
+ if (this.webex.getWindow().history && this.webex.getWindow().history.replaceState) {
293
+ [
294
+ 'access_token',
295
+ 'token_type',
296
+ 'expires_in',
297
+ 'refresh_token',
298
+ 'refresh_token_expires_in',
299
+ ].forEach((key) => Reflect.deleteProperty(location.hash, key));
300
+ if (!isEmpty(location.hash.state)) {
301
+ location.hash.state = base64.encode(
302
+ JSON.stringify(omit(location.hash.state, 'csrf_token'))
303
+ );
304
+ if (location.hash.state === EMPTY_OBJECT_STRING) {
305
+ Reflect.deleteProperty(location.hash, 'state');
306
+ }
307
+ } else {
308
+ Reflect.deleteProperty(location.hash, 'state');
309
+ }
310
+ location.hash = querystring.stringify(location.hash);
311
+ this.webex.getWindow().history.replaceState({}, null, url.format(location));
312
+ }
313
+ },
314
+
315
+ /**
316
+ * Generates a CSRF token and sticks in in sessionStorage
317
+ * @instance
318
+ * @memberof AuthorizationBrowser
319
+ * @private
320
+ * @returns {Promise}
321
+ */
322
+ _generateSecurityToken() {
323
+ this.logger.info('authorization: generating csrf token');
324
+
325
+ const token = uuid.v4();
326
+
327
+ this.webex.getWindow().sessionStorage.setItem('oauth2-csrf-token', token);
328
+
329
+ return token;
330
+ },
331
+
332
+ /**
333
+ * Parses the url hash into an access token object
334
+ * @instance
335
+ * @memberof AuthorizationBrowser
336
+ * @param {Object} location
337
+ * @private
338
+ * @returns {Object}
339
+ */
340
+ _parseHash(location) {
341
+ const hash = cloneDeep(location.hash);
342
+
343
+ if (hash) {
344
+ this._verifySecurityToken(hash);
345
+ }
346
+ if (!hash.access_token) {
347
+ this.ready = true;
348
+
349
+ return undefined;
350
+ }
351
+ if (hash.expires_in) {
352
+ hash.expires_in = parseInt(hash.expires_in, 10);
353
+ }
354
+ if (hash.refresh_token_expires_in) {
355
+ hash.refresh_token_expires_in = parseInt(hash.refresh_token_expires_in, 10);
356
+ }
357
+
358
+ return hash;
359
+ },
360
+
361
+ /**
362
+ * Checks if the CSRF token in sessionStorage is the same as the one returned
363
+ * in the url.
364
+ * @instance
365
+ * @memberof AuthorizationBrowser
366
+ * @param {Object} hash
367
+ * @private
368
+ * @returns {Promise}
369
+ */
370
+ _verifySecurityToken(hash) {
371
+ const sessionToken = this.webex.getWindow().sessionStorage.getItem(OAUTH2_CSRF_TOKEN);
372
+
373
+ this.webex.getWindow().sessionStorage.removeItem(OAUTH2_CSRF_TOKEN);
374
+ if (!sessionToken) {
375
+ return;
376
+ }
377
+
378
+ if (!hash.state) {
379
+ throw new Error(`Expected CSRF token ${sessionToken}, but not found in redirect hash`);
380
+ }
381
+
382
+ if (!hash.state.csrf_token) {
383
+ throw new Error(`Expected CSRF token ${sessionToken}, but not found in redirect hash`);
384
+ }
385
+
386
+ const token = hash.state.csrf_token;
387
+
388
+ if (token !== sessionToken) {
389
+ throw new Error(`CSRF token ${token} does not match stored token ${sessionToken}`);
390
+ }
391
+ },
392
+ });
393
+
394
+ export default Authorization;