pryv 3.12.1 → 3.14.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/README.md +42 -8
- package/package.json +1 -1
- package/src/Auth/AuthController.js +366 -13
- package/src/Auth/AuthStates.js +4 -2
- package/src/Auth/LoginMessages.js +70 -1
- package/src/Auth/ProfileStore.js +137 -0
- package/src/Auth/index.js +5 -2
- package/src/Browser/LoginButton.js +509 -33
- package/src/Connection.js +19 -13
- package/src/Service.js +49 -16
- package/src/ServiceAssets.js +3 -0
- package/src/SharedSecrets.js +4 -1
- package/src/index.d.ts +172 -11
- package/src/lib/handoff.js +139 -0
- package/src/lib/pollUrls.js +71 -0
- package/src/utils.js +7 -1
- package/test/AuthController.test.js +321 -5
- package/test/Connection.apiOneError.test.js +105 -0
- package/test/LoginButton.test.js +573 -10
- package/test/LoginButton.urlCleanup.test.js +453 -14
- package/test/LoginMessages.test.js +8 -0
- package/test/ProfileStore.test.js +88 -0
- package/test/Service.accessRequest.test.js +4 -4
- package/test/Service.accessRequestHandoff.test.js +153 -0
- package/test/Service.errorBodies.test.js +82 -0
- package/test/Service.mfa.test.js +4 -4
- package/test/Service.pollUrlByKey.test.js +114 -0
- package/test/browser-index.html +1 -0
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* [BSD-3-Clause](https://github.com/pryv/lib-js/blob/master/LICENSE)
|
|
4
|
+
*/
|
|
5
|
+
/* global describe, it, afterEach, expect, pryv */
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* [ARQH] Credential hand-off (shared-secret delivery) in `connectFromKey`.
|
|
9
|
+
*
|
|
10
|
+
* When an access request asked for `credentialHandoff: 'shared-secret'`, the
|
|
11
|
+
* ACCEPTED poll body carries a one-time `handoff` key and a token-less
|
|
12
|
+
* `apiEndpoint` instead of the token. `connectFromKey` must redeem that key
|
|
13
|
+
* once, build a token-bearing `Connection`, and reuse the cached result on a
|
|
14
|
+
* second call (the one-time secret cannot be redeemed twice). A legacy inline
|
|
15
|
+
* body must keep working unchanged.
|
|
16
|
+
*
|
|
17
|
+
* Driven through a stubbed global fetch: what is tested is this library's
|
|
18
|
+
* handling of the two ACCEPTED shapes.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
const SERVICE_INFO = {
|
|
22
|
+
register: 'https://reg.test.local/',
|
|
23
|
+
access: 'https://reg.test.local/access',
|
|
24
|
+
api: 'https://{username}.test.local/',
|
|
25
|
+
name: 'Test'
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
let keyCounter = 0;
|
|
29
|
+
function freshKey () { return 'poll-key-' + (++keyCounter); }
|
|
30
|
+
|
|
31
|
+
describe('[ARQH] auth-request credential hand-off', function () {
|
|
32
|
+
let realFetch;
|
|
33
|
+
let retrieveCalls;
|
|
34
|
+
let retrievePosts;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Stub fetch: serve service-info, answer the poll GET with `pollBody`, and
|
|
38
|
+
* answer `shared-secrets/retrieve` with `retrieveResponse` (counting calls).
|
|
39
|
+
*/
|
|
40
|
+
function stubFetch (pollBody, retrieveResponse) {
|
|
41
|
+
realFetch = global.fetch;
|
|
42
|
+
retrieveCalls = 0;
|
|
43
|
+
retrievePosts = [];
|
|
44
|
+
global.fetch = async function (url, options) {
|
|
45
|
+
const u = String(url);
|
|
46
|
+
if (u.includes('shared-secrets/retrieve')) {
|
|
47
|
+
retrieveCalls++;
|
|
48
|
+
retrievePosts.push(JSON.parse(options.body));
|
|
49
|
+
return {
|
|
50
|
+
ok: retrieveResponse.ok !== false,
|
|
51
|
+
status: retrieveResponse.status || 200,
|
|
52
|
+
json: async () => retrieveResponse.body
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
if (u.includes('/access')) { // the poll GET
|
|
56
|
+
return { ok: true, status: 200, json: async () => pollBody };
|
|
57
|
+
}
|
|
58
|
+
return { ok: true, status: 200, json: async () => SERVICE_INFO }; // service-info
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
afterEach(function () {
|
|
63
|
+
if (realFetch) global.fetch = realFetch;
|
|
64
|
+
realFetch = null;
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('[SARH1] redeems a hand-off ACCEPTED body once and builds a token-bearing connection', async function () {
|
|
68
|
+
const key = freshKey();
|
|
69
|
+
stubFetch(
|
|
70
|
+
{
|
|
71
|
+
status: 'ACCEPTED',
|
|
72
|
+
username: 'alice',
|
|
73
|
+
apiEndpoint: 'https://alice.test.local/', // token-less
|
|
74
|
+
handoff: { type: 'shared-secret', key: 'evt1.' + 'a'.repeat(40) }
|
|
75
|
+
},
|
|
76
|
+
{ body: { secret: { username: 'alice', token: 'tok-123', apiEndpoint: 'https://alice.test.local/' } } }
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
const service = new pryv.Service('https://reg.test.local/service/info');
|
|
80
|
+
await service.info();
|
|
81
|
+
const conn = await service.connectFromKey(key);
|
|
82
|
+
|
|
83
|
+
expect(retrieveCalls).to.equal(1);
|
|
84
|
+
expect(retrievePosts[0].key).to.equal('evt1.' + 'a'.repeat(40));
|
|
85
|
+
expect(conn.token).to.equal('tok-123');
|
|
86
|
+
expect(conn.apiEndpoint).to.contain('tok-123@');
|
|
87
|
+
expect(conn.apiEndpoint).to.contain('alice.test.local');
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('[SARH2] redeems only once across two connectFromKey calls for the same key (module cache)', async function () {
|
|
91
|
+
const key = freshKey();
|
|
92
|
+
stubFetch(
|
|
93
|
+
{
|
|
94
|
+
status: 'ACCEPTED',
|
|
95
|
+
username: 'bob',
|
|
96
|
+
apiEndpoint: 'https://bob.test.local/',
|
|
97
|
+
handoff: { type: 'shared-secret', key: 'evt2.' + 'b'.repeat(40) }
|
|
98
|
+
},
|
|
99
|
+
{ body: { secret: { username: 'bob', token: 'tok-xyz', apiEndpoint: 'https://bob.test.local/' } } }
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
// Module-level convenience builds a fresh Service each call; the shared
|
|
103
|
+
// cache is what stops a second redeem of the one-shot secret.
|
|
104
|
+
const c1 = await pryv.connectFromKey(key, 'https://reg.test.local/service/info');
|
|
105
|
+
const c2 = await pryv.connectFromKey(key, 'https://reg.test.local/service/info');
|
|
106
|
+
|
|
107
|
+
expect(retrieveCalls).to.equal(1);
|
|
108
|
+
expect(c1.token).to.equal('tok-xyz');
|
|
109
|
+
expect(c2.token).to.equal('tok-xyz');
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('[SARH3] leaves a legacy inline ACCEPTED body unchanged (no retrieve)', async function () {
|
|
113
|
+
const key = freshKey();
|
|
114
|
+
stubFetch(
|
|
115
|
+
{
|
|
116
|
+
status: 'ACCEPTED',
|
|
117
|
+
username: 'carol',
|
|
118
|
+
token: 'tok-inline',
|
|
119
|
+
apiEndpoint: 'https://tok-inline@carol.test.local/'
|
|
120
|
+
},
|
|
121
|
+
{ body: { secret: {} } }
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
const service = new pryv.Service('https://reg.test.local/service/info');
|
|
125
|
+
await service.info();
|
|
126
|
+
const conn = await service.connectFromKey(key);
|
|
127
|
+
|
|
128
|
+
expect(retrieveCalls).to.equal(0);
|
|
129
|
+
expect(conn.token).to.equal('tok-inline');
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('[SARH4] surfaces a consumed/expired hand-off as a PryvError telling the caller to restart', async function () {
|
|
133
|
+
const key = freshKey();
|
|
134
|
+
stubFetch(
|
|
135
|
+
{
|
|
136
|
+
status: 'ACCEPTED',
|
|
137
|
+
username: 'dan',
|
|
138
|
+
apiEndpoint: 'https://dan.test.local/',
|
|
139
|
+
handoff: { type: 'shared-secret', key: 'evt4.' + 'd'.repeat(40) }
|
|
140
|
+
},
|
|
141
|
+
{ ok: false, status: 403, body: { error: { id: 'forbidden', message: 'used', data: { id: 'shared-secret-unavailable' } } } }
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
const service = new pryv.Service('https://reg.test.local/service/info');
|
|
145
|
+
await service.info();
|
|
146
|
+
let caught;
|
|
147
|
+
try {
|
|
148
|
+
await service.connectFromKey(key);
|
|
149
|
+
} catch (e) { caught = e; }
|
|
150
|
+
expect(caught).to.be.instanceof(pryv.PryvError);
|
|
151
|
+
expect(caught.id).to.equal('credential-handoff-failed');
|
|
152
|
+
});
|
|
153
|
+
});
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* [BSD-3-Clause](https://github.com/pryv/lib-js/blob/master/LICENSE)
|
|
4
|
+
*/
|
|
5
|
+
/* global describe, it, afterEach, expect, pryv */
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* [SEBX] A malformed server answer never lands in the error message.
|
|
9
|
+
*
|
|
10
|
+
* `login`, `mfaVerify` and `startAccessRequest` throw when a 2xx answer lacks
|
|
11
|
+
* what they need. The answer may hold secrets (a misplaced token) or the
|
|
12
|
+
* request's data, and error messages end up in logs and on screen: it rides
|
|
13
|
+
* on `innerObject` only. `fetch` is stubbed: no server.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const SECRET = 'S3CRET-in-the-answer';
|
|
17
|
+
const SERVICE_INFO = {
|
|
18
|
+
register: 'https://reg.seb.test.local/',
|
|
19
|
+
access: 'https://access.seb.test.local/access/',
|
|
20
|
+
api: 'https://{username}.seb.test.local/',
|
|
21
|
+
name: 'Test'
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
describe('[SEBX] malformed answers stay out of error messages', function () {
|
|
25
|
+
let realFetch;
|
|
26
|
+
|
|
27
|
+
/** Service info on GET; `postBody` with HTTP 200 on every POST. */
|
|
28
|
+
function stubFetch (postBody) {
|
|
29
|
+
realFetch = global.fetch;
|
|
30
|
+
global.fetch = async function (url, options) {
|
|
31
|
+
if (options?.method === 'POST') return { ok: true, status: 200, json: async () => postBody };
|
|
32
|
+
return { ok: true, status: 200, json: async () => SERVICE_INFO };
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
afterEach(function () {
|
|
37
|
+
if (realFetch) global.fetch = realFetch;
|
|
38
|
+
realFetch = null;
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
async function caught (promise) {
|
|
42
|
+
try {
|
|
43
|
+
await promise;
|
|
44
|
+
} catch (e) {
|
|
45
|
+
return e;
|
|
46
|
+
}
|
|
47
|
+
throw new Error('expected a rejection');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
it('[SEB1] login: an answer without token', async () => {
|
|
51
|
+
const body = { session: SECRET };
|
|
52
|
+
stubFetch(body);
|
|
53
|
+
const service = new pryv.Service('https://reg.seb.test.local/service/info');
|
|
54
|
+
const e = await caught(service.login('alice', 'pw', 'app-id'));
|
|
55
|
+
expect(e).to.be.instanceOf(pryv.PryvError);
|
|
56
|
+
expect(e.message).to.include('Invalid login response');
|
|
57
|
+
expect(e.message).to.not.include(SECRET);
|
|
58
|
+
expect(e.innerObject).to.deep.equal(body);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('[SEB2] mfaVerify: an answer without token', async () => {
|
|
62
|
+
const body = { session: SECRET };
|
|
63
|
+
stubFetch(body);
|
|
64
|
+
const service = new pryv.Service('https://reg.seb.test.local/service/info');
|
|
65
|
+
const e = await caught(service.mfaVerify('alice', 'mfa-token', '123456'));
|
|
66
|
+
expect(e).to.be.instanceOf(pryv.PryvError);
|
|
67
|
+
expect(e.message).to.include('did not return a token');
|
|
68
|
+
expect(e.message).to.not.include(SECRET);
|
|
69
|
+
expect(e.innerObject).to.deep.equal(body);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('[SEB3] startAccessRequest: an answer without key or poll', async () => {
|
|
73
|
+
const body = { requestedPermissions: [{ streamId: SECRET, level: 'read' }] };
|
|
74
|
+
stubFetch(body);
|
|
75
|
+
const service = new pryv.Service('https://reg.seb.test.local/service/info');
|
|
76
|
+
const e = await caught(service.startAccessRequest({ requestingAppId: 'app-id', requestedPermissions: [] }));
|
|
77
|
+
expect(e).to.be.instanceOf(pryv.PryvError);
|
|
78
|
+
expect(e.message).to.include('Invalid access-request response');
|
|
79
|
+
expect(e.message).to.not.include(SECRET);
|
|
80
|
+
expect(e.innerObject).to.deep.equal(body);
|
|
81
|
+
});
|
|
82
|
+
});
|
package/test/Service.mfa.test.js
CHANGED
|
@@ -65,8 +65,8 @@ describe('[MFLX] Service MFA', function () {
|
|
|
65
65
|
});
|
|
66
66
|
});
|
|
67
67
|
|
|
68
|
-
describe('[
|
|
69
|
-
it('[
|
|
68
|
+
describe('[MFEX] MfaRequiredError', function () {
|
|
69
|
+
it('[MFEA] is exported on the package root and extends PryvError', function () {
|
|
70
70
|
expect(pryv.MfaRequiredError).to.be.a('function');
|
|
71
71
|
const err = new pryv.MfaRequiredError(
|
|
72
72
|
'tok-abc',
|
|
@@ -81,7 +81,7 @@ describe('[MFLX] Service MFA', function () {
|
|
|
81
81
|
expect(err.name).to.equal('MfaRequiredError');
|
|
82
82
|
});
|
|
83
83
|
|
|
84
|
-
it('[
|
|
84
|
+
it('[MFEB] picks up id/message from API error body when provided', function () {
|
|
85
85
|
const err = new pryv.MfaRequiredError(
|
|
86
86
|
'tok-xyz',
|
|
87
87
|
{ status: 401 },
|
|
@@ -91,7 +91,7 @@ describe('[MFLX] Service MFA', function () {
|
|
|
91
91
|
expect(err.message).to.equal('custom msg');
|
|
92
92
|
});
|
|
93
93
|
|
|
94
|
-
it('[
|
|
94
|
+
it('[MFEC] surfaces the MFA method from the API body when present', function () {
|
|
95
95
|
const totp = new pryv.MfaRequiredError(
|
|
96
96
|
'tok-1',
|
|
97
97
|
{ status: 200 },
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* [BSD-3-Clause](https://github.com/pryv/lib-js/blob/master/LICENSE)
|
|
4
|
+
*/
|
|
5
|
+
/* global describe, it, afterEach, expect, pryv */
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* [PUKX] Polling an auth request by key reaches the core that holds it.
|
|
9
|
+
*
|
|
10
|
+
* On a multi-core platform a pending auth request lives on the core that
|
|
11
|
+
* created it, and the server answers the access request with a poll URL on
|
|
12
|
+
* that core, while the service's `access` URL may reach any core. Polling by
|
|
13
|
+
* key must use the server-issued URL when this process started the request.
|
|
14
|
+
* Observed: the URLs actually fetched (a stubbed global fetch), not only the
|
|
15
|
+
* result.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const pollUrls = require('../src/lib/pollUrls');
|
|
19
|
+
const AuthController = require('../src/Auth/AuthController');
|
|
20
|
+
const utils = require('../src/utils');
|
|
21
|
+
|
|
22
|
+
const SERVICE_INFO = {
|
|
23
|
+
register: 'https://reg.mc.test.local/',
|
|
24
|
+
access: 'https://access.mc.test.local/access/',
|
|
25
|
+
api: 'https://{username}.mc.test.local/',
|
|
26
|
+
name: 'Test'
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
let keyCounter = 0;
|
|
30
|
+
function freshKey () { return 'puk-key-' + (++keyCounter); }
|
|
31
|
+
function corePoll (key) { return 'https://core-a.mc.test.local/reg/access/' + key; }
|
|
32
|
+
|
|
33
|
+
describe('[PUKX] polling an auth request by key', function () {
|
|
34
|
+
let realFetch;
|
|
35
|
+
let gets;
|
|
36
|
+
|
|
37
|
+
/** service-info, POST <access> -> NEED_SIGNIN with a core poll URL, GET -> `pollBody` */
|
|
38
|
+
function stubFetch (key, pollBody) {
|
|
39
|
+
realFetch = global.fetch;
|
|
40
|
+
gets = [];
|
|
41
|
+
global.fetch = async function (url, options) {
|
|
42
|
+
const u = String(url);
|
|
43
|
+
if (options?.method === 'POST' && u === SERVICE_INFO.access) {
|
|
44
|
+
return { ok: true, status: 201, json: async () => ({ status: 'NEED_SIGNIN', key, poll: corePoll(key), authUrl: 'https://ui.mc.test.local/access?key=' + key, poll_rate_ms: 1000 }) };
|
|
45
|
+
}
|
|
46
|
+
if (u.includes('/access/')) {
|
|
47
|
+
gets.push(u);
|
|
48
|
+
return { ok: true, status: 200, json: async () => pollBody };
|
|
49
|
+
}
|
|
50
|
+
return { ok: true, status: 200, json: async () => SERVICE_INFO };
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
afterEach(function () {
|
|
55
|
+
if (realFetch) global.fetch = realFetch;
|
|
56
|
+
realFetch = null;
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('[PUK1] remembers valid entries only, keeps a bounded number and expires them', function () {
|
|
60
|
+
const realNow = Date.now;
|
|
61
|
+
pollUrls.clear();
|
|
62
|
+
try {
|
|
63
|
+
pollUrls.remember('puk1-a', 'https://core-a.example.com/reg/access/puk1-a');
|
|
64
|
+
pollUrls.remember('puk1-b', 'javascript:alert(1)');
|
|
65
|
+
pollUrls.remember(null, 'https://core-a.example.com/reg/access/x');
|
|
66
|
+
expect(pollUrls.lookup('puk1-a')).to.equal('https://core-a.example.com/reg/access/puk1-a');
|
|
67
|
+
expect(pollUrls.lookup('puk1-b')).to.equal(null);
|
|
68
|
+
for (let i = 0; i < pollUrls.MAX_ENTRIES; i++) pollUrls.remember('puk1-fill-' + i, 'https://c.example.com/' + i);
|
|
69
|
+
expect(pollUrls.lookup('puk1-a')).to.equal(null); // oldest dropped
|
|
70
|
+
expect(pollUrls.lookup('puk1-fill-' + (pollUrls.MAX_ENTRIES - 1))).to.equal('https://c.example.com/' + (pollUrls.MAX_ENTRIES - 1));
|
|
71
|
+
const later = realNow() + pollUrls.TTL_MS + 1000;
|
|
72
|
+
Date.now = () => later;
|
|
73
|
+
expect(pollUrls.lookup('puk1-fill-' + (pollUrls.MAX_ENTRIES - 1))).to.equal(null); // expired
|
|
74
|
+
} finally {
|
|
75
|
+
Date.now = realNow;
|
|
76
|
+
pollUrls.clear();
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('[PUK2] pollAccessRequest(key) and connectFromKey(key) poll the core URL the server issued', async function () {
|
|
81
|
+
const key = freshKey();
|
|
82
|
+
stubFetch(key, { status: 'ACCEPTED', username: 'alice', apiEndpoint: 'https://tok@alice.mc.test.local/', token: 'tok' });
|
|
83
|
+
const service = new pryv.Service(null, SERVICE_INFO);
|
|
84
|
+
const env = await service.startAccessRequest({ requestingAppId: 'puk', requestedPermissions: [] });
|
|
85
|
+
expect(env.poll).to.equal(corePoll(key));
|
|
86
|
+
|
|
87
|
+
await service.pollAccessRequest(key);
|
|
88
|
+
// a fresh Service, as pryv.connectFromKey(key, url) builds one
|
|
89
|
+
const connection = await new pryv.Service(null, SERVICE_INFO).connectFromKey(key);
|
|
90
|
+
expect(gets).to.deep.equal([corePoll(key), corePoll(key)]);
|
|
91
|
+
expect(connection.apiEndpoint).to.equal('https://tok@alice.mc.test.local/');
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('[PUK3] a key this process never saw falls back to access + key', async function () {
|
|
95
|
+
const key = freshKey();
|
|
96
|
+
stubFetch(key, { status: 'NEED_SIGNIN' });
|
|
97
|
+
await new pryv.Service(null, SERVICE_INFO).pollAccessRequest(key);
|
|
98
|
+
expect(gets).to.deep.equal([SERVICE_INFO.access + key]);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('[PUK4] a sign-in started by the button is remembered for connectFromKey', async function () {
|
|
102
|
+
const key = freshKey();
|
|
103
|
+
const { fetchPost, fetchGet } = utils;
|
|
104
|
+
utils.fetchPost = async () => ({ response: { ok: true }, body: { status: 'NEED_SIGNIN', key, poll: corePoll(key), poll_rate_ms: 1, authUrl: 'https://ui.mc.test.local/access?key=' + key } });
|
|
105
|
+
utils.fetchGet = async () => ({ response: { status: 200 }, body: { status: 'REFUSED' } });
|
|
106
|
+
const service = { infoSync: () => SERVICE_INFO, assets: async () => ({}) };
|
|
107
|
+
const auth = new AuthController({ authRequest: { requestingAppId: 'puk4', requestedPermissions: [] } }, service);
|
|
108
|
+
auth.serviceInfo = SERVICE_INFO;
|
|
109
|
+
try {
|
|
110
|
+
await auth.startAuthRequest();
|
|
111
|
+
} finally { utils.fetchPost = fetchPost; utils.fetchGet = fetchGet; }
|
|
112
|
+
expect(pollUrls.lookup(key)).to.equal(corePoll(key));
|
|
113
|
+
});
|
|
114
|
+
});
|