pryv 3.14.2 → 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 +7 -0
- package/package.json +1 -1
- package/src/Service.js +2 -0
- package/src/index.d.ts +43 -0
- package/src/lib/MfaRequiredError.js +2 -0
- package/src/lib/PryvError.js +4 -0
- package/test/PryvError.test.js +10 -0
- package/test/Service.accessRequestConsent.test.js +16 -0
- package/test/Service.mfa.test.js +13 -0
package/README.md
CHANGED
|
@@ -131,6 +131,13 @@ Here is an implementation of the [Pryv.io authentication process](https://api.pr
|
|
|
131
131
|
optIn: ['location'] // offered UNticked; the user opts in
|
|
132
132
|
// a permission in neither list is optional and shown pre-selected
|
|
133
133
|
},
|
|
134
|
+
// optional (core 2.0.0-rc.32+): cross-account messaging invites the
|
|
135
|
+
// user answers in the same sign-in (1 to 8). The ACCEPTED state then
|
|
136
|
+
// carries `cmcInvites`, one outcome per invite ({ acceptEventId, ... },
|
|
137
|
+
// { declined: true } or { reason }), a hint: the requester learns the
|
|
138
|
+
// truth from its own inbox. Declining a mandatory one refuses the sign-in
|
|
139
|
+
// (REFUSED, reasonId 'REFUSED_MANDATORY_CONSENT'). Ignored by older cores.
|
|
140
|
+
// cmcInvites: [{ capabilityUrl: invite.capabilityUrl, mandatory: true }],
|
|
134
141
|
clientData: {
|
|
135
142
|
'app-web-auth:description': {
|
|
136
143
|
'type': 'note/txt', 'content': 'This is a consent message.'
|
package/package.json
CHANGED
package/src/Service.js
CHANGED
|
@@ -524,6 +524,8 @@ class Service {
|
|
|
524
524
|
// all-or-nothing, which a caller may want to know before showing the
|
|
525
525
|
// approve link.
|
|
526
526
|
if (body.consent != null) envelope.consent = body.consent;
|
|
527
|
+
// Same for consent invites: echoed only by a core that understood them.
|
|
528
|
+
if (body.cmcInvites != null) envelope.cmcInvites = body.cmcInvites;
|
|
527
529
|
// polling by key (pollAccessRequest, connectFromKey) then reaches the
|
|
528
530
|
// core that holds the request
|
|
529
531
|
pollUrls.remember(envelope.key, envelope.poll);
|
package/src/index.d.ts
CHANGED
|
@@ -891,6 +891,8 @@ declare module 'pryv' {
|
|
|
891
891
|
pollRateMs: number;
|
|
892
892
|
/** Echoed only by a core that understood `authRequest.consent`. */
|
|
893
893
|
consent?: AuthRequestConsentForm;
|
|
894
|
+
/** Echoed only by a core that understood `authRequest.cmcInvites`. */
|
|
895
|
+
cmcInvites?: AuthCmcInvite[];
|
|
894
896
|
}>;
|
|
895
897
|
/**
|
|
896
898
|
* Poll an access request once, by its full poll URL (recommended) or its
|
|
@@ -982,6 +984,35 @@ declare module 'pryv' {
|
|
|
982
984
|
>;
|
|
983
985
|
};
|
|
984
986
|
|
|
987
|
+
/**
|
|
988
|
+
* A cross-account messaging invite the user is asked to answer in the same
|
|
989
|
+
* authorisation request (`authRequest.cmcInvites`): the capability URL the
|
|
990
|
+
* app received, whether declining it refuses the whole request, and for
|
|
991
|
+
* which account it is accepted (`'target'`: the account the access is
|
|
992
|
+
* granted for, when the user acts for an account it manages).
|
|
993
|
+
*/
|
|
994
|
+
export type AuthRequestCmcInvite = {
|
|
995
|
+
capabilityUrl: string;
|
|
996
|
+
mandatory?: boolean;
|
|
997
|
+
for?: 'self' | 'target';
|
|
998
|
+
};
|
|
999
|
+
|
|
1000
|
+
/** An invite as the core echoes it back, with its defaults filled in. */
|
|
1001
|
+
export type AuthCmcInvite = {
|
|
1002
|
+
capabilityUrl: string;
|
|
1003
|
+
mandatory: boolean;
|
|
1004
|
+
for: 'self' | 'target';
|
|
1005
|
+
};
|
|
1006
|
+
|
|
1007
|
+
/**
|
|
1008
|
+
* What the auth page did with one invite, in the ACCEPTED body (one per
|
|
1009
|
+
* invite, in order). A hint: the requester learns the truth from its inbox.
|
|
1010
|
+
*/
|
|
1011
|
+
export type AuthCmcInviteOutcome =
|
|
1012
|
+
| { acceptEventId: string; dataGrantAccessId?: string; acceptedFor?: 'self' }
|
|
1013
|
+
| { declined: true }
|
|
1014
|
+
| { reason: string };
|
|
1015
|
+
|
|
985
1016
|
export type States =
|
|
986
1017
|
| 'ERROR'
|
|
987
1018
|
| 'LOADING'
|
|
@@ -1025,6 +1056,8 @@ declare module 'pryv' {
|
|
|
1025
1056
|
defaultName: string;
|
|
1026
1057
|
}>;
|
|
1027
1058
|
consent?: AuthRequestConsentForm;
|
|
1059
|
+
/** Present only when the request carried `cmcInvites` the core understood. */
|
|
1060
|
+
cmcInvites?: AuthCmcInvite[];
|
|
1028
1061
|
requestingAppId: string;
|
|
1029
1062
|
returnURL?: string | null;
|
|
1030
1063
|
serviceInfo?: ServiceInfo;
|
|
@@ -1055,6 +1088,8 @@ declare module 'pryv' {
|
|
|
1055
1088
|
profile?: AuthProfile;
|
|
1056
1089
|
/** One-time credential hand-off key (shared-secret delivery), in place of `token`. */
|
|
1057
1090
|
handoff?: { type: 'shared-secret'; key: string };
|
|
1091
|
+
/** What the auth page did with each invite of the request (a hint). */
|
|
1092
|
+
cmcInvites?: AuthCmcInviteOutcome[];
|
|
1058
1093
|
};
|
|
1059
1094
|
SIGNOUT: {};
|
|
1060
1095
|
SWITCHING: {
|
|
@@ -1089,6 +1124,7 @@ declare module 'pryv' {
|
|
|
1089
1124
|
requestingAppId: string;
|
|
1090
1125
|
requestedPermissions: AuthRequestedPermission[];
|
|
1091
1126
|
consent?: AuthRequestConsentForm;
|
|
1127
|
+
cmcInvites?: AuthCmcInvite[];
|
|
1092
1128
|
lang?: string;
|
|
1093
1129
|
returnURL?: string;
|
|
1094
1130
|
clientData?: KeyValue;
|
|
@@ -1125,6 +1161,13 @@ declare module 'pryv' {
|
|
|
1125
1161
|
languageCode?: string;
|
|
1126
1162
|
requestedPermissions: AuthRequestedPermission[];
|
|
1127
1163
|
consent?: AuthRequestConsent;
|
|
1164
|
+
/**
|
|
1165
|
+
* Cross-account messaging invites the user answers in the same
|
|
1166
|
+
* sign-in (1 to 8). The ACCEPTED state carries one outcome per invite;
|
|
1167
|
+
* a declined mandatory invite ends the request REFUSED with
|
|
1168
|
+
* `reasonId: 'REFUSED_MANDATORY_CONSENT'`. An older core ignores the field.
|
|
1169
|
+
*/
|
|
1170
|
+
cmcInvites?: AuthRequestCmcInvite[];
|
|
1128
1171
|
/**
|
|
1129
1172
|
* Where the sign-in happens: `'auto#'` (default, also when unset or
|
|
1130
1173
|
* `false`) opens a popup on desktop and redirects on a phone or tablet;
|
|
@@ -41,6 +41,8 @@ class MfaRequiredError extends PryvError {
|
|
|
41
41
|
this.id = (apiErr && apiErr.id) || 'mfa-required';
|
|
42
42
|
this.status = response && response.status;
|
|
43
43
|
this.response = { body, status: response && response.status };
|
|
44
|
+
// Read by name, but not printed with the error (like `innerObject`).
|
|
45
|
+
Object.defineProperty(this, 'mfaToken', { enumerable: false });
|
|
44
46
|
|
|
45
47
|
if (Error.captureStackTrace) {
|
|
46
48
|
Error.captureStackTrace(this, MfaRequiredError);
|
package/src/lib/PryvError.js
CHANGED
|
@@ -35,6 +35,10 @@ class PryvError extends Error {
|
|
|
35
35
|
this.status = undefined;
|
|
36
36
|
/** @type {{ body: any, status: number }|undefined} Raw response */
|
|
37
37
|
this.response = undefined;
|
|
38
|
+
// `innerObject` and `response` hold raw platform answers: still readable and
|
|
39
|
+
// writable, but not enumerable, so `console.log(err)` does not print them.
|
|
40
|
+
Object.defineProperty(this, 'innerObject', { enumerable: false });
|
|
41
|
+
Object.defineProperty(this, 'response', { enumerable: false });
|
|
38
42
|
|
|
39
43
|
if (Error.captureStackTrace) {
|
|
40
44
|
Error.captureStackTrace(this, PryvError);
|
package/test/PryvError.test.js
CHANGED
|
@@ -71,4 +71,14 @@ describe('[PERX] PryvError', function () {
|
|
|
71
71
|
expect(error.status).to.equal(502);
|
|
72
72
|
expect(error.response).to.deep.equal({ body: undefined, status: 502 });
|
|
73
73
|
});
|
|
74
|
+
|
|
75
|
+
it('[PERJ] innerObject and response are readable but not enumerable (not printed with the error)', function () {
|
|
76
|
+
const error = new PryvError('Outer', { id: 'invalid-access-token', secret: 'tok' });
|
|
77
|
+
error.response = { body: { secret: 'tok' }, status: 401 };
|
|
78
|
+
expect(error.innerObject.id).to.equal('invalid-access-token');
|
|
79
|
+
expect(error.response.status).to.equal(401);
|
|
80
|
+
expect(Object.keys(error)).to.not.include('innerObject');
|
|
81
|
+
expect(Object.keys(error)).to.not.include('response');
|
|
82
|
+
expect(require('util').inspect(error)).to.not.include('tok');
|
|
83
|
+
});
|
|
74
84
|
});
|
|
@@ -108,4 +108,20 @@ describe('[ARQC] auth-request consent sidecar', function () {
|
|
|
108
108
|
expect(envOld.key).to.equal('k2');
|
|
109
109
|
expect(envOld).to.not.have.property('consent');
|
|
110
110
|
});
|
|
111
|
+
|
|
112
|
+
it('[LCI3] posts cmcInvites verbatim and returns the echo a core sends, nothing when it does not', async function () {
|
|
113
|
+
const invites = [{ capabilityUrl: 'https://cap@doctor.test.local/', mandatory: true }];
|
|
114
|
+
const echo = [{ capabilityUrl: 'https://cap@doctor.test.local/', mandatory: true, for: 'self' }];
|
|
115
|
+
stubFetch({ key: 'k3', authUrl: 'https://auth/', poll: 'https://poll/k3', poll_rate_ms: 1000, cmcInvites: echo });
|
|
116
|
+
const service = new pryv.Service('https://reg.test.local/service/info');
|
|
117
|
+
const env = await service.startAccessRequest({ ...REQUEST, cmcInvites: invites });
|
|
118
|
+
expect(posted[0].cmcInvites).to.deep.equal(invites);
|
|
119
|
+
expect(env.cmcInvites).to.deep.equal(echo);
|
|
120
|
+
if (realFetch) { global.fetch = realFetch; realFetch = null; }
|
|
121
|
+
|
|
122
|
+
stubFetch({ key: 'k4', authUrl: 'https://auth/', poll: 'https://poll/k4', poll_rate_ms: 1000 });
|
|
123
|
+
const older = new pryv.Service('https://reg.test.local/service/info');
|
|
124
|
+
const envOld = await older.startAccessRequest({ ...REQUEST, cmcInvites: invites });
|
|
125
|
+
expect(envOld).to.not.have.property('cmcInvites');
|
|
126
|
+
});
|
|
111
127
|
});
|
package/test/Service.mfa.test.js
CHANGED
|
@@ -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
|
});
|