@sphereon/oid4vci-client 0.10.3 → 0.10.4-next.119

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 (142) hide show
  1. package/README.md +24 -5
  2. package/dist/AccessTokenClient.d.ts +5 -5
  3. package/dist/AccessTokenClient.d.ts.map +1 -1
  4. package/dist/AccessTokenClient.js +51 -37
  5. package/dist/AccessTokenClient.js.map +1 -1
  6. package/dist/AccessTokenClientV1_0_11.d.ts +29 -0
  7. package/dist/AccessTokenClientV1_0_11.d.ts.map +1 -0
  8. package/dist/AccessTokenClientV1_0_11.js +209 -0
  9. package/dist/AccessTokenClientV1_0_11.js.map +1 -0
  10. package/dist/AuthorizationCodeClient.d.ts +9 -4
  11. package/dist/AuthorizationCodeClient.d.ts.map +1 -1
  12. package/dist/AuthorizationCodeClient.js +102 -18
  13. package/dist/AuthorizationCodeClient.js.map +1 -1
  14. package/dist/AuthorizationCodeClientV1_0_11.d.ts +9 -0
  15. package/dist/AuthorizationCodeClientV1_0_11.d.ts.map +1 -0
  16. package/dist/AuthorizationCodeClientV1_0_11.js +134 -0
  17. package/dist/AuthorizationCodeClientV1_0_11.js.map +1 -0
  18. package/dist/CredentialOfferClient.d.ts.map +1 -1
  19. package/dist/CredentialOfferClient.js +18 -13
  20. package/dist/CredentialOfferClient.js.map +1 -1
  21. package/dist/CredentialOfferClientV1_0_11.d.ts +10 -0
  22. package/dist/CredentialOfferClientV1_0_11.d.ts.map +1 -0
  23. package/dist/CredentialOfferClientV1_0_11.js +101 -0
  24. package/dist/CredentialOfferClientV1_0_11.js.map +1 -0
  25. package/dist/CredentialOfferClientV1_0_13.d.ts +10 -0
  26. package/dist/CredentialOfferClientV1_0_13.d.ts.map +1 -0
  27. package/dist/CredentialOfferClientV1_0_13.js +94 -0
  28. package/dist/CredentialOfferClientV1_0_13.js.map +1 -0
  29. package/dist/CredentialRequestClient.d.ts +20 -7
  30. package/dist/CredentialRequestClient.d.ts.map +1 -1
  31. package/dist/CredentialRequestClient.js +46 -30
  32. package/dist/CredentialRequestClient.js.map +1 -1
  33. package/dist/CredentialRequestClientBuilder.d.ts +11 -6
  34. package/dist/CredentialRequestClientBuilder.d.ts.map +1 -1
  35. package/dist/CredentialRequestClientBuilder.js +22 -9
  36. package/dist/CredentialRequestClientBuilder.js.map +1 -1
  37. package/dist/CredentialRequestClientBuilderV1_0_11.d.ts +48 -0
  38. package/dist/CredentialRequestClientBuilderV1_0_11.d.ts.map +1 -0
  39. package/dist/CredentialRequestClientBuilderV1_0_11.js +121 -0
  40. package/dist/CredentialRequestClientBuilderV1_0_11.js.map +1 -0
  41. package/dist/CredentialRequestClientV1_0_11.d.ts +50 -0
  42. package/dist/CredentialRequestClientV1_0_11.d.ts.map +1 -0
  43. package/dist/CredentialRequestClientV1_0_11.js +151 -0
  44. package/dist/CredentialRequestClientV1_0_11.js.map +1 -0
  45. package/dist/MetadataClient.d.ts +5 -15
  46. package/dist/MetadataClient.d.ts.map +1 -1
  47. package/dist/MetadataClient.js +41 -44
  48. package/dist/MetadataClient.js.map +1 -1
  49. package/dist/MetadataClientV1_0_11.d.ts +31 -0
  50. package/dist/MetadataClientV1_0_11.d.ts.map +1 -0
  51. package/dist/MetadataClientV1_0_11.js +182 -0
  52. package/dist/MetadataClientV1_0_11.js.map +1 -0
  53. package/dist/MetadataClientV1_0_13.d.ts +31 -0
  54. package/dist/MetadataClientV1_0_13.d.ts.map +1 -0
  55. package/dist/MetadataClientV1_0_13.js +181 -0
  56. package/dist/MetadataClientV1_0_13.js.map +1 -0
  57. package/dist/OpenID4VCIClient.d.ts +14 -19
  58. package/dist/OpenID4VCIClient.d.ts.map +1 -1
  59. package/dist/OpenID4VCIClient.js +111 -61
  60. package/dist/OpenID4VCIClient.js.map +1 -1
  61. package/dist/OpenID4VCIClientV1_0_11.d.ts +108 -0
  62. package/dist/OpenID4VCIClientV1_0_11.d.ts.map +1 -0
  63. package/dist/OpenID4VCIClientV1_0_11.js +449 -0
  64. package/dist/OpenID4VCIClientV1_0_11.js.map +1 -0
  65. package/dist/OpenID4VCIClientV1_0_13.d.ts +112 -0
  66. package/dist/OpenID4VCIClientV1_0_13.d.ts.map +1 -0
  67. package/dist/OpenID4VCIClientV1_0_13.js +478 -0
  68. package/dist/OpenID4VCIClientV1_0_13.js.map +1 -0
  69. package/dist/ProofOfPossessionBuilder.d.ts +14 -3
  70. package/dist/ProofOfPossessionBuilder.d.ts.map +1 -1
  71. package/dist/ProofOfPossessionBuilder.js +20 -21
  72. package/dist/ProofOfPossessionBuilder.js.map +1 -1
  73. package/dist/functions/OpenIDUtils.d.ts +12 -0
  74. package/dist/functions/OpenIDUtils.d.ts.map +1 -0
  75. package/dist/functions/OpenIDUtils.js +37 -0
  76. package/dist/functions/OpenIDUtils.js.map +1 -0
  77. package/dist/functions/index.d.ts +2 -3
  78. package/dist/functions/index.d.ts.map +1 -1
  79. package/dist/functions/index.js +2 -3
  80. package/dist/functions/index.js.map +1 -1
  81. package/dist/functions/notifications.d.ts +4 -0
  82. package/dist/functions/notifications.d.ts.map +1 -0
  83. package/dist/functions/notifications.js +39 -0
  84. package/dist/functions/notifications.js.map +1 -0
  85. package/dist/index.d.ts +13 -1
  86. package/dist/index.d.ts.map +1 -1
  87. package/dist/index.js +14 -1
  88. package/dist/index.js.map +1 -1
  89. package/dist/types/index.d.ts +2 -0
  90. package/dist/types/index.d.ts.map +1 -1
  91. package/dist/types/index.js +5 -0
  92. package/dist/types/index.js.map +1 -1
  93. package/lib/AccessTokenClient.ts +59 -34
  94. package/lib/AccessTokenClientV1_0_11.ts +250 -0
  95. package/lib/AuthorizationCodeClient.ts +131 -28
  96. package/lib/AuthorizationCodeClientV1_0_11.ts +170 -0
  97. package/lib/CredentialOfferClient.ts +21 -8
  98. package/lib/CredentialOfferClientV1_0_11.ts +112 -0
  99. package/lib/CredentialOfferClientV1_0_13.ts +103 -0
  100. package/lib/CredentialRequestClient.ts +65 -26
  101. package/lib/CredentialRequestClientBuilder.ts +34 -16
  102. package/lib/CredentialRequestClientBuilderV1_0_11.ts +163 -0
  103. package/lib/CredentialRequestClientV1_0_11.ts +197 -0
  104. package/lib/MetadataClient.ts +64 -49
  105. package/lib/MetadataClientV1_0_11.ts +189 -0
  106. package/lib/MetadataClientV1_0_13.ts +188 -0
  107. package/lib/OpenID4VCIClient.ts +132 -68
  108. package/lib/OpenID4VCIClientV1_0_11.ts +635 -0
  109. package/lib/OpenID4VCIClientV1_0_13.ts +677 -0
  110. package/lib/ProofOfPossessionBuilder.ts +41 -11
  111. package/lib/__tests__/AccessTokenClient.spec.ts +40 -12
  112. package/lib/__tests__/AuthorizationDetailsBuilder.spec.ts +0 -12
  113. package/lib/__tests__/CredentialRequestClient.spec.ts +87 -50
  114. package/lib/__tests__/CredentialRequestClientBuilder.spec.ts +18 -12
  115. package/lib/__tests__/CredentialRequestClientV1_0_11.spec.ts +317 -0
  116. package/lib/__tests__/EBSIE2E.spec.test.ts +2 -2
  117. package/lib/__tests__/HttpUtils.spec.ts +1 -1
  118. package/lib/__tests__/IT.spec.ts +264 -14
  119. package/lib/__tests__/IssuanceInitiation.spec.ts +59 -4
  120. package/lib/__tests__/IssuanceInitiationV1_0_11.spec.ts +62 -0
  121. package/lib/__tests__/MattrE2E.spec.test.ts +2 -2
  122. package/lib/__tests__/MetadataClient.spec.ts +53 -3
  123. package/lib/__tests__/MetadataMocks.ts +42 -2
  124. package/lib/__tests__/OpenID4VCIClient.spec.ts +58 -2
  125. package/lib/__tests__/{OpenID4VCIClientPAR.spec.ts → OpenID4VCIClientPARV1_0_11.spec.ts} +5 -5
  126. package/lib/__tests__/OpenID4VCIClientV1_0_11.spec.ts +226 -0
  127. package/lib/__tests__/OpenID4VCIClientV1_0_13.spec.ts +204 -0
  128. package/lib/__tests__/ProofOfPossessionBuilder.spec.ts +1 -1
  129. package/lib/__tests__/SdJwt.spec.ts +36 -30
  130. package/lib/__tests__/SphereonE2E.spec.test.ts +10 -7
  131. package/lib/__tests__/data/VciDataFixtures.ts +712 -27
  132. package/lib/functions/OpenIDUtils.ts +25 -0
  133. package/lib/functions/index.ts +2 -3
  134. package/lib/functions/notifications.ts +32 -0
  135. package/lib/index.ts +16 -1
  136. package/lib/types/index.ts +6 -0
  137. package/package.json +4 -4
  138. package/dist/functions/ProofUtil.d.ts +0 -30
  139. package/dist/functions/ProofUtil.d.ts.map +0 -1
  140. package/dist/functions/ProofUtil.js +0 -106
  141. package/dist/functions/ProofUtil.js.map +0 -1
  142. package/lib/functions/ProofUtil.ts +0 -128
