@sphereon/oid4vci-common 0.8.2-next.6 → 0.8.2-next.87

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 (104) hide show
  1. package/dist/functions/AuthorizationResponseUtil.d.ts +3 -0
  2. package/dist/functions/AuthorizationResponseUtil.d.ts.map +1 -0
  3. package/dist/functions/AuthorizationResponseUtil.js +21 -0
  4. package/dist/functions/AuthorizationResponseUtil.js.map +1 -0
  5. package/dist/functions/CredentialOfferUtil.d.ts +5 -0
  6. package/dist/functions/CredentialOfferUtil.d.ts.map +1 -1
  7. package/dist/functions/CredentialOfferUtil.js +92 -2
  8. package/dist/functions/CredentialOfferUtil.js.map +1 -1
  9. package/dist/functions/CredentialRequestUtil.d.ts +3 -2
  10. package/dist/functions/CredentialRequestUtil.d.ts.map +1 -1
  11. package/dist/functions/CredentialRequestUtil.js +41 -2
  12. package/dist/functions/CredentialRequestUtil.js.map +1 -1
  13. package/dist/functions/CredentialResponseUtil.d.ts +11 -0
  14. package/dist/functions/CredentialResponseUtil.d.ts.map +1 -0
  15. package/dist/functions/CredentialResponseUtil.js +80 -0
  16. package/dist/functions/CredentialResponseUtil.js.map +1 -0
  17. package/dist/functions/Encoding.js +1 -1
  18. package/dist/functions/Encoding.js.map +1 -1
  19. package/dist/functions/FormatUtils.d.ts +15 -0
  20. package/dist/functions/FormatUtils.d.ts.map +1 -0
  21. package/dist/functions/FormatUtils.js +44 -0
  22. package/dist/functions/FormatUtils.js.map +1 -0
  23. package/dist/functions/HttpUtils.d.ts +3 -3
  24. package/dist/functions/HttpUtils.d.ts.map +1 -1
  25. package/dist/functions/HttpUtils.js +2 -2
  26. package/dist/functions/HttpUtils.js.map +1 -1
  27. package/dist/functions/IssuerMetadataUtils.d.ts +8 -1
  28. package/dist/functions/IssuerMetadataUtils.d.ts.map +1 -1
  29. package/dist/functions/IssuerMetadataUtils.js +42 -7
  30. package/dist/functions/IssuerMetadataUtils.js.map +1 -1
  31. package/dist/functions/RandomUtils.d.ts +10 -0
  32. package/dist/functions/RandomUtils.d.ts.map +1 -0
  33. package/dist/functions/RandomUtils.js +73 -0
  34. package/dist/functions/RandomUtils.js.map +1 -0
  35. package/dist/functions/index.d.ts +8 -0
  36. package/dist/functions/index.d.ts.map +1 -1
  37. package/dist/functions/index.js +9 -0
  38. package/dist/functions/index.js.map +1 -1
  39. package/dist/functions/randomBytes.d.ts +7 -0
  40. package/dist/functions/randomBytes.d.ts.map +1 -0
  41. package/dist/functions/randomBytes.js +56 -0
  42. package/dist/functions/randomBytes.js.map +1 -0
  43. package/dist/types/Authorization.types.d.ts +64 -15
  44. package/dist/types/Authorization.types.d.ts.map +1 -1
  45. package/dist/types/Authorization.types.js +30 -9
  46. package/dist/types/Authorization.types.js.map +1 -1
  47. package/dist/types/CredentialIssuance.types.d.ts +5 -1
  48. package/dist/types/CredentialIssuance.types.d.ts.map +1 -1
  49. package/dist/types/CredentialIssuance.types.js +2 -2
  50. package/dist/types/CredentialIssuance.types.js.map +1 -1
  51. package/dist/types/Generic.types.d.ts +63 -18
  52. package/dist/types/Generic.types.d.ts.map +1 -1
  53. package/dist/types/Generic.types.js.map +1 -1
  54. package/dist/types/OpenID4VCIVersions.types.d.ts +1 -0
  55. package/dist/types/OpenID4VCIVersions.types.d.ts.map +1 -1
  56. package/dist/types/OpenID4VCIVersions.types.js +4 -3
  57. package/dist/types/OpenID4VCIVersions.types.js.map +1 -1
  58. package/dist/types/QRCode.types.js +0 -2
  59. package/dist/types/QRCode.types.js.map +1 -1
  60. package/dist/types/ServerMetadata.d.ts +2 -0
  61. package/dist/types/ServerMetadata.d.ts.map +1 -1
  62. package/dist/types/ServerMetadata.js +1 -1
  63. package/dist/types/ServerMetadata.js.map +1 -1
  64. package/dist/types/StateManager.types.d.ts +4 -4
  65. package/dist/types/StateManager.types.d.ts.map +1 -1
  66. package/dist/types/StateManager.types.js +1 -1
  67. package/dist/types/StateManager.types.js.map +1 -1
  68. package/dist/types/Token.types.d.ts +1 -1
  69. package/dist/types/Token.types.d.ts.map +1 -1
  70. package/dist/types/Token.types.js +1 -1
  71. package/dist/types/Token.types.js.map +1 -1
  72. package/dist/types/v1_0_08.types.d.ts +6 -2
  73. package/dist/types/v1_0_08.types.d.ts.map +1 -1
  74. package/dist/types/v1_0_11.types.d.ts +13 -13
  75. package/dist/types/v1_0_11.types.d.ts.map +1 -1
  76. package/dist/types/v1_0_11.types.js.map +1 -1
  77. package/dist/types/v1_0_12.types.d.ts +3 -0
  78. package/dist/types/v1_0_12.types.d.ts.map +1 -0
  79. package/dist/types/v1_0_12.types.js +3 -0
  80. package/dist/types/v1_0_12.types.js.map +1 -0
  81. package/lib/__tests__/CredentialOfferUtil.spec.ts +51 -2
  82. package/lib/__tests__/Encoding.spec.ts +15 -0
  83. package/lib/__tests__/randomBytes.spec.ts +15 -0
  84. package/lib/functions/AuthorizationResponseUtil.ts +18 -0
  85. package/lib/functions/CredentialOfferUtil.ts +89 -1
  86. package/lib/functions/CredentialRequestUtil.ts +46 -3
  87. package/lib/functions/CredentialResponseUtil.ts +90 -0
  88. package/lib/functions/Encoding.ts +1 -1
  89. package/lib/functions/FormatUtils.ts +52 -0
  90. package/lib/functions/HttpUtils.ts +6 -6
  91. package/lib/functions/IssuerMetadataUtils.ts +45 -4
  92. package/lib/functions/RandomUtils.ts +43 -0
  93. package/lib/functions/index.ts +8 -0
  94. package/lib/functions/randomBytes.js +61 -0
  95. package/lib/types/Authorization.types.ts +100 -11
  96. package/lib/types/CredentialIssuance.types.ts +9 -2
  97. package/lib/types/Generic.types.ts +89 -16
  98. package/lib/types/OpenID4VCIVersions.types.ts +2 -1
  99. package/lib/types/QRCode.types.ts +2 -2
  100. package/lib/types/ServerMetadata.ts +2 -0
  101. package/lib/types/v1_0_08.types.ts +7 -2
  102. package/lib/types/v1_0_11.types.ts +19 -16
  103. package/lib/types/v1_0_12.types.ts +4 -0
  104. package/package.json +17 -6
