@webex/plugin-authorization-browser-first-party 3.0.0-beta.4 → 3.0.0-beta.400
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 +10 -75
- package/dist/authorization.js.map +1 -1
- package/dist/config.js +0 -3
- package/dist/config.js.map +1 -1
- package/dist/index.js +1 -8
- package/dist/index.js.map +1 -1
- package/package.json +10 -10
- package/src/authorization.js +36 -36
- package/src/config.js +18 -17
- package/src/index.js +2 -5
- package/test/automation/fixtures/app.js +15 -15
- package/test/automation/fixtures/index.html +18 -15
- package/test/automation/spec/authorization-code-grant.js +86 -68
- package/test/unit/spec/authorization.js +202 -155
package/src/authorization.js
CHANGED
|
@@ -43,8 +43,8 @@ const Authorization = WebexPlugin.extend({
|
|
|
43
43
|
deps: ['isAuthorizing'],
|
|
44
44
|
fn() {
|
|
45
45
|
return this.isAuthorizing;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
48
|
},
|
|
49
49
|
|
|
50
50
|
session: {
|
|
@@ -56,12 +56,12 @@ const Authorization = WebexPlugin.extend({
|
|
|
56
56
|
*/
|
|
57
57
|
isAuthorizing: {
|
|
58
58
|
default: false,
|
|
59
|
-
type: 'boolean'
|
|
59
|
+
type: 'boolean',
|
|
60
60
|
},
|
|
61
61
|
ready: {
|
|
62
62
|
default: false,
|
|
63
|
-
type: 'boolean'
|
|
64
|
-
}
|
|
63
|
+
type: 'boolean',
|
|
64
|
+
},
|
|
65
65
|
},
|
|
66
66
|
|
|
67
67
|
namespace: 'Credentials',
|
|
@@ -90,8 +90,7 @@ const Authorization = WebexPlugin.extend({
|
|
|
90
90
|
|
|
91
91
|
if (location.query.state) {
|
|
92
92
|
location.query.state = JSON.parse(base64.decode(location.query.state));
|
|
93
|
-
}
|
|
94
|
-
else {
|
|
93
|
+
} else {
|
|
95
94
|
location.query.state = {};
|
|
96
95
|
}
|
|
97
96
|
|
|
@@ -99,7 +98,6 @@ const Authorization = WebexPlugin.extend({
|
|
|
99
98
|
|
|
100
99
|
this.webex.getWindow().sessionStorage.removeItem(OAUTH2_CODE_VERIFIER);
|
|
101
100
|
|
|
102
|
-
|
|
103
101
|
const {emailhash} = location.query.state;
|
|
104
102
|
|
|
105
103
|
this._verifySecurityToken(location.query);
|
|
@@ -107,15 +105,18 @@ const Authorization = WebexPlugin.extend({
|
|
|
107
105
|
|
|
108
106
|
// Wait until nextTick in case `credentials` hasn't initialized yet
|
|
109
107
|
process.nextTick(() => {
|
|
110
|
-
this.webex.internal.services
|
|
108
|
+
this.webex.internal.services
|
|
109
|
+
.collectPreauthCatalog({emailhash})
|
|
111
110
|
.catch(() => Promise.resolve())
|
|
112
111
|
.then(() => this.requestAuthorizationCodeGrant({code, codeVerifier}))
|
|
112
|
+
.catch((error) => {
|
|
113
|
+
this.logger.warn('authorization: failed initial authorization code grant request', error)
|
|
114
|
+
})
|
|
113
115
|
.then(() => {
|
|
114
116
|
this.ready = true;
|
|
115
117
|
});
|
|
116
118
|
});
|
|
117
119
|
|
|
118
|
-
|
|
119
120
|
return ret;
|
|
120
121
|
},
|
|
121
122
|
|
|
@@ -140,7 +141,6 @@ const Authorization = WebexPlugin.extend({
|
|
|
140
141
|
options.code_challenge = this._generateCodeChallenge();
|
|
141
142
|
options.code_challenge_method = 'S256';
|
|
142
143
|
|
|
143
|
-
|
|
144
144
|
return this.initiateAuthorizationCodeGrant(options);
|
|
145
145
|
},
|
|
146
146
|
|
|
@@ -155,7 +155,9 @@ const Authorization = WebexPlugin.extend({
|
|
|
155
155
|
*/
|
|
156
156
|
initiateAuthorizationCodeGrant(options) {
|
|
157
157
|
this.logger.info('authorization: initiating authorization code grant flow');
|
|
158
|
-
this.webex.getWindow().location = this.webex.credentials.buildLoginUrl(
|
|
158
|
+
this.webex.getWindow().location = this.webex.credentials.buildLoginUrl(
|
|
159
|
+
Object.assign({response_type: 'code'}, options)
|
|
160
|
+
);
|
|
159
161
|
|
|
160
162
|
return Promise.resolve();
|
|
161
163
|
},
|
|
@@ -174,7 +176,6 @@ const Authorization = WebexPlugin.extend({
|
|
|
174
176
|
}
|
|
175
177
|
},
|
|
176
178
|
|
|
177
|
-
|
|
178
179
|
@whileInFlight('isAuthorizing')
|
|
179
180
|
@oneFlight
|
|
180
181
|
/**
|
|
@@ -196,24 +197,25 @@ const Authorization = WebexPlugin.extend({
|
|
|
196
197
|
grant_type: 'authorization_code',
|
|
197
198
|
redirect_uri: this.config.redirect_uri,
|
|
198
199
|
code: options.code,
|
|
199
|
-
self_contained_token: true
|
|
200
|
+
self_contained_token: true,
|
|
200
201
|
};
|
|
201
202
|
|
|
202
203
|
if (options.codeVerifier) {
|
|
203
204
|
form.code_verifier = options.codeVerifier;
|
|
204
205
|
}
|
|
205
206
|
|
|
206
|
-
return this.webex
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
207
|
+
return this.webex
|
|
208
|
+
.request({
|
|
209
|
+
method: 'POST',
|
|
210
|
+
uri: this.config.tokenUrl,
|
|
211
|
+
form,
|
|
212
|
+
auth: {
|
|
213
|
+
user: this.config.client_id,
|
|
214
|
+
pass: this.config.client_secret,
|
|
215
|
+
sendImmediately: true,
|
|
216
|
+
},
|
|
217
|
+
shouldRefreshAccessToken: false,
|
|
218
|
+
})
|
|
217
219
|
.then((res) => {
|
|
218
220
|
this.webex.credentials.set({supertoken: res.body});
|
|
219
221
|
})
|
|
@@ -260,9 +262,10 @@ const Authorization = WebexPlugin.extend({
|
|
|
260
262
|
Reflect.deleteProperty(location.query, 'code');
|
|
261
263
|
if (isEmpty(omit(location.query.state, 'csrf_token'))) {
|
|
262
264
|
Reflect.deleteProperty(location.query, 'state');
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
|
|
265
|
+
} else {
|
|
266
|
+
location.query.state = base64.encode(
|
|
267
|
+
JSON.stringify(omit(location.query.state, 'csrf_token'))
|
|
268
|
+
);
|
|
266
269
|
}
|
|
267
270
|
location.search = querystring.stringify(location.query);
|
|
268
271
|
Reflect.deleteProperty(location, 'query');
|
|
@@ -283,16 +286,13 @@ const Authorization = WebexPlugin.extend({
|
|
|
283
286
|
// eslint-disable-next-line no-underscore-dangle
|
|
284
287
|
const safeCharacterMap = base64url._safe_map;
|
|
285
288
|
|
|
286
|
-
const codeVerifier = lodash
|
|
287
|
-
128,
|
|
288
|
-
|
|
289
|
-
).join('');
|
|
289
|
+
const codeVerifier = lodash
|
|
290
|
+
.times(128, () => safeCharacterMap[lodash.random(0, safeCharacterMap.length - 1)])
|
|
291
|
+
.join('');
|
|
290
292
|
|
|
291
293
|
const codeChallenge = CryptoJS.SHA256(codeVerifier).toString(base64url);
|
|
292
294
|
|
|
293
|
-
this.webex.getWindow().sessionStorage.setItem(
|
|
294
|
-
OAUTH2_CODE_VERIFIER, codeVerifier
|
|
295
|
-
);
|
|
295
|
+
this.webex.getWindow().sessionStorage.setItem(OAUTH2_CODE_VERIFIER, codeVerifier);
|
|
296
296
|
|
|
297
297
|
return codeChallenge;
|
|
298
298
|
},
|
|
@@ -344,7 +344,7 @@ const Authorization = WebexPlugin.extend({
|
|
|
344
344
|
if (token !== sessionToken) {
|
|
345
345
|
throw new Error(`CSRF token ${token} does not match stored token ${sessionToken}`);
|
|
346
346
|
}
|
|
347
|
-
}
|
|
347
|
+
},
|
|
348
348
|
});
|
|
349
349
|
|
|
350
350
|
export default Authorization;
|
package/src/config.js
CHANGED
|
@@ -15,23 +15,24 @@ export default {
|
|
|
15
15
|
|
|
16
16
|
refreshCallback(webex, token) {
|
|
17
17
|
/* eslint-disable camelcase */
|
|
18
|
-
return webex
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
18
|
+
return webex
|
|
19
|
+
.request({
|
|
20
|
+
method: 'POST',
|
|
21
|
+
uri: token.config.tokenUrl,
|
|
22
|
+
form: {
|
|
23
|
+
grant_type: 'refresh_token',
|
|
24
|
+
redirect_uri: token.config.redirect_uri,
|
|
25
|
+
refresh_token: token.refresh_token,
|
|
26
|
+
},
|
|
27
|
+
auth: {
|
|
28
|
+
user: token.config.client_id,
|
|
29
|
+
pass: token.config.client_secret,
|
|
30
|
+
sendImmediately: true,
|
|
31
|
+
},
|
|
32
|
+
shouldRefreshAccessToken: false,
|
|
33
|
+
})
|
|
33
34
|
.then((res) => res.body);
|
|
34
35
|
/* eslint-enable camelcase */
|
|
35
|
-
}
|
|
36
|
-
}
|
|
36
|
+
},
|
|
37
|
+
},
|
|
37
38
|
};
|
package/src/index.js
CHANGED
|
@@ -7,14 +7,11 @@ import {registerPlugin} from '@webex/webex-core';
|
|
|
7
7
|
import Authorization from './authorization';
|
|
8
8
|
import config from './config';
|
|
9
9
|
|
|
10
|
-
const proxies = [
|
|
11
|
-
'isAuthorizing',
|
|
12
|
-
'isAuthenticating'
|
|
13
|
-
];
|
|
10
|
+
const proxies = ['isAuthorizing', 'isAuthenticating'];
|
|
14
11
|
|
|
15
12
|
registerPlugin('authorization', Authorization, {
|
|
16
13
|
config,
|
|
17
|
-
proxies
|
|
14
|
+
proxies,
|
|
18
15
|
});
|
|
19
16
|
|
|
20
17
|
export {default} from './authorization';
|
|
@@ -11,22 +11,23 @@ import WebexCore from '@webex/webex-core';
|
|
|
11
11
|
|
|
12
12
|
import pkg from '../../../package';
|
|
13
13
|
|
|
14
|
-
const webex = window.webex = new WebexCore({
|
|
14
|
+
const webex = (window.webex = new WebexCore({
|
|
15
15
|
config: {
|
|
16
16
|
storage: {
|
|
17
|
-
boundedAdapter: new StorageAdapterLocalStorage('webex')
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
});
|
|
17
|
+
boundedAdapter: new StorageAdapterLocalStorage('webex'),
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
}));
|
|
21
21
|
|
|
22
22
|
webex.once('ready', () => {
|
|
23
23
|
if (webex.canAuthorize) {
|
|
24
24
|
document.getElementById('access-token').innerHTML = webex.credentials.supertoken.access_token;
|
|
25
25
|
document.getElementById('refresh-token').innerHTML = webex.credentials.supertoken.refresh_token;
|
|
26
26
|
|
|
27
|
-
webex
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
webex
|
|
28
|
+
.request({
|
|
29
|
+
uri: 'https://locus-a.wbx2.com/locus/api/v1/ping',
|
|
30
|
+
})
|
|
30
31
|
.then(() => {
|
|
31
32
|
document.getElementById('ping-complete').innerHTML = 'success';
|
|
32
33
|
});
|
|
@@ -41,18 +42,17 @@ document.getElementById('initiate-authorization-code-grant').addEventListener('c
|
|
|
41
42
|
webex.authorization.initiateLogin({
|
|
42
43
|
state: {
|
|
43
44
|
exchange: false,
|
|
44
|
-
name: pkg.name
|
|
45
|
-
}
|
|
45
|
+
name: pkg.name,
|
|
46
|
+
},
|
|
46
47
|
});
|
|
47
48
|
});
|
|
48
49
|
|
|
49
50
|
document.getElementById('token-refresh').addEventListener('click', () => {
|
|
50
51
|
document.getElementById('access-token').innerHTML = '';
|
|
51
|
-
webex.refresh({force: true})
|
|
52
|
-
.
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
});
|
|
52
|
+
webex.refresh({force: true}).then(() => {
|
|
53
|
+
document.getElementById('access-token').innerHTML = webex.credentials.supertoken.access_token;
|
|
54
|
+
document.getElementById('refresh-token').innerHTML = webex.credentials.supertoken.refresh_token;
|
|
55
|
+
});
|
|
56
56
|
});
|
|
57
57
|
|
|
58
58
|
document.getElementById('logout').addEventListener('click', () => {
|
|
@@ -1,21 +1,24 @@
|
|
|
1
1
|
<!DOCTYPE html>
|
|
2
2
|
<html>
|
|
3
|
-
<head>
|
|
4
|
-
|
|
5
|
-
</head>
|
|
6
|
-
<body class="authorization-automation-test">
|
|
7
|
-
|
|
8
|
-
|
|
3
|
+
<head>
|
|
4
|
+
<title>Authorization Automation Test</title>
|
|
5
|
+
</head>
|
|
6
|
+
<body class="authorization-automation-test">
|
|
7
|
+
<button title="Login with Authorization Code Grant" id="initiate-authorization-code-grant">
|
|
8
|
+
Login with Authorization Code Grant
|
|
9
|
+
</button>
|
|
10
|
+
<button title="Refresh Access Token" id="token-refresh">Refresh Access Token</button>
|
|
9
11
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
<h1>Access Token</h1>
|
|
13
|
+
<div id="access-token"></div>
|
|
14
|
+
<h1>Refresh Token</h1>
|
|
15
|
+
<div id="refresh-token"></div>
|
|
14
16
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
+
<h1>Pinging WDM</h1>
|
|
18
|
+
<div id="ping-complete"></div>
|
|
17
19
|
|
|
18
|
-
|
|
20
|
+
<button title="Logout" id="logout">Logout</button>
|
|
19
21
|
|
|
20
|
-
|
|
21
|
-
</body>
|
|
22
|
+
<script src="app.js"></script>
|
|
23
|
+
</body>
|
|
24
|
+
</html>
|
|
@@ -16,9 +16,17 @@ const redirectUri = process.env.WEBEX_REDIRECT_URI || process.env.REDIRECT_URI;
|
|
|
16
16
|
// for test users in EU (Federation) and US
|
|
17
17
|
// Also try US user with Federation enabled
|
|
18
18
|
const runs = [
|
|
19
|
-
{
|
|
19
|
+
{
|
|
20
|
+
it: 'with EU user with Federation enabled',
|
|
21
|
+
EUUser: true,
|
|
22
|
+
attrs: {config: {credentials: {federation: true}}},
|
|
23
|
+
},
|
|
20
24
|
{it: 'with US user without Federation enabled', EUUser: false, attrs: {}},
|
|
21
|
-
{
|
|
25
|
+
{
|
|
26
|
+
it: 'with US user with Federation enabled',
|
|
27
|
+
EUUser: false,
|
|
28
|
+
attrs: {config: {credentials: {federation: true}}},
|
|
29
|
+
},
|
|
22
30
|
];
|
|
23
31
|
|
|
24
32
|
runs.forEach((run) => {
|
|
@@ -35,98 +43,108 @@ runs.forEach((run) => {
|
|
|
35
43
|
testUserParm.config = {orgId: process.env.EU_PRIMARY_ORG_ID};
|
|
36
44
|
}
|
|
37
45
|
|
|
38
|
-
before(() =>
|
|
39
|
-
.then((users) => {
|
|
46
|
+
before(() =>
|
|
47
|
+
testUsers.create(testUserParm).then((users) => {
|
|
40
48
|
user = users[0];
|
|
41
|
-
})
|
|
49
|
+
})
|
|
50
|
+
);
|
|
42
51
|
|
|
43
|
-
before(() =>
|
|
44
|
-
.then((b) => {
|
|
52
|
+
before(() =>
|
|
53
|
+
createBrowser(pkg).then((b) => {
|
|
45
54
|
browser = b;
|
|
46
|
-
})
|
|
55
|
+
})
|
|
56
|
+
);
|
|
47
57
|
|
|
48
58
|
after(() => browser && browser.printLogs());
|
|
49
59
|
|
|
50
|
-
after(
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
60
|
+
after(
|
|
61
|
+
() =>
|
|
62
|
+
browser &&
|
|
63
|
+
browser.quit().catch((reason) => {
|
|
64
|
+
console.warn(reason);
|
|
65
|
+
})
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
it('authorizes a user', () =>
|
|
69
|
+
browser
|
|
70
|
+
.get(`${redirectUri}/${pkg.name}`)
|
|
71
|
+
.waitForElementByClassName('ready')
|
|
72
|
+
.title()
|
|
59
73
|
.should.eventually.become('Authorization Automation Test')
|
|
60
|
-
|
|
74
|
+
.waitForElementByCssSelector('[title="Login with Authorization Code Grant"]')
|
|
61
75
|
.click()
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
76
|
+
.login(user)
|
|
77
|
+
.waitForElementByClassName('authorization-automation-test')
|
|
78
|
+
.waitForElementById('refresh-token')
|
|
65
79
|
.text()
|
|
66
|
-
|
|
67
|
-
|
|
80
|
+
.should.eventually.not.be.empty.waitForElementByCssSelector(
|
|
81
|
+
'#ping-complete:not(:empty)'
|
|
82
|
+
)
|
|
68
83
|
.text()
|
|
69
|
-
|
|
84
|
+
.should.eventually.become('success'));
|
|
70
85
|
|
|
71
|
-
it('is still logged in after reloading the page', () =>
|
|
72
|
-
|
|
73
|
-
.
|
|
74
|
-
.should.eventually.not.be.empty
|
|
75
|
-
.get(`${redirectUri}/${pkg.name}`)
|
|
76
|
-
.sleep(500)
|
|
77
|
-
.waitForElementById('access-token')
|
|
86
|
+
it('is still logged in after reloading the page', () =>
|
|
87
|
+
browser
|
|
88
|
+
.waitForElementById('access-token')
|
|
78
89
|
.text()
|
|
79
|
-
|
|
90
|
+
.should.eventually.not.be.empty.get(`${redirectUri}/${pkg.name}`)
|
|
91
|
+
.sleep(500)
|
|
92
|
+
.waitForElementById('access-token')
|
|
93
|
+
.text().should.eventually.not.be.empty);
|
|
80
94
|
|
|
81
|
-
it(
|
|
95
|
+
it("refreshes the user's access token", () => {
|
|
82
96
|
let accessToken = '';
|
|
83
97
|
|
|
84
|
-
return
|
|
85
|
-
|
|
98
|
+
return (
|
|
99
|
+
browser
|
|
100
|
+
.waitForElementByCssSelector('#access-token:not(:empty)')
|
|
86
101
|
.text()
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
102
|
+
.then((text) => {
|
|
103
|
+
accessToken = text;
|
|
104
|
+
assert.isString(accessToken);
|
|
105
|
+
assert.isAbove(accessToken.length, 0);
|
|
106
|
+
|
|
107
|
+
return browser;
|
|
108
|
+
})
|
|
109
|
+
.waitForElementByCssSelector('[title="Refresh Access Token"]')
|
|
95
110
|
.click()
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
111
|
+
// Not thrilled by a sleep, but we just need to give the button click
|
|
112
|
+
// enough time to clear the #access-token box
|
|
113
|
+
.sleep(500)
|
|
114
|
+
.waitForElementByCssSelector('#access-token:not(:empty)')
|
|
100
115
|
.text()
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
116
|
+
.then((text) => {
|
|
117
|
+
assert.isString(text);
|
|
118
|
+
assert.isAbove(text.length, 0);
|
|
119
|
+
assert.notEqual(text, accessToken);
|
|
120
|
+
|
|
121
|
+
return browser;
|
|
122
|
+
})
|
|
123
|
+
);
|
|
108
124
|
});
|
|
109
125
|
|
|
110
|
-
it('logs out a user', () =>
|
|
111
|
-
|
|
126
|
+
it('logs out a user', () =>
|
|
127
|
+
browser
|
|
128
|
+
.title()
|
|
112
129
|
.should.eventually.become('Authorization Automation Test')
|
|
113
|
-
|
|
130
|
+
.waitForElementByCssSelector('[title="Logout"]')
|
|
114
131
|
.click()
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
132
|
+
// We need to revoke three tokens before the window.location assignment.
|
|
133
|
+
// So far, I haven't found any ques to wait for, so sleep seems to be
|
|
134
|
+
// the only option.
|
|
135
|
+
.sleep(3000)
|
|
136
|
+
.title()
|
|
120
137
|
.should.eventually.become('Redirect Dispatcher')
|
|
121
|
-
|
|
122
|
-
|
|
138
|
+
.get(`${redirectUri}/${pkg.name}`)
|
|
139
|
+
.title()
|
|
123
140
|
.should.eventually.become('Authorization Automation Test')
|
|
124
|
-
|
|
141
|
+
.waitForElementById('access-token')
|
|
125
142
|
.text()
|
|
126
|
-
|
|
127
|
-
|
|
143
|
+
.should.eventually.be.empty.waitForElementByCssSelector(
|
|
144
|
+
'[title="Login with Authorization Code Grant"]'
|
|
145
|
+
)
|
|
128
146
|
.click()
|
|
129
|
-
|
|
147
|
+
.waitForElementById('IDToken1'));
|
|
130
148
|
});
|
|
131
149
|
});
|
|
132
150
|
});
|