@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
@@ -1,9 +1,12 @@
1
1
  import {
2
+ convertJsonToURI,
3
+ convertURIToJsonObject,
2
4
  CredentialOffer,
3
5
  CredentialOfferPayload,
4
6
  CredentialOfferPayloadV1_0_09,
5
7
  CredentialOfferRequestWithBaseUrl,
6
8
  CredentialOfferV1_0_11,
9
+ CredentialOfferV1_0_13,
7
10
  determineSpecVersionFromURI,
8
11
  getClientIdFromCredentialOfferPayload,
9
12
  OpenId4VCIVersion,
@@ -11,7 +14,7 @@ import {
11
14
  } from '@sphereon/oid4vci-common';
12
15
  import Debug from 'debug';
13
16
 
14
- import { convertJsonToURI, convertURIToJsonObject } from './functions';
17
+ import { LOG } from './types';
15
18
 
16
19
  const debug = Debug('sphereon:oid4vci:offer');
17
20
 
@@ -25,21 +28,24 @@ export class CredentialOfferClient {
25
28
  const scheme = uri.split('://')[0];
26
29
  const baseUrl = uri.split('?')[0];
27
30
  const version = determineSpecVersionFromURI(uri);
31
+ LOG.log(`Offer URL determined to be of version ${version}`);
28
32
  let credentialOffer: CredentialOffer;
29
33
  let credentialOfferPayload: CredentialOfferPayload;
34
+ // credential offer was introduced in draft 9 and credential_offer_uri in draft 11
30
35
  if (version < OpenId4VCIVersion.VER_1_0_11) {
31
36
  credentialOfferPayload = convertURIToJsonObject(uri, {
32
37
  arrayTypeProperties: ['credential_type'],
33
- requiredProperties: uri.includes('credential_offer_uri=') ? ['credential_offer_uri'] : ['issuer', 'credential_type'],
38
+ requiredProperties: uri.includes('credential_offer=') ? ['credential_offer'] : ['issuer', 'credential_type'],
34
39
  }) as CredentialOfferPayloadV1_0_09;
35
40
  credentialOffer = {
36
41
  credential_offer: credentialOfferPayload,
37
42
  };
38
43
  } else {
39
44
  credentialOffer = convertURIToJsonObject(uri, {
40
- arrayTypeProperties: ['credentials'],
41
- requiredProperties: uri.includes('credential_offer_uri=') ? ['credential_offer_uri'] : ['credential_offer'],
42
- }) as CredentialOfferV1_0_11;
45
+ // It must have the '=' sign after credential_offer otherwise the uri will get split at openid_credential_offer
46
+ arrayTypeProperties: uri.includes('credential_offer_uri=') ? ['credential_offer_uri='] : ['credential_offer='],
47
+ requiredProperties: uri.includes('credential_offer_uri=') ? ['credential_offer_uri='] : ['credential_offer='],
48
+ }) as CredentialOfferV1_0_11 | CredentialOfferV1_0_13;
43
49
  if (credentialOffer?.credential_offer_uri === undefined && !credentialOffer?.credential_offer) {
44
50
  throw Error('Either a credential_offer or credential_offer_uri should be present in ' + uri);
45
51
  }
@@ -55,13 +61,20 @@ export class CredentialOfferClient {
55
61
  return {
56
62
  scheme,
57
63
  baseUrl,
58
- clientId,
64
+ ...(clientId && { clientId }),
59
65
  ...request,
60
66
  ...(grants?.authorization_code?.issuer_state && { issuerState: grants.authorization_code.issuer_state }),
61
67
  ...(grants?.['urn:ietf:params:oauth:grant-type:pre-authorized_code']?.['pre-authorized_code'] && {
62
68
  preAuthorizedCode: grants['urn:ietf:params:oauth:grant-type:pre-authorized_code']['pre-authorized_code'],
63
69
  }),
64
- userPinRequired: request.credential_offer?.grants?.['urn:ietf:params:oauth:grant-type:pre-authorized_code']?.user_pin_required ?? false,
70
+ userPinRequired:
71
+ request.credential_offer?.grants?.['urn:ietf:params:oauth:grant-type:pre-authorized_code']?.user_pin_required ??
72
+ !!request.credential_offer?.grants?.['urn:ietf:params:oauth:grant-type:pre-authorized_code']?.tx_code ??
73
+ false,
74
+ ...(request.credential_offer?.grants?.['urn:ietf:params:oauth:grant-type:pre-authorized_code']?.tx_code &&
75
+ {
76
+ // txCode: request.credential_offer?.grants?.['urn:ietf:params:oauth:grant-type:pre-authorized_code']?.tx_code,
77
+ }),
65
78
  };
66
79
  }
67
80
 
@@ -101,7 +114,7 @@ export class CredentialOfferClient {
101
114
  arrayTypeProperties: isUri ? [] : ['credential_type'],
102
115
  uriTypeProperties: isUri
103
116
  ? ['credential_offer_uri']
104
- : version >= OpenId4VCIVersion.VER_1_0_11
117
+ : version >= OpenId4VCIVersion.VER_1_0_13
105
118
  ? ['credential_issuer', 'credential_type']
106
119
  : ['issuer', 'credential_type'],
107
120
  param,
@@ -0,0 +1,112 @@
1
+ import {
2
+ convertJsonToURI,
3
+ convertURIToJsonObject,
4
+ CredentialOffer,
5
+ CredentialOfferPayload,
6
+ CredentialOfferPayloadV1_0_09,
7
+ CredentialOfferRequestWithBaseUrl,
8
+ CredentialOfferRequestWithBaseUrlV1_0_11,
9
+ CredentialOfferV1_0_11,
10
+ determineSpecVersionFromURI,
11
+ getClientIdFromCredentialOfferPayload,
12
+ OpenId4VCIVersion,
13
+ toUniformCredentialOfferRequest,
14
+ } from '@sphereon/oid4vci-common';
15
+ import Debug from 'debug';
16
+
17
+ const debug = Debug('sphereon:oid4vci:offer');
18
+
19
+ export class CredentialOfferClientV1_0_11 {
20
+ public static async fromURI(uri: string, opts?: { resolve?: boolean }): Promise<CredentialOfferRequestWithBaseUrlV1_0_11> {
21
+ debug(`Credential Offer URI: ${uri}`);
22
+ if (!uri.includes('?') || !uri.includes('://')) {
23
+ debug(`Invalid Credential Offer URI: ${uri}`);
24
+ throw Error(`Invalid Credential Offer Request`);
25
+ }
26
+ const scheme = uri.split('://')[0];
27
+ const baseUrl = uri.split('?')[0];
28
+ const version = determineSpecVersionFromURI(uri);
29
+ let credentialOffer: CredentialOffer;
30
+ let credentialOfferPayload: CredentialOfferPayload;
31
+ if (version < OpenId4VCIVersion.VER_1_0_11) {
32
+ credentialOfferPayload = convertURIToJsonObject(uri, {
33
+ arrayTypeProperties: ['credential_type'],
34
+ requiredProperties: uri.includes('credential_offer_uri=') ? ['credential_offer_uri='] : ['issuer', 'credential_type='],
35
+ }) as CredentialOfferPayloadV1_0_09;
36
+ credentialOffer = {
37
+ credential_offer: credentialOfferPayload,
38
+ };
39
+ } else {
40
+ credentialOffer = convertURIToJsonObject(uri, {
41
+ arrayTypeProperties: ['credentials'],
42
+ requiredProperties: uri.includes('credential_offer_uri=') ? ['credential_offer_uri='] : ['credential_offer='],
43
+ }) as CredentialOfferV1_0_11;
44
+ if (credentialOffer?.credential_offer_uri === undefined && !credentialOffer?.credential_offer) {
45
+ throw Error('Either a credential_offer or credential_offer_uri should be present in ' + uri);
46
+ }
47
+ }
48
+
49
+ const request = await toUniformCredentialOfferRequest(credentialOffer, {
50
+ ...opts,
51
+ version,
52
+ });
53
+ const clientId = getClientIdFromCredentialOfferPayload(request.credential_offer);
54
+ const grants = request.credential_offer?.grants;
55
+
56
+ return {
57
+ scheme,
58
+ baseUrl,
59
+ ...(clientId && { clientId }),
60
+ ...request,
61
+ ...(grants?.authorization_code?.issuer_state && { issuerState: grants.authorization_code.issuer_state }),
62
+ ...(grants?.['urn:ietf:params:oauth:grant-type:pre-authorized_code']?.['pre-authorized_code'] && {
63
+ preAuthorizedCode: grants['urn:ietf:params:oauth:grant-type:pre-authorized_code']['pre-authorized_code'],
64
+ }),
65
+ userPinRequired: !!request.credential_offer?.grants?.['urn:ietf:params:oauth:grant-type:pre-authorized_code']?.user_pin_required ?? false,
66
+ };
67
+ }
68
+
69
+ public static toURI(
70
+ requestWithBaseUrl: CredentialOfferRequestWithBaseUrl,
71
+ opts?: {
72
+ version?: OpenId4VCIVersion;
73
+ },
74
+ ): string {
75
+ debug(`Credential Offer Request with base URL: ${JSON.stringify(requestWithBaseUrl)}`);
76
+ const version = opts?.version ?? requestWithBaseUrl.version;
77
+ let baseUrl = requestWithBaseUrl.baseUrl.includes(requestWithBaseUrl.scheme)
78
+ ? requestWithBaseUrl.baseUrl
79
+ : `${requestWithBaseUrl.scheme.replace('://', '')}://${requestWithBaseUrl.baseUrl}`;
80
+ let param: string | undefined;
81
+
82
+ const isUri = requestWithBaseUrl.credential_offer_uri !== undefined;
83
+
84
+ if (version.valueOf() >= OpenId4VCIVersion.VER_1_0_11.valueOf()) {
85
+ // v11 changed from encoding every param to a encoded json object with a credential_offer param key
86
+ if (!baseUrl.includes('?')) {
87
+ param = isUri ? 'credential_offer_uri' : 'credential_offer';
88
+ } else {
89
+ const split = baseUrl.split('?');
90
+ if (split.length > 1 && split[1] !== '') {
91
+ if (baseUrl.endsWith('&')) {
92
+ param = isUri ? 'credential_offer_uri' : 'credential_offer';
93
+ } else if (!baseUrl.endsWith('=')) {
94
+ baseUrl += `&`;
95
+ param = isUri ? 'credential_offer_uri' : 'credential_offer';
96
+ }
97
+ }
98
+ }
99
+ }
100
+ return convertJsonToURI(requestWithBaseUrl.credential_offer_uri ?? requestWithBaseUrl.original_credential_offer, {
101
+ baseUrl,
102
+ arrayTypeProperties: isUri ? [] : ['credential_type'],
103
+ uriTypeProperties: isUri
104
+ ? ['credential_offer_uri']
105
+ : version >= OpenId4VCIVersion.VER_1_0_11
106
+ ? ['credential_issuer', 'credential_type']
107
+ : ['issuer', 'credential_type'],
108
+ param,
109
+ version,
110
+ });
111
+ }
112
+ }
@@ -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
+ }
@@ -1,22 +1,26 @@
1
1
  import {
2
2
  acquireDeferredCredential,
3
+ CredentialRequestV1_0_13,
3
4
  CredentialResponse,
4
5
  getCredentialRequestForVersion,
5
6
  getUniformFormat,
6
7
  isDeferredCredentialResponse,
8
+ isValidURL,
9
+ JsonLdIssuerCredentialDefinition,
7
10
  OID4VCICredentialFormat,
8
11
  OpenId4VCIVersion,
9
12
  OpenIDResponse,
13
+ post,
10
14
  ProofOfPossession,
11
15
  UniformCredentialRequest,
12
16
  URL_NOT_VALID,
13
17
  } from '@sphereon/oid4vci-common';
18
+ import { ExperimentalSubjectIssuance } from '@sphereon/oid4vci-common/dist/experimental/holder-vci';
14
19
  import { CredentialFormat } from '@sphereon/ssi-types';
15
20
  import Debug from 'debug';
16
21
 
17
22
  import { CredentialRequestClientBuilder } from './CredentialRequestClientBuilder';
18
23
  import { ProofOfPossessionBuilder } from './ProofOfPossessionBuilder';
19
- import { isValidURL, post } from './functions';
20
24
 
21
25
  const debug = Debug('sphereon:oid4vci:credential');
22
26
 
@@ -24,12 +28,15 @@ export interface CredentialRequestOpts {
24
28
  deferredCredentialAwait?: boolean;
25
29
  deferredCredentialIntervalInMS?: number;
26
30
  credentialEndpoint: string;
31
+ notificationEndpoint?: string;
27
32
  deferredCredentialEndpoint?: string;
28
- credentialTypes: string[];
33
+ credentialTypes?: string[];
34
+ credentialIdentifier?: string;
29
35
  format?: CredentialFormat | OID4VCICredentialFormat;
30
36
  proof: ProofOfPossession;
31
37
  token: string;
32
38
  version: OpenId4VCIVersion;
39
+ subjectIssuance?: ExperimentalSubjectIssuance;
33
40
  }
34
41
 
35
42
  export async function buildProof<DIDDoc>(
@@ -41,7 +48,7 @@ export async function buildProof<DIDDoc>(
41
48
  ) {
42
49
  if ('proof_type' in proofInput) {
43
50
  if (opts.cNonce) {
44
- throw Error(`Cnonce param is only supported when using a Proof of Posession builder`);
51
+ throw Error(`Cnonce param is only supported when using a Proof of possession builder`);
45
52
  }
46
53
  return await ProofOfPossessionBuilder.fromProof(proofInput as ProofOfPossession, opts.version).build();
47
54
  }
@@ -77,18 +84,33 @@ export class CredentialRequestClient {
77
84
 
78
85
  public async acquireCredentialsUsingProof<DIDDoc>(opts: {
79
86
  proofInput: ProofOfPossessionBuilder<DIDDoc> | ProofOfPossession;
87
+ credentialIdentifier?: string;
80
88
  credentialTypes?: string | string[];
81
89
  context?: string[];
82
90
  format?: CredentialFormat | OID4VCICredentialFormat;
83
- }): Promise<OpenIDResponse<CredentialResponse>> {
84
- const { credentialTypes, proofInput, format, context } = opts;
85
-
86
- const request = await this.createCredentialRequest({ proofInput, credentialTypes, context, format, version: this.version() });
91
+ subjectIssuance?: ExperimentalSubjectIssuance;
92
+ }): Promise<OpenIDResponse<CredentialResponse> & { access_token: string }> {
93
+ const { credentialIdentifier, credentialTypes, proofInput, format, context, subjectIssuance } = opts;
94
+
95
+ const request = await this.createCredentialRequest({
96
+ proofInput,
97
+ credentialTypes,
98
+ context,
99
+ format,
100
+ version: this.version(),
101
+ credentialIdentifier,
102
+ subjectIssuance,
103
+ });
87
104
  return await this.acquireCredentialsUsingRequest(request);
88
105
  }
89
106
 
90
- public async acquireCredentialsUsingRequest(uniformRequest: UniformCredentialRequest): Promise<OpenIDResponse<CredentialResponse>> {
91
- const request = getCredentialRequestForVersion(uniformRequest, this.version());
107
+ public async acquireCredentialsUsingRequest(
108
+ uniformRequest: UniformCredentialRequest,
109
+ ): Promise<OpenIDResponse<CredentialResponse> & { access_token: string }> {
110
+ if (this.version() < OpenId4VCIVersion.VER_1_0_13) {
111
+ throw new Error('Versions below v1.0.13 (draft 13) are not supported by the V13 credential request client.');
112
+ }
113
+ const request: CredentialRequestV1_0_13 = getCredentialRequestForVersion(uniformRequest, this.version()) as CredentialRequestV1_0_13;
92
114
  const credentialEndpoint: string = this.credentialRequestOpts.credentialEndpoint;
93
115
  if (!isValidURL(credentialEndpoint)) {
94
116
  debug(`Invalid credential endpoint: ${credentialEndpoint}`);
@@ -97,12 +119,20 @@ export class CredentialRequestClient {
97
119
  debug(`Acquiring credential(s) from: ${credentialEndpoint}`);
98
120
  debug(`request\n: ${JSON.stringify(request, null, 2)}`);
99
121
  const requestToken: string = this.credentialRequestOpts.token;
100
- let response: OpenIDResponse<CredentialResponse> = await post(credentialEndpoint, JSON.stringify(request), { bearerToken: requestToken });
122
+ let response = (await post(credentialEndpoint, JSON.stringify(request), { bearerToken: requestToken })) as OpenIDResponse<CredentialResponse> & {
123
+ access_token: string;
124
+ };
101
125
  this._isDeferred = isDeferredCredentialResponse(response);
102
126
  if (this.isDeferred() && this.credentialRequestOpts.deferredCredentialAwait && response.successBody) {
103
127
  response = await this.acquireDeferredCredential(response.successBody, { bearerToken: this.credentialRequestOpts.token });
104
128
  }
129
+ response.access_token = requestToken;
105
130
 
131
+ if ((uniformRequest.credential_subject_issuance && response.successBody) || response.successBody?.credential_subject_issuance) {
132
+ if (JSON.stringify(uniformRequest.credential_subject_issuance) !== JSON.stringify(response.successBody?.credential_subject_issuance)) {
133
+ throw Error('Subject signing was requested, but issuer did not provide the options in its response');
134
+ }
135
+ }
106
136
  debug(`Credential endpoint ${credentialEndpoint} response:\r\n${JSON.stringify(response, null, 2)}`);
107
137
  return response;
108
138
  }
@@ -112,7 +142,7 @@ export class CredentialRequestClient {
112
142
  opts?: {
113
143
  bearerToken?: string;
114
144
  },
115
- ): Promise<OpenIDResponse<CredentialResponse>> {
145
+ ): Promise<OpenIDResponse<CredentialResponse> & { access_token: string }> {
116
146
  const transactionId = response.transaction_id;
117
147
  const bearerToken = response.acceptance_token ?? opts?.bearerToken;
118
148
  const deferredCredentialEndpoint = this.getDeferredCredentialEndpoint();
@@ -133,12 +163,24 @@ export class CredentialRequestClient {
133
163
 
134
164
  public async createCredentialRequest<DIDDoc>(opts: {
135
165
  proofInput: ProofOfPossessionBuilder<DIDDoc> | ProofOfPossession;
166
+ credentialIdentifier?: string;
136
167
  credentialTypes?: string | string[];
137
168
  context?: string[];
138
169
  format?: CredentialFormat | OID4VCICredentialFormat;
170
+ subjectIssuance?: ExperimentalSubjectIssuance;
139
171
  version: OpenId4VCIVersion;
140
- }): Promise<UniformCredentialRequest> {
141
- const { proofInput } = opts;
172
+ }): Promise<CredentialRequestV1_0_13> {
173
+ const { proofInput, credentialIdentifier: credential_identifier } = opts;
174
+ const proof = await buildProof(proofInput, opts);
175
+ if (credential_identifier) {
176
+ if (opts.format || opts.credentialTypes || opts.context) {
177
+ throw Error(`You cannot mix credential_identifier with format, credential types and/or context`);
178
+ }
179
+ return {
180
+ credential_identifier,
181
+ proof,
182
+ };
183
+ }
142
184
  const formatSelection = opts.format ?? this.credentialRequestOpts.format;
143
185
 
144
186
  if (!formatSelection) {
@@ -149,15 +191,13 @@ export class CredentialRequestClient {
149
191
  opts?.credentialTypes && (typeof opts.credentialTypes === 'string' || opts.credentialTypes.length > 0)
150
192
  ? opts.credentialTypes
151
193
  : this.credentialRequestOpts.credentialTypes;
194
+ if (!typesSelection) {
195
+ throw Error(`Credential type(s) need to be provided`);
196
+ }
152
197
  const types = Array.isArray(typesSelection) ? typesSelection : [typesSelection];
153
198
  if (types.length === 0) {
154
199
  throw Error(`Credential type(s) need to be provided`);
155
200
  }
156
- // FIXME: this is mixing up the type (as id) from v8/v9 and the types (from the vc.type) from v11
157
- else if (!this.isV11OrHigher() && types.length !== 1) {
158
- throw Error('Only a single credential type is supported for V8/V9');
159
- }
160
- const proof = await buildProof(proofInput, opts);
161
201
 
162
202
  // TODO: we should move format specific logic
163
203
  if (format === 'jwt_vc_json' || format === 'jwt_vc') {
@@ -165,6 +205,7 @@ export class CredentialRequestClient {
165
205
  types,
166
206
  format,
167
207
  proof,
208
+ ...opts.subjectIssuance,
168
209
  };
169
210
  } else if (format === 'jwt_vc_json-ld' || format === 'ldp_vc') {
170
211
  if (this.version() >= OpenId4VCIVersion.VER_1_0_12 && !opts.context) {
@@ -174,6 +215,7 @@ export class CredentialRequestClient {
174
215
  return {
175
216
  format,
176
217
  proof,
218
+ ...opts.subjectIssuance,
177
219
 
178
220
  // Ignored because v11 does not have the context value, but it is required in v12
179
221
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
@@ -181,28 +223,25 @@ export class CredentialRequestClient {
181
223
  credential_definition: {
182
224
  types,
183
225
  ...(opts.context && { '@context': opts.context }),
184
- },
226
+ } as JsonLdIssuerCredentialDefinition,
185
227
  };
186
228
  } else if (format === 'vc+sd-jwt') {
187
229
  if (types.length > 1) {
188
230
  throw Error(`Only a single credential type is supported for ${format}`);
189
231
  }
190
-
232
+ // fixme: this isn't up to the CredentialRequest that we see in the version v1_0_13
191
233
  return {
192
234
  format,
193
235
  proof,
194
236
  vct: types[0],
195
- };
237
+ ...opts.subjectIssuance,
238
+ } as CredentialRequestV1_0_13;
196
239
  }
197
240
 
198
241
  throw new Error(`Unsupported format: ${format}`);
199
242
  }
200
243
 
201
244
  private version(): OpenId4VCIVersion {
202
- return this.credentialRequestOpts?.version ?? OpenId4VCIVersion.VER_1_0_11;
203
- }
204
-
205
- private isV11OrHigher(): boolean {
206
- return this.version() >= OpenId4VCIVersion.VER_1_0_11;
245
+ return this.credentialRequestOpts?.version ?? OpenId4VCIVersion.VER_1_0_13;
207
246
  }
208
247
  }
@@ -1,12 +1,12 @@
1
1
  import {
2
2
  AccessTokenResponse,
3
- CredentialIssuerMetadata,
4
- CredentialOfferPayloadV1_0_08,
3
+ CredentialIssuerMetadataV1_0_13,
4
+ CredentialOfferPayloadV1_0_13,
5
5
  CredentialOfferRequestWithBaseUrl,
6
6
  determineSpecVersionFromOffer,
7
7
  EndpointMetadata,
8
+ ExperimentalSubjectIssuance,
8
9
  getIssuerFromCredentialOfferPayload,
9
- getTypesFromOffer,
10
10
  OID4VCICredentialFormat,
11
11
  OpenId4VCIVersion,
12
12
  UniformCredentialOfferRequest,
@@ -21,21 +21,25 @@ export class CredentialRequestClientBuilder {
21
21
  deferredCredentialEndpoint?: string;
22
22
  deferredCredentialAwait = false;
23
23
  deferredCredentialIntervalInMS = 5000;
24
- credentialTypes: string[] = [];
24
+ credentialIdentifier?: string;
25
+ credentialTypes?: string[] = [];
25
26
  format?: CredentialFormat | OID4VCICredentialFormat;
26
27
  token?: string;
27
28
  version?: OpenId4VCIVersion;
29
+ subjectIssuance?: ExperimentalSubjectIssuance;
28
30
 
29
31
  public static fromCredentialIssuer({
30
32
  credentialIssuer,
31
33
  metadata,
32
34
  version,
35
+ credentialIdentifier,
33
36
  credentialTypes,
34
37
  }: {
35
38
  credentialIssuer: string;
36
39
  metadata?: EndpointMetadata;
37
40
  version?: OpenId4VCIVersion;
38
- credentialTypes: string | string[];
41
+ credentialIdentifier?: string;
42
+ credentialTypes?: string | string[];
39
43
  }): CredentialRequestClientBuilder {
40
44
  const issuer = credentialIssuer;
41
45
  const builder = new CredentialRequestClientBuilder();
@@ -44,7 +48,12 @@ export class CredentialRequestClientBuilder {
44
48
  if (metadata?.deferred_credential_endpoint) {
45
49
  builder.withDeferredCredentialEndpoint(metadata.deferred_credential_endpoint);
46
50
  }
47
- builder.withCredentialType(credentialTypes);
51
+ if (credentialIdentifier) {
52
+ builder.withCredentialIdentifier(credentialIdentifier);
53
+ }
54
+ if (credentialTypes) {
55
+ builder.withCredentialType(credentialTypes);
56
+ }
48
57
  return builder;
49
58
  }
50
59
 
@@ -62,6 +71,9 @@ export class CredentialRequestClientBuilder {
62
71
  }): CredentialRequestClientBuilder {
63
72
  const { request, metadata } = opts;
64
73
  const version = opts.version ?? request.version ?? determineSpecVersionFromOffer(request.original_credential_offer);
74
+ if (version < OpenId4VCIVersion.VER_1_0_13) {
75
+ throw new Error('Versions below v1.0.13 (draft 13) are not supported.');
76
+ }
65
77
  const builder = new CredentialRequestClientBuilder();
66
78
  const issuer = getIssuerFromCredentialOfferPayload(request.credential_offer) ?? (metadata?.issuer as string);
67
79
  builder.withVersion(version);
@@ -69,15 +81,11 @@ export class CredentialRequestClientBuilder {
69
81
  if (metadata?.deferred_credential_endpoint) {
70
82
  builder.withDeferredCredentialEndpoint(metadata.deferred_credential_endpoint);
71
83
  }
72
-
73
- if (version <= OpenId4VCIVersion.VER_1_0_08) {
74
- //todo: This basically sets all types available during initiation. Probably the user only wants a subset. So do we want to do this?
75
- builder.withCredentialType((request.original_credential_offer as CredentialOfferPayloadV1_0_08).credential_type);
76
- } else {
77
- // todo: look whether this is correct
78
- builder.withCredentialType(getTypesFromOffer(request.credential_offer));
84
+ const ids: string[] = (request.credential_offer as CredentialOfferPayloadV1_0_13).credential_configuration_ids;
85
+ // if there's only one in the offer, we pre-select it. if not, you should provide the credentialType
86
+ if (ids.length && ids.length === 1) {
87
+ builder.withCredentialIdentifier(ids[0]);
79
88
  }
80
-
81
89
  return builder;
82
90
  }
83
91
 
@@ -95,7 +103,7 @@ export class CredentialRequestClientBuilder {
95
103
  });
96
104
  }
97
105
 
98
- public withCredentialEndpointFromMetadata(metadata: CredentialIssuerMetadata): this {
106
+ public withCredentialEndpointFromMetadata(metadata: CredentialIssuerMetadataV1_0_13): this {
99
107
  this.credentialEndpoint = metadata.credential_endpoint;
100
108
  return this;
101
109
  }
@@ -105,7 +113,7 @@ export class CredentialRequestClientBuilder {
105
113
  return this;
106
114
  }
107
115
 
108
- public withDeferredCredentialEndpointFromMetadata(metadata: CredentialIssuerMetadata): this {
116
+ public withDeferredCredentialEndpointFromMetadata(metadata: CredentialIssuerMetadataV1_0_13): this {
109
117
  this.deferredCredentialEndpoint = metadata.deferred_credential_endpoint;
110
118
  return this;
111
119
  }
@@ -121,6 +129,11 @@ export class CredentialRequestClientBuilder {
121
129
  return this;
122
130
  }
123
131
 
132
+ public withCredentialIdentifier(credentialIdentifier: string): this {
133
+ this.credentialIdentifier = credentialIdentifier;
134
+ return this;
135
+ }
136
+
124
137
  public withCredentialType(credentialTypes: string | string[]): this {
125
138
  this.credentialTypes = Array.isArray(credentialTypes) ? credentialTypes : [credentialTypes];
126
139
  return this;
@@ -131,6 +144,11 @@ export class CredentialRequestClientBuilder {
131
144
  return this;
132
145
  }
133
146
 
147
+ public withSubjectIssuance(subjectIssuance: ExperimentalSubjectIssuance): this {
148
+ this.subjectIssuance = subjectIssuance;
149
+ return this;
150
+ }
151
+
134
152
  public withToken(accessToken: string): this {
135
153
  this.token = accessToken;
136
154
  return this;