@@ -0,0 +1 @@
1
+ {"version":3,"file":"v1_0_12.types.d.ts","sourceRoot":"","sources":["../../lib/types/v1_0_12.types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,0BAA0B,EAAE,oCAAoC,EAAE,wBAAwB,EAAE,MAAM,iBAAiB,CAAC;AAEtJ,MAAM,MAAM,wBAAwB,GAAG,uBAAuB,GAC5D,CAAC,0BAA0B,GAAG,oCAAoC,GAAG,wBAAwB,CAAC,CAAC"}
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=v1_0_12.types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"v1_0_12.types.js","sourceRoot":"","sources":["../../lib/types/v1_0_12.types.ts"],"names":[],"mappings":""}
@@ -1,5 +1,5 @@
1
- import { determineSpecVersionFromURI } from '../functions';
2
- import { OpenId4VCIVersion } from '../types';
1
+ import { determineSpecVersionFromURI, getClientIdFromCredentialOfferPayload } from '../functions';
2
+ import { CredentialOfferPayload, OpenId4VCIVersion } from '../types';
3
3
 
4
4
  export const UNIT_TEST_TIMEOUT = 30000;
5
5
 
@@ -52,4 +52,53 @@ describe('CredentialOfferUtil should', () => {
52
52
  },
53
53
  UNIT_TEST_TIMEOUT,
54
54
  );
