@digitalbazaar/oid4-client 4.1.0 → 4.3.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.
Files changed (2) hide show
  1. package/lib/OID4Client.js +88 -27
  2. package/package.json +8 -8
package/lib/OID4Client.js CHANGED
@@ -18,8 +18,47 @@ export class OID4Client {
18
18
  this.offer = offer;
19
19
  }
20
20
 
21
+ async getNonce({agent, headers = HEADERS} = {}) {
22
+ let response;
23
+ try {
24
+ // get nonce endpoint
25
+ const {nonce_endpoint: url} = this.issuerConfig;
26
+ if(url === undefined) {
27
+ const error = new Error('Credential issuer has no "nonce_endpoint".');
28
+ error.name = 'DataError';
29
+ throw error;
30
+ }
31
+ if(!url?.startsWith('https://')) {
32
+ const error = new Error(
33
+ `Nonce endpoint "${url}" does not start with "https://".`);
34
+ error.name = 'DataError';
35
+ throw error;
36
+ }
37
+
38
+ // get nonce
39
+ response = await httpClient.post(url, {agent, headers});
40
+ if(!response.data) {
41
+ const error = new Error('Nonce response format is not JSON.');
42
+ error.name = 'DataError';
43
+ throw error;
44
+ }
45
+ if(response.data.c_nonce === undefined) {
46
+ const error = new Error('Nonce not provided in response.');
47
+ error.name = 'DataError';
48
+ throw error;
49
+ }
50
+ } catch(cause) {
51
+ const error = new Error('Could not get nonce.', {cause});
52
+ error.name = 'DataError';
53
+ throw error;
54
+ }
55
+
56
+ const {c_nonce: nonce} = response.data;
57
+ return {nonce, response};
58
+ }
59
+
21
60
  async requestCredential({
22
- credentialDefinition, did, didProofSigner, agent, format = 'ldp_vc'
61
+ credentialDefinition, did, didProofSigner, nonce, agent, format = 'ldp_vc'
23
62
  } = {}) {
24
63
  const {issuerConfig, offer} = this;
25
64
  let requests;
@@ -41,13 +80,21 @@ export class OID4Client {
41
80
  credential_definition: credentialDefinition
42
81
  }];
43
82
  }
44
- return this.requestCredentials({requests, did, didProofSigner, agent});
83
+ return this.requestCredentials({
84
+ requests, did, didProofSigner, nonce, agent
85
+ });
45
86
  }
46
87
 
