pryv 3.13.0 → 3.15.0
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 +10 -1
- package/package.json +1 -1
- package/src/Auth/AuthController.js +33 -8
- package/src/Auth/ProfileStore.js +19 -1
- package/src/Auth/index.js +5 -2
- package/src/Browser/LoginButton.js +127 -17
- package/src/Connection.js +19 -13
- package/src/Service.js +29 -15
- package/src/index.d.ts +76 -7
- package/src/lib/MfaRequiredError.js +2 -0
- package/src/lib/PryvError.js +4 -0
- package/src/lib/pollUrls.js +71 -0
- package/src/utils.js +7 -1
- package/test/AuthController.test.js +111 -5
- package/test/Connection.apiOneError.test.js +105 -0
- package/test/LoginButton.test.js +3 -1
- package/test/LoginButton.urlCleanup.test.js +453 -14
- package/test/PryvError.test.js +10 -0
- package/test/Service.accessRequest.test.js +4 -4
- package/test/Service.accessRequestConsent.test.js +16 -0
- package/test/Service.errorBodies.test.js +82 -0
- package/test/Service.mfa.test.js +17 -4
- package/test/Service.pollUrlByKey.test.js +114 -0
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 },
|
|
@@ -115,5 +115,18 @@ describe('[MFLX] Service MFA', function () {
|
|
|
115
115
|
);
|
|
116
116
|
expect(err.method).to.equal(undefined);
|
|
117
117
|
});
|
|
118
|
+
|
|
119
|
+
it('[MFEE] mfaToken and the raw response are readable but not printed with the error', function () {
|
|
120
|
+
const err = new pryv.MfaRequiredError(
|
|
121
|
+
'tok-secret-4',
|
|
122
|
+
{ status: 200 },
|
|
123
|
+
{ mfaToken: 'tok-secret-4' }
|
|
124
|
+
);
|
|
125
|
+
expect(err.mfaToken).to.equal('tok-secret-4');
|
|
126
|
+
expect(err.response.body.mfaToken).to.equal('tok-secret-4');
|
|
127
|
+
expect(Object.keys(err)).to.not.include('mfaToken');
|
|
128
|
+
expect(Object.keys(err)).to.not.include('response');
|
|
129
|
+
expect(require('util').inspect(err)).to.not.include('tok-secret-4');
|
|
130
|
+
});
|
|
118
131
|
});
|
|
119
132
|
});
|
|
@@ -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
|
+
});
|