55
+
56
+ it('get client_id from JWT pre-auth code offer', () => {
57
+ const offer = {
58
+ credential_issuer: 'https://conformance-test.ebsi.eu/conformance/v3/issuer-mock',
59
+ credentials: [
60
+ {
61
+ format: 'jwt_vc',
62
+ types: ['VerifiableCredential', 'VerifiableAttestation', 'CTWalletCrossPreAuthorisedInTime'],
63
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
64
+ // @ts-ignore
65
+ trust_framework: { name: 'ebsi', type: 'Accreditation', uri: 'TIR link towards accreditation' },
66
+ },
67
+ ],
68
+ grants: {
69
+ 'urn:ietf:params:oauth:grant-type:pre-authorized_code': {
70
+ 'pre-authorized_code':
71
+ 'eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6IkJFTmRqRGZhdGxLai11UW92WUpsT184U2pPY1ZIdmk2SHJYS0xLRUI3UG8ifQ.eyJjbGllbnRfaWQiOiJkaWQ6a2V5OnoyZG16RDgxY2dQeDhWa2k3SmJ1dU1tRllyV1BnWW95dHlrVVozZXlxaHQxajlLYnFTWlpGakc0dFZnS2hFd0twcm9qcUxCM0MyWXBqNEg3M1N0Z2pNa1NYZzJtUXh1V0xmenVSMTJRc052Z1FXenJ6S1NmN1lSQk5yUlhLNzF2ZnExMkJieXhUTEZFWkJXZm5IcWV6QlZHUWlOTGZxZXV5d1pIZ3N0TUNjUzQ0VFhmYjIiLCJhdXRob3JpemF0aW9uX2RldGFpbHMiOlt7InR5cGUiOiJvcGVuaWRfY3JlZGVudGlhbCIsImZvcm1hdCI6Imp3dF92YyIsImxvY2F0aW9ucyI6WyJodHRwczovL2NvbmZvcm1hbmNlLXRlc3QuZWJzaS5ldS9jb25mb3JtYW5jZS92My9pc3N1ZXItbW9jayJdLCJ0eXBlcyI6WyJWZXJpZmlhYmxlQ3JlZGVudGlhbCIsIlZlcmlmaWFibGVBdHRlc3RhdGlvbiIsIkNUV2FsbGV0Q3Jvc3NQcmVBdXRob3Jpc2VkSW5UaW1lIl19XSwiaWF0IjoxNzA2MTI2NDI5LCJleHAiOjE3MDYxMjY3MjksImlzcyI6Imh0dHBzOi8vY29uZm9ybWFuY2UtdGVzdC5lYnNpLmV1L2NvbmZvcm1hbmNlL3YzL2lzc3Vlci1tb2NrIiwiYXVkIjoiaHR0cHM6Ly9jb25mb3JtYW5jZS10ZXN0LmVic2kuZXUvY29uZm9ybWFuY2UvdjMvYXV0aC1tb2NrIiwic3ViIjoiZGlkOmtleTp6MmRtekQ4MWNnUHg4VmtpN0pidXVNbUZZcldQZ1lveXR5a1VaM2V5cWh0MWo5S2JxU1paRmpHNHRWZ0toRXdLcHJvanFMQjNDMllwajRINzNTdGdqTWtTWGcybVF4dVdMZnp1UjEyUXNOdmdRV3pyektTZjdZUkJOclJYSzcxdmZxMTJCYnl4VExGRVpCV2ZuSHFlekJWR1FpTkxmcWV1eXdaSGdzdE1DY1M0NFRYZmIyIn0.Zq2o33CU4wlRNtWOIITI5qbJcuNc2c9hLwIio7OlsHBa5ZAyQR8UUU_r5EufSChe4ji15Ihrr20m5-oUiZW80A',
72
+ user_pin_required: true,
73
+ },
74
+ },
75
+ } as CredentialOfferPayload;
76
+ expect(getClientIdFromCredentialOfferPayload(offer)).toEqual(
77
+ 'did:key:z2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9KbqSZZFjG4tVgKhEwKprojqLB3C2Ypj4H73StgjMkSXg2mQxuWLfzuR12QsNvgQWzrzKSf7YRBNrRXK71vfq12BbyxTLFEZBWfnHqezBVGQiNLfqeuywZHgstMCcS44TXfb2',
78
+ );
79
+ });
80
+
81
+ it('get client_id from JWT authorization code offer', () => {
82
+ const offer = {
83
+ credential_issuer: 'https://conformance-test.ebsi.eu/conformance/v3/issuer-mock',
84
+ credentials: [
85
+ {
86
+ format: 'jwt_vc',
87
+ types: ['VerifiableCredential', 'VerifiableAttestation', 'CTWalletCrossAuthorisedInTime'],
88
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
89
+ // @ts-ignore
90
+ trust_framework: { name: 'ebsi', type: 'Accreditation', uri: 'TIR link towards accreditation' },
91
+ },
92
+ ],
93
+ grants: {
94
+ authorization_code: {
95
+ issuer_state:
96
+ 'eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6IkJFTmRqRGZhdGxLai11UW92WUpsT184U2pPY1ZIdmk2SHJYS0xLRUI3UG8ifQ.eyJjbGllbnRfaWQiOiJkaWQ6a2V5OnoyZG16RDgxY2dQeDhWa2k3SmJ1dU1tRllyV1BnWW95dHlrVVozZXlxaHQxajlLYnFTWlpGakc0dFZnS2hFd0twcm9qcUxCM0MyWXBqNEg3M1N0Z2pNa1NYZzJtUXh1V0xmenVSMTJRc052Z1FXenJ6S1NmN1lSQk5yUlhLNzF2ZnExMkJieXhUTEZFWkJXZm5IcWV6QlZHUWlOTGZxZXV5d1pIZ3N0TUNjUzQ0VFhmYjIiLCJjcmVkZW50aWFsX3R5cGVzIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIiwiVmVyaWZpYWJsZUF0dGVzdGF0aW9uIiwiQ1RXYWxsZXRDcm9zc0F1dGhvcmlzZWRJblRpbWUiXSwiaWF0IjoxNzA2MTI1ODUwLCJleHAiOjE3MDYxMjYxNTAsImlzcyI6Imh0dHBzOi8vY29uZm9ybWFuY2UtdGVzdC5lYnNpLmV1L2NvbmZvcm1hbmNlL3YzL2lzc3Vlci1tb2NrIiwiYXVkIjoiaHR0cHM6Ly9jb25mb3JtYW5jZS10ZXN0LmVic2kuZXUvY29uZm9ybWFuY2UvdjMvYXV0aC1tb2NrIiwic3ViIjoiZGlkOmtleTp6MmRtekQ4MWNnUHg4VmtpN0pidXVNbUZZcldQZ1lveXR5a1VaM2V5cWh0MWo5S2JxU1paRmpHNHRWZ0toRXdLcHJvanFMQjNDMllwajRINzNTdGdqTWtTWGcybVF4dVdMZnp1UjEyUXNOdmdRV3pyektTZjdZUkJOclJYSzcxdmZxMTJCYnl4VExGRVpCV2ZuSHFlekJWR1FpTkxmcWV1eXdaSGdzdE1DY1M0NFRYZmIyIn0.jxzbE6OdqnJfLzSfwYcgRZQURI5UcAtuYU9gPOZYyUwjWMDtVo1k4PCYH4mnjok7pfj47ik8FnaHWE7d99u-_w',
97
+ },
98
+ },
99
+ } as CredentialOfferPayload;
100
+ expect(getClientIdFromCredentialOfferPayload(offer)).toEqual(
101
+ 'did:key:z2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9KbqSZZFjG4tVgKhEwKprojqLB3C2Ypj4H73StgjMkSXg2mQxuWLfzuR12QsNvgQWzrzKSf7YRBNrRXK71vfq12BbyxTLFEZBWfnHqezBVGQiNLfqeuywZHgstMCcS44TXfb2',
102
+ );
103
+ });
55
104
  });