47
88
  async requestCredentials({
48
- requests, did, didProofSigner, agent, format = 'ldp_vc',
89
+ requests, did, didProofSigner, agent, nonce, format = 'ldp_vc',
49
90
  alwaysUseBatchEndpoint = false
50
91
  } = {}) {
92
+ // if `nonce` is given, then `did` and `didProofSigner` must also be
93
+ if(nonce !== undefined && !(did && didProofSigner)) {
94
+ throw new Error(
95
+ 'If "nonce" is given then "did" and "didProofSigner" are required.');
96
+ }
97
+
51
98
  const {issuerConfig, offer} = this;
52
99
  if(requests === undefined && offer) {
53
100
  requests = _createCredentialRequestsFromOffer({
@@ -61,7 +108,8 @@ export class OID4Client {
61
108
  requests = requests.map(r => ({format, ...r}));
62
109
 
63
110
  try {
64
- /* First send credential request(s) to DS without DID proof JWT, e.g.:
111
+ /* First send credential request(s) to DS without DID proof JWT (unless
112
+ `nonce` is given) e.g.:
65
113
 
66
114
  POST /credential HTTP/1.1
67
115
  Host: server.example.com
@@ -71,7 +119,7 @@ export class OID4Client {
71
119
  {
72
120
  "format": "ldp_vc",
73
121
  "credential_definition": {...},
74
- // only present on retry after server requests it
122
+ // only present on retry after server requests it or if nonce is given
75
123
  "proof": {
76
124
  "proof_type": "jwt",
77
125
  "jwt": "eyJraW..."
@@ -109,6 +157,11 @@ export class OID4Client {
109
157
  json = {...requests[0]};
110
158
  }
111
159
 
160
+ if(nonce !== undefined) {
161
+ // add DID proof JWT to json
162
+ await _addDIDProofJWT({issuerConfig, json, nonce, did, didProofSigner});
163
+ }
164
+
112
165
  let result;
113
166
  const headers = {
114
167
  ...HEADERS,
@@ -149,32 +202,16 @@ export class OID4Client {
149
202
  }
150
203
 
151
204
  // validate that `result` has
152
- const {data: {c_nonce: nonce}} = cause;
205
+ let {data: {c_nonce: nonce}} = cause;
153
206
  if(!(nonce && typeof nonce === 'string')) {
154
- const error = new Error('No DID proof challenge specified.');
155
- error.name = 'DataError';
156
- throw error;
207
+ // try to get a nonce
208
+ ({nonce} = await this.getNonce({agent}));
157
209
  }
158
210
 
159
- // generate a DID proof JWT
160
- const {issuer: aud} = this.issuerConfig;
161
- const jwt = await generateDIDProofJWT({
162
- signer: didProofSigner,
163
- nonce,
164
- // the entity identified by the DID is issuing this JWT
165
- iss: did,
166
- // audience MUST be the target issuer per the OID4VCI spec
167
- aud
211
+ // add DID proof JWT to json
212
+ await _addDIDProofJWT({
213
+ issuerConfig, json, nonce, did, didProofSigner
168
214
  });
169
-
170
- // add proof to body to be posted and loop to retry
171
- const proof = {proof_type: 'jwt', jwt};
172
- if(json.credential_requests) {
173
- json.credential_requests = json.credential_requests.map(
174
- cr => ({...cr, proof}));
175
- } else {
176
- json.proof = proof;
177
- }
178
215
  }
179
216
  }
180
217
 
@@ -344,6 +381,30 @@ export class OID4Client {
344
381
  }
345
382
  }
346
383
 
384
+ async function _addDIDProofJWT({
385
+ issuerConfig, json, nonce, did, didProofSigner
386
+ }) {
387
+ // generate a DID proof JWT
388
+ const {issuer: aud} = issuerConfig;
389
+ const jwt = await generateDIDProofJWT({
390
+ signer: didProofSigner,
391
+ nonce,
392
+ // the entity identified by the DID is issuing this JWT
393
+ iss: did,
394
+ // audience MUST be the target issuer per the OID4VCI spec
395
+ aud
396
+ });
397
+
398
+ // add proof to body to be posted and loop to retry
399
+ const proof = {proof_type: 'jwt', jwt};
400
+ if(json.credential_requests) {
401
+ json.credential_requests = json.credential_requests.map(
402
+ cr => ({...cr, proof}));
403
+ } else {
404
+ json.proof = proof;
405
+ }
406
+ }
407
+
347
408
  function _assertRequest(request) {
348
409
  if(!(request && typeof request === 'object')) {
349
410
  throw new TypeError('"request" must be an object.');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@digitalbazaar/oid4-client",
3
- "version": "4.1.0",
3
+ "version": "4.3.0",
4
4
  "description": "An OID4 (VC + VP) client",
5
5
  "homepage": "https://github.com/digitalbazaar/oid4-client",
6
6
  "author": {
@@ -25,10 +25,10 @@
25
25
  "dependencies": {
26
26
  "@digitalbazaar/http-client": "^4.0.0",
27
27
  "base64url-universal": "^2.0.0",
28
- "jose": "^4.15.4",
29
- "jsonpath-plus": "^7.2.0",
28
+ "jose": "^5.9.4",
29
+ "jsonpath-plus": "^10.0.0",
30
30
  "jsonpointer": "^5.0.1",
31
- "uuid": "^9.0.1"
31
+ "uuid": "^10.0.0"
32
32
  },
33
33
  "devDependencies": {
34
34
  "c8": "^7.11.3",
@@ -36,16 +36,16 @@
36
36
  "cross-env": "^7.0.3",
37
37
  "eslint": "^8.41.0",
38
38
  "eslint-config-digitalbazaar": "^5.0.1",
39
- "eslint-plugin-jsdoc": "^45.0.0",
40
- "eslint-plugin-unicorn": "^42.0.0",
39
+ "eslint-plugin-jsdoc": "^50.4.1",
40
+ "eslint-plugin-unicorn": "^56.0.0",
41
41
  "jsdoc": "^4.0.2",
42
- "jsdoc-to-markdown": "^8.0.0",
42
+ "jsdoc-to-markdown": "^9.0.2",
43
43
  "karma": "^6.3.20",
44
44
  "karma-chai": "^0.1.0",
45
45
  "karma-chrome-launcher": "^3.1.1",
46
46
  "karma-mocha": "^2.0.1",
47
47
  "karma-mocha-reporter": "^2.2.5",
48
- "karma-sourcemap-loader": "^0.3.8",
48
+ "karma-sourcemap-loader": "^0.4.0",
49
49
  "karma-webpack": "^5.0.0",
50
50
  "mocha": "^10.0.0",
51
51
  "mocha-lcov-reporter": "^1.3.0",