@sphereon/oid4vci-client 0.10.4-unstable.98 → 0.12.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 (87) hide show
  1. package/README.md +7 -7
  2. package/dist/AccessTokenClient.d.ts.map +1 -1
  3. package/dist/AccessTokenClient.js +5 -6
  4. package/dist/AccessTokenClient.js.map +1 -1
  5. package/dist/AccessTokenClientV1_0_11.d.ts.map +1 -1
  6. package/dist/AccessTokenClientV1_0_11.js +3 -5
  7. package/dist/AccessTokenClientV1_0_11.js.map +1 -1
  8. package/dist/AuthorizationCodeClient.d.ts +4 -1
  9. package/dist/AuthorizationCodeClient.d.ts.map +1 -1
  10. package/dist/AuthorizationCodeClient.js +47 -8
  11. package/dist/AuthorizationCodeClient.js.map +1 -1
  12. package/dist/AuthorizationCodeClientV1_0_11.d.ts.map +1 -1
  13. package/dist/AuthorizationCodeClientV1_0_11.js +5 -3
  14. package/dist/AuthorizationCodeClientV1_0_11.js.map +1 -1
  15. package/dist/CredentialOfferClient.d.ts.map +1 -1
  16. package/dist/CredentialOfferClient.js +26 -12
  17. package/dist/CredentialOfferClient.js.map +1 -1
  18. package/dist/CredentialOfferClientV1_0_11.js +6 -7
  19. package/dist/CredentialOfferClientV1_0_11.js.map +1 -1
  20. package/dist/CredentialOfferClientV1_0_13.d.ts +10 -0
  21. package/dist/CredentialOfferClientV1_0_13.d.ts.map +1 -0
  22. package/dist/CredentialOfferClientV1_0_13.js +94 -0
  23. package/dist/CredentialOfferClientV1_0_13.js.map +1 -0
  24. package/dist/CredentialRequestClient.js +1 -1
  25. package/dist/CredentialRequestClient.js.map +1 -1
  26. package/dist/CredentialRequestClientBuilderV1_0_11.d.ts +3 -1
  27. package/dist/CredentialRequestClientBuilderV1_0_11.d.ts.map +1 -1
  28. package/dist/CredentialRequestClientBuilderV1_0_11.js +4 -0
  29. package/dist/CredentialRequestClientBuilderV1_0_11.js.map +1 -1
  30. package/dist/CredentialRequestClientV1_0_11.d.ts +9 -3
  31. package/dist/CredentialRequestClientV1_0_11.d.ts.map +1 -1
  32. package/dist/CredentialRequestClientV1_0_11.js +2 -1
  33. package/dist/CredentialRequestClientV1_0_11.js.map +1 -1
  34. package/dist/MetadataClient.d.ts +5 -5
  35. package/dist/MetadataClient.d.ts.map +1 -1
  36. package/dist/MetadataClient.js +30 -13
  37. package/dist/MetadataClient.js.map +1 -1
  38. package/dist/MetadataClientV1_0_13.d.ts +31 -0
  39. package/dist/MetadataClientV1_0_13.d.ts.map +1 -0
  40. package/dist/MetadataClientV1_0_13.js +181 -0
  41. package/dist/MetadataClientV1_0_13.js.map +1 -0
  42. package/dist/OpenID4VCIClient.d.ts +13 -24
  43. package/dist/OpenID4VCIClient.d.ts.map +1 -1
  44. package/dist/OpenID4VCIClient.js +126 -103
  45. package/dist/OpenID4VCIClient.js.map +1 -1
  46. package/dist/OpenID4VCIClientV1_0_11.d.ts +3 -2
  47. package/dist/OpenID4VCIClientV1_0_11.d.ts.map +1 -1
  48. package/dist/OpenID4VCIClientV1_0_11.js +5 -18
  49. package/dist/OpenID4VCIClientV1_0_11.js.map +1 -1
  50. package/dist/OpenID4VCIClientV1_0_13.d.ts +112 -0
  51. package/dist/OpenID4VCIClientV1_0_13.d.ts.map +1 -0
  52. package/dist/OpenID4VCIClientV1_0_13.js +478 -0
  53. package/dist/OpenID4VCIClientV1_0_13.js.map +1 -0
  54. package/dist/ProofOfPossessionBuilder.d.ts +14 -3
  55. package/dist/ProofOfPossessionBuilder.d.ts.map +1 -1
  56. package/dist/ProofOfPossessionBuilder.js +20 -20
  57. package/dist/ProofOfPossessionBuilder.js.map +1 -1
  58. package/dist/index.d.ts +5 -0
  59. package/dist/index.d.ts.map +1 -1
  60. package/dist/index.js +6 -0
  61. package/dist/index.js.map +1 -1
  62. package/lib/AccessTokenClient.ts +5 -11
  63. package/lib/AccessTokenClientV1_0_11.ts +3 -9
  64. package/lib/AuthorizationCodeClient.ts +47 -8
  65. package/lib/AuthorizationCodeClientV1_0_11.ts +8 -6
  66. package/lib/CredentialOfferClient.ts +31 -9
  67. package/lib/CredentialOfferClientV1_0_11.ts +6 -6
  68. package/lib/CredentialOfferClientV1_0_13.ts +103 -0
  69. package/lib/CredentialRequestClient.ts +1 -1
  70. package/lib/CredentialRequestClientBuilderV1_0_11.ts +7 -0
  71. package/lib/CredentialRequestClientV1_0_11.ts +9 -4
  72. package/lib/MetadataClient.ts +49 -14
  73. package/lib/MetadataClientV1_0_13.ts +188 -0
  74. package/lib/OpenID4VCIClient.ts +131 -115
  75. package/lib/OpenID4VCIClientV1_0_11.ts +9 -19
  76. package/lib/OpenID4VCIClientV1_0_13.ts +677 -0
  77. package/lib/ProofOfPossessionBuilder.ts +40 -9
  78. package/lib/__tests__/CredentialRequestClientV1_0_11.spec.ts +2 -2
  79. package/lib/__tests__/MetadataClient.spec.ts +3 -4
  80. package/lib/__tests__/MetadataMocks.ts +1 -0
  81. package/lib/__tests__/OpenID4VCIClient.spec.ts +42 -9
  82. package/lib/__tests__/OpenID4VCIClientV1_0_11.spec.ts +24 -0
  83. package/lib/__tests__/OpenID4VCIClientV1_0_13.spec.ts +204 -0
  84. package/lib/__tests__/SdJwt.spec.ts +2 -2
  85. package/lib/__tests__/SphereonE2E.spec.test.ts +4 -3
  86. package/lib/index.ts +8 -0
  87. package/package.json +4 -4