@@ -0,0 +1,15 @@
1
+ import { convertURIToJsonObject } from '../functions';
2
+
3
+ import { UNIT_TEST_TIMEOUT } from './CredentialOfferUtil.spec';
4
+
5
+ describe('URI to json enconding', () => {
6
+ const CREDENTIAL_OFFER_CODE = 'openid-credential-offer://code=1234-1234-1234';
7
+
8
+ it(
9
+ 'should get code from auth-code redirect URI',
10
+ async () => {
11
+ expect(convertURIToJsonObject(CREDENTIAL_OFFER_CODE)).toEqual({ code: '1234-1234-1234' });
12
+ },
13
+ UNIT_TEST_TIMEOUT,
14
+ );
15
+ });
@@ -0,0 +1,15 @@
1
+ import { randomBytes } from '../functions';
2
+
3
+ import { UNIT_TEST_TIMEOUT } from './CredentialOfferUtil.spec';
4
+
5
+ describe('randomBytes should', () => {
6
+ it(
7
+ 'generate random bytes of length 32',
8
+ () => {
9
+ const bytes = randomBytes(32);
10
+ expect(bytes).toBeDefined();
11
+ expect(bytes.length).toEqual(32);
12
+ },
13
+ UNIT_TEST_TIMEOUT,
14
+ );
15
+ });
@@ -0,0 +1,18 @@
1
+ import { AuthorizationResponse } from '../types';
2
+
3
+ import { convertURIToJsonObject } from './Encoding';
4
+
5
+ export const toAuthorizationResponsePayload = (input: AuthorizationResponse | string): AuthorizationResponse => {
6
+ let response = input;
7
+ if (typeof input === 'string') {
8
+ if (input.trim().startsWith('{') && input.trim().endsWith('}')) {
9
+ response = JSON.parse(input);
10
+ } else if (input.includes('?') && input.includes('code')) {
11
+ response = convertURIToJsonObject(input) as AuthorizationResponse;
12
+ }
13
+ }
14
+ if (response && typeof response !== 'string') {
15
+ return response;
16
+ }
17
+ throw Error(`Could not create authorization response from the input ${input}`);
18
+ };
@@ -1,4 +1,5 @@
1
1
  import Debug from 'debug';
2
+ import jwtDecode, { JwtPayload } from 'jwt-decode';
2
3
 
