@sphereon/oid4vci-common 0.4.1-next.285

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 (108) hide show
  1. package/LICENSE +201 -0
  2. package/dist/functions/CredentialOfferUtil.d.ts +25 -0
  3. package/dist/functions/CredentialOfferUtil.d.ts.map +1 -0
  4. package/dist/functions/CredentialOfferUtil.js +303 -0
  5. package/dist/functions/CredentialOfferUtil.js.map +1 -0
  6. package/dist/functions/CredentialRequestUtil.d.ts +3 -0
  7. package/dist/functions/CredentialRequestUtil.d.ts.map +1 -0
  8. package/dist/functions/CredentialRequestUtil.js +8 -0
  9. package/dist/functions/CredentialRequestUtil.js.map +1 -0
  10. package/dist/functions/Encoding.d.ts +20 -0
  11. package/dist/functions/Encoding.d.ts.map +1 -0
  12. package/dist/functions/Encoding.js +164 -0
  13. package/dist/functions/Encoding.js.map +1 -0
  14. package/dist/functions/HttpUtils.d.ts +33 -0
  15. package/dist/functions/HttpUtils.d.ts.map +1 -0
  16. package/dist/functions/HttpUtils.js +146 -0
  17. package/dist/functions/HttpUtils.js.map +1 -0
  18. package/dist/functions/IssuerMetadataUtils.d.ts +10 -0
  19. package/dist/functions/IssuerMetadataUtils.d.ts.map +1 -0
  20. package/dist/functions/IssuerMetadataUtils.js +89 -0
  21. package/dist/functions/IssuerMetadataUtils.js.map +1 -0
  22. package/dist/functions/TypeConversionUtils.d.ts +2 -0
  23. package/dist/functions/TypeConversionUtils.d.ts.map +1 -0
  24. package/dist/functions/TypeConversionUtils.js +8 -0
  25. package/dist/functions/TypeConversionUtils.js.map +1 -0
  26. package/dist/functions/index.d.ts +4 -0
  27. package/dist/functions/index.d.ts.map +1 -0
  28. package/dist/functions/index.js +20 -0
  29. package/dist/functions/index.js.map +1 -0
  30. package/dist/index.d.ts +3 -0
  31. package/dist/index.d.ts.map +1 -0
  32. package/dist/index.js +19 -0
  33. package/dist/index.js.map +1 -0
  34. package/dist/types/Authorization.types.d.ts +198 -0
  35. package/dist/types/Authorization.types.d.ts.map +1 -0
  36. package/dist/types/Authorization.types.js +40 -0
  37. package/dist/types/Authorization.types.js.map +1 -0
  38. package/dist/types/CredentialIssuance.types.d.ts +139 -0
  39. package/dist/types/CredentialIssuance.types.d.ts.map +1 -0
  40. package/dist/types/CredentialIssuance.types.js +10 -0
  41. package/dist/types/CredentialIssuance.types.js.map +1 -0
  42. package/dist/types/Generic.types.d.ts +156 -0
  43. package/dist/types/Generic.types.d.ts.map +1 -0
  44. package/dist/types/Generic.types.js +11 -0
  45. package/dist/types/Generic.types.js.map +1 -0
  46. package/dist/types/OAuth2ASMetadata.d.ts +41 -0
  47. package/dist/types/OAuth2ASMetadata.d.ts.map +1 -0
  48. package/dist/types/OAuth2ASMetadata.js +3 -0
  49. package/dist/types/OAuth2ASMetadata.js.map +1 -0
  50. package/dist/types/OpenID4VCIErrors.d.ts +34 -0
  51. package/dist/types/OpenID4VCIErrors.d.ts.map +1 -0
  52. package/dist/types/OpenID4VCIErrors.js +38 -0
  53. package/dist/types/OpenID4VCIErrors.js.map +1 -0
  54. package/dist/types/OpenID4VCIServerMetadata.d.ts +4 -0
  55. package/dist/types/OpenID4VCIServerMetadata.d.ts.map +1 -0
  56. package/dist/types/OpenID4VCIServerMetadata.js +17 -0
  57. package/dist/types/OpenID4VCIServerMetadata.js.map +1 -0
  58. package/dist/types/OpenID4VCIVersions.types.d.ts +11 -0
  59. package/dist/types/OpenID4VCIVersions.types.d.ts.map +1 -0
  60. package/dist/types/OpenID4VCIVersions.types.js +16 -0
  61. package/dist/types/OpenID4VCIVersions.types.js.map +1 -0
  62. package/dist/types/StateManager.types.d.ts +33 -0
  63. package/dist/types/StateManager.types.d.ts.map +1 -0
  64. package/dist/types/StateManager.types.js +3 -0
  65. package/dist/types/StateManager.types.js.map +1 -0
  66. package/dist/types/Token.types.d.ts +15 -0
  67. package/dist/types/Token.types.d.ts.map +1 -0
  68. package/dist/types/Token.types.js +30 -0
  69. package/dist/types/Token.types.js.map +1 -0
  70. package/dist/types/index.d.ts +13 -0
  71. package/dist/types/index.d.ts.map +1 -0
  72. package/dist/types/index.js +29 -0
  73. package/dist/types/index.js.map +1 -0
  74. package/dist/types/v1_0_08.types.d.ts +38 -0
  75. package/dist/types/v1_0_08.types.d.ts.map +1 -0
  76. package/dist/types/v1_0_08.types.js +3 -0
  77. package/dist/types/v1_0_08.types.js.map +1 -0
  78. package/dist/types/v1_0_09.types.d.ts +29 -0
  79. package/dist/types/v1_0_09.types.d.ts.map +1 -0
  80. package/dist/types/v1_0_09.types.js +9 -0
  81. package/dist/types/v1_0_09.types.js.map +1 -0
  82. package/dist/types/v1_0_11.types.d.ts +49 -0
  83. package/dist/types/v1_0_11.types.d.ts.map +1 -0
  84. package/dist/types/v1_0_11.types.js +9 -0
  85. package/dist/types/v1_0_11.types.js.map +1 -0
  86. package/lib/__tests__/CredentialOfferUtil.spec.ts +55 -0
  87. package/lib/functions/CredentialOfferUtil.ts +338 -0
  88. package/lib/functions/CredentialRequestUtil.ts +5 -0
  89. package/lib/functions/Encoding.ts +166 -0
  90. package/lib/functions/HttpUtils.ts +168 -0
  91. package/lib/functions/IssuerMetadataUtils.ts +101 -0
  92. package/lib/functions/TypeConversionUtils.ts +3 -0
  93. package/lib/functions/index.ts +3 -0
  94. package/lib/index.ts +2 -0
  95. package/lib/types/Authorization.types.ts +234 -0
  96. package/lib/types/CredentialIssuance.types.ts +167 -0
  97. package/lib/types/Generic.types.ts +234 -0
  98. package/lib/types/OAuth2ASMetadata.ts +49 -0
  99. package/lib/types/OpenID4VCIErrors.ts +35 -0
  100. package/lib/types/OpenID4VCIServerMetadata.ts +20 -0
  101. package/lib/types/OpenID4VCIVersions.types.ts +11 -0
  102. package/lib/types/StateManager.types.ts +45 -0
  103. package/lib/types/Token.types.ts +29 -0
  104. package/lib/types/index.ts +12 -0
  105. package/lib/types/v1_0_08.types.ts +44 -0
  106. package/lib/types/v1_0_09.types.ts +36 -0
  107. package/lib/types/v1_0_11.types.ts +68 -0
  108. package/package.json +49 -0