@@ -0,0 +1,103 @@
1
+ import {
2
+ convertJsonToURI,
3
+ convertURIToJsonObject,
4
+ CredentialOfferRequestWithBaseUrl,
5
+ CredentialOfferV1_0_13,
6
+ determineSpecVersionFromURI,
7
+ getClientIdFromCredentialOfferPayload,
8
+ OpenId4VCIVersion,
9
+ toUniformCredentialOfferRequest,
10
+ } from '@sphereon/oid4vci-common';
11
+ import Debug from 'debug';
12
+
13
+ const debug = Debug('sphereon:oid4vci:offer');
14
+
15
+ export class CredentialOfferClientV1_0_13 {
16
+ public static async fromURI(uri: string, opts?: { resolve?: boolean }): Promise<CredentialOfferRequestWithBaseUrl> {
17
+ debug(`Credential Offer URI: ${uri}`);
18
+ if (!uri.includes('?') || !uri.includes('://')) {
19
+ debug(`Invalid Credential Offer URI: ${uri}`);
20
+ throw Error(`Invalid Credential Offer Request`);
21
+ }
22
+ const scheme = uri.split('://')[0];
23
+ const baseUrl = uri.split('?')[0];
24
+ const version = determineSpecVersionFromURI(uri);
25
+ const credentialOffer = convertURIToJsonObject(uri, {
26
+ // It must have the '=' sign after credential_offer otherwise the uri will get split at openid_credential_offer
27
+ arrayTypeProperties: uri.includes('credential_offer_uri=')
28
+ ? ['credential_configuration_ids', 'credential_offer_uri=']
29
+ : ['credential_configuration_ids', 'credential_offer='],
30
+ requiredProperties: uri.includes('credential_offer_uri=') ? ['credential_offer_uri='] : ['credential_offer='],
31
+ }) as CredentialOfferV1_0_13;
32
+ if (credentialOffer?.credential_offer_uri === undefined && !credentialOffer?.credential_offer) {
33
+ throw Error('Either a credential_offer or credential_offer_uri should be present in ' + uri);
34
+ }
35
+
36
+ const request = await toUniformCredentialOfferRequest(credentialOffer, {
37
+ ...opts,
38
+ version,
39
+ });
40
+ const clientId = getClientIdFromCredentialOfferPayload(request.credential_offer);
41
+ const grants = request.credential_offer?.grants;
42
+
43
+ return {
44
+ scheme,
45
+ baseUrl,
46
+ ...(clientId && { clientId }),
47
+ ...request,
48
+ ...(grants?.authorization_code?.issuer_state && { issuerState: grants.authorization_code.issuer_state }),
49
+ ...(grants?.['urn:ietf:params:oauth:grant-type:pre-authorized_code']?.['pre-authorized_code'] && {
50
+ preAuthorizedCode: grants['urn:ietf:params:oauth:grant-type:pre-authorized_code']['pre-authorized_code'],
51
+ }),
52
+ userPinRequired: !!request.credential_offer?.grants?.['urn:ietf:params:oauth:grant-type:pre-authorized_code']?.tx_code ?? false,
53
+ ...(request.credential_offer?.grants?.['urn:ietf:params:oauth:grant-type:pre-authorized_code']?.tx_code &&
54
+ {
55
+ // txCode: request.credential_offer?.grants?.['urn:ietf:params:oauth:grant-type:pre-authorized_code']?.tx_code,
56
+ }),
57
+ };
58
+ }
59
+
60
+ public static toURI(
61
+ requestWithBaseUrl: CredentialOfferRequestWithBaseUrl,
62
+ opts?: {
63
+ version?: OpenId4VCIVersion;
64
+ },
65
+ ): string {
66
+ debug(`Credential Offer Request with base URL: ${JSON.stringify(requestWithBaseUrl)}`);
67
+ const version = opts?.version ?? requestWithBaseUrl.version;
68
+ let baseUrl = requestWithBaseUrl.baseUrl.includes(requestWithBaseUrl.scheme)
69
+ ? requestWithBaseUrl.baseUrl
70
+ : `${requestWithBaseUrl.scheme.replace('://', '')}://${requestWithBaseUrl.baseUrl}`;
71
+ let param: string | undefined;
72
+
73
+ const isUri = requestWithBaseUrl.credential_offer_uri !== undefined;
74
+
75
+ if (version.valueOf() >= OpenId4VCIVersion.VER_1_0_11.valueOf()) {
76
+ // v11 changed from encoding every param to a encoded json object with a credential_offer param key
77
+ if (!baseUrl.includes('?')) {
78
+ param = isUri ? 'credential_offer_uri' : 'credential_offer';
79
+ } else {
80
+ const split = baseUrl.split('?');
81
+ if (split.length > 1 && split[1] !== '') {
82
+ if (baseUrl.endsWith('&')) {
83
+ param = isUri ? 'credential_offer_uri' : 'credential_offer';
84
+ } else if (!baseUrl.endsWith('=')) {
85
+ baseUrl += `&`;
86
+ param = isUri ? 'credential_offer_uri' : 'credential_offer';
87
+ }
88
+ }
89
+ }
90
+ }
91
+ return convertJsonToURI(requestWithBaseUrl.credential_offer_uri ?? requestWithBaseUrl.original_credential_offer, {
92
+ baseUrl,
93
+ arrayTypeProperties: isUri ? [] : ['credential_type'],
94
+ uriTypeProperties: isUri
95
+ ? ['credential_offer_uri']
96
+ : version >= OpenId4VCIVersion.VER_1_0_13
97
+ ? ['credential_issuer', 'credential_type']
98
+ : ['issuer', 'credential_type'],
99
+ param,
100
+ version,
101
+ });
102
+ }
103
+ }
@@ -108,7 +108,7 @@ export class CredentialRequestClient {
108
108
  uniformRequest: UniformCredentialRequest,
109
109
  ): Promise<OpenIDResponse<CredentialResponse> & { access_token: string }> {
110
110
  if (this.version() < OpenId4VCIVersion.VER_1_0_13) {
111
- throw new Error('Versions below v1.0.13 (draft 13) are not supported.');
111
+ throw new Error('Versions below v1.0.13 (draft 13) are not supported by the V13 credential request client.');
112
112
  }
113
113
  const request: CredentialRequestV1_0_13 = getCredentialRequestForVersion(uniformRequest, this.version()) as CredentialRequestV1_0_13;
114
114
  const credentialEndpoint: string = this.credentialRequestOpts.credentialEndpoint;
@@ -6,6 +6,7 @@ import {
6
6
  CredentialOfferRequestWithBaseUrl,
7
7
  determineSpecVersionFromOffer,
8
8
  EndpointMetadata,
9
+ ExperimentalSubjectIssuance,
9
10
  getIssuerFromCredentialOfferPayload,
10
11
  getTypesFromOfferV1_0_11,
11
12
  OID4VCICredentialFormat,
@@ -26,6 +27,7 @@ export class CredentialRequestClientBuilderV1_0_11 {
26
27
  format?: CredentialFormat | OID4VCICredentialFormat;
27
28
  token?: string;
28
29
  version?: OpenId4VCIVersion;
30
+ subjectIssuance?: ExperimentalSubjectIssuance;
29
31
 
30
32
  public static fromCredentialIssuer({
31
33
  credentialIssuer,
@@ -132,6 +134,11 @@ export class CredentialRequestClientBuilderV1_0_11 {
132
134
  return this;
133
135
  }
134
136
 
137
+ public withSubjectIssuance(subjectIssuance: ExperimentalSubjectIssuance): this {
138
+ this.subjectIssuance = subjectIssuance;
139
+ return this;
140
+ }
141
+
135
142
  public withToken(accessToken: string): this {
136
143
  this.token = accessToken;
137
144
  return this;
@@ -64,14 +64,16 @@ export class CredentialRequestClientV1_0_11 {
64
64
  credentialTypes?: string | string[];
65
65
  context?: string[];
66
66
  format?: CredentialFormat | OID4VCICredentialFormat;
67
- }): Promise<OpenIDResponse<CredentialResponse>> {
67
+ }): Promise<OpenIDResponse<CredentialResponse> & { access_token: string }> {
68
68
  const { credentialTypes, proofInput, format, context } = opts;
69
69
 
70
70
  const request = await this.createCredentialRequest({ proofInput, credentialTypes, context, format, version: this.version() });
71
71
  return await this.acquireCredentialsUsingRequest(request);
72
72
  }
73
73
 
74
- public async acquireCredentialsUsingRequest(uniformRequest: UniformCredentialRequest): Promise<OpenIDResponse<CredentialResponse>> {
74
+ public async acquireCredentialsUsingRequest(
75
+ uniformRequest: UniformCredentialRequest,
76
+ ): Promise<OpenIDResponse<CredentialResponse> & { access_token: string }> {
75
77
  const request = getCredentialRequestForVersion(uniformRequest, this.version());
76
78
  const credentialEndpoint: string = this.credentialRequestOpts.credentialEndpoint;
77
79
  if (!isValidURL(credentialEndpoint)) {
@@ -81,11 +83,14 @@ export class CredentialRequestClientV1_0_11 {
81
83
  debug(`Acquiring credential(s) from: ${credentialEndpoint}`);
82
84
  debug(`request\n: ${JSON.stringify(request, null, 2)}`);
83
85
  const requestToken: string = this.credentialRequestOpts.token;
84
- let response: OpenIDResponse<CredentialResponse> = await post(credentialEndpoint, JSON.stringify(request), { bearerToken: requestToken });
86
+ let response = (await post(credentialEndpoint, JSON.stringify(request), { bearerToken: requestToken })) as OpenIDResponse<CredentialResponse> & {
87
+ access_token: string;
88
+ };
85
89
  this._isDeferred = isDeferredCredentialResponse(response);
86
90
  if (this.isDeferred() && this.credentialRequestOpts.deferredCredentialAwait && response.successBody) {
87
91
  response = await this.acquireDeferredCredential(response.successBody, { bearerToken: this.credentialRequestOpts.token });
88
92
  }
93
+ response.access_token = requestToken;
89
94
 
90
95
  debug(`Credential endpoint ${credentialEndpoint} response:\r\n${JSON.stringify(response, null, 2)}`);
91
96
  return response;
@@ -96,7 +101,7 @@ export class CredentialRequestClientV1_0_11 {
96
101
  opts?: {
97
102
  bearerToken?: string;
98
103
  },
99
- ): Promise<OpenIDResponse<CredentialResponse>> {
104
+ ): Promise<OpenIDResponse<CredentialResponse> & { access_token: string }> {
100
105
  const transactionId = response.transaction_id;
101
106
  const bearerToken = response.acceptance_token ?? opts?.bearerToken;
102
107
  const deferredCredentialEndpoint = this.getDeferredCredentialEndpoint();
@@ -1,17 +1,24 @@
1
1
  import {
2
2
  AuthorizationServerMetadata,
3
3
  AuthorizationServerType,
4
+ CredentialIssuerMetadataV1_0_11,
4
5
  CredentialIssuerMetadataV1_0_13,
6
+ CredentialOfferPayload,
5
7
  CredentialOfferPayloadV1_0_13,
6
8
  CredentialOfferRequestWithBaseUrl,
9
+ determineSpecVersionFromOffer,
10
+ EndpointMetadataResultV1_0_11,
7
11
  EndpointMetadataResultV1_0_13,
8
12
  getIssuerFromCredentialOfferPayload,
9
- IssuerMetadataV1_0_13,
13
+ IssuerMetadataV1_0_08,
14
+ OpenId4VCIVersion,
10
15
  OpenIDResponse,
11
16
  WellKnownEndpoints,
12
17
  } from '@sphereon/oid4vci-common';
13
18
  import Debug from 'debug';
14
19
 
20
+ import { MetadataClientV1_0_11 } from './MetadataClientV1_0_11';
21
+ import { MetadataClientV1_0_13 } from './MetadataClientV1_0_13';
15
22
  import { retrieveWellknown } from './functions/OpenIDUtils';
16
23
 
17
24
  const debug = Debug('sphereon:oid4vci:metadata');
@@ -24,18 +31,28 @@ export class MetadataClient {
24
31
  */
25
32
  public static async retrieveAllMetadataFromCredentialOffer(
26
33
  credentialOffer: CredentialOfferRequestWithBaseUrl,
27
- ): Promise<EndpointMetadataResultV1_0_13> {
28
- return MetadataClient.retrieveAllMetadataFromCredentialOfferRequest(credentialOffer.credential_offer as CredentialOfferPayloadV1_0_13);
34
+ ): Promise<EndpointMetadataResultV1_0_13 | EndpointMetadataResultV1_0_11> {
35
+ if (determineSpecVersionFromOffer(credentialOffer.credential_offer) >= OpenId4VCIVersion.VER_1_0_13) {
36
+ return await MetadataClientV1_0_13.retrieveAllMetadataFromCredentialOffer(credentialOffer);
37
+ } else {
38
+ return await MetadataClientV1_0_11.retrieveAllMetadataFromCredentialOffer(credentialOffer);
39
+ }
29
40
  }
30
41
 
31
42
  /**
32
43
  * Retrieve the metada using the initiation request obtained from a previous step
33
44
  * @param request
34
45
  */
35
- public static async retrieveAllMetadataFromCredentialOfferRequest(request: CredentialOfferPayloadV1_0_13): Promise<EndpointMetadataResultV1_0_13> {
46
+ public static async retrieveAllMetadataFromCredentialOfferRequest(
47
+ request: CredentialOfferPayload,
48
+ ): Promise<EndpointMetadataResultV1_0_13 | EndpointMetadataResultV1_0_11> {
36
49
  const issuer = getIssuerFromCredentialOfferPayload(request);
37
50
  if (issuer) {
38
- return MetadataClient.retrieveAllMetadata(issuer);
51
+ if (determineSpecVersionFromOffer(request) >= OpenId4VCIVersion.VER_1_0_13) {
52
+ return MetadataClientV1_0_13.retrieveAllMetadataFromCredentialOfferRequest(request as CredentialOfferPayloadV1_0_13);
53
+ } else {
54
+ return MetadataClientV1_0_11.retrieveAllMetadataFromCredentialOfferRequest(request);
55
+ }
39
56
  }
40
57
  throw new Error("can't retrieve metadata from CredentialOfferRequest. No issuer field is present");
41
58
  }
@@ -45,24 +62,33 @@ export class MetadataClient {
45
62
  * @param issuer The issuer URL
46
63
  * @param opts
47
64
  */
48
- public static async retrieveAllMetadata(issuer: string, opts?: { errorOnNotFound: boolean }): Promise<EndpointMetadataResultV1_0_13> {
65
+ public static async retrieveAllMetadata(
66
+ issuer: string,
67
+ opts?: { errorOnNotFound: boolean },
68
+ ): Promise<EndpointMetadataResultV1_0_13 | EndpointMetadataResultV1_0_11> {
49
69
  let token_endpoint: string | undefined;
50
70
  let credential_endpoint: string | undefined;
51
71
  let deferred_credential_endpoint: string | undefined;
52
72
  let authorization_endpoint: string | undefined;
53
73
  let authorizationServerType: AuthorizationServerType = 'OID4VCI';
54
- let authorization_servers: string[] = [issuer];
74
+ let authorization_servers: string[] | undefined = [issuer];
75
+ let authorization_server: string | undefined = undefined;
55
76
  const oid4vciResponse = await MetadataClient.retrieveOpenID4VCIServerMetadata(issuer, { errorOnNotFound: false }); // We will handle errors later, given we will also try other metadata locations
56
77
  let credentialIssuerMetadata = oid4vciResponse?.successBody;
57
78
  if (credentialIssuerMetadata) {
58
79
  debug(`Issuer ${issuer} OID4VCI well-known server metadata\r\n${JSON.stringify(credentialIssuerMetadata)}`);
59
80
  credential_endpoint = credentialIssuerMetadata.credential_endpoint;
60
- deferred_credential_endpoint = credentialIssuerMetadata.deferred_credential_endpoint;
81
+ deferred_credential_endpoint = credentialIssuerMetadata.deferred_credential_endpoint
82
+ ? (credentialIssuerMetadata.deferred_credential_endpoint as string)
83
+ : undefined;
61
84
  if (credentialIssuerMetadata.token_endpoint) {
62
85
  token_endpoint = credentialIssuerMetadata.token_endpoint;
63
86
  }
64
87
  if (credentialIssuerMetadata.authorization_servers) {
65
- authorization_servers = credentialIssuerMetadata.authorization_servers;
88
+ authorization_servers = credentialIssuerMetadata.authorization_servers as string[];
89
+ } else if (credentialIssuerMetadata.authorization_server) {
90
+ authorization_server = credentialIssuerMetadata.authorization_server as string;
91
+ authorization_servers = [authorization_server];
66
92
  }
67
93
  }
68
94
  // No specific OID4VCI endpoint. Either can be an OAuth2 AS or an OIDC IDP. Let's start with OIDC first
@@ -154,7 +180,9 @@ export class MetadataClient {
154
180
 
155
181
  if (!credentialIssuerMetadata && authMetadata) {
156
182
  // Apparently everything worked out and the issuer is exposing everything in oAuth2/OIDC well-knowns. Spec is vague about this situation, but we can support it
157
- credentialIssuerMetadata = authMetadata as CredentialIssuerMetadataV1_0_13;
183
+ credentialIssuerMetadata = authorization_server
184
+ ? (authMetadata as CredentialIssuerMetadataV1_0_11)
185
+ : (authMetadata as CredentialIssuerMetadataV1_0_13);
158
186
  }
159
187
  debug(`Issuer ${issuer} token endpoint ${token_endpoint}, credential endpoint ${credential_endpoint}`);
160
188
  return {
@@ -162,12 +190,14 @@ export class MetadataClient {
162
190
  token_endpoint,
163
191
  credential_endpoint,
164
192
  deferred_credential_endpoint,
165
- authorization_server: authorization_servers[0],
193
+ ...(authorization_server ? { authorization_server } : { authorization_servers: authorization_servers }),
166
194
  authorization_endpoint,
167
195
  authorizationServerType,
168
- credentialIssuerMetadata: credentialIssuerMetadata,
196
+ credentialIssuerMetadata: authorization_server
197
+ ? (credentialIssuerMetadata as IssuerMetadataV1_0_08 & Partial<AuthorizationServerMetadata>)
198
+ : (credentialIssuerMetadata as CredentialIssuerMetadataV1_0_13),
169
199
  authorizationServerMetadata: authMetadata,
170
- };
200
+ } as EndpointMetadataResultV1_0_13 | EndpointMetadataResultV1_0_11;
171
201
  }
172
202
 
173
203
  /**
@@ -180,7 +210,12 @@ export class MetadataClient {
180
210
  opts?: {
181
211
  errorOnNotFound?: boolean;
182
212
  },
183
- ): Promise<OpenIDResponse<IssuerMetadataV1_0_13> | undefined> {
213
+ ): Promise<
214
+ | OpenIDResponse<
215
+ CredentialIssuerMetadataV1_0_11 | CredentialIssuerMetadataV1_0_13 | (IssuerMetadataV1_0_08 & Partial<AuthorizationServerMetadata>)
216
+ >
217
+ | undefined
218
+ > {
184
219
  return retrieveWellknown(issuerHost, WellKnownEndpoints.OPENID4VCI_ISSUER, {
185
220
  errorOnNotFound: opts?.errorOnNotFound === undefined ? true : opts.errorOnNotFound,
186
221
  });
@@ -0,0 +1,188 @@
1
+ import {
2
+ AuthorizationServerMetadata,
3
+ AuthorizationServerType,
4
+ CredentialIssuerMetadataV1_0_13,
5
+ CredentialOfferPayloadV1_0_13,
6
+ CredentialOfferRequestWithBaseUrl,
7
+ EndpointMetadataResultV1_0_13,
8
+ getIssuerFromCredentialOfferPayload,
9
+ IssuerMetadataV1_0_13,
10
+ OpenIDResponse,
11
+ WellKnownEndpoints,
12
+ } from '@sphereon/oid4vci-common';
13
+ import Debug from 'debug';
14
+
15
+ import { retrieveWellknown } from './functions/OpenIDUtils';
16
+
17
+ const debug = Debug('sphereon:oid4vci:metadata');
18
+
19
+ export class MetadataClientV1_0_13 {
20
+ /**
21
+ * Retrieve metadata using the Initiation obtained from a previous step
22
+ *
23
+ * @param credentialOffer
24
+ */
25
+ public static async retrieveAllMetadataFromCredentialOffer(
26
+ credentialOffer: CredentialOfferRequestWithBaseUrl,
27
+ ): Promise<EndpointMetadataResultV1_0_13> {
28
+ return MetadataClientV1_0_13.retrieveAllMetadataFromCredentialOfferRequest(credentialOffer.credential_offer as CredentialOfferPayloadV1_0_13);
29
+ }
30
+
31
+ /**
32
+ * Retrieve the metada using the initiation request obtained from a previous step
33
+ * @param request
34
+ */
35
+ public static async retrieveAllMetadataFromCredentialOfferRequest(request: CredentialOfferPayloadV1_0_13): Promise<EndpointMetadataResultV1_0_13> {
36
+ const issuer = getIssuerFromCredentialOfferPayload(request);
37
+ if (issuer) {
38
+ return MetadataClientV1_0_13.retrieveAllMetadata(issuer);
39
+ }
40
+ throw new Error("can't retrieve metadata from CredentialOfferRequest. No issuer field is present");
41
+ }
42
+
43
+ /**
44
+ * Retrieve all metadata from an issuer
45
+ * @param issuer The issuer URL
46
+ * @param opts
47
+ */
48
+ public static async retrieveAllMetadata(issuer: string, opts?: { errorOnNotFound: boolean }): Promise<EndpointMetadataResultV1_0_13> {
49
+ let token_endpoint: string | undefined;
50
+ let credential_endpoint: string | undefined;
51
+ let deferred_credential_endpoint: string | undefined;
52
+ let authorization_endpoint: string | undefined;
53
+ let authorizationServerType: AuthorizationServerType = 'OID4VCI';
54
+ let authorization_servers: string[] = [issuer];
55
+ const oid4vciResponse = await MetadataClientV1_0_13.retrieveOpenID4VCIServerMetadata(issuer, { errorOnNotFound: false }); // We will handle errors later, given we will also try other metadata locations
56
+ let credentialIssuerMetadata = oid4vciResponse?.successBody;
57
+ if (credentialIssuerMetadata) {
58
+ debug(`Issuer ${issuer} OID4VCI well-known server metadata\r\n${JSON.stringify(credentialIssuerMetadata)}`);
59
+ credential_endpoint = credentialIssuerMetadata.credential_endpoint;
60
+ deferred_credential_endpoint = credentialIssuerMetadata.deferred_credential_endpoint;
61
+ if (credentialIssuerMetadata.token_endpoint) {
62
+ token_endpoint = credentialIssuerMetadata.token_endpoint;
63
+ }
64
+ if (credentialIssuerMetadata.authorization_servers) {
65
+ authorization_servers = credentialIssuerMetadata.authorization_servers;
66
+ }
67
+ }
68
+ // No specific OID4VCI endpoint. Either can be an OAuth2 AS or an OIDC IDP. Let's start with OIDC first
69
+ // TODO: for now we're taking just the first one
70
+ let response: OpenIDResponse<AuthorizationServerMetadata> = await retrieveWellknown(
71
+ authorization_servers[0],
72
+ WellKnownEndpoints.OPENID_CONFIGURATION,
73
+ {
74
+ errorOnNotFound: false,
75
+ },
76
+ );
77
+ let authMetadata = response.successBody;
78
+ if (authMetadata) {
79
+ debug(`Issuer ${issuer} has OpenID Connect Server metadata in well-known location`);
80
+ authorizationServerType = 'OIDC';
81
+ } else {
82
+ // Now let's do OAuth2
83
+ // TODO: for now we're taking just the first one
84
+ response = await retrieveWellknown(authorization_servers[0], WellKnownEndpoints.OAUTH_AS, { errorOnNotFound: false });
85
+ authMetadata = response.successBody;
86
+ }
87
+ if (!authMetadata) {
88
+ // We will always throw an error, no matter whether the user provided the option not to, because this is bad.
89
+ if (!authorization_servers.includes(issuer)) {
90
+ throw Error(`Issuer ${issuer} provided a separate authorization server ${authorization_servers}, but that server did not provide metadata`);
91
+ }
92
+ } else {
93
+ if (!authorizationServerType) {
94
+ authorizationServerType = 'OAuth 2.0';
95
+ }
96
+ debug(`Issuer ${issuer} has ${authorizationServerType} Server metadata in well-known location`);
97
+ if (!authMetadata.authorization_endpoint) {
98
+ console.warn(
99
+ `Issuer ${issuer} of type ${authorizationServerType} has no authorization_endpoint! Will use ${authorization_endpoint}. This only works for pre-authorized flows`,
100
+ );
101
+ } else if (authorization_endpoint && authMetadata.authorization_endpoint !== authorization_endpoint) {
102
+ throw Error(
103
+ `Credential issuer has a different authorization_endpoint (${authorization_endpoint}) from the Authorization Server (${authMetadata.authorization_endpoint})`,
104
+ );
105
+ }
106
+ authorization_endpoint = authMetadata.authorization_endpoint;
107
+ if (!authMetadata.token_endpoint) {
108
+ throw Error(`Authorization Sever ${authorization_servers} did not provide a token_endpoint`);
109
+ } else if (token_endpoint && authMetadata.token_endpoint !== token_endpoint) {
110
+ throw Error(
111
+ `Credential issuer has a different token_endpoint (${token_endpoint}) from the Authorization Server (${authMetadata.token_endpoint})`,
112
+ );
113
+ }
114
+ token_endpoint = authMetadata.token_endpoint;
115
+ if (authMetadata.credential_endpoint) {
116
+ if (credential_endpoint && authMetadata.credential_endpoint !== credential_endpoint) {
117
+ debug(
118
+ `Credential issuer has a different credential_endpoint (${credential_endpoint}) from the Authorization Server (${authMetadata.credential_endpoint}). Will use the issuer value`,
119
+ );
120
+ } else {
121
+ credential_endpoint = authMetadata.credential_endpoint;
122
+ }
123
+ }
124
+ if (authMetadata.deferred_credential_endpoint) {
125
+ if (deferred_credential_endpoint && authMetadata.deferred_credential_endpoint !== deferred_credential_endpoint) {
126
+ debug(
127
+ `Credential issuer has a different deferred_credential_endpoint (${deferred_credential_endpoint}) from the Authorization Server (${authMetadata.deferred_credential_endpoint}). Will use the issuer value`,
128
+ );
129
+ } else {
130
+ deferred_credential_endpoint = authMetadata.deferred_credential_endpoint;
131
+ }
132
+ }
133
+ }
134
+
135
+ if (!authorization_endpoint) {
136
+ debug(`Issuer ${issuer} does not expose authorization_endpoint, so only pre-auth will be supported`);
137
+ }
138
+ if (!token_endpoint) {
139
+ debug(`Issuer ${issuer} does not have a token_endpoint listed in well-known locations!`);
140
+ if (opts?.errorOnNotFound) {
141
+ throw Error(`Could not deduce the token_endpoint for ${issuer}`);
142
+ } else {
143
+ token_endpoint = `${issuer}${issuer.endsWith('/') ? 'token' : '/token'}`;
144
+ }
145
+ }
146
+ if (!credential_endpoint) {
147
+ debug(`Issuer ${issuer} does not have a credential_endpoint listed in well-known locations!`);
148
+ if (opts?.errorOnNotFound) {
149
+ throw Error(`Could not deduce the credential endpoint for ${issuer}`);
150
+ } else {
151
+ credential_endpoint = `${issuer}${issuer.endsWith('/') ? 'credential' : '/credential'}`;
152
+ }
153
+ }
154
+
155
+ if (!credentialIssuerMetadata && authMetadata) {
156
+ // Apparently everything worked out and the issuer is exposing everything in oAuth2/OIDC well-knowns. Spec is vague about this situation, but we can support it
157
+ credentialIssuerMetadata = authMetadata as CredentialIssuerMetadataV1_0_13;
158
+ }
159
+ debug(`Issuer ${issuer} token endpoint ${token_endpoint}, credential endpoint ${credential_endpoint}`);
160
+ return {
161
+ issuer,
162
+ token_endpoint,
163
+ credential_endpoint,
164
+ deferred_credential_endpoint,
165
+ authorization_server: authorization_servers[0],
166
+ authorization_endpoint,
167
+ authorizationServerType,
168
+ credentialIssuerMetadata: credentialIssuerMetadata,
169
+ authorizationServerMetadata: authMetadata,
170
+ };
171
+ }
172
+
173
+ /**
174
+ * Retrieve only the OID4VCI metadata for the issuer. So no OIDC/OAuth2 metadata
175
+ *
176
+ * @param issuerHost The issuer hostname
177
+ */
178
+ public static async retrieveOpenID4VCIServerMetadata(
179
+ issuerHost: string,
180
+ opts?: {
181
+ errorOnNotFound?: boolean;
182
+ },
183
+ ): Promise<OpenIDResponse<IssuerMetadataV1_0_13> | undefined> {
184
+ return retrieveWellknown(issuerHost, WellKnownEndpoints.OPENID4VCI_ISSUER, {
185
+ errorOnNotFound: opts?.errorOnNotFound === undefined ? true : opts.errorOnNotFound,
186
+ });
187
+ }
188
+ }