@sphereon/oid4vci-client 0.8.1 → 0.8.2-next.26

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 (40) hide show
  1. package/README.md +9 -8
  2. package/dist/AccessTokenClient.d.ts.map +1 -1
  3. package/dist/AccessTokenClient.js +8 -8
  4. package/dist/AccessTokenClient.js.map +1 -1
  5. package/dist/AuthorizationDetailsBuilder.d.ts.map +1 -1
  6. package/dist/CredentialRequestClient.d.ts.map +1 -1
  7. package/dist/CredentialRequestClient.js +36 -36
  8. package/dist/CredentialRequestClient.js.map +1 -1
  9. package/dist/CredentialRequestClientBuilder.d.ts +6 -0
  10. package/dist/CredentialRequestClientBuilder.d.ts.map +1 -1
  11. package/dist/CredentialRequestClientBuilder.js +10 -1
  12. package/dist/CredentialRequestClientBuilder.js.map +1 -1
  13. package/dist/OpenID4VCIClient.d.ts +15 -5
  14. package/dist/OpenID4VCIClient.d.ts.map +1 -1
  15. package/dist/OpenID4VCIClient.js +111 -41
  16. package/dist/OpenID4VCIClient.js.map +1 -1
  17. package/dist/ProofOfPossessionBuilder.d.ts +3 -1
  18. package/dist/ProofOfPossessionBuilder.d.ts.map +1 -1
  19. package/dist/ProofOfPossessionBuilder.js +5 -0
  20. package/dist/ProofOfPossessionBuilder.js.map +1 -1
  21. package/dist/functions/ProofUtil.d.ts +2 -1
  22. package/dist/functions/ProofUtil.d.ts.map +1 -1
  23. package/dist/functions/ProofUtil.js +6 -4
  24. package/dist/functions/ProofUtil.js.map +1 -1
  25. package/lib/AccessTokenClient.ts +10 -8
  26. package/lib/AuthorizationDetailsBuilder.ts +2 -2
  27. package/lib/CredentialRequestClient.ts +42 -38
  28. package/lib/CredentialRequestClientBuilder.ts +21 -1
  29. package/lib/OpenID4VCIClient.ts +140 -42
  30. package/lib/ProofOfPossessionBuilder.ts +8 -0
  31. package/lib/__tests__/CredentialRequestClient.spec.ts +21 -9
  32. package/lib/__tests__/EBSIE2E.spec.test.ts +138 -0
  33. package/lib/__tests__/MattrE2E.spec.test.ts +1 -1
  34. package/lib/__tests__/MetadataClient.spec.ts +4 -1
  35. package/lib/__tests__/OpenID4VCIClient.spec.ts +5 -0
  36. package/lib/__tests__/SdJwt.spec.ts +161 -0
  37. package/lib/__tests__/SphereonE2E.spec.test.ts +169 -0
  38. package/lib/__tests__/data/VciDataFixtures.ts +14 -13
  39. package/lib/functions/ProofUtil.ts +18 -4
  40. package/package.json +10 -5