@@ -0,0 +1,338 @@
1
+ import {
2
+ AssertedUniformCredentialOffer,
3
+ AuthzFlowType,
4
+ CredentialOffer,
5
+ CredentialOfferPayload,
6
+ CredentialOfferPayloadV1_0_08,
7
+ CredentialOfferPayloadV1_0_09,
8
+ CredentialOfferPayloadV1_0_11,
9
+ DefaultURISchemes,
10
+ Grant,
11
+ GrantTypes,
12
+ OpenId4VCIVersion,
13
+ OpenIDResponse,
14
+ UniformCredentialOffer,
15
+ UniformCredentialOfferPayload,
16
+ UniformCredentialOfferRequest,
17
+ } from '../types';
18
+
19
+ import { getJson } from './HttpUtils';
20
+
21
+ export function determineSpecVersionFromURI(uri: string): OpenId4VCIVersion {
22
+ let version: OpenId4VCIVersion = OpenId4VCIVersion.VER_UNKNOWN;
23
+
24
+ version = determineSpecVersionFromScheme(uri, version);
25
+ version = getVersionFromURIParam(uri, version, OpenId4VCIVersion.VER_1_0_08, 'initiate_issuance');
26
+ version = getVersionFromURIParam(uri, version, OpenId4VCIVersion.VER_1_0_08, 'credential_type');
27
+ version = getVersionFromURIParam(uri, version, OpenId4VCIVersion.VER_1_0_08, 'op_state');
28
+
29
+ // version = getVersionFromURIParam(uri, version, OpenId4VCIVersion.VER_1_0_09, 'credentials');
30
+ // version = getVersionFromURIParam(uri, version, OpenId4VCIVersion.VER_1_0_09, 'initiate_issuance_uri')
31
+
32
+ version = getVersionFromURIParam(uri, version, OpenId4VCIVersion.VER_1_0_11, 'credential_offer_uri=');
33
+ version = getVersionFromURIParam(uri, version, OpenId4VCIVersion.VER_1_0_11, 'credential_issuer');
34
+ version = getVersionFromURIParam(uri, version, OpenId4VCIVersion.VER_1_0_11, 'grants');
35
+
36
+ if (version === OpenId4VCIVersion.VER_UNKNOWN) {
37
+ version = OpenId4VCIVersion.VER_1_0_11;
38
+ }
39
+ return version;
40
+ }
41
+
42
+ export function determineSpecVersionFromScheme(credentialOfferURI: string, openId4VCIVersion: OpenId4VCIVersion) {
43
+ const scheme = getScheme(credentialOfferURI);
44
+ if (credentialOfferURI.includes(DefaultURISchemes.INITIATE_ISSUANCE)) {
45
+ return recordVersion(openId4VCIVersion, OpenId4VCIVersion.VER_1_0_08, scheme);
46
+ } else if (credentialOfferURI.includes(DefaultURISchemes.CREDENTIAL_OFFER)) {
47
+ return recordVersion(openId4VCIVersion, OpenId4VCIVersion.VER_1_0_11, scheme);
48
+ } else {
49
+ return recordVersion(openId4VCIVersion, OpenId4VCIVersion.VER_UNKNOWN, scheme);
50
+ }
51
+ }
52
+
53
+ export function getScheme(credentialOfferURI: string) {
54
+ if (!credentialOfferURI || !credentialOfferURI.includes('://')) {
55
+ throw Error('Invalid credential offer URI');
56
+ }
57
+ return credentialOfferURI.split('://')[0];
58
+ }
59
+
60
+ export function getIssuerFromCredentialOfferPayload(request: CredentialOfferPayload): string | undefined {
61
+ if (!request || (!('issuer' in request) && !('credential_issuer' in request))) {
62
+ return undefined;
63
+ }
64
+ return 'issuer' in request ? request.issuer : request['credential_issuer'];
65
+ }
66
+
67
+ export function determineSpecVersionFromOffer(offer: CredentialOfferPayload | CredentialOffer): OpenId4VCIVersion {
68
+ if (isCredentialOfferV1_0_11(offer)) {
69
+ return OpenId4VCIVersion.VER_1_0_11;
70
+ } else if (isCredentialOfferV1_0_09(offer)) {
71
+ return OpenId4VCIVersion.VER_1_0_09;
72
+ } else if (isCredentialOfferV1_0_08(offer)) {
73
+ return OpenId4VCIVersion.VER_1_0_08;
74
+ }
75
+ return OpenId4VCIVersion.VER_UNKNOWN;
76
+ }
77
+
78
+ export function isCredentialOfferVersion(offer: CredentialOfferPayload | CredentialOffer, min: OpenId4VCIVersion, max?: OpenId4VCIVersion) {
79
+ if (max && max.valueOf() < min.valueOf()) {
80
+ throw Error(`Cannot have a max ${max.valueOf()} version smaller than the min version ${min.valueOf()}`);
81
+ }
82
+ const version = determineSpecVersionFromOffer(offer);
83
+ if (version.valueOf() < min.valueOf()) {
84
+ console.log(`Credential offer version (${version.valueOf()}) is lower than minimum required version (${min.valueOf()})`);
85
+ return false;
86
+ } else if (max && version.valueOf() > max.valueOf()) {
87
+ console.log(`Credential offer version (${version.valueOf()}) is higher than maximum required version (${max.valueOf()})`);
88
+ return false;
89
+ }
90
+ return true;
91
+ }
92
+
93
+ function isCredentialOfferV1_0_08(offer: CredentialOfferPayload | CredentialOffer): boolean {
94
+ if (!offer) {
95
+ return false;
96
+ }
97
+ if ('issuer' in offer && 'credential_type' in offer) {
98
+ // payload
99
+ return true;
100
+ }
101
+ if ('credential_offer' in offer && offer['credential_offer']) {
102
+ // offer, so check payload
103
+ return isCredentialOfferV1_0_08(offer['credential_offer']);
104
+ }
105
+ return false;
106
+ }
107
+
108
+ function isCredentialOfferV1_0_09(offer: CredentialOfferPayload | CredentialOffer): boolean {
109
+ if (!offer) {
110
+ return false;
111
+ }
112
+ if ('issuer' in offer && 'credentials' in offer) {
113
+ // payload
114
+ return true;
115
+ }
116
+ if ('credential_offer' in offer && offer['credential_offer']) {
117
+ // offer, so check payload
118
+ return isCredentialOfferV1_0_09(offer['credential_offer']);
119
+ }
120
+ return false;
121
+ }
122
+
123
+ function isCredentialOfferV1_0_11(offer: CredentialOfferPayload | CredentialOffer): boolean {
124
+ if (!offer) {
125
+ return false;
126
+ }
127
+ if ('credential_issuer' in offer && 'credentials' in offer) {
128
+ // payload
129
+ return true;
130
+ }
131
+ if ('credential_offer' in offer && offer['credential_offer']) {
132
+ // offer, so check payload
133
+ return isCredentialOfferV1_0_11(offer['credential_offer']);
134
+ }
135
+ return 'credential_offer_uri' in offer;
136
+ }
137
+
138
+ export async function toUniformCredentialOfferRequest(
139
+ offer: CredentialOffer,
140
+ opts?: {
141
+ resolve?: boolean;
142
+ version?: OpenId4VCIVersion;
143
+ }
144
+ ): Promise<UniformCredentialOfferRequest> {
145
+ const version = opts?.version ?? determineSpecVersionFromOffer(offer);
146
+ let originalCredentialOffer = offer.credential_offer;
147
+ let credentialOfferURI: string | undefined;
148
+ if ('credential_offer_uri' in offer && offer?.credential_offer_uri !== undefined) {
149
+ credentialOfferURI = offer.credential_offer_uri;
150
+ if (opts?.resolve || opts?.resolve === undefined) {
151
+ originalCredentialOffer = (await resolveCredentialOfferURI(credentialOfferURI)) as CredentialOfferPayloadV1_0_11;
152
+ } else if (!originalCredentialOffer) {
153
+ throw Error(`Credential offer uri (${credentialOfferURI}) found, but resolution was explicitly disabled and credential_offer was supplied`);
154
+ }
155
+ }
156
+ if (!originalCredentialOffer) {
157
+ throw Error('No credential offer available');
158
+ }
159
+ const payload = toUniformCredentialOfferPayload(originalCredentialOffer, opts);
160
+ const supportedFlows = determineFlowType(payload, version);
161
+ return {
162
+ credential_offer: payload,
163
+ original_credential_offer: originalCredentialOffer,
164
+ ...(credentialOfferURI && { credential_offer_uri: credentialOfferURI }),
165
+ supportedFlows,
166
+ version,
167
+ };
168
+ }
169
+
170
+ export function isPreAuthCode(request: UniformCredentialOfferPayload | UniformCredentialOffer) {
171
+ const payload = 'credential_offer' in request ? request.credential_offer : (request as UniformCredentialOfferPayload);
172
+ return payload?.grants?.['urn:ietf:params:oauth:grant-type:pre-authorized_code']?.['pre-authorized_code'] !== undefined;
173
+ }
174
+
175
+ export async function assertedUniformCredentialOffer(
176
+ origCredentialOffer: UniformCredentialOffer,
177
+ opts?: {
178
+ resolve?: boolean;
179
+ }
180
+ ): Promise<AssertedUniformCredentialOffer> {
181
+ const credentialOffer = JSON.parse(JSON.stringify(origCredentialOffer));
182
+ if (credentialOffer.credential_offer_uri && !credentialOffer.credential_offer) {
183
+ if (opts?.resolve === undefined || opts.resolve) {
184
+ credentialOffer.credential_offer = await resolveCredentialOfferURI(credentialOffer.credential_offer_uri);
185
+ } else {
186
+ throw Error(`No credential_offer present, but we did get a URI, but resolution was explicitly disabled`);
187
+ }
188
+ }
189
+ if (!credentialOffer.credential_offer) {
190
+ throw Error(`No credential_offer present`);
191
+ }
192
+ credentialOffer.credential_offer = await toUniformCredentialOfferPayload(credentialOffer.credential_offer, { version: credentialOffer.version });
193
+ return credentialOffer as AssertedUniformCredentialOffer;
194
+ }
195
+
196
+ export async function resolveCredentialOfferURI(uri?: string): Promise<UniformCredentialOfferPayload | undefined> {
197
+ if (!uri) {
198
+ return undefined;
199
+ }
200
+ const response = (await getJson(uri)) as OpenIDResponse<UniformCredentialOfferPayload>;
201
+ if (!response || !response.successBody) {
202
+ throw Error(`Could not get credential offer from uri: ${uri}: ${JSON.stringify(response?.errorBody)}`);
203
+ }
204
+ return response.successBody as UniformCredentialOfferPayload;
205
+ }
206
+
207
+ export function toUniformCredentialOfferPayload(
208
+ offer: CredentialOfferPayload,
209
+ opts?: {
210
+ version?: OpenId4VCIVersion;
211
+ }
212
+ ): UniformCredentialOfferPayload {
213
+ // todo: create test to check idempotence once a payload is already been made uniform.
214
+ const version = opts?.version ?? determineSpecVersionFromOffer(offer);
215
+ if (version >= OpenId4VCIVersion.VER_1_0_11) {
216
+ const orig = offer as UniformCredentialOfferPayload;
217
+ return {
218
+ ...orig,
219
+ };
220
+ }
221
+ const grants: Grant = 'grants' in offer ? (offer.grants as Grant) : {};
222
+ let offerPayloadAsV8V9 = offer as CredentialOfferPayloadV1_0_08 | CredentialOfferPayloadV1_0_09;
223
+ if (isCredentialOfferVersion(offer, OpenId4VCIVersion.VER_1_0_08, OpenId4VCIVersion.VER_1_0_09)) {
224
+ if (offerPayloadAsV8V9.op_state) {
225
+ grants.authorization_code = {
226
+ ...grants.authorization_code,
227
+ issuer_state: offerPayloadAsV8V9.op_state,
228
+ };
229
+ }
230
+ let user_pin_required = false;
231
+ if (typeof offerPayloadAsV8V9.user_pin_required === 'string') {
232
+ user_pin_required = offerPayloadAsV8V9.user_pin_required === 'true' || offerPayloadAsV8V9.user_pin_required === 'yes';
233
+ } else if (offerPayloadAsV8V9.user_pin_required !== undefined) {
234
+ user_pin_required = offerPayloadAsV8V9.user_pin_required;
235
+ }
236
+ if (offerPayloadAsV8V9['pre-authorized_code']) {
237
+ grants['urn:ietf:params:oauth:grant-type:pre-authorized_code'] = {
238
+ 'pre-authorized_code': offerPayloadAsV8V9['pre-authorized_code'],
239
+ user_pin_required,
240
+ };
241
+ }
242
+ }
243
+ const issuer = getIssuerFromCredentialOfferPayload(offer);
244
+ if (version === OpenId4VCIVersion.VER_1_0_09) {
245
+ offerPayloadAsV8V9 = offer as CredentialOfferPayloadV1_0_09;
246
+ return {
247
+ // credential_definition: getCredentialsSupported(never, offerPayloadAsV8V9.credentials).map(sup => {credentialSubject: sup.credentialSubject})[0],
248
+ credential_issuer: issuer ?? offerPayloadAsV8V9.issuer,
249
+ credentials: offerPayloadAsV8V9.credentials,
250
+ grants,
251
+ };
252
+ }
253
+ if (version === OpenId4VCIVersion.VER_1_0_08) {
254
+ offerPayloadAsV8V9 = offer as CredentialOfferPayloadV1_0_08;
255
+ return {
256
+ credential_issuer: issuer ?? offerPayloadAsV8V9.issuer,
257
+ credentials: Array.isArray(offerPayloadAsV8V9.credential_type) ? offerPayloadAsV8V9.credential_type : [offerPayloadAsV8V9.credential_type],
258
+ grants,
259
+ } as UniformCredentialOfferPayload;
260
+ }
261
+ throw Error(`Could not create uniform payload for version ${version}`);
262
+ }
263
+
264
+ export function determineFlowType(
265
+ suppliedOffer: AssertedUniformCredentialOffer | UniformCredentialOfferPayload,
266
+ version: OpenId4VCIVersion
267
+ ): AuthzFlowType[] {
268
+ const payload: UniformCredentialOfferPayload = getCredentialOfferPayload(suppliedOffer);
269
+ const supportedFlows: AuthzFlowType[] = [];
270
+ if (payload.grants?.authorization_code) {
271
+ supportedFlows.push(AuthzFlowType.AUTHORIZATION_CODE_FLOW);
272
+ }
273
+ if (payload.grants?.['urn:ietf:params:oauth:grant-type:pre-authorized_code']?.['pre-authorized_code']) {
274
+ supportedFlows.push(AuthzFlowType.PRE_AUTHORIZED_CODE_FLOW);
275
+ }
276
+ if (supportedFlows.length === 0 && version < OpenId4VCIVersion.VER_1_0_09) {
277
+ // auth flow without op_state was possible in v08. The only way to know is that the detections would result in finding nothing.
278
+ supportedFlows.push(AuthzFlowType.AUTHORIZATION_CODE_FLOW);
279
+ }
280
+ return supportedFlows;
281
+ }
282
+
283
+ export function getCredentialOfferPayload(offer: AssertedUniformCredentialOffer | UniformCredentialOfferPayload): UniformCredentialOfferPayload {
284
+ let payload: UniformCredentialOfferPayload;
285
+ if ('credential_offer' in offer && offer['credential_offer']) {
286
+ payload = offer.credential_offer;
287
+ } else {
288
+ payload = offer as UniformCredentialOfferPayload;
289
+ }
290
+ return payload;
291
+ }
292
+
293
+ export function determineGrantTypes(
294
+ offer:
295
+ | AssertedUniformCredentialOffer
296
+ | UniformCredentialOfferPayload
297
+ | ({
298
+ grants: Grant;
299
+ } & Record<never, never>)
300
+ ): GrantTypes[] {
301
+ let grants: Grant | undefined;
302
+ if ('grants' in offer && offer.grants) {
303
+ grants = offer.grants;
304
+ } else {
305
+ grants = getCredentialOfferPayload(offer as AssertedUniformCredentialOffer | UniformCredentialOfferPayload).grants;
306
+ }
307
+
308
+ const types: GrantTypes[] = [];
309
+ if (grants) {
310
+ if (grants.authorization_code) {
311
+ types.push(GrantTypes.AUTHORIZATION_CODE);
312
+ }
313
+ if (
314
+ grants['urn:ietf:params:oauth:grant-type:pre-authorized_code'] &&
315
+ grants['urn:ietf:params:oauth:grant-type:pre-authorized_code']['pre-authorized_code']
316
+ ) {
317
+ types.push(GrantTypes.PRE_AUTHORIZED_CODE);
318
+ }
319
+ }
320
+ return types;
321
+ }
322
+
323
+ function getVersionFromURIParam(credentialOfferURI: string, currentVersion: OpenId4VCIVersion, matchingVersion: OpenId4VCIVersion, param: string) {
324
+ if (credentialOfferURI.includes(param)) {
325
+ return recordVersion(currentVersion, matchingVersion, param);
326
+ }
327
+ return currentVersion;
328
+ }
329
+
330
+ function recordVersion(currentVersion: OpenId4VCIVersion, matchingVersion: OpenId4VCIVersion, key: string) {
331
+ if (currentVersion === OpenId4VCIVersion.VER_UNKNOWN || matchingVersion === currentVersion) {
332
+ return matchingVersion;
333
+ }
334
+
335
+ throw new Error(
336
+ `Invalid param. Some keys have been used from version: ${currentVersion} version while '${key}' is used from version: ${matchingVersion}`
337
+ );
338
+ }
@@ -0,0 +1,5 @@
1
+ import { CredentialRequestV1_0_11 } from '../types';
2
+
3
+ export function getIssuerStateAndPreAuthCode(credentialRequest: CredentialRequestV1_0_11) {
4
+ console.log('nothing yet: ' + credentialRequest);
5
+ }
@@ -0,0 +1,166 @@
1
+ // import * as u8a from 'uint8arrays'
2
+
3
+ import { BAD_PARAMS, DecodeURIAsJsonOpts, EncodeJsonAsURIOpts, OpenId4VCIVersion, SearchValue } from '../types';
4
+
5
+ /**
6
+ * @function encodeJsonAsURI encodes a Json object into a URI
7
+ * @param json object
8
+ * @param opts:
9
+ * - urlTypeProperties: a list of properties of which the value is a URL
10
+ * - arrayTypeProperties: a list of properties which are an array
11
+ */
12
+
13
+ // /* eslint-disable @typescript-eslint/no-explicit-any */
14
+ export function convertJsonToURI(
15
+ json:
16
+ | {
17
+ [s: string]: never;
18
+ }
19
+ | ArrayLike<never>
20
+ | string
21
+ | object,
22
+ opts?: EncodeJsonAsURIOpts
23
+ ): string {
24
+ if (typeof json === 'string') {
25
+ return convertJsonToURI(JSON.parse(json), opts);
26
+ }
27
+
28
+ const results = [];
29
+
30
+ function encodeAndStripWhitespace(key: string): string {
31
+ return encodeURIComponent(key.replace(' ', ''));
32
+ }
33
+
34
+ let components: string;
35
+ if (opts?.version && opts.version > OpenId4VCIVersion.VER_1_0_08) {
36
+ // v11 changed from encoding every param to a encoded json object with a credential_offer param key
37
+ components = encodeAndStripWhitespace(JSON.stringify(json));
38
+ } else {
39
+ for (const [key, value] of Object.entries(json)) {
40
+ if (!value) {
41
+ continue;
42
+ }
43
+ //Skip properties that are not of URL type
44
+ if (!opts?.uriTypeProperties?.includes(key)) {
45
+ results.push(`${key}=${value}`);
46
+ continue;
47
+ }
48
+ if (opts?.arrayTypeProperties?.includes(key) && Array.isArray(value)) {
49
+ results.push(value.map((v) => `${encodeAndStripWhitespace(key)}=${customEncodeURIComponent(v, /\./g)}`).join('&'));
50
+ continue;
51
+ }
52
+ const isBool = typeof value == 'boolean';
53
+ const isNumber = typeof value == 'number';
54
+ const isString = typeof value == 'string';
55
+ let encoded;
56
+ if (isBool || isNumber) {
57
+ encoded = `${encodeAndStripWhitespace(key)}=${value}`;
58
+ } else if (isString) {
59
+ encoded = `${encodeAndStripWhitespace(key)}=${customEncodeURIComponent(value, /\./g)}`;
60
+ } else {
61
+ encoded = `${encodeAndStripWhitespace(key)}=${customEncodeURIComponent(JSON.stringify(value), /\./g)}`;
62
+ }
63
+ results.push(encoded);
64
+ }
65
+ components = results.join('&');
66
+ }
67
+ if (opts?.baseUrl) {
68
+ if (opts.baseUrl.endsWith('=')) {
69
+ if (opts.param) {
70
+ throw Error('Cannot combine param with an url ending in =');
71
+ }
72
+ return `${opts.baseUrl}${components}`;
73
+ } else if (!opts.baseUrl.includes('?')) {
74
+ return `${opts.baseUrl}?${opts.param ? opts.param + '=' : ''}${components}`;
75
+ } else if (opts.baseUrl.endsWith('?')) {
76
+ return `${opts.baseUrl}${opts.param ? opts.param + '=' : ''}${components}`;
77
+ } else {
78
+ return `${opts.baseUrl}${opts.param ? '&' + opts.param : ''}=${components}`;
79
+ }
80
+ }
81
+ return components;
82
+ }
83
+
84
+ /**
85
+ * @function decodeUriAsJson decodes a URI into a Json object
86
+ * @param uri string
87
+ * @param opts:
88
+ * - requiredProperties: the required properties
89
+ * - arrayTypeProperties: properties that can show up more that once
90
+ */
91
+ export function convertURIToJsonObject(uri: string, opts?: DecodeURIAsJsonOpts): unknown {
92
+ if (!uri || !opts?.requiredProperties?.every((p) => uri.includes(p))) {
93
+ throw new Error(BAD_PARAMS);
94
+ }
95
+ const uriComponents = getURIComponentsAsArray(uri, opts?.arrayTypeProperties);
96
+ return decodeJsonProperties(uriComponents);
97
+ }
98
+
99
+ function decodeJsonProperties(parts: string[] | string[][]): unknown {
100
+ const json: { [s: string]: unknown } | ArrayLike<unknown> = {};
101
+ for (const key in parts) {
102
+ const value = parts[key];
103
+ if (!value) {
104
+ continue;
105
+ }
106
+ if (Array.isArray(value)) {
107
+ // if (value.length > 1) {
108
+ json[decodeURIComponent(key)] = value.map((v) => decodeURIComponent(v));
109
+ /*} else {
110
+ json[decodeURIComponent(key)] = decodeURIComponent(value[0]);
111
+ }*/
112
+ }
113
+ const isBool = typeof value == 'boolean';
114
+ const isNumber = typeof value == 'number';
115
+ const isString = typeof value == 'string';
116
+ if (isBool || isNumber) {
117
+ json[decodeURIComponent(key)] = value;
118
+ } else if (isString) {
119
+ const decoded = decodeURIComponent(value);
120
+ if (decoded.startsWith('{') && decoded.endsWith('}')) {
121
+ json[decodeURIComponent(key)] = JSON.parse(decoded);
122
+ } else {
123
+ json[decodeURIComponent(key)] = decoded;
124
+ }
125
+ }
126
+ }
127
+ return json;
128
+ }
129
+
130
+ /**
131
+ * @function get URI Components as Array
132
+ * @param uri string
133
+ * @param arrayTypes array of string containing array like keys
134
+ */
135
+ function getURIComponentsAsArray(uri: string, arrayTypes?: string[]): string[] | string[][] {
136
+ const parts = uri.includes('?') ? uri.split('?')[1] : uri.includes('://') ? uri.split('://')[1] : uri;
137
+ const json: string[] | string[][] = [];
138
+ const dict: string[] = parts.split('&');
139
+ for (const entry of dict) {
140
+ const pair: string[] = entry.split('=');
141
+ const p0: any = pair[0];
142
+ const p1: any = pair[1];
143
+ if (arrayTypes?.includes(p0)) {
144
+ const key = json[p0];
145
+ if (Array.isArray(key)) {
146
+ key.push(p1);
147
+ } else {
148
+ json[p0] = [p1];
149
+ }
150
+ continue;
151
+ }
152
+ json[p0] = p1;
153
+ }
154
+ return json;
155
+ }
156
+
157
+ /**
158
+ * @function customEncodeURIComponent is used to encode chars that are not encoded by default
159
+ * @param searchValue The pattern/regexp to find the char(s) to be encoded
160
+ * @param uriComponent query string
161
+ */
162
+ function customEncodeURIComponent(uriComponent: string, searchValue: SearchValue): string {
163
+ // -_.!~*'() are not escaped because they are considered safe.
164
+ // Add them to the regex as you need
165
+ return encodeURIComponent(uriComponent).replace(searchValue, (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`);
166
+ }
@@ -0,0 +1,168 @@
1
+ import { fetch } from 'cross-fetch';
2
+ import Debug from 'debug';
3
+
4
+ import { Encoding, OpenIDResponse } from '../types';
5
+
6
+ const debug = Debug('sphereon:openid4vci:http');
7
+
8
+ export const getJson = async <T>(
9
+ URL: string,
10
+ opts?: { bearerToken?: string; contentType?: string; accept?: string; customHeaders?: Record<string, string>; exceptionOnHttpErrorStatus?: boolean }
11
+ ): Promise<OpenIDResponse<T>> => {
12
+ return await openIdFetch(URL, undefined, { method: 'GET', ...opts });
13
+ };
14
+
15
+ export const formPost = async <T>(
16
+ url: string,
17
+ body: BodyInit,
18
+ opts?: { bearerToken?: string; contentType?: string; accept?: string; customHeaders?: Record<string, string>; exceptionOnHttpErrorStatus?: boolean }
19
+ ): Promise<OpenIDResponse<T>> => {
20
+ return await post(url, body, opts?.contentType ? { ...opts } : { contentType: Encoding.FORM_URL_ENCODED, ...opts });
21
+ };
22
+
23
+ export const post = async <T>(
24
+ url: string,
25
+ body?: BodyInit,
26
+ opts?: { bearerToken?: string; contentType?: string; accept?: string; customHeaders?: Record<string, string>; exceptionOnHttpErrorStatus?: boolean }
27
+ ): Promise<OpenIDResponse<T>> => {
28
+ return await openIdFetch(url, body, { method: 'POST', ...opts });
29
+ };
30
+
31
+ const openIdFetch = async <T>(
32
+ url: string,
33
+ body?: BodyInit,
34
+ opts?: {
35
+ method?: string;
36
+ bearerToken?: string;
37
+ contentType?: string;
38
+ accept?: string;
39
+ customHeaders?: Record<string, string>;
40
+ exceptionOnHttpErrorStatus?: boolean;
41
+ }
42
+ ): Promise<OpenIDResponse<T>> => {
43
+ const headers: Record<string, string> = opts?.customHeaders ?? {};
44
+ if (opts?.bearerToken) {
45
+ headers['Authorization'] = `Bearer ${opts.bearerToken}`;
46
+ }
47
+ const method = opts?.method ? opts.method : body ? 'POST' : 'GET';
48
+ const accept = opts?.accept ? opts.accept : 'application/json';
49
+ headers['Accept'] = accept;
50
+ if (headers['Content-Type']) {
51
+ if (opts?.contentType && opts.contentType !== headers['Content-Type']) {
52
+ throw Error(
53
+ `Mismatch in content-types from custom headers (${headers['Content-Type']}) and supplied content type option (${opts.contentType})`
54
+ );
55
+ }
56
+ } else {
57
+ if (opts?.contentType) {
58
+ headers['Content-Type'] = opts.contentType;
59
+ } else if (method !== 'GET') {
60
+ headers['Content-Type'] = 'application/json';
61
+ }
62
+ }
63
+
64
+ const payload: RequestInit = {
65
+ method,
66
+ headers,
67
+ body,
68
+ };
69
+
70
+ console.log(`START fetching url: ${url}`);
71
+ if (body) {
72
+ debug(`Body:\r\n${JSON.stringify(body)}`);
73
+ }
74
+ debug(`Headers:\r\n${JSON.stringify(payload.headers)}`);
75
+ const origResponse = await fetch(url, payload);
76
+ const isJSONResponse = accept === 'application/json' || origResponse.headers.get('Content-Type') === 'application/json';
77
+ const success = origResponse && origResponse.status >= 200 && origResponse.status < 400;
78
+ const responseText = await origResponse.text();
79
+ const responseBody = isJSONResponse && responseText.includes('{') ? JSON.parse(responseText) : responseText;
80
+
81
+ console.log(`${success ? 'success' : 'error'} status: ${origResponse.status}, body:\r\n${JSON.stringify(responseBody)}`);
82
+ if (!success && opts?.exceptionOnHttpErrorStatus) {
83
+ const error = JSON.stringify(responseBody);
84
+ throw new Error(error === '{}' ? '{"error": "not found"}' : error);
85
+ }
86
+ debug(`END fetching url: ${url}`);
87
+
88
+ return {
89
+ origResponse,
90
+ successBody: success ? responseBody : undefined,
91
+ errorBody: !success ? responseBody : undefined,
92
+ };
93
+ };
94
+
95
+ export const isValidURL = (url: string): boolean => {
96
+ const urlPattern = new RegExp(
97
+ '^(https?:\\/\\/)' + // validate protocol
98
+ '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|' + // validate domain name
99
+ '((localhost))|' + // validate OR localhost
100
+ '((\\d{1,3}\\.){3}\\d{1,3}))' + // validate OR ip (v4) address
101
+ '(\\:\\d+)?(\\/[-a-z\\d%_.~+:]*)*' + // validate port and path
102
+ '(\\?[;&a-z\\d%_.~+=-]*)?' + // validate query string
103
+ '(\\#[-a-z\\d_]*)?$', // validate fragment locator
104
+ 'i'
105
+ );
106
+ return urlPattern.test(url);
107
+ };
108
+
109
+ export const trimBoth = (value: string, trim: string): string => {
110
+ return trimEnd(trimStart(value, trim), trim);
111
+ };
112
+
113
+ export const trimEnd = (value: string, trim: string): string => {
114
+ return value.endsWith(trim) ? value.substring(0, value.length - trim.length) : value;
115
+ };
116
+
117
+ export const trimStart = (value: string, trim: string): string => {
118
+ return value.startsWith(trim) ? value.substring(trim.length) : value;
119
+ };
120
+
121
+ export const adjustUrl = <T extends string | URL>(
122
+ urlOrPath: T,
123
+ opts?: {
124
+ stripSlashEnd?: boolean;
125
+ stripSlashStart?: boolean;
126
+ prepend?: string;
127
+ append?: string;
128
+ }
129
+ ): T => {
130
+ let url = typeof urlOrPath === 'object' ? urlOrPath.toString() : (urlOrPath as string);
131
+ if (opts?.append) {
132
+ url = trimEnd(url, '/') + '/' + trimStart(opts.append, '/');
133
+ }
134
+ if (opts?.prepend) {
135
+ if (opts.prepend.includes('://')) {
136
+ // includes domain/hostname
137
+ if (!url.startsWith(opts.prepend)) {
138
+ url = trimEnd(opts.prepend, '/') + '/' + trimStart(url, '/');
139
+ }
140
+ } else {
141
+ // path only for prepend
142
+ let host = '';
143
+ let path = url;
144
+ if (url.includes('://')) {
145
+ // includes domain/hostname
146
+ host = new URL(url).host;
147
+ path = new URL(url).pathname;
148
+ }
149
+ if (!path.startsWith(opts.prepend)) {
150
+ if (host && host !== '') {
151
+ url = trimEnd(host, '/');
152
+ }
153
+ url += trimEnd(url, '/') + '/' + trimBoth(opts.prepend, '/') + '/' + trimStart(path, '/');
154
+ }
155
+ }
156
+ }
157
+ if (opts?.stripSlashStart) {
158
+ url = trimStart(url, '/');
159
+ }
160
+ if (opts?.stripSlashEnd) {
161
+ url = trimEnd(url, '/');
162
+ }
163
+
164
+ if (typeof urlOrPath === 'string') {
165
+ return url as T;
166
+ }
167
+ return new URL(url) as T;
168
+ };