3
4
  import {
4
5
  AssertedUniformCredentialOffer,
@@ -68,8 +69,53 @@ export function getIssuerFromCredentialOfferPayload(request: CredentialOfferPayl
68
69
  return 'issuer' in request ? request.issuer : request['credential_issuer'];
69
70
  }
70
71
 
72
+ export const getClientIdFromCredentialOfferPayload = (credentialOffer?: CredentialOfferPayload): string | undefined => {
73
+ if (!credentialOffer) {
74
+ return;
75
+ }
76
+ if ('client_id' in credentialOffer) {
77
+ return credentialOffer.client_id;
78
+ }
79
+
80
+ const state: string | undefined = getStateFromCredentialOfferPayload(credentialOffer);
81
+ if (state && isJWT(state)) {
82
+ const decoded = jwtDecode<JwtPayload>(state, { header: false });
83
+ if ('client_id' in decoded && typeof decoded.client_id === 'string') {
84
+ return decoded.client_id;
85
+ }
86
+ }
87
+ return;
88
+ };
89
+
90
+ const isJWT = (input?: string) => {
91
+ if (!input) {
92
+ return false;
93
+ }
94
+ const noParts = input?.split('.').length;
95
+ return input?.startsWith('ey') && noParts === 3;
96
+ };
97
+ export const getStateFromCredentialOfferPayload = (credentialOffer: CredentialOfferPayload): string | undefined => {
98
+ if ('grants' in credentialOffer) {
99
+ if (credentialOffer.grants?.authorization_code) {
100
+ return credentialOffer.grants.authorization_code.issuer_state;
101
+ } else if (credentialOffer.grants?.['urn:ietf:params:oauth:grant-type:pre-authorized_code']) {
102
+ return credentialOffer.grants?.['urn:ietf:params:oauth:grant-type:pre-authorized_code']?.['pre-authorized_code'];
103
+ }
104
+ }
105
+ if ('op_state' in credentialOffer) {
106
+ // older spec versions
107
+ return credentialOffer.op_state;
108
+ } else if ('pre-authorized_code' in credentialOffer) {
109
+ return credentialOffer['pre-authorized_code'];
110
+ }
111
+
112
+ return;
113
+ };
114
+
71
115
  export function determineSpecVersionFromOffer(offer: CredentialOfferPayload | CredentialOffer): OpenId4VCIVersion {
72
- if (isCredentialOfferV1_0_11(offer)) {
116
+ if (isCredentialOfferV1_0_12(offer)) {
117
+ return OpenId4VCIVersion.VER_1_0_12;
118
+ } else if (isCredentialOfferV1_0_11(offer)) {
73
119
  return OpenId4VCIVersion.VER_1_0_11;
74
120
  } else if (isCredentialOfferV1_0_09(offer)) {
75
121
  return OpenId4VCIVersion.VER_1_0_09;
@@ -139,6 +185,21 @@ function isCredentialOfferV1_0_11(offer: CredentialOfferPayload | CredentialOffe
139
185
  return 'credential_offer_uri' in offer;
140
186
  }
141
187
 
188
+ function isCredentialOfferV1_0_12(offer: CredentialOfferPayload | CredentialOffer): boolean {
189
+ if (!offer) {
190
+ return false;
191
+ }
192
+ if ('credential_issuer' in offer && 'credentials' in offer) {
193
+ // payload
194
+ return true;
195
+ }
196
+ if ('credential_offer' in offer && offer['credential_offer']) {
197
+ // offer, so check payload
198
+ return isCredentialOfferV1_0_12(offer['credential_offer']);
199
+ }
200
+ return 'credential_offer_uri' in offer;
201
+ }
202
+
142
203
  export async function toUniformCredentialOfferRequest(
143
204
  offer: CredentialOffer,
144
205
  opts?: {
@@ -340,3 +401,30 @@ function recordVersion(currentVersion: OpenId4VCIVersion, matchingVersion: OpenI
340
401
  `Invalid param. Some keys have been used from version: ${currentVersion} version while '${key}' is used from version: ${matchingVersion}`,
341
402
  );
342
403
  }
404
+
405
+ export function getTypesFromOffer(credentialOffer: UniformCredentialOfferPayload, opts?: { filterVerifiableCredential: boolean }) {
406
+ const types = credentialOffer.credentials.reduce<string[]>((prev, curr) => {
407
+ // FIXME returning the string value is wrong (as it's an id), but just matching the current behavior of this library
408
+ // The credential_type (from draft 8) and the actual 'type' value in a VC (from draft 11) are mixed up
409
+ // Fix for this here: https://github.com/Sphereon-Opensource/OID4VCI/pull/54
410
+ if (typeof curr === 'string') {
411
+ return [...prev, curr];
412
+ } else if (curr.format === 'jwt_vc_json-ld' || curr.format === 'ldp_vc') {
413
+ return [...prev, ...curr.credential_definition.types];
414
+ } else if (curr.format === 'jwt_vc_json' || curr.format === 'jwt_vc') {
415
+ return [...prev, ...curr.types];
416
+ } else if (curr.format === 'vc+sd-jwt') {
417
+ return [...prev, curr.vct];
418
+ }
419
+
420
+ return prev;
421
+ }, []);
422
+
423
+ if (!types || types.length === 0) {
424
+ throw Error('Could not deduce types from credential offer');
425
+ }
426
+ if (opts?.filterVerifiableCredential) {
427
+ return types.filter((type) => type !== 'VerifiableCredential');
428
+ }
429
+ return types;
430
+ }
@@ -1,7 +1,24 @@
1
- import { CredentialRequestV1_0_11 } from '../types';
1
+ import { CredentialRequestV1_0_08, CredentialRequestV1_0_11, OpenId4VCIVersion, UniformCredentialRequest } from '../types';
2
+
3
+ import { getFormatForVersion } from './FormatUtils';
4
+
5
+ export function getTypesFromRequest(credentialRequest: UniformCredentialRequest, opts?: { filterVerifiableCredential: boolean }) {
6
+ let types: string[] = [];
7
+ if (credentialRequest.format === 'jwt_vc_json' || credentialRequest.format === 'jwt_vc') {
8
+ types = credentialRequest.types;
9
+ } else if (credentialRequest.format === 'jwt_vc_json-ld' || credentialRequest.format === 'ldp_vc') {
10
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
11
+ // @ts-ignore
12
+ types =
13
+ 'credential_definition' in credentialRequest && credentialRequest.credential_definition
14
+ ? // eslint-disable-next-line @typescript-eslint/ban-ts-comment
15
+ // @ts-ignore
16
+ credentialRequest.credential_definition.types
17
+ : credentialRequest.types;
18
+ } else if (credentialRequest.format === 'vc+sd-jwt') {
19
+ types = [credentialRequest.vct];
20
+ }
2
21
 
3
- export function getTypesFromRequest(credentialRequest: CredentialRequestV1_0_11, opts?: { filterVerifiableCredential: boolean }) {
4
- const types = 'types' in credentialRequest ? credentialRequest.types : credentialRequest.credential_definition.types;
5
22
  if (!types || types.length === 0) {
6
23
  throw Error('Could not deduce types from credential request');
7
24
  }
@@ -10,3 +27,29 @@ export function getTypesFromRequest(credentialRequest: CredentialRequestV1_0_11,
10
27
  }
11
28
  return types;
12
29
  }
30
+
31
+ export function getCredentialRequestForVersion(
32
+ credentialRequest: UniformCredentialRequest,
33
+ version: OpenId4VCIVersion,
34
+ ): UniformCredentialRequest | CredentialRequestV1_0_08 | CredentialRequestV1_0_11 {
35
+ if (version === OpenId4VCIVersion.VER_1_0_08) {
36
+ const draft8Format = getFormatForVersion(credentialRequest.format, version);
37
+ const types = getTypesFromRequest(credentialRequest, { filterVerifiableCredential: true });
38
+
39
+ return {
40
+ format: draft8Format,
41
+ proof: credentialRequest.proof,
42
+ type: types[0],
43
+ } satisfies CredentialRequestV1_0_08;
44
+ /* } else if (version === OpenId4VCIVersion.VER_1_0_11) {
45
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
46
+ // @ts-ignore
47
+ const { credential_definition = undefined, ...requestv11 } = credentialRequest;
48
+ return {
49
+ ...requestv11,
50
+ ...credential_definition,
51
+ } as CredentialRequestV1_0_11;*/
52
+ }
53
+
54
+ return credentialRequest;
55
+ }
@@ -0,0 +1,90 @@
1
+ import { CredentialResponse, OpenIDResponse } from '../types';
2
+
3
+ import { post } from './HttpUtils';
4
+
5
+ export function isDeferredCredentialResponse(credentialResponse: OpenIDResponse<CredentialResponse>) {
6
+ const orig = credentialResponse.successBody;
7
+ // Specs mention 202, but some implementations like EBSI return 200
8
+ return credentialResponse.origResponse.status % 200 <= 2 && !!orig && !orig.credential && (!!orig.acceptance_token || !!orig.transaction_id);
9
+ }
10
+ function assertNonFatalError(credentialResponse: OpenIDResponse<CredentialResponse>) {
11
+ if (credentialResponse.origResponse.status === 400 && credentialResponse.errorBody?.error) {
12
+ if (credentialResponse.errorBody.error === 'invalid_transaction_id' || credentialResponse.errorBody.error.includes('acceptance_token')) {
13
+ throw Error('Invalid transaction id. Probably the deferred credential request expired');
14
+ }
15
+ }
16
+ }
17
+
18
+ export function isDeferredCredentialIssuancePending(credentialResponse: OpenIDResponse<CredentialResponse>) {
19
+ if (isDeferredCredentialResponse(credentialResponse)) {
20
+ return !!credentialResponse?.successBody?.transaction_id ?? !!credentialResponse?.successBody?.acceptance_token;
21
+ }
22
+ if (credentialResponse.origResponse.status === 400 && credentialResponse.errorBody?.error) {
23
+ if (credentialResponse.errorBody.error === 'issuance_pending') {
24
+ return true;
25
+ } else if (credentialResponse.errorBody.error_description?.toLowerCase().includes('not available yet')) {
26
+ return true;
27
+ }
28
+ }
29
+ return false;
30
+ }
31
+
32
+ function sleep(ms: number) {
33
+ return new Promise((resolve) => {
34
+ setTimeout(resolve, ms);
35
+ });
36
+ }
37
+
38
+ export async function acquireDeferredCredential({
39
+ bearerToken,
40
+ transactionId,
41
+ deferredCredentialEndpoint,
42
+ deferredCredentialIntervalInMS,
43
+ deferredCredentialAwait,
44
+ }: {
45
+ bearerToken: string;
46
+ transactionId?: string;
47
+ deferredCredentialIntervalInMS?: number;
48
+ deferredCredentialAwait?: boolean;
49
+ deferredCredentialEndpoint: string;
50
+ }): Promise<OpenIDResponse<CredentialResponse>> {
51
+ let credentialResponse: OpenIDResponse<CredentialResponse> = await acquireDeferredCredentialImpl({
52
+ bearerToken,
53
+ transactionId,
54
+ deferredCredentialEndpoint,
55
+ });
56
+
57
+ const DEFAULT_SLEEP_IN_MS = 5000;
58
+ while (!credentialResponse.successBody?.credential && deferredCredentialAwait) {
59
+ assertNonFatalError(credentialResponse);
60
+ const pending = isDeferredCredentialIssuancePending(credentialResponse);
61
+ console.log(`Issuance still pending?: ${pending}`);
62
+ if (!pending) {
63
+ throw Error(`Issuance isn't pending anymore: ${credentialResponse}`);
64
+ }
65
+
66
+ await sleep(deferredCredentialIntervalInMS ?? DEFAULT_SLEEP_IN_MS);
67
+ credentialResponse = await acquireDeferredCredentialImpl({ bearerToken, transactionId, deferredCredentialEndpoint });
68
+ }
69
+ return credentialResponse;
70
+ }
71
+
72
+ async function acquireDeferredCredentialImpl({
73
+ bearerToken,
74
+ transactionId,
75
+ deferredCredentialEndpoint,
76
+ }: {
77
+ bearerToken: string;
78
+ transactionId?: string;
79
+ deferredCredentialEndpoint: string;
80
+ }): Promise<OpenIDResponse<CredentialResponse>> {
81
+ const response: OpenIDResponse<CredentialResponse> = await post(
82
+ deferredCredentialEndpoint,
83
+ JSON.stringify(transactionId ? { transaction_id: transactionId } : ''),
84
+ { bearerToken },
85
+ );
86
+ console.log(JSON.stringify(response, null, 2));
87
+ assertNonFatalError(response);
88
+
89
+ return response;
90
+ }
@@ -88,7 +88,7 @@ export function convertJsonToURI(
88
88
  * - arrayTypeProperties: properties that can show up more that once
89
89
  */
90
90
  export function convertURIToJsonObject(uri: string, opts?: DecodeURIAsJsonOpts): unknown {
91
- if (!uri || !opts?.requiredProperties?.every((p) => uri.includes(p))) {
91
+ if (!uri || (opts?.requiredProperties && !opts.requiredProperties?.every((p) => uri.includes(p)))) {
92
92
  throw new Error(BAD_PARAMS);
93
93
  }
94
94
  const uriComponents = getURIComponentsAsArray(uri, opts?.arrayTypeProperties);
@@ -0,0 +1,52 @@
1
+ import { CredentialFormat } from '@sphereon/ssi-types';
2
+
3
+ import { OID4VCICredentialFormat, OpenId4VCIVersion } from '../types';
4
+
5
+ export function isFormat<T extends { format?: OID4VCICredentialFormat }, Format extends OID4VCICredentialFormat>(
6
+ formatObject: T,
7
+ format: Format,
8
+ ): formatObject is T & { format: Format } {
9
+ return formatObject.format === format;
10
+ }
11
+
12
+ export function isNotFormat<T extends { format?: OID4VCICredentialFormat }, Format extends OID4VCICredentialFormat>(
13
+ formatObject: T,
14
+ format: Format,
15
+ ): formatObject is T & { format: Exclude<OID4VCICredentialFormat, Format> } {
16
+ return formatObject.format !== format;
17
+ }
18
+
19
+ const isUniformFormat = (format: string): format is OID4VCICredentialFormat => {
20
+ return ['jwt_vc_json', 'jwt_vc_json-ld', 'ldp_vc', 'vc+sd-jwt'].includes(format);
21
+ };
22
+
23
+ export function getUniformFormat(format: string | OID4VCICredentialFormat | CredentialFormat): OID4VCICredentialFormat {
24
+ // Already valid format
25
+ if (isUniformFormat(format)) {
26
+ return format;
27
+ }
28
+
29
+ // Older formats
30
+ if (format === 'jwt_vc' || format === 'jwt') {
31
+ return 'jwt_vc';
32
+ }
33
+ if (format === 'ldp_vc' || format === 'ldp') {
34
+ return 'ldp_vc';
35
+ }
36
+
37
+ throw new Error(`Invalid format: ${format}`);
38
+ }
39
+
40
+ export function getFormatForVersion(format: string, version: OpenId4VCIVersion) {
41
+ const uniformFormat = isUniformFormat(format) ? format : getUniformFormat(format);
42
+
43
+ if (version === OpenId4VCIVersion.VER_1_0_08) {
44
+ if (uniformFormat === 'jwt_vc_json') {
45
+ return 'jwt_vc' as const;
46
+ } else if (uniformFormat === 'ldp_vc' || uniformFormat === 'jwt_vc_json-ld') {
47
+ return 'ldp_vc' as const;
48
+ }
49
+ }
50
+
51
+ return uniformFormat;
52
+ }
@@ -8,7 +8,7 @@ const debug = Debug('sphereon:openid4vci:http');
8
8
  export const getJson = async <T>(
9
9
  URL: string,
10
10
  opts?: {
11
- bearerToken?: string;
11
+ bearerToken?: (() => Promise<string>) | string;
12
12
  contentType?: string;
13
13
  accept?: string;
14
14
  customHeaders?: Record<string, string>;
@@ -22,7 +22,7 @@ export const formPost = async <T>(
22
22
  url: string,
23
23
  body: BodyInit,
24
24
  opts?: {
25
- bearerToken?: string;
25
+ bearerToken?: (() => Promise<string>) | string;
26
26
  contentType?: string;
27
27
  accept?: string;
28
28
  customHeaders?: Record<string, string>;
@@ -36,7 +36,7 @@ export const post = async <T>(
36
36
  url: string,
37
37
  body?: BodyInit,
38
38
  opts?: {
39
- bearerToken?: string;
39
+ bearerToken?: (() => Promise<string>) | string;
40
40
  contentType?: string;
41
41
  accept?: string;
42
42
  customHeaders?: Record<string, string>;
@@ -51,7 +51,7 @@ const openIdFetch = async <T>(
51
51
  body?: BodyInit,
52
52
  opts?: {
53
53
  method?: string;
54
- bearerToken?: string;
54
+ bearerToken?: (() => Promise<string>) | string;
55
55
  contentType?: string;
56
56
  accept?: string;
57
57
  customHeaders?: Record<string, string>;
@@ -60,7 +60,7 @@ const openIdFetch = async <T>(
60
60
  ): Promise<OpenIDResponse<T>> => {
61
61
  const headers: Record<string, string> = opts?.customHeaders ?? {};
62
62
  if (opts?.bearerToken) {
63
- headers['Authorization'] = `Bearer ${opts.bearerToken}`;
63
+ headers['Authorization'] = `Bearer ${typeof opts.bearerToken === 'function' ? await opts.bearerToken() : opts.bearerToken}`;
64
64
  }
65
65
  const method = opts?.method ? opts.method : body ? 'POST' : 'GET';
66
66
  const accept = opts?.accept ? opts.accept : 'application/json';
@@ -87,7 +87,7 @@ const openIdFetch = async <T>(
87
87
 
88
88
  debug(`START fetching url: ${url}`);
89
89
  if (body) {
90
- debug(`Body:\r\n${JSON.stringify(body)}`);
90
+ debug(`Body:\r\n${typeof body == 'string' ? body : JSON.stringify(body)}`);
91
91
  }
92
92
  debug(`Headers:\r\n${JSON.stringify(payload.headers)}`);
93
93
  const origResponse = await fetch(url, payload);
@@ -1,4 +1,5 @@
1
1
  import {
2
+ AuthorizationServerMetadata,
2
3
  CredentialIssuerMetadata,
3
4
  CredentialOfferFormat,
4
5
  CredentialSupported,
@@ -43,7 +44,7 @@ export function getSupportedCredential(opts?: {
43
44
  }
44
45
  const { version, types } = opts ?? { version: OpenId4VCIVersion.VER_1_0_11 };
45
46
  if (version === OpenId4VCIVersion.VER_1_0_08 || !Array.isArray(issuerMetadata.credentials_supported)) {
46
- credentialsSupported = credentialsSupportedV8ToV11((issuerMetadata as IssuerMetadataV1_0_08).credentials_supported);
47
+ credentialsSupported = credentialsSupportedV8ToV11((issuerMetadata as IssuerMetadataV1_0_08).credentials_supported ?? {});
47
48
  } else {
48
49
  credentialsSupported = (issuerMetadata as CredentialIssuerMetadata).credentials_supported;
49
50
  }
@@ -73,7 +74,7 @@ export function getSupportedCredential(opts?: {
73
74
  if ((opts?.types && typeof opts?.types === 'string') || opts?.types?.length === 1) {
74
75
  const types = Array.isArray(opts.types) ? opts.types[0] : opts.types;
75
76
  const supported = credentialsSupported.filter(
76
- (sup) => sup.id === types || (initiationTypes && arrayEqualsIgnoreOrder(sup.types, initiationTypes)),
77
+ (sup) => sup.id === types || (initiationTypes && arrayEqualsIgnoreOrder(getTypesFromCredentialSupported(sup), initiationTypes)),
77
78
  );
78
79
  if (supported) {
79
80
  credentialSupportedOverlap.push(...supported);
@@ -86,7 +87,7 @@ export function getSupportedCredential(opts?: {
86
87
  initiationTypes.push('VerifiableCredential');
87
88
  }
88
89
  const supported = credentialsSupported.filter((sup) => {
89
- const supTypes = sup.types;
90
+ const supTypes = getTypesFromCredentialSupported(sup);
90
91
  if (!supTypes.includes('VerifiableCredential')) {
91
92
  supTypes.push('VerifiableCredential');
92
93
  }
@@ -99,6 +100,28 @@ export function getSupportedCredential(opts?: {
99
100
  return credentialSupportedOverlap;
100
101
  }
101
102
 
103
+ export function getTypesFromCredentialSupported(credentialSupported: CredentialSupported, opts?: { filterVerifiableCredential: boolean }) {
104
+ let types: string[] = [];
105
+ if (
106
+ credentialSupported.format === 'jwt_vc_json' ||
107
+ credentialSupported.format === 'jwt_vc' ||
108
+ credentialSupported.format === 'jwt_vc_json-ld' ||
109
+ credentialSupported.format === 'ldp_vc'
110
+ ) {
111
+ types = credentialSupported.types;
112
+ } else if (credentialSupported.format === 'vc+sd-jwt') {
113
+ types = [credentialSupported.vct];
114
+ }
115
+
116
+ if (!types || types.length === 0) {
117
+ throw Error('Could not deduce types from credential supported');
118
+ }
119
+ if (opts?.filterVerifiableCredential) {
120
+ return types.filter((type) => type !== 'VerifiableCredential');
121
+ }
122
+ return types;
123
+ }
124
+
102
125
  function arrayEqualsIgnoreOrder(a: string[], b: string[]) {
103
126
  if (a.length !== b.length) return false;
104
127
  const uniqueValues = new Set([...a, ...b]);
@@ -127,7 +150,7 @@ export function credentialSupportedV8ToV11(key: string, supportedV8: CredentialS
127
150
  }
128
151
  let credentialSupport: Partial<CredentialSupported> = {};
129
152
  credentialSupport = {
130
- format,
153
+ format: format as OID4VCICredentialFormat,
131
154
  display: supportedV8.display,
132
155
  ...credentialSupportBrief,
133
156
  credentialSubject: supportedV8.claims,
@@ -143,3 +166,21 @@ export function getIssuerDisplays(metadata: CredentialIssuerMetadata | IssuerMet
143
166
  ) ?? [];
144
167
  return matchedDisplays.sort((item) => (item.locale ? opts?.prefLocales.indexOf(item.locale) ?? 1 : Number.MAX_VALUE));
145
168
  }
169
+
170
+ /**
171
+ * TODO check again when WAL-617 is done to replace how we get the issuer name.
172
+ */
173
+ export function getIssuerName(
174
+ url: string,
175
+ credentialIssuerMetadata?: Partial<AuthorizationServerMetadata> & (CredentialIssuerMetadata | IssuerMetadataV1_0_08),
176
+ ): string {
177
+ if (credentialIssuerMetadata) {
178
+ const displays: Array<MetadataDisplay> = credentialIssuerMetadata ? getIssuerDisplays(credentialIssuerMetadata) : [];
179
+ for (const display of displays) {
180
+ if (display.name) {
181
+ return display.name;
182
+ }
183
+ }
184
+ }
185
+ return url;
186
+ }
@@ -0,0 +1,43 @@
1
+ import SHA from 'sha.js';
2
+ import * as u8a from 'uint8arrays';
3
+ import { SupportedEncodings } from 'uint8arrays/to-string';
4
+
5
+ import { CodeChallengeMethod } from '../types';
6
+
7
+ import { randomBytes } from './randomBytes';
8
+
9
+ export const CODE_VERIFIER_DEFAULT_LENGTH = 128;
10
+ export const NONCE_LENGTH = 32;
11
+
12
+ export const generateRandomString = (length: number, encoding?: SupportedEncodings): string => {
13
+ return u8a.toString(randomBytes(length), encoding).slice(0, length);
14
+ };
15
+
16
+ export const generateNonce = (length?: number): string => {
17
+ return generateRandomString(length ?? NONCE_LENGTH);
18
+ };
19
+ export const generateCodeVerifier = (length?: number): string => {
20
+ const codeVerifier = generateRandomString(length ?? CODE_VERIFIER_DEFAULT_LENGTH, 'base64url');
21
+ assertValidCodeVerifier(codeVerifier);
22
+ return codeVerifier;
23
+ };
24
+
25
+ export const createCodeChallenge = (codeVerifier: string, codeChallengeMethod?: CodeChallengeMethod): string => {
26
+ if (codeChallengeMethod === CodeChallengeMethod.plain) {
27
+ return codeVerifier;
28
+ } else if (!codeChallengeMethod || codeChallengeMethod === CodeChallengeMethod.S256) {
29
+ return u8a.toString(SHA('sha256').update(codeVerifier).digest(), 'base64url');
30
+ } else {
31
+ // Just a precaution if a new method would be introduced
32
+ throw Error(`code challenge method ${codeChallengeMethod} not implemented`);
33
+ }
34
+ };
35
+
36
+ export const assertValidCodeVerifier = (codeVerifier: string) => {
37
+ const length = codeVerifier.length;
38
+ if (length < 43) {
39
+ throw Error(`code_verifier should have a minimum length of 43; see rfc7636`);
40
+ } else if (length > 128) {
41
+ throw Error(`code_verifier should have a maximum length of 128; see rfc7636`);
42
+ }
43
+ };
@@ -1,4 +1,12 @@
1
+ import { randomBytes } from './randomBytes.js';
1
2
  export * from './CredentialRequestUtil';
3
+ export * from './CredentialResponseUtil';
2
4
  export * from './CredentialOfferUtil';
3
5
  export * from './Encoding';
4
6
  export * from './TypeConversionUtils';
7
+ export * from './IssuerMetadataUtils';
8
+ export * from './FormatUtils';
9
+ export * from './HttpUtils';
10
+ export * from './AuthorizationResponseUtil';
11
+ export { randomBytes };
12
+ export * from './RandomUtils';