@@ -1,6 +1,7 @@
1
1
  import {
2
- CredentialRequestV1_0_08,
3
2
  CredentialResponse,
3
+ getCredentialRequestForVersion,
4
+ getUniformFormat,
4
5
  OID4VCICredentialFormat,
5
6
  OpenId4VCIVersion,
6
7
  OpenIDResponse,
@@ -53,33 +54,17 @@ export class CredentialRequestClient {
53
54
  }
54
55
 
55
56
  public async acquireCredentialsUsingRequest(uniformRequest: UniformCredentialRequest): Promise<OpenIDResponse<CredentialResponse>> {
56
- let request: CredentialRequestV1_0_08 | UniformCredentialRequest = uniformRequest;
57
- if (!this.isV11OrHigher()) {
58
- let format: string = uniformRequest.format;
59
- if (format === 'jwt_vc_json') {
60
- format = 'jwt_vc';
61
- } else if (format === 'jwt_vc_json-ld') {
62
- format = 'ldp_vc';
63
- }
64
-
65
- request = {
66
- format,
67
- proof: uniformRequest.proof,
68
- type:
69
- 'types' in uniformRequest
70
- ? uniformRequest.types.filter((t) => t !== 'VerifiableCredential')[0]
71
- : uniformRequest.credential_definition.types[0],
72
- } as CredentialRequestV1_0_08;
73
- }
57
+ const request = getCredentialRequestForVersion(uniformRequest, this.version());
74
58
  const credentialEndpoint: string = this.credentialRequestOpts.credentialEndpoint;
75
59
  if (!isValidURL(credentialEndpoint)) {
76
60
  debug(`Invalid credential endpoint: ${credentialEndpoint}`);
77
61
  throw new Error(URL_NOT_VALID);
78
62
  }
79
63
  debug(`Acquiring credential(s) from: ${credentialEndpoint}`);
64
+ debug(`request\n: ${JSON.stringify(request, null, 2)}`);
80
65
  const requestToken: string = this.credentialRequestOpts.token;
81
66
  const response: OpenIDResponse<CredentialResponse> = await post(credentialEndpoint, JSON.stringify(request), { bearerToken: requestToken });
82
- debug(`Credential endpoint ${credentialEndpoint} response:\r\n${response}`);
67
+ debug(`Credential endpoint ${credentialEndpoint} response:\r\n${JSON.stringify(response, null, 2)}`);
83
68
  return response;
84
69
  }
85
70
 
@@ -92,20 +77,10 @@ export class CredentialRequestClient {
92
77
  const { proofInput } = opts;
93
78
  const formatSelection = opts.format ?? this.credentialRequestOpts.format;
94
79
 
95
- let format: OID4VCICredentialFormat = formatSelection as OID4VCICredentialFormat;
96
- if (opts.version < OpenId4VCIVersion.VER_1_0_11) {
97
- if (formatSelection === 'jwt_vc' || formatSelection === 'jwt') {
98
- format = 'jwt_vc_json';
99
- } else if (formatSelection === 'ldp_vc' || formatSelection === 'ldp') {
100
- format = 'jwt_vc_json-ld';
101
- }
102
- }
103
-
104
- if (!format) {
80
+ if (!formatSelection) {
105
81
  throw Error(`Format of credential to be issued is missing`);
106
- } else if (format !== 'jwt_vc_json-ld' && format !== 'jwt_vc_json' && format !== 'ldp_vc') {
107
- throw Error(`Invalid format of credential to be issued: ${format}`);
108
82
  }
83
+ const format = getUniformFormat(formatSelection);
109
84
  const typesSelection =
110
85
  opts?.credentialTypes && (typeof opts.credentialTypes === 'string' || opts.credentialTypes.length > 0)
111
86
  ? opts.credentialTypes
@@ -113,7 +88,9 @@ export class CredentialRequestClient {
113
88
  const types = Array.isArray(typesSelection) ? typesSelection : [typesSelection];
114
89
  if (types.length === 0) {
115
90
  throw Error(`Credential type(s) need to be provided`);
116
- } else if (!this.isV11OrHigher() && types.length !== 1) {
91
+ }
92
+ // FIXME: this is mixing up the type (as id) from v8/v9 and the types (from the vc.type) from v11
93
+ else if (!this.isV11OrHigher() && types.length !== 1) {
117
94
  throw Error('Only a single credential type is supported for V8/V9');
118
95
  }
119
96
 
@@ -121,16 +98,43 @@ export class CredentialRequestClient {
121
98
  'proof_type' in proofInput
122
99
  ? await ProofOfPossessionBuilder.fromProof(proofInput as ProofOfPossession, opts.version).build()
123
100
  : await proofInput.build();
124
- return {
125
- types,
126
- format,
127
- proof,
128
- } as UniformCredentialRequest;
101
+
102
+ // TODO: we should move format specific logic
103
+ if (format === 'jwt_vc_json' || format === 'jwt_vc') {
104
+ return {
105
+ types,
106
+ format,
107
+ proof,
108
+ };
109
+ } else if (format === 'jwt_vc_json-ld' || format === 'ldp_vc') {
110
+ return {
111
+ format,
112
+ proof,
113
+ credential_definition: {
114
+ types,
115
+ // FIXME: this was not included in the original code, but it is required
116
+ '@context': [],
117
+ },
118
+ };
119
+ } else if (format === 'vc+sd-jwt') {
120
+ if (types.length > 1) {
121
+ throw Error(`Only a single credential type is supported for ${format}`);
122
+ }
123
+
124
+ return {
125
+ format,
126
+ proof,
127
+ vct: types[0],
128
+ };
129
+ }
130
+
131
+ throw new Error(`Unsupported format: ${format}`);
129
132
  }
130
133
 
131
134
  private version(): OpenId4VCIVersion {
132
135
  return this.credentialRequestOpts?.version ?? OpenId4VCIVersion.VER_1_0_11;
133
136
  }
137
+
134
138
  private isV11OrHigher(): boolean {
135
139
  return this.version() >= OpenId4VCIVersion.VER_1_0_11;
136
140
  }
@@ -6,6 +6,7 @@ import {
6
6
  determineSpecVersionFromOffer,
7
7
  EndpointMetadata,
8
8
  getIssuerFromCredentialOfferPayload,
9
+ getTypesFromOffer,
9
10
  OID4VCICredentialFormat,
10
11
  OpenId4VCIVersion,
11
12
  UniformCredentialOfferRequest,
@@ -22,6 +23,25 @@ export class CredentialRequestClientBuilder {
22
23
  token?: string;
23
24
  version?: OpenId4VCIVersion;
24
25
 
26
+ public static fromCredentialIssuer({
27
+ credentialIssuer,
28
+ metadata,
29
+ version,
30
+ credentialTypes,
31
+ }: {
32
+ credentialIssuer: string;
33
+ metadata?: EndpointMetadata;
34
+ version?: OpenId4VCIVersion;
35
+ credentialTypes: string | string[];
36
+ }): CredentialRequestClientBuilder {
37
+ const issuer = credentialIssuer;
38
+ const builder = new CredentialRequestClientBuilder();
39
+ builder.withVersion(version ?? OpenId4VCIVersion.VER_1_0_11);
40
+ builder.withCredentialEndpoint(metadata?.credential_endpoint ?? (issuer.endsWith('/') ? `${issuer}credential` : `${issuer}/credential`));
41
+ builder.withCredentialType(credentialTypes);
42
+ return builder;
43
+ }
44
+
25
45
  public static async fromURI({ uri, metadata }: { uri: string; metadata?: EndpointMetadata }): Promise<CredentialRequestClientBuilder> {
26
46
  const offer = await CredentialOfferClient.fromURI(uri);
27
47
  return CredentialRequestClientBuilder.fromCredentialOfferRequest({ request: offer, ...offer, metadata, version: offer.version });
@@ -46,7 +66,7 @@ export class CredentialRequestClientBuilder {
46
66
  builder.withCredentialType((request.original_credential_offer as CredentialOfferPayloadV1_0_08).credential_type);
47
67
  } else {
48
68
  // todo: look whether this is correct
49
- builder.withCredentialType(request.credential_offer.credentials.flatMap((c) => (typeof c === 'string' ? c : c.types)));
69
+ builder.withCredentialType(getTypesFromOffer(request.credential_offer));
50
70
  }
51
71
 
52
72
  return builder;
@@ -8,15 +8,18 @@ import {
8
8
  CredentialResponse,
9
9
  CredentialSupported,
10
10
  EndpointMetadataResult,
11
+ getIssuerFromCredentialOfferPayload,
12
+ getSupportedCredentials,
13
+ getTypesFromCredentialSupported,
11
14
  JsonURIMode,
15
+ JWK,
16
+ KID_JWK_X5C_ERROR,
12
17
  OID4VCICredentialFormat,
13
18
  OpenId4VCIVersion,
14
19
  ProofOfPossessionCallbacks,
15
20
  PushedAuthorizationResponse,
16
21
  ResponseType,
17
22
  } from '@sphereon/oid4vci-common';
18
- import { getSupportedCredentials } from '@sphereon/oid4vci-common/dist/functions/IssuerMetadataUtils';
19
- import { CredentialSupportedTypeV1_0_08 } from '@sphereon/oid4vci-common/dist/types/v1_0_08.types';
20
23
  import { CredentialFormat } from '@sphereon/ssi-types';
21
24
  import Debug from 'debug';
22
25
 
@@ -39,27 +42,66 @@ interface AuthDetails {
39
42
 
40
43
  interface AuthRequestOpts {
41
44
  codeChallenge: string;
42
- codeChallengeMethod: CodeChallengeMethod;
45
+ codeChallengeMethod?: CodeChallengeMethod;
43
46
  authorizationDetails?: AuthDetails | AuthDetails[];
44
47
  redirectUri: string;
45
48
  scope?: string;
46
49
  }
47
50
 
48
51
  export class OpenID4VCIClient {
49
- private readonly _credentialOffer: CredentialOfferRequestWithBaseUrl;
52
+ private readonly _credentialOffer?: CredentialOfferRequestWithBaseUrl;
53
+ private _credentialIssuer: string;
50
54
  private _clientId?: string;
51
55
  private _kid: string | undefined;
56
+ private _jwk: JWK | undefined;
52
57
  private _alg: Alg | string | undefined;
53
58
  private _endpointMetadata: EndpointMetadataResult | undefined;
54
59
  private _accessTokenResponse: AccessTokenResponse | undefined;
55
60
 
56
- private constructor(credentialOffer: CredentialOfferRequestWithBaseUrl, kid?: string, alg?: Alg | string, clientId?: string) {
61
+ private constructor({
62
+ credentialOffer,
63
+ clientId,
64
+ kid,
65
+ alg,
66
+ credentialIssuer,
67
+ }: {
68
+ credentialOffer?: CredentialOfferRequestWithBaseUrl;
69
+ kid?: string;
70
+ alg?: Alg | string;
71
+ clientId?: string;
72
+ credentialIssuer?: string;
73
+ }) {
57
74
  this._credentialOffer = credentialOffer;
75
+ const issuer = credentialIssuer ?? (credentialOffer ? getIssuerFromCredentialOfferPayload(credentialOffer.credential_offer) : undefined);
76
+ if (!issuer) {
77
+ throw Error('No credential issuer supplied or deduced from offer');
78
+ }
79
+ this._credentialIssuer = issuer;
58
80
  this._kid = kid;
59
81
  this._alg = alg;
60
82
  this._clientId = clientId;
61
83
  }
62
84
 
85
+ public static async fromCredentialIssuer({
86
+ kid,
87
+ alg,
88
+ retrieveServerMetadata,
89
+ clientId,
90
+ credentialIssuer,
91
+ }: {
92
+ credentialIssuer: string;
93
+ kid?: string;
94
+ alg?: Alg | string;
95
+ retrieveServerMetadata?: boolean;
96
+ clientId?: string;
97
+ }) {
98
+ const client = new OpenID4VCIClient({ kid, alg, clientId, credentialIssuer });
99
+ if (retrieveServerMetadata === undefined || retrieveServerMetadata) {
100
+ await client.retrieveServerMetadata();
101
+ }
102
+ return client;
103
+ }
104
+
63
105
  public static async fromURI({
64
106
  uri,
65
107
  kid,
@@ -75,7 +117,12 @@ export class OpenID4VCIClient {
75
117
  resolveOfferUri?: boolean;
76
118
  clientId?: string;
77
119
  }): Promise<OpenID4VCIClient> {
78
- const client = new OpenID4VCIClient(await CredentialOfferClient.fromURI(uri, { resolve: resolveOfferUri }), kid, alg, clientId);
120
+ const client = new OpenID4VCIClient({
121
+ credentialOffer: await CredentialOfferClient.fromURI(uri, { resolve: resolveOfferUri }),
122
+ kid,
123
+ alg,
124
+ clientId,
125
+ });
79
126
 
80
127
  if (retrieveServerMetadata === undefined || retrieveServerMetadata) {
81
128
  await client.retrieveServerMetadata();
@@ -86,16 +133,41 @@ export class OpenID4VCIClient {
86
133
  public async retrieveServerMetadata(): Promise<EndpointMetadataResult> {
87
134
  this.assertIssuerData();
88
135
  if (!this._endpointMetadata) {
89
- this._endpointMetadata = await MetadataClient.retrieveAllMetadataFromCredentialOffer(this.credentialOffer);
136
+ if (this.credentialOffer) {
137
+ this._endpointMetadata = await MetadataClient.retrieveAllMetadataFromCredentialOffer(this.credentialOffer);
138
+ } else if (this._credentialIssuer) {
139
+ this._endpointMetadata = await MetadataClient.retrieveAllMetadata(this._credentialIssuer);
140
+ } else {
141
+ throw Error(`Cannot retrieve issuer metadata without either a credential offer, or issuer value`);
142
+ }
90
143
  }
91
144
  return this.endpointMetadata;
92
145
  }
93
146
 
147
+ // todo: Unify this method with the par method
148
+
94
149
  public createAuthorizationRequestUrl({ codeChallengeMethod, codeChallenge, authorizationDetails, redirectUri, scope }: AuthRequestOpts): string {
95
150
  // Scope and authorization_details can be used in the same authorization request
96
151
  // https://datatracker.ietf.org/doc/html/draft-ietf-oauth-rar-23#name-relationship-to-scope-param
97
152
  if (!scope && !authorizationDetails) {
98
- throw Error('Please provide a scope or authorization_details');
153
+ if (!this.credentialOffer) {
154
+ throw Error('Please provide a scope or authorization_details');
155
+ }
156
+ const creds = this.credentialOffer.credential_offer.credentials;
157
+
158
+ authorizationDetails = creds
159
+ .flatMap((cred) => (typeof cred === 'string' ? this.getCredentialsSupported(true) : (cred as CredentialSupported)))
160
+ .map((cred) => {
161
+ return {
162
+ ...cred,
163
+ type: 'openid_credential',
164
+ locations: [this._credentialIssuer],
165
+ format: cred.format,
166
+ } satisfies AuthDetails;
167
+ });
168
+ if (authorizationDetails.length === 0) {
169
+ throw Error(`Could not create authorization details from credential offer. Please pass in explicit details`);
170
+ }
99
171
  }
100
172
  // todo: Probably can go with current logic in MetadataClient who will always set the authorization_endpoint when found
101
173
  // handling this because of the support for v1_0-08
@@ -117,7 +189,7 @@ export class OpenID4VCIClient {
117
189
 
118
190
  const queryObj: { [key: string]: string } = {
119
191
  response_type: ResponseType.AUTH_CODE,
120
- code_challenge_method: codeChallengeMethod,
192
+ code_challenge_method: codeChallengeMethod ?? CodeChallengeMethod.SHA256,
121
193
  code_challenge: codeChallenge,
122
194
  authorization_details: JSON.stringify(this.handleAuthorizationDetails(authorizationDetails)),
123
195
  redirect_uri: redirectUri,
@@ -128,7 +200,7 @@ export class OpenID4VCIClient {
128
200
  queryObj['client_id'] = this.clientId;
129
201
  }
130
202
 
131
- if (this.credentialOffer.issuerState) {
203
+ if (this.credentialOffer?.issuerState) {
132
204
  queryObj['issuer_state'] = this.credentialOffer.issuerState;
133
205
  }
134
206
 
@@ -140,6 +212,7 @@ export class OpenID4VCIClient {
140
212
  });
141
213
  }
142
214
 
215
+ // todo: Unify this method with the create auth request url method
143
216
  public async acquirePushedAuthorizationRequestURI({
144
217
  codeChallengeMethod,
145
218
  codeChallenge,
@@ -173,7 +246,7 @@ export class OpenID4VCIClient {
173
246
 
174
247
  const queryObj: { [key: string]: string } = {
175
248
  response_type: ResponseType.AUTH_CODE,
176
- code_challenge_method: codeChallengeMethod,
249
+ code_challenge_method: codeChallengeMethod ?? CodeChallengeMethod.SHA256,
177
250
  code_challenge: codeChallenge,
178
251
  authorization_details: JSON.stringify(this.handleAuthorizationDetails(authorizationDetails)),
179
252
  redirect_uri: redirectUri,
@@ -184,7 +257,7 @@ export class OpenID4VCIClient {
184
257
  queryObj['client_id'] = this.clientId;
185
258
  }
186
259
 
187
- if (this.credentialOffer.issuerState) {
260
+ if (this.credentialOffer?.issuerState) {
188
261
  queryObj['issuer_state'] = this.credentialOffer.issuerState;
189
262
  }
190
263
 
@@ -249,6 +322,7 @@ export class OpenID4VCIClient {
249
322
  const response = await accessTokenClient.acquireAccessToken({
250
323
  credentialOffer: this.credentialOffer,
251
324
  metadata: this.endpointMetadata,
325
+ credentialIssuer: this.getIssuer(),
252
326
  pin,
253
327
  codeVerifier,
254
328
  code,
@@ -281,6 +355,7 @@ export class OpenID4VCIClient {
281
355
  proofCallbacks,
282
356
  format,
283
357
  kid,
358
+ jwk,
284
359
  alg,
285
360
  jti,
286
361
  }: {
@@ -288,46 +363,54 @@ export class OpenID4VCIClient {
288
363
  proofCallbacks: ProofOfPossessionCallbacks<any>;
289
364
  format?: CredentialFormat | OID4VCICredentialFormat;
290
365
  kid?: string;
366
+ jwk?: JWK;
291
367
  alg?: Alg | string;
292
368
  jti?: string;
293
369
  }): Promise<CredentialResponse> {
294
- if (alg) {
295
- this._alg = alg;
296
- }
297
- if (kid) {
298
- this._kid = kid;
370
+ if ([jwk, kid].filter((v) => v !== undefined).length > 1) {
371
+ throw new Error(KID_JWK_X5C_ERROR + `. jwk: ${jwk !== undefined}, kid: ${kid !== undefined}`);
299
372
  }
300
373
 
301
- const requestBuilder = CredentialRequestClientBuilder.fromCredentialOffer({
302
- credentialOffer: this.credentialOffer,
303
- metadata: this.endpointMetadata,
304
- });
374
+ if (alg) this._alg = alg;
375
+ if (jwk) this._jwk = jwk;
376
+ if (kid) this._kid = kid;
377
+
378
+ const requestBuilder = this.credentialOffer
379
+ ? CredentialRequestClientBuilder.fromCredentialOffer({
380
+ credentialOffer: this.credentialOffer,
381
+ metadata: this.endpointMetadata,
382
+ })
383
+ : CredentialRequestClientBuilder.fromCredentialIssuer({
384
+ credentialIssuer: this.getIssuer(),
385
+ credentialTypes,
386
+ metadata: this.endpointMetadata,
387
+ version: this.version(),
388
+ });
305
389
 
306
390
  requestBuilder.withTokenFromResponse(this.accessTokenResponse);
307
391
  if (this.endpointMetadata?.credentialIssuerMetadata) {
308
392
  const metadata = this.endpointMetadata.credentialIssuerMetadata;
309
- const types = Array.isArray(credentialTypes) ? credentialTypes.sort() : [credentialTypes];
393
+ const types = Array.isArray(credentialTypes) ? [...credentialTypes].sort() : [credentialTypes];
310
394
 
311
395
  if (metadata.credentials_supported && Array.isArray(metadata.credentials_supported)) {
312
396
  let typeSupported = false;
313
397
 
314
398
  metadata.credentials_supported.forEach((supportedCredential) => {
315
- if (!supportedCredential.types || supportedCredential.types.length === 0) {
316
- throw Error('types is required in the credentials supported');
317
- }
399
+ const subTypes = getTypesFromCredentialSupported(supportedCredential);
318
400
  if (
319
- supportedCredential.types.sort().every((t, i) => types[i] === t) ||
320
- (types.length === 1 && (types[0] === supportedCredential.id || supportedCredential.types.includes(types[0])))
401
+ subTypes.sort().every((t, i) => types[i] === t) ||
402
+ (types.length === 1 && (types[0] === supportedCredential.id || subTypes.includes(types[0])))
321
403
  ) {
322
404
  typeSupported = true;
323
405
  }
324
406
  });
325
407
 
326
408
  if (!typeSupported) {
327
- throw Error(`Not all credential types ${JSON.stringify(credentialTypes)} are supported by issuer ${this.getIssuer()}`);
409
+ console.log(`Not all credential types ${JSON.stringify(credentialTypes)} are present in metadata for ${this.getIssuer()}`);
410
+ // throw Error(`Not all credential types ${JSON.stringify(credentialTypes)} are supported by issuer ${this.getIssuer()}`);
328
411
  }
329
412
  } else if (metadata.credentials_supported && !Array.isArray(metadata.credentials_supported)) {
330
- const credentialsSupported = metadata.credentials_supported as CredentialSupportedTypeV1_0_08;
413
+ const credentialsSupported = metadata.credentials_supported;
331
414
  if (types.some((type) => !metadata.credentials_supported || !credentialsSupported[type])) {
332
415
  throw Error(`Not all credential types ${JSON.stringify(credentialTypes)} are supported by issuer ${this.getIssuer()}`);
333
416
  }
@@ -341,8 +424,14 @@ export class OpenID4VCIClient {
341
424
  version: this.version(),
342
425
  })
343
426
  .withIssuer(this.getIssuer())
344
- .withAlg(this.alg)
345
- .withKid(this.kid);
427
+ .withAlg(this.alg);
428
+
429
+ if (this._jwk) {
430
+ proofBuilder.withJWK(this._jwk);
431
+ }
432
+ if (this._kid) {
433
+ proofBuilder.withKid(this._kid);
434
+ }
346
435
 
347
436
  if (this.clientId) {
348
437
  proofBuilder.withClientId(this.clientId);
@@ -356,7 +445,7 @@ export class OpenID4VCIClient {
356
445
  format,
357
446
  });
358
447
  if (response.errorBody) {
359
- debug(`Credential request error:\r\n${response.errorBody}`);
448
+ debug(`Credential request error:\r\n${JSON.stringify(response.errorBody)}`);
360
449
  throw Error(
361
450
  `Retrieving a credential from ${this._endpointMetadata?.credential_endpoint} for issuer ${this.getIssuer()} failed with status: ${
362
451
  response.origResponse.status
@@ -389,7 +478,9 @@ export class OpenID4VCIClient {
389
478
  }
390
479
 
391
480
  getCredentialOfferTypes(): string[][] {
392
- if (this.credentialOffer.version < OpenId4VCIVersion.VER_1_0_11) {
481
+ if (!this.credentialOffer) {
482
+ return [];
483
+ } else if (this.credentialOffer.version < OpenId4VCIVersion.VER_1_0_11) {
393
484
  const orig = this.credentialOffer.original_credential_offer as CredentialOfferPayloadV1_0_08;
394
485
  const types: string[] = typeof orig.credential_type === 'string' ? [orig.credential_type] : orig.credential_type;
395
486
  const result: string[][] = [];
@@ -397,21 +488,29 @@ export class OpenID4VCIClient {
397
488
  return result;
398
489
  } else {
399
490
  return this.credentialOffer.credential_offer.credentials.map((c) => {
400
- return typeof c === 'string' ? [c] : c.types;
491
+ if (typeof c === 'string') {
492
+ return [c];
493
+ } else if ('types' in c) {
494
+ return c.types;
495
+ } else if ('vct' in c) {
496
+ return [c.vct];
497
+ } else {
498
+ return c.credential_definition.types;
499
+ }
401
500
  });
402
501
  }
403
502
  }
404
503
 
405
504
  issuerSupportedFlowTypes(): AuthzFlowType[] {
406
- return this.credentialOffer.supportedFlows;
505
+ return this.credentialOffer?.supportedFlows ?? [AuthzFlowType.AUTHORIZATION_CODE_FLOW];
407
506
  }
408
507
 
409
- get credentialOffer(): CredentialOfferRequestWithBaseUrl {
508
+ get credentialOffer(): CredentialOfferRequestWithBaseUrl | undefined {
410
509
  return this._credentialOffer;
411
510
  }
412
511
 
413
512
  public version(): OpenId4VCIVersion {
414
- return this.credentialOffer.version;
513
+ return this.credentialOffer?.version ?? OpenId4VCIVersion.VER_1_0_11;
415
514
  }
416
515
 
417
516
  public get endpointMetadata(): EndpointMetadataResult {
@@ -437,9 +536,6 @@ export class OpenID4VCIClient {
437
536
  }
438
537
 
439
538
  get clientId(): string | undefined {
440
- /*if (!this._clientId) {
441
- throw Error('No client id present');
442
- }*/
443
539
  return this._clientId;
444
540
  }
445
541
 
@@ -451,7 +547,7 @@ export class OpenID4VCIClient {
451
547
 
452
548
  public getIssuer(): string {
453
549
  this.assertIssuerData();
454
- return this._endpointMetadata ? this.endpointMetadata.issuer : this.getIssuer();
550
+ return this._credentialIssuer!;
455
551
  }
456
552
 
457
553
  public getAccessTokenEndpoint(): string {
@@ -467,8 +563,10 @@ export class OpenID4VCIClient {
467
563
  }
468
564
 
469
565
  private assertIssuerData(): void {
470
- if (!this._credentialOffer) {
566
+ if (!this._credentialOffer && this.issuerSupportedFlowTypes().includes(AuthzFlowType.PRE_AUTHORIZED_CODE_FLOW)) {
471
567
  throw Error(`No issuance initiation or credential offer present`);
568
+ } else if (!this._credentialIssuer) {
569
+ throw Error(`No credential issuer value present`);
472
570
  }
473
571
  }
474
572
 
@@ -2,6 +2,7 @@ import {
2
2
  AccessTokenResponse,
3
3
  Alg,
4
4
  EndpointMetadata,
5
+ JWK,
5
6
  Jwt,
6
7
  NO_JWT_PROVIDED,
7
8
  OpenId4VCIVersion,
@@ -19,6 +20,7 @@ export class ProofOfPossessionBuilder<DIDDoc> {
19
20
  private readonly version: OpenId4VCIVersion;
20
21
 
21
22
  private kid?: string;
23
+ private jwk?: JWK;
22
24
  private clientId?: string;
23
25
  private issuer?: string;
24
26
  private jwt?: Jwt;
@@ -91,6 +93,11 @@ export class ProofOfPossessionBuilder<DIDDoc> {
91
93
  return this;
92
94
  }
93
95
 
96
+ withJWK(jwk: JWK): this {
97
+ this.jwk = jwk;
98
+ return this;
99
+ }
100
+
94
101
  withIssuer(issuer: string): this {
95
102
  this.issuer = issuer;
96
103
  return this;
@@ -182,6 +189,7 @@ export class ProofOfPossessionBuilder<DIDDoc> {
182
189
  {
183
190
  typ: this.typ ?? (this.version < OpenId4VCIVersion.VER_1_0_11 ? 'jwt' : 'openid4vci-proof+jwt'),
184
191
  kid: this.kid,
192
+ jwk: this.jwk,
185
193
  jti: this.jti,
186
194
  alg: this.alg,
187
195
  issuer: this.issuer,
@@ -3,6 +3,7 @@ import { KeyObject } from 'crypto';
3
3
  import {
4
4
  Alg,
5
5
  EndpointMetadata,
6
+ getCredentialRequestForVersion,
6
7
  getIssuerFromCredentialOfferPayload,
7
8
  Jwt,
8
9
  OpenId4VCIVersion,
@@ -127,7 +128,7 @@ describe('Credential Request Client ', () => {
127
128
  version: OpenId4VCIVersion.VER_1_0_08,
128
129
  });
129
130
  expect(credentialRequest.proof?.jwt?.includes(partialJWT)).toBeTruthy();
130
- expect(credentialRequest.format).toEqual('jwt_vc_json');
131
+ expect(credentialRequest.format).toEqual('jwt_vc');
131
132
  const result = await credReqClient.acquireCredentialsUsingRequest(credentialRequest);
132
133
  expect(result?.successBody?.credential).toEqual(mockedVC);
133
134
  });
@@ -149,17 +150,22 @@ describe('Credential Request Client ', () => {
149
150
  .withKid(kid)
150
151
  .withClientId('sphereon:wallet')
151
152
  .build();
152
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
153
- // @ts-ignore
154
- await expect(credReqClient.acquireCredentialsUsingRequest({ format: 'jwt_vc_json-ld', types: ['random'], proof })).rejects.toThrow(
153
+ await expect(credReqClient.acquireCredentialsUsingRequest({ format: 'jwt_vc_json', types: ['random'], proof })).rejects.toThrow(
155
154
  Error(URL_NOT_VALID),
156
155
  );
157
156
  });
158
157
  });
159
158
 
160
159
  describe('Credential Request Client with Walt.id ', () => {
161
- it('should have correct metadata endpoints', async function () {
160
+ beforeAll(() => {
162
161
  nock.cleanAll();
162
+ });
163
+
164
+ afterEach(() => {
165
+ nock.cleanAll();
166
+ });
167
+ it('should have correct metadata endpoints', async function () {
168
+ // nock.cleanAll();
163
169
  const WALT_IRR_URI =
164
170
  'openid-initiate-issuance://?issuer=https%3A%2F%2Fjff.walt.id%2Fissuer-api%2Foidc%2F&credential_type=OpenBadgeCredential&pre-authorized_code=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhOTUyZjUxNi1jYWVmLTQ4YjMtODIxYy00OTRkYzgyNjljZjAiLCJwcmUtYXV0aG9yaXplZCI6dHJ1ZX0.YE5DlalcLC2ChGEg47CQDaN1gTxbaQqSclIVqsSAUHE&user_pin_required=false';
165
171
  const credentialOffer = await CredentialOfferClient.fromURI(WALT_IRR_URI);
@@ -194,10 +200,11 @@ describe('Credential Request Client with different issuers ', () => {
194
200
  jwt: getMockData('spruce')?.credential.request.proof.jwt as string,
195
201
  },
196
202
  credentialTypes: ['OpenBadgeCredential'],
197
- format: 'jwt_vc_json-ld',
203
+ format: 'jwt_vc',
198
204
  version: OpenId4VCIVersion.VER_1_0_08,
199
205
  });
200
- expect(credentialRequest).toEqual(getMockData('spruce')?.credential.request);
206
+ const draft8CredentialRequest = getCredentialRequestForVersion(credentialRequest, OpenId4VCIVersion.VER_1_0_08);
207
+ expect(draft8CredentialRequest).toEqual(getMockData('spruce')?.credential.request);
201
208
  });
202
209
 
203
210
  it('should create correct CredentialRequest for Walt', async () => {
@@ -264,7 +271,8 @@ describe('Credential Request Client with different issuers ', () => {
264
271
  format: 'ldp_vc',
265
272
  version: OpenId4VCIVersion.VER_1_0_08,
266
273
  });
267
- expect(credentialOffer).toEqual(getMockData('mattr')?.credential.request);
274
+ const credentialRequest = getCredentialRequestForVersion(credentialOffer, OpenId4VCIVersion.VER_1_0_08);
275
+ expect(credentialRequest).toEqual(getMockData('mattr')?.credential.request);
268
276
  });
269
277
 
270
278
  it('should create correct CredentialRequest for diwala', async () => {
@@ -286,6 +294,10 @@ describe('Credential Request Client with different issuers ', () => {
286
294
  format: 'ldp_vc',
287
295
  version: OpenId4VCIVersion.VER_1_0_08,
288
296
  });
289
- expect(credentialOffer).toEqual(getMockData('diwala')?.credential.request);
297
+
298
+ // createCredentialRequest returns uniform format in draft 11
299
+ const credentialRequest = getCredentialRequestForVersion(credentialOffer, OpenId4VCIVersion.VER_1_0_08);
300
+
301
+ expect(credentialRequest).toEqual(getMockData('diwala')?.credential.request);
290
302
  });
291
303
  });