@webex/webex-core 3.12.0-next.5 → 3.12.0-next.51
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -2
- package/dist/config.js +38 -0
- package/dist/config.js.map +1 -1
- package/dist/credentials-config.js +12 -0
- package/dist/credentials-config.js.map +1 -1
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -1
- package/dist/interceptors/catalog-url.js +87 -0
- package/dist/interceptors/catalog-url.js.map +1 -0
- package/dist/interceptors/payload-transformer.js +25 -4
- package/dist/interceptors/payload-transformer.js.map +1 -1
- package/dist/lib/batcher.js +23 -7
- package/dist/lib/batcher.js.map +1 -1
- package/dist/lib/credentials/credentials.js +103 -4
- package/dist/lib/credentials/credentials.js.map +1 -1
- package/dist/lib/credentials/token.js +1 -1
- package/dist/lib/domains.js +90 -0
- package/dist/lib/domains.js.map +1 -0
- package/dist/lib/services/service-catalog.js +108 -18
- package/dist/lib/services/service-catalog.js.map +1 -1
- package/dist/lib/services/services.js +232 -34
- package/dist/lib/services/services.js.map +1 -1
- package/dist/lib/services-v2/service-catalog.js +14 -13
- package/dist/lib/services-v2/service-catalog.js.map +1 -1
- package/dist/lib/services-v2/services-v2.js +241 -37
- package/dist/lib/services-v2/services-v2.js.map +1 -1
- package/dist/plugins/logger.js +1 -1
- package/dist/webex-core.js +11 -3
- package/dist/webex-core.js.map +1 -1
- package/package.json +13 -13
- package/src/config.js +42 -0
- package/src/credentials-config.js +13 -0
- package/src/index.js +1 -0
- package/src/interceptors/catalog-url.js +66 -0
- package/src/interceptors/payload-transformer.js +23 -1
- package/src/lib/batcher.js +25 -10
- package/src/lib/credentials/credentials.js +93 -3
- package/src/lib/domains.ts +94 -0
- package/src/lib/services/service-catalog.js +112 -16
- package/src/lib/services/services.js +208 -20
- package/src/lib/services-v2/service-catalog.ts +16 -12
- package/src/lib/services-v2/services-v2.ts +218 -21
- package/src/webex-core.js +8 -0
- package/test/fixtures/activation-email.ts +22 -0
- package/test/integration/spec/services/services.js +183 -112
- package/test/integration/spec/services-v2/services-v2.js +169 -98
- package/test/unit/spec/credentials/credentials.js +247 -3
- package/test/unit/spec/interceptors/auth.js +56 -0
- package/test/unit/spec/interceptors/catalog-url.js +224 -0
- package/test/unit/spec/interceptors/payload-transformer.js +119 -1
- package/test/unit/spec/lib/batcher.js +56 -0
- package/test/unit/spec/services/service-catalog.js +368 -11
- package/test/unit/spec/services/services.js +440 -2
- package/test/unit/spec/services-v2/service-catalog.ts +134 -11
- package/test/unit/spec/services-v2/services-v2.ts +467 -0
- package/test/unit/spec/webex-core.js +77 -1
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import '@webex/internal-plugin-device';
|
|
6
6
|
|
|
7
7
|
import {assert} from '@webex/test-helper-chai';
|
|
8
|
-
import {flaky} from '@webex/test-helper-mocha';
|
|
8
|
+
import {flaky, retryOn429} from '@webex/test-helper-mocha';
|
|
9
9
|
import WebexCore, {
|
|
10
10
|
ServiceCatalogV2,
|
|
11
11
|
ServiceDetail,
|
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
formattedServiceHostmapEntryTest,
|
|
26
26
|
serviceHostmapV2,
|
|
27
27
|
} from '../../../fixtures/host-catalog-v2';
|
|
28
|
+
import {createActivationEmail} from '../../../fixtures/activation-email';
|
|
28
29
|
|
|
29
30
|
// /* eslint-disable no-underscore-dangle */
|
|
30
31
|
describe('webex-core', () => {
|
|
@@ -320,11 +321,13 @@ describe('webex-core', () => {
|
|
|
320
321
|
services._loadCatalogFromCache = sinon.stub().resolves(false);
|
|
321
322
|
services.initServiceCatalogs = sinon.stub().resolves();
|
|
322
323
|
services.initialize();
|
|
324
|
+
// The mode-specific ('ready'/'loaded') listener is registered inside the
|
|
325
|
+
// change:config handler, so fire change:config first, then 'ready'.
|
|
326
|
+
webex.trigger('change:config');
|
|
323
327
|
webex.trigger('ready');
|
|
324
328
|
// Wait for the async 'ready' handler to complete
|
|
325
329
|
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
326
330
|
assert.called(services.initServiceCatalogs);
|
|
327
|
-
assert.isTrue(catalog.isReady);
|
|
328
331
|
});
|
|
329
332
|
|
|
330
333
|
it('should collect different catalogs based on OrgId region', () =>
|
|
@@ -341,6 +344,45 @@ describe('webex-core', () => {
|
|
|
341
344
|
done();
|
|
342
345
|
}, 2000);
|
|
343
346
|
});
|
|
347
|
+
|
|
348
|
+
it('blocks webex.ready until services.ready flips when waitForCatalogInit is enabled', async () => {
|
|
349
|
+
const gatedWebex = new WebexCore({
|
|
350
|
+
credentials: {supertoken: webexUser.token},
|
|
351
|
+
config: {services: {waitForCatalogInit: true}},
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
// Before init settles, webex.ready must be false because services.ready
|
|
355
|
+
// is a dependency and starts false in the gated path.
|
|
356
|
+
assert.isFalse(gatedWebex.internal.services.ready, 'services.ready should start false');
|
|
357
|
+
assert.isFalse(
|
|
358
|
+
gatedWebex.ready,
|
|
359
|
+
'webex.ready should not fire while services.ready is false'
|
|
360
|
+
);
|
|
361
|
+
|
|
362
|
+
// Wait up to 30s for services init to complete and flip ready.
|
|
363
|
+
await new Promise((resolve, reject) => {
|
|
364
|
+
if (gatedWebex.internal.services.ready) {
|
|
365
|
+
resolve();
|
|
366
|
+
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
const timer = setTimeout(
|
|
370
|
+
() => reject(new Error('timed out waiting for services.ready')),
|
|
371
|
+
30_000
|
|
372
|
+
);
|
|
373
|
+
|
|
374
|
+
gatedWebex.internal.services.once('change:ready', () => {
|
|
375
|
+
clearTimeout(timer);
|
|
376
|
+
resolve();
|
|
377
|
+
});
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
assert.isTrue(
|
|
381
|
+
gatedWebex.internal.services.ready,
|
|
382
|
+
'services.ready should flip true after init settles'
|
|
383
|
+
);
|
|
384
|
+
assert.isTrue(gatedWebex.ready, 'webex.ready should fire once services.ready flips');
|
|
385
|
+
});
|
|
344
386
|
});
|
|
345
387
|
|
|
346
388
|
describe('#initServiceCatalogs()', () => {
|
|
@@ -790,18 +832,22 @@ describe('webex-core', () => {
|
|
|
790
832
|
}));
|
|
791
833
|
|
|
792
834
|
it('validates an authorized user and webex instance', () =>
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
835
|
+
retryOn429(() =>
|
|
836
|
+
services.validateUser({email: webexUser.email}).then((r) => {
|
|
837
|
+
assert.hasAllKeys(r, ['activated', 'exists', 'user', 'details']);
|
|
838
|
+
assert.equal(r.activated, true);
|
|
839
|
+
assert.equal(r.exists, true);
|
|
840
|
+
})
|
|
841
|
+
));
|
|
798
842
|
|
|
799
843
|
it('validates an authorized EU user and webex instance', () =>
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
844
|
+
retryOn429(() =>
|
|
845
|
+
servicesEU.validateUser({email: webexUserEU.email}).then((r) => {
|
|
846
|
+
assert.hasAllKeys(r, ['activated', 'exists', 'user', 'details']);
|
|
847
|
+
assert.equal(r.activated, true);
|
|
848
|
+
assert.equal(r.exists, true);
|
|
849
|
+
})
|
|
850
|
+
));
|
|
805
851
|
|
|
806
852
|
it("returns a rejected promise if the provided email isn't valid", () =>
|
|
807
853
|
unauthServices
|
|
@@ -814,113 +860,134 @@ describe('webex-core', () => {
|
|
|
814
860
|
}));
|
|
815
861
|
|
|
816
862
|
it('validates a non-existing user', () =>
|
|
817
|
-
|
|
818
|
-
.validateUser({email:
|
|
819
|
-
.then((r) => {
|
|
863
|
+
retryOn429(() =>
|
|
864
|
+
unauthServices.validateUser({email: createActivationEmail()}).then((r) => {
|
|
820
865
|
assert.hasAllKeys(r, ['activated', 'exists', 'user', 'details']);
|
|
821
866
|
assert.equal(r.activated, false);
|
|
822
867
|
assert.equal(r.exists, false);
|
|
823
|
-
})
|
|
868
|
+
})
|
|
869
|
+
));
|
|
824
870
|
|
|
825
871
|
it('validates new user with activationOptions suppressEmail false', () =>
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
872
|
+
retryOn429(() =>
|
|
873
|
+
unauthServices
|
|
874
|
+
.validateUser({
|
|
875
|
+
email: createActivationEmail(),
|
|
876
|
+
activationOptions: {suppressEmail: false},
|
|
877
|
+
})
|
|
878
|
+
.then((r) => {
|
|
879
|
+
assert.hasAllKeys(r, ['activated', 'exists', 'user', 'details']);
|
|
880
|
+
assert.equal(r.activated, false);
|
|
881
|
+
assert.equal(r.exists, false);
|
|
882
|
+
assert.equal(r.user.verificationEmailTriggered, true);
|
|
883
|
+
})
|
|
884
|
+
));
|
|
837
885
|
|
|
838
886
|
it('validates new user with activationOptions suppressEmail true', () =>
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
887
|
+
retryOn429(() =>
|
|
888
|
+
unauthServices
|
|
889
|
+
.validateUser({
|
|
890
|
+
email: createActivationEmail(),
|
|
891
|
+
activationOptions: {suppressEmail: true},
|
|
892
|
+
})
|
|
893
|
+
.then((r) => {
|
|
894
|
+
assert.hasAllKeys(r, ['activated', 'exists', 'user', 'details']);
|
|
895
|
+
assert.equal(r.activated, false);
|
|
896
|
+
assert.equal(r.exists, false);
|
|
897
|
+
assert.equal(r.user.verificationEmailTriggered, false);
|
|
898
|
+
})
|
|
899
|
+
));
|
|
850
900
|
|
|
851
901
|
it('validates an inactive user', () => {
|
|
852
902
|
const inactive = 'webex.web.client+nonactivated@gmail.com';
|
|
853
903
|
|
|
854
|
-
return
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
904
|
+
return retryOn429(() =>
|
|
905
|
+
unauthServices
|
|
906
|
+
.validateUser({email: inactive, activationOptions: {suppressEmail: true}})
|
|
907
|
+
.then((r) => {
|
|
908
|
+
assert.hasAllKeys(r, ['activated', 'exists', 'user', 'details']);
|
|
909
|
+
assert.equal(r.activated, false, 'activated');
|
|
910
|
+
assert.equal(r.exists, true, 'exists');
|
|
911
|
+
})
|
|
912
|
+
).catch(() => {
|
|
913
|
+
assert(true);
|
|
914
|
+
});
|
|
864
915
|
});
|
|
865
916
|
|
|
866
917
|
it('validates an existing user', () =>
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
918
|
+
retryOn429(() =>
|
|
919
|
+
unauthServices.validateUser({email: webexUser.email}).then((r) => {
|
|
920
|
+
assert.hasAllKeys(r, ['activated', 'exists', 'user', 'details']);
|
|
921
|
+
assert.equal(r.activated, true);
|
|
922
|
+
assert.equal(r.exists, true);
|
|
923
|
+
})
|
|
924
|
+
));
|
|
872
925
|
|
|
873
926
|
it('validates an existing EU user', () =>
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
927
|
+
retryOn429(() =>
|
|
928
|
+
unauthServices.validateUser({email: webexUserEU.email}).then((r) => {
|
|
929
|
+
assert.hasAllKeys(r, ['activated', 'exists', 'user', 'details']);
|
|
930
|
+
assert.equal(r.activated, true);
|
|
931
|
+
assert.equal(r.exists, true);
|
|
932
|
+
})
|
|
933
|
+
));
|
|
879
934
|
|
|
880
935
|
it('sends the prelogin user id as undefined when not specified', () => {
|
|
881
936
|
const requestStub = sandbox.spy(unauthServices, 'request');
|
|
882
937
|
|
|
883
|
-
return
|
|
884
|
-
.
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
938
|
+
return retryOn429(() => {
|
|
939
|
+
requestStub.resetHistory();
|
|
940
|
+
|
|
941
|
+
return unauthServices
|
|
942
|
+
.validateUser({
|
|
943
|
+
email: createActivationEmail(),
|
|
944
|
+
activationOptions: {suppressEmail: true},
|
|
945
|
+
})
|
|
946
|
+
.then(() => {
|
|
947
|
+
assert.isUndefined(getActivationRequest(requestStub).headers['x-prelogin-userid']);
|
|
948
|
+
});
|
|
949
|
+
});
|
|
891
950
|
});
|
|
892
951
|
|
|
893
952
|
it('sends the prelogin user id as provided when specified', () => {
|
|
894
953
|
const requestStub = sandbox.spy(unauthServices, 'request');
|
|
895
954
|
const preloginUserId = uuid.v4();
|
|
896
955
|
|
|
897
|
-
return
|
|
898
|
-
.
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
956
|
+
return retryOn429(() => {
|
|
957
|
+
requestStub.resetHistory();
|
|
958
|
+
|
|
959
|
+
return unauthServices
|
|
960
|
+
.validateUser({
|
|
961
|
+
email: createActivationEmail(),
|
|
962
|
+
activationOptions: {suppressEmail: true},
|
|
963
|
+
preloginUserId,
|
|
964
|
+
})
|
|
965
|
+
.then(() => {
|
|
966
|
+
assert.strictEqual(
|
|
967
|
+
getActivationRequest(requestStub).headers['x-prelogin-userid'],
|
|
968
|
+
preloginUserId
|
|
969
|
+
);
|
|
970
|
+
});
|
|
971
|
+
});
|
|
909
972
|
});
|
|
910
973
|
|
|
911
974
|
it('uses the license service by default', () => {
|
|
912
975
|
const requestStub = sandbox.spy(unauthServices, 'request');
|
|
913
976
|
|
|
914
|
-
return
|
|
915
|
-
.
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
977
|
+
return retryOn429(() => {
|
|
978
|
+
requestStub.resetHistory();
|
|
979
|
+
|
|
980
|
+
return unauthServices
|
|
981
|
+
.validateUser({
|
|
982
|
+
email: createActivationEmail(),
|
|
983
|
+
activationOptions: {suppressEmail: true},
|
|
984
|
+
})
|
|
985
|
+
.then(() => {
|
|
986
|
+
const request = getActivationRequest(requestStub, false);
|
|
987
|
+
assert.strictEqual(request.service, 'license');
|
|
988
|
+
assert.strictEqual(request.resource, 'users/activations');
|
|
989
|
+
});
|
|
990
|
+
});
|
|
924
991
|
});
|
|
925
992
|
|
|
926
993
|
it('uses the user-onboarding service when useUserOnboardingServiceForActivations config is true', () => {
|
|
@@ -934,16 +1001,20 @@ describe('webex-core', () => {
|
|
|
934
1001
|
const userOnboardingServices = userOnboardingWebex.internal.services;
|
|
935
1002
|
const requestStub = sandbox.spy(userOnboardingServices, 'request');
|
|
936
1003
|
|
|
937
|
-
return
|
|
938
|
-
.
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
1004
|
+
return retryOn429(() => {
|
|
1005
|
+
requestStub.resetHistory();
|
|
1006
|
+
|
|
1007
|
+
return userOnboardingServices
|
|
1008
|
+
.validateUser({
|
|
1009
|
+
email: createActivationEmail(),
|
|
1010
|
+
activationOptions: {suppressEmail: true},
|
|
1011
|
+
})
|
|
1012
|
+
.then(() => {
|
|
1013
|
+
const request = getActivationRequest(requestStub, true);
|
|
1014
|
+
assert.strictEqual(request.service, 'user-onboarding');
|
|
1015
|
+
assert.strictEqual(request.resource, 'api/v1/users/activations');
|
|
1016
|
+
});
|
|
1017
|
+
});
|
|
947
1018
|
});
|
|
948
1019
|
});
|
|
949
1020
|
|
|
@@ -7,7 +7,7 @@ import {assert} from '@webex/test-helper-chai';
|
|
|
7
7
|
import sinon from 'sinon';
|
|
8
8
|
import MockWebex from '@webex/test-helper-mock-webex';
|
|
9
9
|
import {Credentials, Token, grantErrors} from '@webex/webex-core';
|
|
10
|
-
import {inBrowser} from '@webex/common';
|
|
10
|
+
import {inBrowser, base64} from '@webex/common';
|
|
11
11
|
import FakeTimers from '@sinonjs/fake-timers';
|
|
12
12
|
import {skipInBrowser} from '@webex/test-helper-mocha';
|
|
13
13
|
import Logger from '@webex/plugin-logger';
|
|
@@ -183,6 +183,10 @@ describe('webex-core', () => {
|
|
|
183
183
|
webex.credentials.buildLoginUrl({state: 'state'});
|
|
184
184
|
}, /if specified, `options.state` must be an object/);
|
|
185
185
|
|
|
186
|
+
assert.throws(() => {
|
|
187
|
+
webex.credentials.buildLoginUrl({state: null});
|
|
188
|
+
}, /if specified, `options.state` must be an object/);
|
|
189
|
+
|
|
186
190
|
assert.doesNotThrow(() => {
|
|
187
191
|
webex.credentials.buildLoginUrl({state: {}});
|
|
188
192
|
}, /if specified, `options.state` must be an object/);
|
|
@@ -232,6 +236,132 @@ describe('webex-core', () => {
|
|
|
232
236
|
});
|
|
233
237
|
});
|
|
234
238
|
|
|
239
|
+
describe('#buildThirdPartyLoginUrl()', () => {
|
|
240
|
+
it('throws if both `oauth2provider` and `returnURL` are missing', () => {
|
|
241
|
+
const webex = new MockWebex();
|
|
242
|
+
const credentials = new Credentials(undefined, {parent: webex});
|
|
243
|
+
|
|
244
|
+
webex.trigger('change:config');
|
|
245
|
+
|
|
246
|
+
assert.throws(() => {
|
|
247
|
+
credentials.buildThirdPartyLoginUrl({});
|
|
248
|
+
}, /`options.oauth2provider` is required/);
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
it('throws if `oauth2provider` is missing', () => {
|
|
252
|
+
const webex = new MockWebex();
|
|
253
|
+
const credentials = new Credentials(undefined, {parent: webex});
|
|
254
|
+
|
|
255
|
+
webex.trigger('change:config');
|
|
256
|
+
|
|
257
|
+
assert.throws(() => {
|
|
258
|
+
credentials.buildThirdPartyLoginUrl({returnURL: 'https://web.webex.com'});
|
|
259
|
+
}, /`options.oauth2provider` is required/);
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
it('throws if `returnURL` is missing', () => {
|
|
263
|
+
const webex = new MockWebex();
|
|
264
|
+
const credentials = new Credentials(undefined, {parent: webex});
|
|
265
|
+
|
|
266
|
+
webex.trigger('change:config');
|
|
267
|
+
|
|
268
|
+
assert.throws(() => {
|
|
269
|
+
credentials.buildThirdPartyLoginUrl({oauth2provider: 'google'});
|
|
270
|
+
}, /`options.returnURL` is required/);
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
skipInBrowser(it)('generates the third-party login url', () => {
|
|
274
|
+
const webex = new MockWebex();
|
|
275
|
+
const credentials = new Credentials(undefined, {parent: webex});
|
|
276
|
+
|
|
277
|
+
webex.trigger('change:config');
|
|
278
|
+
|
|
279
|
+
assert.equal(
|
|
280
|
+
credentials.buildThirdPartyLoginUrl({
|
|
281
|
+
oauth2provider: 'google',
|
|
282
|
+
returnURL: 'https://web.webex.com',
|
|
283
|
+
}),
|
|
284
|
+
`${
|
|
285
|
+
process.env.IDBROKER_BASE_URL || 'https://idbroker.webex.com'
|
|
286
|
+
}/idb/ThirdPartyLogin?oauth2provider=google&returnURL=https%3A%2F%2Fweb.webex.com`
|
|
287
|
+
);
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
skipInBrowser(it)('generates the url with different parameter values', () => {
|
|
291
|
+
const webex = new MockWebex();
|
|
292
|
+
const credentials = new Credentials(undefined, {parent: webex});
|
|
293
|
+
|
|
294
|
+
webex.trigger('change:config');
|
|
295
|
+
|
|
296
|
+
assert.equal(
|
|
297
|
+
credentials.buildThirdPartyLoginUrl({
|
|
298
|
+
oauth2provider: 'apple',
|
|
299
|
+
returnURL: 'https://example.com/callback',
|
|
300
|
+
}),
|
|
301
|
+
`${
|
|
302
|
+
process.env.IDBROKER_BASE_URL || 'https://idbroker.webex.com'
|
|
303
|
+
}/idb/ThirdPartyLogin?oauth2provider=apple&returnURL=https%3A%2F%2Fexample.com%2Fcallback`
|
|
304
|
+
);
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
it('throws if `state` is not an object', () => {
|
|
308
|
+
const webex = new MockWebex();
|
|
309
|
+
const credentials = new Credentials(undefined, {parent: webex});
|
|
310
|
+
|
|
311
|
+
webex.trigger('change:config');
|
|
312
|
+
|
|
313
|
+
assert.throws(() => {
|
|
314
|
+
credentials.buildThirdPartyLoginUrl({
|
|
315
|
+
oauth2provider: 'google',
|
|
316
|
+
returnURL: 'https://web.webex.com',
|
|
317
|
+
state: 'not-an-object',
|
|
318
|
+
});
|
|
319
|
+
}, /`options.state` must be an object/);
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
skipInBrowser(it)('omits `state` when an empty object is provided', () => {
|
|
323
|
+
const webex = new MockWebex();
|
|
324
|
+
const credentials = new Credentials(undefined, {parent: webex});
|
|
325
|
+
|
|
326
|
+
webex.trigger('change:config');
|
|
327
|
+
|
|
328
|
+
const result = credentials.buildThirdPartyLoginUrl({
|
|
329
|
+
oauth2provider: 'google',
|
|
330
|
+
returnURL: 'https://web.webex.com',
|
|
331
|
+
state: {},
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
const parsed = new URL(result);
|
|
335
|
+
|
|
336
|
+
assert.isFalse(parsed.searchParams.has('state'));
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
skipInBrowser(it)('base64url-encodes a non-empty `state` and emits it as a top-level query param', () => {
|
|
340
|
+
const webex = new MockWebex();
|
|
341
|
+
const credentials = new Credentials(undefined, {parent: webex});
|
|
342
|
+
|
|
343
|
+
webex.trigger('change:config');
|
|
344
|
+
|
|
345
|
+
const result = credentials.buildThirdPartyLoginUrl({
|
|
346
|
+
oauth2provider: 'google',
|
|
347
|
+
returnURL: 'https://web.webex.com',
|
|
348
|
+
state: {csrf_token: 'abc', popUpSignIn: true},
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
// Literal base64url of '{"csrf_token":"abc","popUpSignIn":true}'
|
|
352
|
+
const expectedState = 'eyJjc3JmX3Rva2VuIjoiYWJjIiwicG9wVXBTaWduSW4iOnRydWV9';
|
|
353
|
+
|
|
354
|
+
assert.equal(
|
|
355
|
+
result,
|
|
356
|
+
`${
|
|
357
|
+
process.env.IDBROKER_BASE_URL || 'https://idbroker.webex.com'
|
|
358
|
+
}/idb/ThirdPartyLogin?oauth2provider=google&returnURL=${encodeURIComponent(
|
|
359
|
+
'https://web.webex.com'
|
|
360
|
+
)}&state=${expectedState}`
|
|
361
|
+
);
|
|
362
|
+
});
|
|
363
|
+
});
|
|
364
|
+
|
|
235
365
|
describe('#buildLogoutUrl()', () => {
|
|
236
366
|
skipInBrowser(it)('generates the logout url', () => {
|
|
237
367
|
const webex = new MockWebex();
|
|
@@ -368,12 +498,96 @@ describe('webex-core', () => {
|
|
|
368
498
|
});
|
|
369
499
|
|
|
370
500
|
it('should throw when provided an invalid token', () =>
|
|
371
|
-
expect(() => credentials.extractOrgIdFromUserToken()).toThrow(
|
|
501
|
+
expect(() => credentials.extractOrgIdFromUserToken()).toThrow(
|
|
502
|
+
'the provided token is not a valid format, token has 1 sections'
|
|
503
|
+
));
|
|
372
504
|
|
|
373
505
|
it('should throw when no token is provided', () =>
|
|
374
506
|
expect(() => credentials.extractOrgIdFromUserToken()).toThrow());
|
|
375
507
|
});
|
|
376
508
|
|
|
509
|
+
describe('#extractUserIdFromToken()', () => {
|
|
510
|
+
let credentials;
|
|
511
|
+
let webex;
|
|
512
|
+
|
|
513
|
+
beforeEach(() => {
|
|
514
|
+
webex = new MockWebex();
|
|
515
|
+
credentials = new Credentials(undefined, {parent: webex});
|
|
516
|
+
});
|
|
517
|
+
|
|
518
|
+
const buildToken = (payload) =>
|
|
519
|
+
`header.${base64.toBase64Url(JSON.stringify(payload))}.signature`;
|
|
520
|
+
|
|
521
|
+
it('should return the cis_uuid from the provided token', () => {
|
|
522
|
+
const token = buildToken({cis_uuid: 'my-user-id'});
|
|
523
|
+
|
|
524
|
+
assert.equal(credentials.extractUserIdFromToken(token), 'my-user-id');
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
it('should throw if the token does not contain a cis_uuid', () => {
|
|
528
|
+
const token = buildToken({foo: 'bar'});
|
|
529
|
+
|
|
530
|
+
expect(() => credentials.extractUserIdFromToken(token)).toThrow(
|
|
531
|
+
'the provided token does not contain a user ID'
|
|
532
|
+
);
|
|
533
|
+
});
|
|
534
|
+
|
|
535
|
+
it('should throw when provided an unparseable token', () =>
|
|
536
|
+
expect(() => credentials.extractUserIdFromToken('not-a-valid-token')).toThrow());
|
|
537
|
+
|
|
538
|
+
it('should throw when no token is provided', () =>
|
|
539
|
+
expect(() => credentials.extractUserIdFromToken()).toThrow());
|
|
540
|
+
});
|
|
541
|
+
|
|
542
|
+
describe('#getUserId()', () => {
|
|
543
|
+
let credentials;
|
|
544
|
+
let webex;
|
|
545
|
+
|
|
546
|
+
const buildToken = (userId) =>
|
|
547
|
+
`header.${base64.toBase64Url(JSON.stringify({cis_uuid: userId}))}.signature`;
|
|
548
|
+
|
|
549
|
+
beforeEach(() => {
|
|
550
|
+
webex = new MockWebex();
|
|
551
|
+
credentials = new Credentials(undefined, {parent: webex});
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
it('should return the userId from the supertoken', () => {
|
|
555
|
+
credentials.supertoken = makeToken(webex, {
|
|
556
|
+
access_token: buildToken('supertoken-user-id'),
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
assert.equal(credentials.getUserId(), 'supertoken-user-id');
|
|
560
|
+
});
|
|
561
|
+
|
|
562
|
+
it('should fall back to a user token when the supertoken has no userId', () => {
|
|
563
|
+
credentials.supertoken = makeToken(webex, {access_token: 'AT'});
|
|
564
|
+
credentials.userTokens.add(
|
|
565
|
+
makeToken(webex, {access_token: buildToken('user-token-user-id'), scope: 'scope1'})
|
|
566
|
+
);
|
|
567
|
+
|
|
568
|
+
assert.equal(credentials.getUserId(), 'user-token-user-id');
|
|
569
|
+
});
|
|
570
|
+
|
|
571
|
+
it('should prefer the supertoken over the user tokens', () => {
|
|
572
|
+
credentials.supertoken = makeToken(webex, {
|
|
573
|
+
access_token: buildToken('supertoken-user-id'),
|
|
574
|
+
});
|
|
575
|
+
credentials.userTokens.add(
|
|
576
|
+
makeToken(webex, {access_token: buildToken('user-token-user-id'), scope: 'scope1'})
|
|
577
|
+
);
|
|
578
|
+
|
|
579
|
+
assert.equal(credentials.getUserId(), 'supertoken-user-id');
|
|
580
|
+
});
|
|
581
|
+
|
|
582
|
+
it('should throw if no available token contains a userId', () => {
|
|
583
|
+
credentials.supertoken = makeToken(webex, {access_token: 'AT'});
|
|
584
|
+
|
|
585
|
+
expect(() => credentials.getUserId()).toThrow(
|
|
586
|
+
'could not extract the user ID from any available token'
|
|
587
|
+
);
|
|
588
|
+
});
|
|
589
|
+
});
|
|
590
|
+
|
|
377
591
|
describe('#initialize()', () => {
|
|
378
592
|
it('turns a portal auth string into a configuration object', () => {
|
|
379
593
|
const webex = new MockWebex();
|
|
@@ -498,6 +712,37 @@ describe('webex-core', () => {
|
|
|
498
712
|
});
|
|
499
713
|
});
|
|
500
714
|
|
|
715
|
+
describe('#getClientToken()', () => {
|
|
716
|
+
it('requests a client token without bypassing catalog URL validation', async () => {
|
|
717
|
+
const webex = new MockWebex();
|
|
718
|
+
const credentials = new Credentials(undefined, {parent: webex});
|
|
719
|
+
const uri = 'https://idbroker.webex.com/idb/oauth2/v1/access_token';
|
|
720
|
+
const scope = 'spark:all';
|
|
721
|
+
|
|
722
|
+
webex.request.resolves({body: {access_token: 'AT', token_type: 'Bearer'}});
|
|
723
|
+
|
|
724
|
+
const token = await credentials.getClientToken({uri, scope});
|
|
725
|
+
|
|
726
|
+
assert.calledOnceWithExactly(webex.request, {
|
|
727
|
+
method: 'POST',
|
|
728
|
+
uri,
|
|
729
|
+
form: {
|
|
730
|
+
grant_type: 'client_credentials',
|
|
731
|
+
scope,
|
|
732
|
+
self_contained_token: true,
|
|
733
|
+
},
|
|
734
|
+
auth: {
|
|
735
|
+
user: 'fake',
|
|
736
|
+
pass: 'fake',
|
|
737
|
+
sendImmediately: true,
|
|
738
|
+
},
|
|
739
|
+
shouldRefreshAccessToken: false,
|
|
740
|
+
});
|
|
741
|
+
assert.instanceOf(token, Token);
|
|
742
|
+
assert.equal(token.access_token, 'AT');
|
|
743
|
+
});
|
|
744
|
+
});
|
|
745
|
+
|
|
501
746
|
describe('#getUserToken()', () => {
|
|
502
747
|
it('resolves with the supertoken if the supertoken matches the requested scopes', () => {
|
|
503
748
|
const webex = new MockWebex();
|
|
@@ -799,7 +1044,6 @@ describe('webex-core', () => {
|
|
|
799
1044
|
.then(() => assert.isRejected(webex.boundedStorage.get('Credentials', '@'), /NotFound/));
|
|
800
1045
|
});
|
|
801
1046
|
|
|
802
|
-
|
|
803
1047
|
// it('does not induce any token refreshes');
|
|
804
1048
|
|
|
805
1049
|
it('prevents #getUserToken() from being invoked', () => {
|