@@ -0,0 +1,635 @@
1
+ import {
2
+ AccessTokenResponse,
3
+ Alg,
4
+ AuthorizationRequestOpts,
5
+ AuthorizationResponse,
6
+ AuthzFlowType,
7
+ CodeChallengeMethod,
8
+ CredentialConfigurationSupported,
9
+ CredentialOfferPayloadV1_0_08,
10
+ CredentialOfferPayloadV1_0_11,
11
+ CredentialOfferRequestWithBaseUrl,
12
+ CredentialResponse,
13
+ CredentialsSupportedLegacy,
14
+ DefaultURISchemes,
15
+ EndpointMetadataResultV1_0_11,
16
+ getClientIdFromCredentialOfferPayload,
17
+ getIssuerFromCredentialOfferPayload,
18
+ getSupportedCredentials,
19
+ getTypesFromCredentialSupported,
20
+ getTypesFromObject,
21
+ JWK,
22
+ KID_JWK_X5C_ERROR,
23
+ OID4VCICredentialFormat,
24
+ OpenId4VCIVersion,
25
+ PKCEOpts,
26
+ ProofOfPossessionCallbacks,
27
+ toAuthorizationResponsePayload,
28
+ } from '@sphereon/oid4vci-common';
29
+ import { CredentialFormat } from '@sphereon/ssi-types';
30
+ import Debug from 'debug';
31
+
32
+ import { AccessTokenClientV1_0_11 } from './AccessTokenClientV1_0_11';
33
+ import { createAuthorizationRequestUrlV1_0_11 } from './AuthorizationCodeClientV1_0_11';
34
+ import { CredentialOfferClientV1_0_11 } from './CredentialOfferClientV1_0_11';
35
+ import { CredentialRequestClientBuilderV1_0_11 } from './CredentialRequestClientBuilderV1_0_11';
36
+ import { MetadataClientV1_0_11 } from './MetadataClientV1_0_11';
37
+ import { ProofOfPossessionBuilder } from './ProofOfPossessionBuilder';
38
+ import { generateMissingPKCEOpts } from './functions/AuthorizationUtil';
39
+
40
+ const debug = Debug('sphereon:oid4vci');
41
+
42
+ export interface OpenID4VCIClientStateV1_0_11 {
43
+ credentialIssuer: string;
44
+ credentialOffer?: CredentialOfferRequestWithBaseUrl;
45
+ clientId?: string;
46
+ kid?: string;
47
+ jwk?: JWK;
48
+ alg?: Alg | string;
49
+ endpointMetadata?: EndpointMetadataResultV1_0_11;
50
+ accessTokenResponse?: AccessTokenResponse;
51
+ authorizationRequestOpts?: AuthorizationRequestOpts;
52
+ authorizationCodeResponse?: AuthorizationResponse;
53
+ pkce: PKCEOpts;
54
+ accessToken?: string;
55
+ authorizationURL?: string;
56
+ }
57
+
58
+ export class OpenID4VCIClientV1_0_11 {
59
+ private readonly _state: OpenID4VCIClientStateV1_0_11;
60
+
61
+ private constructor({
62
+ credentialOffer,
63
+ clientId,
64
+ kid,
65
+ alg,
66
+ credentialIssuer,
67
+ pkce,
68
+ authorizationRequest,
69
+ jwk,
70
+ endpointMetadata,
71
+ accessTokenResponse,
72
+ authorizationRequestOpts,
73
+ authorizationCodeResponse,
74
+ authorizationURL,
75
+ }: {
76
+ credentialOffer?: CredentialOfferRequestWithBaseUrl;
77
+ kid?: string;
78
+ alg?: Alg | string;
79
+ clientId?: string;
80
+ credentialIssuer?: string;
81
+ pkce?: PKCEOpts;
82
+ authorizationRequest?: AuthorizationRequestOpts; // Can be provided here, or when manually calling createAuthorizationUrl
83
+ jwk?: JWK;
84
+ endpointMetadata?: EndpointMetadataResultV1_0_11;
85
+ accessTokenResponse?: AccessTokenResponse;
86
+ authorizationRequestOpts?: AuthorizationRequestOpts;
87
+ authorizationCodeResponse?: AuthorizationResponse;
88
+ authorizationURL?: string;
89
+ }) {
90
+ const issuer = credentialIssuer ?? (credentialOffer ? getIssuerFromCredentialOfferPayload(credentialOffer.credential_offer) : undefined);
91
+ if (!issuer) {
92
+ throw Error('No credential issuer supplied or deduced from offer');
93
+ }
94
+ this._state = {
95
+ credentialOffer,
96
+ credentialIssuer: issuer,
97
+ kid,
98
+ alg,
99
+ // TODO: We need to refactor this and always explicitly call createAuthorizationRequestUrl, so we can have a credential selection first and use the kid as a default for the client id
100
+ clientId: clientId ?? (credentialOffer && getClientIdFromCredentialOfferPayload(credentialOffer.credential_offer)) ?? kid?.split('#')[0],
101
+ pkce: { disabled: false, codeChallengeMethod: CodeChallengeMethod.S256, ...pkce },
102
+ authorizationRequestOpts,
103
+ authorizationCodeResponse,
104
+ jwk,
105
+ endpointMetadata,
106
+ accessTokenResponse,
107
+ authorizationURL,
108
+ };
109
+ // Running syncAuthorizationRequestOpts later as it is using the state
110
+ if (!this._state.authorizationRequestOpts) {
111
+ this._state.authorizationRequestOpts = this.syncAuthorizationRequestOpts(authorizationRequest);
112
+ }
113
+ debug(`Authorization req options: ${JSON.stringify(this._state.authorizationRequestOpts, null, 2)}`);
114
+ }
115
+
116
+ public static async fromCredentialIssuer({
117
+ kid,
118
+ alg,
119
+ retrieveServerMetadata,
120
+ clientId,
121
+ credentialIssuer,
122
+ pkce,
123
+ authorizationRequest,
124
+ createAuthorizationRequestURL,
125
+ }: {
126
+ credentialIssuer: string;
127
+ kid?: string;
128
+ alg?: Alg | string;
129
+ retrieveServerMetadata?: boolean;
130
+ clientId?: string;
131
+ createAuthorizationRequestURL?: boolean;
132
+ authorizationRequest?: AuthorizationRequestOpts; // Can be provided here, or when manually calling createAuthorizationUrl
133
+ pkce?: PKCEOpts;
134
+ }) {
135
+ const client = new OpenID4VCIClientV1_0_11({
136
+ kid,
137
+ alg,
138
+ clientId: clientId ?? authorizationRequest?.clientId,
139
+ credentialIssuer,
140
+ pkce,
141
+ authorizationRequest,
142
+ });
143
+ if (retrieveServerMetadata === undefined || retrieveServerMetadata) {
144
+ await client.retrieveServerMetadata();
145
+ }
146
+ if (createAuthorizationRequestURL === undefined || createAuthorizationRequestURL) {
147
+ await client.createAuthorizationRequestUrl({ authorizationRequest, pkce });
148
+ }
149
+ return client;
150
+ }
151
+
152
+ public static async fromState({ state }: { state: OpenID4VCIClientStateV1_0_11 | string }): Promise<OpenID4VCIClientV1_0_11> {
153
+ const clientState = typeof state === 'string' ? JSON.parse(state) : state;
154
+
155
+ return new OpenID4VCIClientV1_0_11(clientState);
156
+ }
157
+
158
+ public static async fromURI({
159
+ uri,
160
+ kid,
161
+ alg,
162
+ retrieveServerMetadata,
163
+ clientId,
164
+ pkce,
165
+ createAuthorizationRequestURL,
166
+ authorizationRequest,
167
+ resolveOfferUri,
168
+ }: {
169
+ uri: string;
170
+ kid?: string;
171
+ alg?: Alg | string;
172
+ retrieveServerMetadata?: boolean;
173
+ createAuthorizationRequestURL?: boolean;
174
+ resolveOfferUri?: boolean;
175
+ pkce?: PKCEOpts;
176
+ clientId?: string;
177
+ authorizationRequest?: AuthorizationRequestOpts; // Can be provided here, or when manually calling createAuthorizationUrl
178
+ }): Promise<OpenID4VCIClientV1_0_11> {
179
+ const credentialOfferClient = await CredentialOfferClientV1_0_11.fromURI(uri, { resolve: resolveOfferUri });
180
+ const client = new OpenID4VCIClientV1_0_11({
181
+ credentialOffer: credentialOfferClient,
182
+ kid,
183
+ alg,
184
+ clientId: clientId ?? authorizationRequest?.clientId ?? credentialOfferClient.clientId,
185
+ pkce,
186
+ authorizationRequest,
187
+ });
188
+
189
+ if (retrieveServerMetadata === undefined || retrieveServerMetadata) {
190
+ await client.retrieveServerMetadata();
191
+ }
192
+ if (
193
+ credentialOfferClient.supportedFlows.includes(AuthzFlowType.AUTHORIZATION_CODE_FLOW) &&
194
+ (createAuthorizationRequestURL === undefined || createAuthorizationRequestURL)
195
+ ) {
196
+ await client.createAuthorizationRequestUrl({ authorizationRequest, pkce });
197
+ debug(`Authorization Request URL: ${client._state.authorizationURL}`);
198
+ }
199
+
200
+ return client;
201
+ }
202
+
203
+ /**
204
+ * Allows you to create an Authorization Request URL when using an Authorization Code flow. This URL needs to be accessed using the front channel (browser)
205
+ *
206
+ * The Identity provider would present a login screen typically; after you authenticated, it would redirect to the provided redirectUri; which can be same device or cross-device
207
+ * @param opts
208
+ */
209
+ public async createAuthorizationRequestUrl(opts?: { authorizationRequest?: AuthorizationRequestOpts; pkce?: PKCEOpts }): Promise<string> {
210
+ if (!this._state.authorizationURL) {
211
+ this.calculatePKCEOpts(opts?.pkce);
212
+ this._state.authorizationRequestOpts = this.syncAuthorizationRequestOpts(opts?.authorizationRequest);
213
+ if (!this._state.authorizationRequestOpts) {
214
+ throw Error(`No Authorization Request options present or provided in this call`);
215
+ }
216
+
217
+ // todo: Probably can go with current logic in MetadataClientV1_0_13 who will always set the authorization_endpoint when found
218
+ // handling this because of the support for v1_0-08
219
+ if (
220
+ this._state.endpointMetadata?.credentialIssuerMetadata &&
221
+ 'authorization_endpoint' in this._state.endpointMetadata.credentialIssuerMetadata
222
+ ) {
223
+ this._state.endpointMetadata.authorization_endpoint = this._state.endpointMetadata.credentialIssuerMetadata.authorization_endpoint as string;
224
+ }
225
+ this._state.authorizationURL = await createAuthorizationRequestUrlV1_0_11({
226
+ pkce: this._state.pkce,
227
+ endpointMetadata: this.endpointMetadata,
228
+ authorizationRequest: this._state.authorizationRequestOpts,
229
+ credentialOffer: this.credentialOffer,
230
+ credentialsSupported: Object.values(this.getCredentialsSupported()) as CredentialsSupportedLegacy[],
231
+ });
232
+ }
233
+ return this._state.authorizationURL;
234
+ }
235
+
236
+ public async retrieveServerMetadata(): Promise<EndpointMetadataResultV1_0_11> {
237
+ this.assertIssuerData();
238
+ if (!this._state.endpointMetadata) {
239
+ if (this.credentialOffer) {
240
+ this._state.endpointMetadata = await MetadataClientV1_0_11.retrieveAllMetadataFromCredentialOffer(this.credentialOffer);
241
+ } else if (this._state.credentialIssuer) {
242
+ this._state.endpointMetadata = await MetadataClientV1_0_11.retrieveAllMetadata(this._state.credentialIssuer);
243
+ } else {
244
+ throw Error(`Cannot retrieve issuer metadata without either a credential offer, or issuer value`);
245
+ }
246
+ }
247
+
248
+ return this.endpointMetadata;
249
+ }
250
+
251
+ private calculatePKCEOpts(pkce?: PKCEOpts) {
252
+ this._state.pkce = generateMissingPKCEOpts({ ...this._state.pkce, ...pkce });
253
+ }
254
+
255
+ public async acquireAccessToken(opts?: {
256
+ pin?: string;
257
+ clientId?: string;
258
+ codeVerifier?: string;
259
+ authorizationResponse?: string | AuthorizationResponse; // Pass in an auth response, either as URI/redirect, or object
260
+ code?: string; // Directly pass in a code from an auth response
261
+ redirectUri?: string;
262
+ }): Promise<AccessTokenResponse> {
263
+ const { pin, clientId } = opts ?? {};
264
+ let { redirectUri } = opts ?? {};
265
+ if (opts?.authorizationResponse) {
266
+ this._state.authorizationCodeResponse = { ...toAuthorizationResponsePayload(opts.authorizationResponse) };
267
+ } else if (opts?.code) {
268
+ this._state.authorizationCodeResponse = { code: opts.code };
269
+ }
270
+ const code = this._state.authorizationCodeResponse?.code;
271
+
272
+ if (opts?.codeVerifier) {
273
+ this._state.pkce.codeVerifier = opts.codeVerifier;
274
+ }
275
+ this.assertIssuerData();
276
+
277
+ if (clientId) {
278
+ this._state.clientId = clientId;
279
+ }
280
+ if (!this._state.accessTokenResponse) {
281
+ const accessTokenClient = new AccessTokenClientV1_0_11();
282
+
283
+ if (redirectUri && redirectUri !== this._state.authorizationRequestOpts?.redirectUri) {
284
+ console.log(
285
+ `Redirect URI mismatch between access-token (${redirectUri}) and authorization request (${this._state.authorizationRequestOpts?.redirectUri}). According to the specification that is not allowed.`,
286
+ );
287
+ }
288
+ if (this._state.authorizationRequestOpts?.redirectUri && !redirectUri) {
289
+ redirectUri = this._state.authorizationRequestOpts.redirectUri;
290
+ }
291
+
292
+ const response = await accessTokenClient.acquireAccessToken({
293
+ credentialOffer: this.credentialOffer,
294
+ metadata: this.endpointMetadata,
295
+ credentialIssuer: this.getIssuer(),
296
+ pin,
297
+ ...(!this._state.pkce.disabled && { codeVerifier: this._state.pkce.codeVerifier }),
298
+ code,
299
+ redirectUri,
300
+ asOpts: { clientId: this.clientId },
301
+ });
302
+
303
+ if (response.errorBody) {
304
+ debug(`Access token error:\r\n${JSON.stringify(response.errorBody)}`);
305
+ throw Error(
306
+ `Retrieving an access token from ${this._state.endpointMetadata?.token_endpoint} for issuer ${this.getIssuer()} failed with status: ${
307
+ response.origResponse.status
308
+ }`,
309
+ );
310
+ } else if (!response.successBody) {
311
+ debug(`Access token error. No success body`);
312
+ throw Error(
313
+ `Retrieving an access token from ${
314
+ this._state.endpointMetadata?.token_endpoint
315
+ } for issuer ${this.getIssuer()} failed as there was no success response body`,
316
+ );
317
+ }
318
+ this._state.accessTokenResponse = response.successBody;
319
+ }
320
+
321
+ return this.accessTokenResponse;
322
+ }
323
+
324
+ public async acquireCredentials({
325
+ credentialTypes,
326
+ context,
327
+ proofCallbacks,
328
+ format,
329
+ kid,
330
+ jwk,
331
+ alg,
332
+ jti,
333
+ deferredCredentialAwait,
334
+ deferredCredentialIntervalInMS,
335
+ }: {
336
+ credentialTypes: string | string[];
337
+ context?: string[];
338
+ proofCallbacks: ProofOfPossessionCallbacks<any>;
339
+ format?: CredentialFormat | OID4VCICredentialFormat;
340
+ kid?: string;
341
+ jwk?: JWK;
342
+ alg?: Alg | string;
343
+ jti?: string;
344
+ deferredCredentialAwait?: boolean;
345
+ deferredCredentialIntervalInMS?: number;
346
+ }): Promise<CredentialResponse> {
347
+ if ([jwk, kid].filter((v) => v !== undefined).length > 1) {
348
+ throw new Error(KID_JWK_X5C_ERROR + `. jwk: ${jwk !== undefined}, kid: ${kid !== undefined}`);
349
+ }
350
+
351
+ if (alg) this._state.alg = alg;
352
+ if (jwk) this._state.jwk = jwk;
353
+ if (kid) this._state.kid = kid;
354
+
355
+ const requestBuilder = this.credentialOffer
356
+ ? CredentialRequestClientBuilderV1_0_11.fromCredentialOffer({
357
+ credentialOffer: this.credentialOffer,
358
+ metadata: this.endpointMetadata,
359
+ })
360
+ : CredentialRequestClientBuilderV1_0_11.fromCredentialIssuer({
361
+ credentialIssuer: this.getIssuer(),
362
+ credentialTypes,
363
+ metadata: this.endpointMetadata,
364
+ version: this.version(),
365
+ });
366
+
367
+ requestBuilder.withTokenFromResponse(this.accessTokenResponse);
368
+ requestBuilder.withDeferredCredentialAwait(deferredCredentialAwait ?? false, deferredCredentialIntervalInMS);
369
+ if (this.endpointMetadata?.credentialIssuerMetadata) {
370
+ const metadata = this.endpointMetadata.credentialIssuerMetadata;
371
+ const types = Array.isArray(credentialTypes) ? credentialTypes : [credentialTypes];
372
+
373
+ if (metadata.credentials_supported && Array.isArray(metadata.credentials_supported)) {
374
+ let typeSupported = false;
375
+
376
+ metadata.credentials_supported.forEach((supportedCredential) => {
377
+ const subTypes = getTypesFromCredentialSupported(supportedCredential);
378
+ if (
379
+ subTypes.every((t, i) => types[i] === t) ||
380
+ (types.length === 1 && (types[0] === supportedCredential.id || subTypes.includes(types[0])))
381
+ ) {
382
+ typeSupported = true;
383
+ }
384
+ });
385
+
386
+ if (!typeSupported) {
387
+ console.log(`Not all credential types ${JSON.stringify(credentialTypes)} are present in metadata for ${this.getIssuer()}`);
388
+ // throw Error(`Not all credential types ${JSON.stringify(credentialTypes)} are supported by issuer ${this.getIssuer()}`);
389
+ }
390
+ } else if (metadata.credentials_supported && !Array.isArray(metadata.credentials_supported)) {
391
+ const credentialsSupported = metadata.credentials_supported;
392
+ if (types.some((type) => !metadata.credentials_supported || !credentialsSupported[type])) {
393
+ throw Error(`Not all credential types ${JSON.stringify(credentialTypes)} are supported by issuer ${this.getIssuer()}`);
394
+ }
395
+ }
396
+ // todo: Format check? We might end up with some disjoint type / format combinations supported by the server
397
+ }
398
+ const credentialRequestClient = requestBuilder.build();
399
+ const proofBuilder = ProofOfPossessionBuilder.fromAccessTokenResponse({
400
+ accessTokenResponse: this.accessTokenResponse,
401
+ callbacks: proofCallbacks,
402
+ version: this.version(),
403
+ })
404
+ .withIssuer(this.getIssuer())
405
+ .withAlg(this.alg);
406
+
407
+ if (this._state.jwk) {
408
+ proofBuilder.withJWK(this._state.jwk);
409
+ }
410
+ if (this._state.kid) {
411
+ proofBuilder.withKid(this._state.kid);
412
+ }
413
+
414
+ if (this.clientId) {
415
+ proofBuilder.withClientId(this.clientId);
416
+ }
417
+ if (jti) {
418
+ proofBuilder.withJti(jti);
419
+ }
420
+ const response = await credentialRequestClient.acquireCredentialsUsingProof({
421
+ proofInput: proofBuilder,
422
+ credentialTypes,
423
+ context,
424
+ format,
425
+ });
426
+ if (response.errorBody) {
427
+ debug(`Credential request error:\r\n${JSON.stringify(response.errorBody)}`);
428
+ throw Error(
429
+ `Retrieving a credential from ${this._state.endpointMetadata?.credential_endpoint} for issuer ${this.getIssuer()} failed with status: ${
430
+ response.origResponse.status
431
+ }`,
432
+ );
433
+ } else if (!response.successBody) {
434
+ debug(`Credential request error. No success body`);
435
+ throw Error(
436
+ `Retrieving a credential from ${
437
+ this._state.endpointMetadata?.credential_endpoint
438
+ } for issuer ${this.getIssuer()} failed as there was no success response body`,
439
+ );
440
+ }
441
+ return response.successBody;
442
+ }
443
+
444
+ public async exportState(): Promise<string> {
445
+ return JSON.stringify(this._state);
446
+ }
447
+
448
+ // FIXME: We really should convert <v11 to v12 objects first. Right now the logic doesn't map nicely and is brittle.
449
+ // We should resolve IDs to objects first in case of strings.
450
+ // When < v11 convert into a v12 object. When v12 object retain it.
451
+ // Then match the object array on server metadata
452
+ getCredentialsSupportedV11(
453
+ restrictToInitiationTypes: boolean,
454
+ format?: (OID4VCICredentialFormat | string) | (OID4VCICredentialFormat | string)[],
455
+ ): Record<string, CredentialConfigurationSupported> {
456
+ return getSupportedCredentials({
457
+ issuerMetadata: this.endpointMetadata.credentialIssuerMetadata,
458
+ version: this.version(),
459
+ format: format,
460
+ types: restrictToInitiationTypes ? this.getCredentialOfferTypes() : undefined,
461
+ }) as Record<string, CredentialConfigurationSupported>;
462
+ }
463
+
464
+ getCredentialsSupported(format?: (OID4VCICredentialFormat | string) | (OID4VCICredentialFormat | string)[]): CredentialConfigurationSupported[] {
465
+ return getSupportedCredentials({
466
+ issuerMetadata: this.endpointMetadata.credentialIssuerMetadata,
467
+ version: this.version(),
468
+ format: format,
469
+ types: undefined,
470
+ }) as CredentialConfigurationSupported[];
471
+ }
472
+
473
+ getCredentialOfferTypes(): string[][] {
474
+ if (!this.credentialOffer) {
475
+ return [];
476
+ } else if (this.credentialOffer.version < OpenId4VCIVersion.VER_1_0_11) {
477
+ const orig = this.credentialOffer.original_credential_offer as CredentialOfferPayloadV1_0_08;
478
+ const types: string[] = typeof orig.credential_type === 'string' ? [orig.credential_type] : orig.credential_type;
479
+ const result: string[][] = [];
480
+ result[0] = types;
481
+ return result;
482
+ } else if (this.credentialOffer.version < OpenId4VCIVersion.VER_1_0_13) {
483
+ return (this.credentialOffer.credential_offer as CredentialOfferPayloadV1_0_11).credentials.map((c) => getTypesFromObject(c) ?? []);
484
+ }
485
+ // we don't support > V11
486
+ throw Error(`This class only supports version 11 and lower! Version: ${this.version()}`);
487
+ }
488
+
489
+ issuerSupportedFlowTypes(): AuthzFlowType[] {
490
+ return (
491
+ this.credentialOffer?.supportedFlows ??
492
+ (this._state.endpointMetadata?.credentialIssuerMetadata?.authorization_endpoint ? [AuthzFlowType.AUTHORIZATION_CODE_FLOW] : [])
493
+ );
494
+ }
495
+
496
+ isFlowTypeSupported(flowType: AuthzFlowType): boolean {
497
+ return this.issuerSupportedFlowTypes().includes(flowType);
498
+ }
499
+
500
+ get authorizationURL(): string | undefined {
501
+ return this._state.authorizationURL;
502
+ }
503
+
504
+ public hasAuthorizationURL(): boolean {
505
+ return !!this.authorizationURL;
506
+ }
507
+
508
+ get credentialOffer(): CredentialOfferRequestWithBaseUrl | undefined {
509
+ return this._state.credentialOffer;
510
+ }
511
+
512
+ public version(): OpenId4VCIVersion {
513
+ return this.credentialOffer?.version ?? OpenId4VCIVersion.VER_1_0_11;
514
+ }
515
+
516
+ public get endpointMetadata(): EndpointMetadataResultV1_0_11 {
517
+ this.assertServerMetadata();
518
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
519
+ return this._state.endpointMetadata!;
520
+ }
521
+
522
+ get kid(): string {
523
+ this.assertIssuerData();
524
+ if (!this._state.kid) {
525
+ throw new Error('No value for kid is supplied');
526
+ }
527
+ return this._state.kid;
528
+ }
529
+
530
+ get alg(): string {
531
+ this.assertIssuerData();
532
+ if (!this._state.alg) {
533
+ throw new Error('No value for alg is supplied');
534
+ }
535
+ return this._state.alg;
536
+ }
537
+
538
+ set clientId(value: string | undefined) {
539
+ this._state.clientId = value;
540
+ }
541
+
542
+ get clientId(): string | undefined {
543
+ return this._state.clientId;
544
+ }
545
+
546
+ public hasAccessTokenResponse(): boolean {
547
+ return !!this._state.accessTokenResponse;
548
+ }
549
+
550
+ get accessTokenResponse(): AccessTokenResponse {
551
+ this.assertAccessToken();
552
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
553
+ return this._state.accessTokenResponse!;
554
+ }
555
+
556
+ public getIssuer(): string {
557
+ this.assertIssuerData();
558
+ return this._state.credentialIssuer;
559
+ }
560
+
561
+ public getAccessTokenEndpoint(): string {
562
+ this.assertIssuerData();
563
+ return this.endpointMetadata
564
+ ? this.endpointMetadata.token_endpoint
565
+ : AccessTokenClientV1_0_11.determineTokenURL({ issuerOpts: { issuer: this.getIssuer() } });
566
+ }
567
+
568
+ public getCredentialEndpoint(): string {
569
+ this.assertIssuerData();
570
+ return this.endpointMetadata ? this.endpointMetadata.credential_endpoint : `${this.getIssuer()}/credential`;
571
+ }
572
+
573
+ public hasDeferredCredentialEndpoint(): boolean {
574
+ return !!this.getAccessTokenEndpoint();
575
+ }
576
+
577
+ public getDeferredCredentialEndpoint(): string {
578
+ this.assertIssuerData();
579
+ return this.endpointMetadata ? this.endpointMetadata.credential_endpoint : `${this.getIssuer()}/credential`;
580
+ }
581
+
582
+ /**
583
+ * Too bad we need a method like this, but EBSI is not exposing metadata
584
+ */
585
+ public isEBSI() {
586
+ if (
587
+ (this.credentialOffer?.credential_offer as CredentialOfferPayloadV1_0_11)['credentials'] &&
588
+ (this.credentialOffer?.credential_offer as CredentialOfferPayloadV1_0_11).credentials.find(
589
+ (cred) =>
590
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
591
+ // @ts-ignore
592
+ typeof cred !== 'string' && 'trust_framework' in cred && 'name' in cred.trust_framework && cred.trust_framework.name.includes('ebsi'),
593
+ )
594
+ ) {
595
+ return true;
596
+ }
597
+ this.assertIssuerData();
598
+ return this.endpointMetadata.credentialIssuerMetadata?.authorization_endpoint?.includes('ebsi.eu') === true;
599
+ }
600
+
601
+ private assertIssuerData(): void {
602
+ if (!this._state.credentialIssuer) {
603
+ throw Error(`No credential issuer value present`);
604
+ } else if (!this._state.credentialOffer && this._state.endpointMetadata && this.issuerSupportedFlowTypes().length === 0) {
605
+ throw Error(`No issuance initiation or credential offer present`);
606
+ }
607
+ }
608
+
609
+ private assertServerMetadata(): void {
610
+ if (!this._state.endpointMetadata) {
611
+ throw Error('No server metadata');
612
+ }
613
+ }
614
+
615
+ private assertAccessToken(): void {
616
+ if (!this._state.accessTokenResponse) {
617
+ throw Error(`No access token present`);
618
+ }
619
+ }
620
+
621
+ private syncAuthorizationRequestOpts(opts?: AuthorizationRequestOpts): AuthorizationRequestOpts {
622
+ let authorizationRequestOpts = { ...this._state?.authorizationRequestOpts, ...opts } as AuthorizationRequestOpts;
623
+ if (!authorizationRequestOpts) {
624
+ // We only set a redirectUri if no options are provided.
625
+ // Note that this only works for mobile apps, that can handle a code query param on the default openid-credential-offer deeplink.
626
+ // Provide your own options if that is not desired!
627
+ authorizationRequestOpts = { redirectUri: `${DefaultURISchemes.CREDENTIAL_OFFER}://` };
628
+ }
629
+ const clientId = authorizationRequestOpts.clientId ?? this._state.clientId;
630
+ // sync clientId
631
+ this._state.clientId = clientId;
632
+ authorizationRequestOpts.clientId = clientId;
633
+ return authorizationRequestOpts;
634
+ }
635
+ }