@webex/plugin-authorization-browser-first-party 3.0.0-beta.9 → 3.0.0-bnr.2
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 +8 -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/dist/plugin-authorization-browser-first-party.d.ts +21 -0
- package/dist/tsdoc-metadata.json +11 -0
- package/dist/types/authorization.d.ts +12 -0
- package/dist/types/config.d.ts +7 -0
- package/dist/types/index.d.ts +2 -0
- package/package.json +10 -10
- package/src/authorization.js +33 -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 +177 -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,7 +105,8 @@ 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}))
|
|
113
112
|
.then(() => {
|
|
@@ -115,7 +114,6 @@ const Authorization = WebexPlugin.extend({
|
|
|
115
114
|
});
|
|
116
115
|
});
|
|
117
116
|
|
|
118
|
-
|
|
119
117
|
return ret;
|
|
120
118
|
},
|
|
121
119
|
|
|
@@ -140,7 +138,6 @@ const Authorization = WebexPlugin.extend({
|
|
|
140
138
|
options.code_challenge = this._generateCodeChallenge();
|
|
141
139
|
options.code_challenge_method = 'S256';
|
|
142
140
|
|
|
143
|
-
|
|
144
141
|
return this.initiateAuthorizationCodeGrant(options);
|
|
145
142
|
},
|
|
146
143
|
|
|
@@ -155,7 +152,9 @@ const Authorization = WebexPlugin.extend({
|
|
|
155
152
|
*/
|
|
156
153
|
initiateAuthorizationCodeGrant(options) {
|
|
157
154
|
this.logger.info('authorization: initiating authorization code grant flow');
|
|
158
|
-
this.webex.getWindow().location = this.webex.credentials.buildLoginUrl(
|
|
155
|
+
this.webex.getWindow().location = this.webex.credentials.buildLoginUrl(
|
|
156
|
+
Object.assign({response_type: 'code'}, options)
|
|
157
|
+
);
|
|
159
158
|
|
|
160
159
|
return Promise.resolve();
|
|
161
160
|
},
|
|
@@ -174,7 +173,6 @@ const Authorization = WebexPlugin.extend({
|
|
|
174
173
|
}
|
|
175
174
|
},
|
|
176
175
|
|
|
177
|
-
|
|
178
176
|
@whileInFlight('isAuthorizing')
|
|
179
177
|
@oneFlight
|
|
180
178
|
/**
|
|
@@ -196,24 +194,25 @@ const Authorization = WebexPlugin.extend({
|
|
|
196
194
|
grant_type: 'authorization_code',
|
|
197
195
|
redirect_uri: this.config.redirect_uri,
|
|
198
196
|
code: options.code,
|
|
199
|
-
self_contained_token: true
|
|
197
|
+
self_contained_token: true,
|
|
200
198
|
};
|
|
201
199
|
|
|
202
200
|
if (options.codeVerifier) {
|
|
203
201
|
form.code_verifier = options.codeVerifier;
|
|
204
202
|
}
|
|
205
203
|
|
|
206
|
-
return this.webex
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
204
|
+
return this.webex
|
|
205
|
+
.request({
|
|
206
|
+
method: 'POST',
|
|
207
|
+
uri: this.config.tokenUrl,
|
|
208
|
+
form,
|
|
209
|
+
auth: {
|
|
210
|
+
user: this.config.client_id,
|
|
211
|
+
pass: this.config.client_secret,
|
|
212
|
+
sendImmediately: true,
|
|
213
|
+
},
|
|
214
|
+
shouldRefreshAccessToken: false,
|
|
215
|
+
})
|
|
217
216
|
.then((res) => {
|
|
218
217
|
this.webex.credentials.set({supertoken: res.body});
|
|
219
218
|
})
|
|
@@ -260,9 +259,10 @@ const Authorization = WebexPlugin.extend({
|
|
|
260
259
|
Reflect.deleteProperty(location.query, 'code');
|
|
261
260
|
if (isEmpty(omit(location.query.state, 'csrf_token'))) {
|
|
262
261
|
Reflect.deleteProperty(location.query, 'state');
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
|
|
262
|
+
} else {
|
|
263
|
+
location.query.state = base64.encode(
|
|
264
|
+
JSON.stringify(omit(location.query.state, 'csrf_token'))
|
|
265
|
+
);
|
|
266
266
|
}
|
|
267
267
|
location.search = querystring.stringify(location.query);
|
|
268
268
|
Reflect.deleteProperty(location, 'query');
|
|
@@ -283,16 +283,13 @@ const Authorization = WebexPlugin.extend({
|
|
|
283
283
|
// eslint-disable-next-line no-underscore-dangle
|
|
284
284
|
const safeCharacterMap = base64url._safe_map;
|
|
285
285
|
|
|
286
|
-
const codeVerifier = lodash
|
|
287
|
-
128,
|
|
288
|
-
|
|
289
|
-
).join('');
|
|
286
|
+
const codeVerifier = lodash
|
|
287
|
+
.times(128, () => safeCharacterMap[lodash.random(0, safeCharacterMap.length - 1)])
|
|
288
|
+
.join('');
|
|
290
289
|
|
|
291
290
|
const codeChallenge = CryptoJS.SHA256(codeVerifier).toString(base64url);
|
|
292
291
|
|
|
293
|
-
this.webex.getWindow().sessionStorage.setItem(
|
|
294
|
-
OAUTH2_CODE_VERIFIER, codeVerifier
|
|
295
|
-
);
|
|
292
|
+
this.webex.getWindow().sessionStorage.setItem(OAUTH2_CODE_VERIFIER, codeVerifier);
|
|
296
293
|
|
|
297
294
|
return codeChallenge;
|
|
298
295
|
},
|
|
@@ -344,7 +341,7 @@ const Authorization = WebexPlugin.extend({
|
|
|
344
341
|
if (token !== sessionToken) {
|
|
345
342
|
throw new Error(`CSRF token ${token} does not match stored token ${sessionToken}`);
|
|
346
343
|
}
|
|
347
|
-
}
|
|
344
|
+
},
|
|
348
345
|
});
|
|
349
346
|
|
|
350
347
|
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
|
});
|