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
|
@@ -19,11 +19,80 @@ const Messages = {
|
|
|
19
19
|
SIGNOUT_CONFIRM: {
|
|
20
20
|
en: 'Logout?',
|
|
21
21
|
fr: 'Se déconnecter ?'
|
|
22
|
+
},
|
|
23
|
+
MENU_TITLE: {
|
|
24
|
+
en: 'Account',
|
|
25
|
+
fr: 'Compte'
|
|
26
|
+
},
|
|
27
|
+
LOGOUT: {
|
|
28
|
+
en: 'Log out',
|
|
29
|
+
fr: 'Se déconnecter'
|
|
30
|
+
},
|
|
31
|
+
MANAGE_ACCOUNT: {
|
|
32
|
+
en: 'Manage my account',
|
|
33
|
+
fr: 'Gérer mon compte'
|
|
34
|
+
},
|
|
35
|
+
APP: {
|
|
36
|
+
en: 'app',
|
|
37
|
+
fr: 'app'
|
|
38
|
+
},
|
|
39
|
+
CLOSE: {
|
|
40
|
+
en: 'Close',
|
|
41
|
+
fr: 'Fermer'
|
|
42
|
+
},
|
|
43
|
+
CANCEL: {
|
|
44
|
+
en: 'Cancel',
|
|
45
|
+
fr: 'Annuler'
|
|
46
|
+
},
|
|
47
|
+
LOGOUT_ALL: {
|
|
48
|
+
en: 'Log out of all accounts',
|
|
49
|
+
fr: 'Se déconnecter de tous les comptes'
|
|
50
|
+
},
|
|
51
|
+
MANAGE_ACCOUNT_OF: {
|
|
52
|
+
en: 'Manage {username}\'s account',
|
|
53
|
+
fr: 'Gérer le compte de {username}'
|
|
54
|
+
},
|
|
55
|
+
USE_FOR: {
|
|
56
|
+
en: 'Use this app for',
|
|
57
|
+
fr: 'Utiliser cette app pour'
|
|
58
|
+
},
|
|
59
|
+
ME: {
|
|
60
|
+
en: 'me',
|
|
61
|
+
fr: 'moi'
|
|
62
|
+
},
|
|
63
|
+
VIA: {
|
|
64
|
+
en: 'via',
|
|
65
|
+
fr: 'via'
|
|
66
|
+
},
|
|
67
|
+
ACTING_AS: {
|
|
68
|
+
en: 'acting as',
|
|
69
|
+
fr: 'pour le compte'
|
|
70
|
+
},
|
|
71
|
+
SWITCH_BACK: {
|
|
72
|
+
en: 'Switch back to {username}',
|
|
73
|
+
fr: 'Revenir à {username}'
|
|
74
|
+
},
|
|
75
|
+
SWITCHING: {
|
|
76
|
+
en: 'Switching...',
|
|
77
|
+
fr: 'Changement...'
|
|
78
|
+
},
|
|
79
|
+
OTHER_ACCOUNT: {
|
|
80
|
+
en: 'Another account...',
|
|
81
|
+
fr: 'Un autre compte...'
|
|
82
|
+
},
|
|
83
|
+
UNAVAILABLE: {
|
|
84
|
+
en: 'no longer available',
|
|
85
|
+
fr: 'plus disponible'
|
|
22
86
|
}
|
|
23
87
|
};
|
|
24
88
|
|
|
89
|
+
/**
|
|
90
|
+
* Messages for a language. `definitions` (a service's own messages) override
|
|
91
|
+
* the defaults key by key, so a key the service does not define still has
|
|
92
|
+
* its default text.
|
|
93
|
+
*/
|
|
25
94
|
function get (languageCode, definitions) {
|
|
26
|
-
const myMessages = definitions ||
|
|
95
|
+
const myMessages = Object.assign({}, Messages, definitions || {});
|
|
27
96
|
const res = {};
|
|
28
97
|
Object.keys(myMessages).forEach((key) => {
|
|
29
98
|
res[key] = myMessages[key][languageCode] || myMessages[key].en;
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* [BSD-3-Clause](https://github.com/pryv/lib-js/blob/master/LICENSE)
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The accounts a sign-in button remembers for one app, as stored in its
|
|
8
|
+
* authorization data:
|
|
9
|
+
* `{ apiEndpoint, username, actingAs?, authUrl?, profiles: [...] }`.
|
|
10
|
+
* The top-level `apiEndpoint` / `username` are the active account (what
|
|
11
|
+
* versions without profiles read); `profiles` lists every remembered
|
|
12
|
+
* account, most recently used first, the active one included.
|
|
13
|
+
* A profile is `{ username, apiEndpoint, actingAs?, unavailable? }`, where
|
|
14
|
+
* `actingAs = { username, delegate }` marks an account used through account
|
|
15
|
+
* delegation (`delegate` is the username of the person acting).
|
|
16
|
+
* @memberof pryv.Auth
|
|
17
|
+
*/
|
|
18
|
+
module.exports = {
|
|
19
|
+
DEFAULT_LIMIT: 5,
|
|
20
|
+
read,
|
|
21
|
+
write,
|
|
22
|
+
activate,
|
|
23
|
+
remove,
|
|
24
|
+
markUnavailable,
|
|
25
|
+
profileOf,
|
|
26
|
+
fromAccepted,
|
|
27
|
+
carriesToken
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const utils = require('../utils');
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Whether an apiEndpoint carries a token. A stored sign-in without one
|
|
34
|
+
* cannot call the API (3.13.0 saved such sign-ins after a redirect return,
|
|
35
|
+
* the credential hand-off not being redeemed).
|
|
36
|
+
* @param {string} apiEndpoint
|
|
37
|
+
* @returns {boolean}
|
|
38
|
+
*/
|
|
39
|
+
function carriesToken (apiEndpoint) {
|
|
40
|
+
try {
|
|
41
|
+
return Boolean(utils.extractTokenAndAPIEndpoint(apiEndpoint).token);
|
|
42
|
+
} catch (e) {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Parse stored authorization data (any version) into
|
|
49
|
+
* `{ active, profiles, authUrl }`; `active` is `profiles[0]` or null.
|
|
50
|
+
* @param {Object|null|undefined} stored
|
|
51
|
+
*/
|
|
52
|
+
function read (stored) {
|
|
53
|
+
const store = { active: null, profiles: [], authUrl: undefined };
|
|
54
|
+
if (stored == null || typeof stored !== 'object' || stored.deleted === true) return store;
|
|
55
|
+
if (typeof stored.authUrl === 'string') store.authUrl = stored.authUrl;
|
|
56
|
+
if (Array.isArray(stored.profiles)) {
|
|
57
|
+
store.profiles = stored.profiles.filter(isProfile).map(profileOf);
|
|
58
|
+
}
|
|
59
|
+
if (isProfile(stored)) {
|
|
60
|
+
const active = profileOf(stored);
|
|
61
|
+
store.profiles = [active].concat(store.profiles.filter((p) => p.username !== active.username));
|
|
62
|
+
store.active = active;
|
|
63
|
+
}
|
|
64
|
+
return store;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* The authorization data to store for `store`.
|
|
69
|
+
* @returns {Object|null} null when nothing is left to remember
|
|
70
|
+
*/
|
|
71
|
+
function write (store) {
|
|
72
|
+
if (store.profiles.length === 0) return null;
|
|
73
|
+
const data = {};
|
|
74
|
+
if (store.active != null) {
|
|
75
|
+
data.apiEndpoint = store.active.apiEndpoint;
|
|
76
|
+
data.username = store.active.username;
|
|
77
|
+
if (store.active.actingAs != null) data.actingAs = store.active.actingAs;
|
|
78
|
+
}
|
|
79
|
+
if (store.authUrl != null) data.authUrl = store.authUrl;
|
|
80
|
+
data.profiles = store.profiles.map(profileOf);
|
|
81
|
+
return data;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Make `profile` the active account (replacing a stored one with the same
|
|
86
|
+
* username), then forget the least recently used accounts beyond `limit`.
|
|
87
|
+
*/
|
|
88
|
+
function activate (store, profile, limit) {
|
|
89
|
+
const max = Math.max(1, limit || module.exports.DEFAULT_LIMIT);
|
|
90
|
+
const active = profileOf(profile);
|
|
91
|
+
const profiles = [active].concat(store.profiles.filter((p) => p.username !== active.username));
|
|
92
|
+
return Object.assign({}, store, { active, profiles: profiles.slice(0, max) });
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Forget `username`; forgetting the active account leaves none active. */
|
|
96
|
+
function remove (store, username) {
|
|
97
|
+
const profiles = store.profiles.filter((p) => p.username !== username);
|
|
98
|
+
const active = (store.active != null && store.active.username !== username) ? store.active : null;
|
|
99
|
+
return Object.assign({}, store, { active, profiles });
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** Keep `username` listed, marked as no longer usable (revoked, detached). */
|
|
103
|
+
function markUnavailable (store, username) {
|
|
104
|
+
const mark = (p) => (p.username === username ? Object.assign(profileOf(p), { unavailable: true }) : p);
|
|
105
|
+
const profiles = store.profiles.map(mark);
|
|
106
|
+
const active = (store.active != null && store.active.username === username) ? null : store.active;
|
|
107
|
+
return Object.assign({}, store, { active, profiles });
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** The stored fields of a profile, nothing else (credentials included). */
|
|
111
|
+
function profileOf (p) {
|
|
112
|
+
const profile = { username: p.username, apiEndpoint: p.apiEndpoint };
|
|
113
|
+
if (p.actingAs != null && typeof p.actingAs.delegate === 'string') {
|
|
114
|
+
profile.actingAs = { username: p.username, delegate: p.actingAs.delegate };
|
|
115
|
+
}
|
|
116
|
+
if (p.unavailable === true) profile.unavailable = true;
|
|
117
|
+
return profile;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function isProfile (p) {
|
|
121
|
+
return p != null && typeof p.username === 'string' && typeof p.apiEndpoint === 'string';
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* The profile of an ACCEPTED auth-request body. Its `delegation` block is a
|
|
126
|
+
* display hint posted by the auth page; `accessInfo().delegation` is
|
|
127
|
+
* authoritative and replaces it when the profile is next activated.
|
|
128
|
+
*/
|
|
129
|
+
function fromAccepted (body) {
|
|
130
|
+
const profile = { username: body.username, apiEndpoint: body.apiEndpoint };
|
|
131
|
+
const d = body.delegation;
|
|
132
|
+
if (d != null && d.isDelegatedAccess === true && d.controlledUsername === body.username &&
|
|
133
|
+
typeof d.delegate?.username === 'string') {
|
|
134
|
+
profile.actingAs = { username: body.username, delegate: d.delegate.username };
|
|
135
|
+
}
|
|
136
|
+
return profile;
|
|
137
|
+
}
|
package/src/Auth/index.js
CHANGED
|
@@ -26,11 +26,14 @@ module.exports = {
|
|
|
26
26
|
* @param {string} [settings.authRequest.languageCode] Language code, as per LoginButton Messages: 'en', 'fr
|
|
27
27
|
* @param {string} settings.authRequest.requestingAppId Application id, ex: 'my-app'
|
|
28
28
|
* @param {Object} settings.authRequest.requestedPermissions
|
|
29
|
-
* @param {string |
|
|
29
|
+
* @param {string | false} [settings.authRequest.returnURL] 'auto#' (default, also when unset or false):
|
|
30
|
+
* popup on desktop, redirect on a phone or tablet; 'self#': always redirect back to this page;
|
|
31
|
+
* a URL: always redirect back to that URL. Must end with '#', '?' or '&'.
|
|
32
|
+
* @param {string} [settings.authRequest.authUrl] Your own auth page for this request; honoured only
|
|
33
|
+
* when it matches the platform's `access:trustedAuthUrls`
|
|
30
34
|
* @param {string} [settings.authRequest.referer] To track registration source
|
|
31
35
|
* @param {string} settings.spanButtonID set and <span> id in DOM to insert default login button or null for custom
|
|
32
36
|
* @param {Function} settings.onStateChange
|
|
33
|
-
* @param {string} [settings.returnURL] Set to "self#" to disable popup and force using the same page
|
|
34
37
|
* @param {string} serviceInfoUrl
|
|
35
38
|
* @param {Object} [serviceCustomizations] override properties of serviceInfoUrl
|
|
36
39
|
* @returns {Promise<Service>}
|