@sphereon/oid4vci-client 0.8.2-next.6 → 0.8.2-next.88

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 (72) hide show
  1. package/README.md +9 -8
  2. package/dist/AccessTokenClient.d.ts +0 -1
  3. package/dist/AccessTokenClient.d.ts.map +1 -1
  4. package/dist/AccessTokenClient.js +11 -18
  5. package/dist/AccessTokenClient.js.map +1 -1
  6. package/dist/AuthorizationCodeClient.d.ts +9 -0
  7. package/dist/AuthorizationCodeClient.d.ts.map +1 -0
  8. package/dist/AuthorizationCodeClient.js +132 -0
  9. package/dist/AuthorizationCodeClient.js.map +1 -0
  10. package/dist/AuthorizationDetailsBuilder.d.ts.map +1 -1
  11. package/dist/AuthorizationDetailsBuilder.js.map +1 -1
  12. package/dist/CredentialOfferClient.d.ts.map +1 -1
  13. package/dist/CredentialOfferClient.js +3 -1
  14. package/dist/CredentialOfferClient.js.map +1 -1
  15. package/dist/CredentialRequestClient.d.ts +15 -0
  16. package/dist/CredentialRequestClient.d.ts.map +1 -1
  17. package/dist/CredentialRequestClient.js +91 -43
  18. package/dist/CredentialRequestClient.js.map +1 -1
  19. package/dist/CredentialRequestClientBuilder.d.ts +19 -7
  20. package/dist/CredentialRequestClientBuilder.d.ts.map +1 -1
  21. package/dist/CredentialRequestClientBuilder.js +31 -1
  22. package/dist/CredentialRequestClientBuilder.js.map +1 -1
  23. package/dist/MetadataClient.d.ts.map +1 -1
  24. package/dist/MetadataClient.js +12 -1
  25. package/dist/MetadataClient.js.map +1 -1
  26. package/dist/OpenID4VCIClient.d.ts +62 -27
  27. package/dist/OpenID4VCIClient.d.ts.map +1 -1
  28. package/dist/OpenID4VCIClient.js +255 -176
  29. package/dist/OpenID4VCIClient.js.map +1 -1
  30. package/dist/ProofOfPossessionBuilder.d.ts +3 -1
  31. package/dist/ProofOfPossessionBuilder.d.ts.map +1 -1
  32. package/dist/ProofOfPossessionBuilder.js +5 -0
  33. package/dist/ProofOfPossessionBuilder.js.map +1 -1
  34. package/dist/functions/AuthorizationUtil.d.ts +3 -0
  35. package/dist/functions/AuthorizationUtil.d.ts.map +1 -0
  36. package/dist/functions/AuthorizationUtil.js +22 -0
  37. package/dist/functions/AuthorizationUtil.js.map +1 -0
  38. package/dist/functions/ProofUtil.d.ts +2 -1
  39. package/dist/functions/ProofUtil.d.ts.map +1 -1
  40. package/dist/functions/ProofUtil.js +6 -4
  41. package/dist/functions/ProofUtil.js.map +1 -1
  42. package/dist/index.d.ts +1 -0
  43. package/dist/index.d.ts.map +1 -1
  44. package/dist/index.js +1 -0
  45. package/dist/index.js.map +1 -1
  46. package/dist/types/index.d.ts +1 -0
  47. package/dist/types/index.d.ts.map +1 -0
  48. package/dist/types/index.js +2 -0
  49. package/dist/types/index.js.map +1 -0
  50. package/lib/AccessTokenClient.ts +16 -20
  51. package/lib/AuthorizationCodeClient.ts +163 -0
  52. package/lib/AuthorizationDetailsBuilder.ts +2 -2
  53. package/lib/CredentialOfferClient.ts +4 -1
  54. package/lib/CredentialRequestClient.ts +116 -45
  55. package/lib/CredentialRequestClientBuilder.ts +53 -8
  56. package/lib/MetadataClient.ts +13 -1
  57. package/lib/OpenID4VCIClient.ts +348 -216
  58. package/lib/ProofOfPossessionBuilder.ts +8 -0
  59. package/lib/__tests__/AccessTokenClient.spec.ts +2 -0
  60. package/lib/__tests__/CredentialRequestClient.spec.ts +28 -8
  61. package/lib/__tests__/EBSIE2E.spec.test.ts +145 -0
  62. package/lib/__tests__/MetadataClient.spec.ts +4 -1
  63. package/lib/__tests__/OpenID4VCIClient.spec.ts +117 -76
  64. package/lib/__tests__/OpenID4VCIClientPAR.spec.ts +59 -49
  65. package/lib/__tests__/SdJwt.spec.ts +163 -0
  66. package/lib/__tests__/SphereonE2E.spec.test.ts +2 -2
  67. package/lib/__tests__/data/VciDataFixtures.ts +14 -13
  68. package/lib/functions/AuthorizationUtil.ts +18 -0
  69. package/lib/functions/ProofUtil.ts +18 -4
  70. package/lib/index.ts +1 -0
  71. package/lib/types/index.ts +0 -0
  72. package/package.json +8 -6
@@ -2,6 +2,7 @@ import {
2
2
  AccessTokenResponse,
3
3
  Alg,
4
4
  EndpointMetadata,
5
+ JWK,
5
6
  Jwt,
6
7
  NO_JWT_PROVIDED,
7
8
  OpenId4VCIVersion,
@@ -19,6 +20,7 @@ export class ProofOfPossessionBuilder<DIDDoc> {
19
20
  private readonly version: OpenId4VCIVersion;
20
21
 
21
22
  private kid?: string;
23
+ private jwk?: JWK;
22
24
  private clientId?: string;
23
25
  private issuer?: string;
24
26
  private jwt?: Jwt;
@@ -91,6 +93,11 @@ export class ProofOfPossessionBuilder<DIDDoc> {
91
93
  return this;
92
94
  }
93
95
 
96
+ withJWK(jwk: JWK): this {
97
+ this.jwk = jwk;
98
+ return this;
99
+ }
100
+
94
101
  withIssuer(issuer: string): this {
95
102
  this.issuer = issuer;
96
103
  return this;
@@ -182,6 +189,7 @@ export class ProofOfPossessionBuilder<DIDDoc> {
182
189
  {
183
190
  typ: this.typ ?? (this.version < OpenId4VCIVersion.VER_1_0_11 ? 'jwt' : 'openid4vci-proof+jwt'),
184
191
  kid: this.kid,
192
+ jwk: this.jwk,
185
193
  jti: this.jti,
186
194
  alg: this.alg,
187
195
  issuer: this.issuer,
@@ -1,4 +1,6 @@
1
1
  import { AccessTokenRequest, AccessTokenResponse, GrantTypes, OpenIDResponse, WellKnownEndpoints } from '@sphereon/oid4vci-common';
2
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
3
+ // @ts-ignore
2
4
  import nock from 'nock';
3
5
 
4
6
  import { AccessTokenClient } from '../AccessTokenClient';
@@ -3,6 +3,7 @@ import { KeyObject } from 'crypto';
3
3
  import {
4
4
  Alg,
5
5
  EndpointMetadata,
6
+ getCredentialRequestForVersion,
6
7
  getIssuerFromCredentialOfferPayload,
7
8
  Jwt,
8
9
  OpenId4VCIVersion,
@@ -127,7 +128,7 @@ describe('Credential Request Client ', () => {
127
128
  version: OpenId4VCIVersion.VER_1_0_08,
128
129
  });
129
130
  expect(credentialRequest.proof?.jwt?.includes(partialJWT)).toBeTruthy();
130
- expect(credentialRequest.format).toEqual('jwt_vc_json');
131
+ expect(credentialRequest.format).toEqual('jwt_vc');
131
132
  const result = await credReqClient.acquireCredentialsUsingRequest(credentialRequest);
132
133
  expect(result?.successBody?.credential).toEqual(mockedVC);
133
134
  });
@@ -149,15 +150,20 @@ describe('Credential Request Client ', () => {
149
150
  .withKid(kid)
150
151
  .withClientId('sphereon:wallet')
151
152
  .build();
152
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
153
- // @ts-ignore
154
- await expect(credReqClient.acquireCredentialsUsingRequest({ format: 'jwt_vc_json-ld', types: ['random'], proof })).rejects.toThrow(
153
+ await expect(credReqClient.acquireCredentialsUsingRequest({ format: 'jwt_vc_json', types: ['random'], proof })).rejects.toThrow(
155
154
  Error(URL_NOT_VALID),
156
155
  );
157
156
  });
158
157
  });
159
158
 
160
159
  describe('Credential Request Client with Walt.id ', () => {
160
+ beforeEach(() => {
161
+ nock.cleanAll();
162
+ });
163
+
164
+ afterEach(() => {
165
+ nock.cleanAll();
166
+ });
161
167
  it('should have correct metadata endpoints', async function () {
162
168
  nock.cleanAll();
163
169
  const WALT_IRR_URI =
@@ -178,6 +184,13 @@ describe('Credential Request Client with Walt.id ', () => {
178
184
  });
179
185
 
180
186
  describe('Credential Request Client with different issuers ', () => {
187
+ beforeEach(() => {
188
+ nock.cleanAll();
189
+ });
190
+
191
+ afterEach(() => {
192
+ nock.cleanAll();
193
+ });
181
194
  it('should create correct CredentialRequest for Spruce', async () => {
182
195
  const IRR_URI =
183
196
  'openid-initiate-issuance://?issuer=https%3A%2F%2Fngi%2Doidc4vci%2Dtest%2Espruceid%2Exyz&credential_type=OpenBadgeCredential&pre-authorized_code=eyJhbGciOiJFUzI1NiJ9.eyJjcmVkZW50aWFsX3R5cGUiOlsiT3BlbkJhZGdlQ3JlZGVudGlhbCJdLCJleHAiOiIyMDIzLTA0LTIwVDA5OjA0OjM2WiIsIm5vbmNlIjoibWFibmVpT0VSZVB3V3BuRFFweEt3UnRsVVRFRlhGUEwifQ.qOZRPN8sTv_knhp7WaWte2-aDULaPZX--2i9unF6QDQNUllqDhvxgIHMDCYHCV8O2_Gj-T2x1J84fDMajE3asg&user_pin_required=false';
@@ -194,13 +207,15 @@ describe('Credential Request Client with different issuers ', () => {
194
207
  jwt: getMockData('spruce')?.credential.request.proof.jwt as string,
195
208
  },
196
209
  credentialTypes: ['OpenBadgeCredential'],
197
- format: 'jwt_vc_json-ld',
210
+ format: 'jwt_vc',
198
211
  version: OpenId4VCIVersion.VER_1_0_08,
199
212
  });
200
- expect(credentialRequest).toEqual(getMockData('spruce')?.credential.request);
213
+ const draft8CredentialRequest = getCredentialRequestForVersion(credentialRequest, OpenId4VCIVersion.VER_1_0_08);
214
+ expect(draft8CredentialRequest).toEqual(getMockData('spruce')?.credential.request);
201
215
  });
202
216
 
203
217
  it('should create correct CredentialRequest for Walt', async () => {
218
+ nock.cleanAll();
204
219
  const IRR_URI =
205
220
  'openid-initiate-issuance://?issuer=https%3A%2F%2Fjff.walt.id%2Fissuer-api%2Fdefault%2Foidc%2F&credential_type=OpenBadgeCredential&pre-authorized_code=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIwMTc4OTNjYy04ZTY3LTQxNzItYWZlOS1lODcyYmYxNDBlNWMiLCJwcmUtYXV0aG9yaXplZCI6dHJ1ZX0.ODfq2AIhOcB61dAb3zMrXBJjPJaf53zkeHh_AssYyYA&user_pin_required=false';
206
221
  const credentialOffer = await (
@@ -264,7 +279,8 @@ describe('Credential Request Client with different issuers ', () => {
264
279
  format: 'ldp_vc',
265
280
  version: OpenId4VCIVersion.VER_1_0_08,
266
281
  });
267
- expect(credentialOffer).toEqual(getMockData('mattr')?.credential.request);
282
+ const credentialRequest = getCredentialRequestForVersion(credentialOffer, OpenId4VCIVersion.VER_1_0_08);
283
+ expect(credentialRequest).toEqual(getMockData('mattr')?.credential.request);
268
284
  });
269
285
 
270
286
  it('should create correct CredentialRequest for diwala', async () => {
@@ -286,6 +302,10 @@ describe('Credential Request Client with different issuers ', () => {
286
302
  format: 'ldp_vc',
287
303
  version: OpenId4VCIVersion.VER_1_0_08,
288
304
  });
289
- expect(credentialOffer).toEqual(getMockData('diwala')?.credential.request);
305
+
306
+ // createCredentialRequest returns uniform format in draft 11
307
+ const credentialRequest = getCredentialRequestForVersion(credentialOffer, OpenId4VCIVersion.VER_1_0_08);
308
+
309
+ expect(credentialRequest).toEqual(getMockData('diwala')?.credential.request);
290
310
  });
291
311
  });
@@ -0,0 +1,145 @@
1
+ import { Alg, Jwt } from '@sphereon/oid4vci-common';
2
+ import { toJwk } from '@sphereon/ssi-sdk-ext.key-utils';
3
+ import { CredentialMapper } from '@sphereon/ssi-types';
4
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
5
+ //@ts-ignore
6
+ import { from } from '@trust/keyto';
7
+ import { fetch } from 'cross-fetch';
8
+ import debug from 'debug';
9
+ import { base64url, importJWK, JWK, SignJWT } from 'jose';
10
+ import * as u8a from 'uint8arrays';
11
+
12
+ import { OpenID4VCIClient } from '..';
13
+
14
+ export const UNIT_TEST_TIMEOUT = 30000;
15
+
16
+ const ISSUER_URL = 'https://conformance-test.ebsi.eu/conformance/v3/issuer-mock';
17
+ const AUTH_URL = 'https://conformance-test.ebsi.eu/conformance/v3/auth-mock';
18
+
19
+ const jwk: JWK = {
20
+ alg: 'ES256',
21
+ use: 'sig',
22
+ kty: 'EC',
23
+ crv: 'P-256',
24
+ x: 'hUWYK06qFvdudydiqnEhVJhZ-73jcLtuzH8kIyNOSHE',
25
+ y: 'UZf7oUkJdo65SQekMD5ssiRclEimG2SmlsjXf3QwQJo',
26
+ d: 'zDeeo3K0Pk8dofeKcajvJYxMZ1vijx_cVDJQl1IpbAM',
27
+ };
28
+
29
+ console.log(`JWK (private/orig): ${JSON.stringify(jwk, null, 2)}`);
30
+
31
+ const privateKey = from(jwk, 'jwk').toString('blk', 'private');
32
+ const publicKey = from(jwk, 'jwk').toString('blk', 'public');
33
+ console.log(`Private key: ${privateKey}`);
34
+ console.log(`Public key: ${publicKey}`);
35
+ console.log(`Private key (b64): ${base64url.encode(u8a.fromString(privateKey, 'base16'))}`);
36
+ console.log(`JWK (private 2) ${JSON.stringify(toJwk(privateKey, 'Secp256r1', { isPrivateKey: true }))}`);
37
+ console.log(`JWK (public 2) ${JSON.stringify(toJwk(publicKey, 'Secp256r1', { isPrivateKey: false }))}`);
38
+
39
+ // const DID_METHOD = 'did:key'
40
+ const DID =
41
+ 'did:key:z2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9Kbrm54tL4pRrDDhR1QJ5RHPMXUq5MzYpZL2k35vya5eMiNxschNy9AJ74CC3MmcYiZJGZfyhWQ6qDgTVcDSHdquwPYvLDut383JbrgYdZYYSC2merTMgmQtUi3huYhaky1qE';
42
+ const DID_URL_ENCODED =
43
+ 'did%3Akey%3Az2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9Kbrm54tL4pRrDDhR1QJ5RHPMXUq5MzYpZL2k35vya5eMiNxschNy9AJ74CC3MmcYiZJGZfyhWQ6qDgTVcDSHdquwPYvLDut383JbrgYdZYYSC2merTMgmQtUi3huYhaky1qE';
44
+ // const PRIVATE_KEY_HEX = '7dd923e40f4615ac496119f7e793cc2899e99b64b88ca8603db986700089532b'
45
+
46
+ // const PUBLIC_KEY_HEX =
47
+ // '04a23cb4c83901acc2eb0f852599610de0caeac260bf8ed05e7f902eaac0f9c8d74dd4841b94d13424d32af8ec0e9976db9abfa7e3a59e10d565c5d4d901b4be63'
48
+
49
+ // pub hex: 35e03477cb29f3ac518770dccd4e26e703cd21b9741c24b038170c377b0d99d9
50
+ // priv hex: 913466d1a38d1d8c0d3c0fb0fc3b633075085a31372bbd2a8022215a88d9d1e5
51
+ // const did = `did:key:z6Mki5ZwZKN1dBQprfJTikUvkDxrHijiiQngkWviMF5gw2Hv`;
52
+ const kid = `${DID}#z2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9Kbrm54tL4pRrDDhR1QJ5RHPMXUq5MzYpZL2k35vya5eMiNxschNy9AJ74CC3MmcYiZJGZfyhWQ6qDgTVcDSHdquwPYvLDut383JbrgYdZYYSC2merTMgmQtUi3huYhaky1qE`;
53
+
54
+ // const jw = jose.importKey()
55
+
56
+ // EBSI returning a 500 in credential endpoint all of a sudden
57
+ describe.skip('OID4VCI-Client using Sphereon issuer should', () => {
58
+ async function test(credentialType: 'CTWalletCrossPreAuthorisedInTime' | 'CTWalletCrossPreAuthorisedDeferred' | 'CTWalletCrossAuthorisedInTime') {
59
+ debug.enable('*');
60
+ const offer = await getCredentialOffer(credentialType);
61
+ const client = await OpenID4VCIClient.fromURI({
62
+ uri: offer,
63
+ kid,
64
+ alg: Alg.ES256,
65
+ clientId: DID_URL_ENCODED,
66
+ });
67
+ expect(client.credentialOffer).toBeDefined();
68
+ expect(client.endpointMetadata).toBeDefined();
69
+ expect(client.getCredentialEndpoint()).toEqual(`${ISSUER_URL}/credential`);
70
+ expect(client.getAccessTokenEndpoint()).toEqual(`${AUTH_URL}/token`);
71
+
72
+ if (credentialType !== 'CTWalletCrossPreAuthorisedInTime') {
73
+ const url = await client.createAuthorizationRequestUrl({
74
+ authorizationRequest: {
75
+ redirectUri: 'openid4vc%3A',
76
+ },
77
+ });
78
+ const result = await fetch(url);
79
+ console.log(result.text());
80
+ }
81
+
82
+ const accessToken = await client.acquireAccessToken({ pin: '0891' });
83
+ // console.log(accessToken);
84
+ expect(accessToken).toMatchObject({
85
+ expires_in: 86400,
86
+ // scope: 'GuestCredential',
87
+ token_type: 'Bearer',
88
+ });
89
+
90
+ const format = 'jwt_vc';
91
+ const credentialResponse = await client.acquireCredentials({
92
+ credentialTypes: client.getCredentialOfferTypes()[0],
93
+ format,
94
+ proofCallbacks: {
95
+ signCallback: proofOfPossessionCallbackFunction,
96
+ },
97
+ kid,
98
+ deferredCredentialAwait: true,
99
+ deferredCredentialIntervalInMS: 5000,
100
+ });
101
+ console.log(JSON.stringify(credentialResponse, null, 2));
102
+ expect(credentialResponse.credential).toBeDefined();
103
+ const wrappedVC = CredentialMapper.toWrappedVerifiableCredential(credentialResponse.credential!);
104
+ expect(format.startsWith(wrappedVC.format)).toEqual(true);
105
+ }
106
+
107
+ // Current conformance tests is not stable as changes are being applied it seems
108
+
109
+ it(
110
+ 'succeed in a full flow with the client using OpenID4VCI version 11 and jwt_vc_json',
111
+ async () => {
112
+ await test('CTWalletCrossPreAuthorisedInTime');
113
+ await test('CTWalletCrossPreAuthorisedDeferred');
114
+ // await test('CTWalletCrossAuthorisedInTime');
115
+ },
116
+ UNIT_TEST_TIMEOUT,
117
+ );
118
+ });
119
+
120
+ async function getCredentialOffer(
121
+ credentialType: 'CTWalletCrossPreAuthorisedInTime' | 'CTWalletCrossAuthorisedInTime' | 'CTWalletCrossPreAuthorisedDeferred',
122
+ ): Promise<string> {
123
+ const credentialOffer = await fetch(
124
+ `https://conformance-test.ebsi.eu/conformance/v3/issuer-mock/initiate-credential-offer?credential_type=${credentialType}&client_id=${DID_URL_ENCODED}&credential_offer_endpoint=openid-credential-offer%3A%2F%2F`,
125
+ {
126
+ method: 'GET',
127
+ headers: {
128
+ Accept: 'application/json',
129
+ 'Content-Type': 'application/json',
130
+ },
131
+ },
132
+ );
133
+
134
+ return await credentialOffer.text();
135
+ }
136
+
137
+ async function proofOfPossessionCallbackFunction(args: Jwt, kid?: string): Promise<string> {
138
+ const importedJwk = await importJWK(jwk);
139
+ return await new SignJWT({ ...args.payload })
140
+ .setProtectedHeader({ ...args.header, kid: kid! })
141
+ .setIssuer(DID)
142
+ .setIssuedAt()
143
+ .setExpirationTime('2m')
144
+ .sign(importedJwk);
145
+ }
@@ -1,4 +1,6 @@
1
1
  import { getIssuerFromCredentialOfferPayload, WellKnownEndpoints } from '@sphereon/oid4vci-common';
2
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
3
+ // @ts-ignore
2
4
  import nock from 'nock';
3
5
 
4
6
  import { CredentialOfferClient } from '../CredentialOfferClient';
@@ -211,7 +213,8 @@ describe('Metadataclient with Walt-id should', () => {
211
213
  });
212
214
  });
213
215
 
214
- describe('Metadataclient with SpruceId should', () => {
216
+ // Spruce gives back 404's these days, so test is disabled
217
+ describe.skip('Metadataclient with SpruceId should', () => {
215
218
  beforeAll(() => {
216
219
  nock.cleanAll();
217
220
  });
@@ -17,6 +17,7 @@ describe('OpenID4VCIClient should', () => {
17
17
  client = await OpenID4VCIClient.fromURI({
18
18
  clientId: 'test-client',
19
19
  uri: 'openid-initiate-issuance://?issuer=https://server.example.com&credential_type=TestCredential',
20
+ createAuthorizationRequestURL: false,
20
21
  });
21
22
  });
22
23
 
@@ -24,15 +25,15 @@ describe('OpenID4VCIClient should', () => {
24
25
  nock.cleanAll();
25
26
  });
26
27
 
27
- it('should create successfully construct an authorization request url', async () => {
28
+ it('should successfully construct an authorization request url', async () => {
28
29
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
29
30
  // @ts-ignore
30
- client._endpointMetadata?.credentialIssuerMetadata.authorization_endpoint = `${MOCK_URL}v1/auth/authorize`;
31
- const url = client.createAuthorizationRequestUrl({
32
- codeChallengeMethod: CodeChallengeMethod.SHA256,
33
- codeChallenge: 'mE2kPHmIprOqtkaYmESWj35yz-PB5vzdiSu0tAZ8sqs',
34
- scope: 'openid TestCredential',
35
- redirectUri: 'http://localhost:8881/cb',
31
+ client._state.endpointMetadata?.credentialIssuerMetadata.authorization_endpoint = `${MOCK_URL}v1/auth/authorize`;
32
+ const url = await client.createAuthorizationRequestUrl({
33
+ authorizationRequest: {
34
+ scope: 'openid TestCredential',
35
+ redirectUri: 'http://localhost:8881/cb',
36
+ },
36
37
  });
37
38
 
38
39
  const urlSearchParams = new URLSearchParams(url.split('?')[1]);
@@ -41,25 +42,29 @@ describe('OpenID4VCIClient should', () => {
41
42
  expect(scope?.[0]).toBe('openid');
42
43
  });
43
44
  it('throw an error if authorization endpoint is not set in server metadata', async () => {
44
- expect(() => {
45
+ await expect(
45
46
  client.createAuthorizationRequestUrl({
46
- codeChallengeMethod: CodeChallengeMethod.SHA256,
47
- codeChallenge: 'mE2kPHmIprOqtkaYmESWj35yz-PB5vzdiSu0tAZ8sqs',
48
- scope: 'openid TestCredential',
49
- redirectUri: 'http://localhost:8881/cb',
50
- });
51
- }).toThrow(Error('Server metadata does not contain authorization endpoint'));
47
+ authorizationRequest: {
48
+ scope: 'openid TestCredential',
49
+ redirectUri: 'http://localhost:8881/cb',
50
+ },
51
+ }),
52
+ ).rejects.toThrow(Error('Server metadata does not contain authorization endpoint'));
52
53
  });
53
54
  it("injects 'openid' as the first scope if not provided", async () => {
54
55
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
55
56
  // @ts-ignore
56
- client._endpointMetadata?.credentialIssuerMetadata.authorization_endpoint = `${MOCK_URL}v1/auth/authorize`;
57
+ client._state.endpointMetadata?.credentialIssuerMetadata.authorization_endpoint = `${MOCK_URL}v1/auth/authorize`;
57
58
 
58
- const url = client.createAuthorizationRequestUrl({
59
- codeChallengeMethod: CodeChallengeMethod.SHA256,
60
- codeChallenge: 'mE2kPHmIprOqtkaYmESWj35yz-PB5vzdiSu0tAZ8sqs',
61
- scope: 'TestCredential',
62
- redirectUri: 'http://localhost:8881/cb',
59
+ const url = await client.createAuthorizationRequestUrl({
60
+ pkce: {
61
+ codeChallengeMethod: CodeChallengeMethod.S256,
62
+ codeChallenge: 'mE2kPHmIprOqtkaYmESWj35yz-PB5vzdiSu0tAZ8sqs',
63
+ },
64
+ authorizationRequest: {
65
+ scope: 'TestCredential',
66
+ redirectUri: 'http://localhost:8881/cb',
67
+ },
63
68
  });
64
69
 
65
70
  const urlSearchParams = new URLSearchParams(url.split('?')[1]);
@@ -68,94 +73,130 @@ describe('OpenID4VCIClient should', () => {
68
73
  expect(scope?.[0]).toBe('openid');
69
74
  });
70
75
  it('throw an error if no scope and no authorization_details is provided', async () => {
76
+ nock(MOCK_URL).get(/.*/).reply(200, {});
77
+ nock(MOCK_URL).get(WellKnownEndpoints.OAUTH_AS).reply(200, {});
78
+ nock(MOCK_URL).get(WellKnownEndpoints.OPENID_CONFIGURATION).reply(200, {});
79
+ // Use a client with issuer only to trigger the error
80
+ client = await OpenID4VCIClient.fromCredentialIssuer({
81
+ credentialIssuer: MOCK_URL,
82
+ createAuthorizationRequestURL: false,
83
+ retrieveServerMetadata: false,
84
+ });
85
+
71
86
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
72
87
  // @ts-ignore
73
- client._endpointMetadata?.credentialIssuerMetadata.authorization_endpoint = `${MOCK_URL}v1/auth/authorize`;
88
+ client._state.endpointMetadata = {
89
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
90
+ // @ts-ignore
91
+ credentialIssuerMetadata: {
92
+ authorization_endpoint: `${MOCK_URL}v1/auth/authorize`,
93
+ token_endpoint: `${MOCK_URL}/token`,
94
+ },
95
+ };
96
+ // client._state.endpointMetadata.credentialIssuerMetadata.authorization_endpoint = `${MOCK_URL}v1/auth/authorize`;
74
97
 
75
- expect(() => {
98
+ await expect(
76
99
  client.createAuthorizationRequestUrl({
77
- codeChallengeMethod: CodeChallengeMethod.SHA256,
78
- codeChallenge: 'mE2kPHmIprOqtkaYmESWj35yz-PB5vzdiSu0tAZ8sqs',
79
- redirectUri: 'http://localhost:8881/cb',
80
- });
81
- }).toThrow(Error('Please provide a scope or authorization_details'));
100
+ pkce: {
101
+ codeChallengeMethod: CodeChallengeMethod.S256,
102
+ codeChallenge: 'mE2kPHmIprOqtkaYmESWj35yz-PB5vzdiSu0tAZ8sqs',
103
+ },
104
+ authorizationRequest: {
105
+ redirectUri: 'http://localhost:8881/cb',
106
+ },
107
+ }),
108
+ ).rejects.toThrow(Error('Please provide a scope or authorization_details if no credential offer is present'));
82
109
  });
83
110
  it('create an authorization request url with authorization_details array property', async () => {
84
111
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
85
112
  // @ts-ignore
86
- client._endpointMetadata?.credentialIssuerMetadata.authorization_endpoint = `${MOCK_URL}v1/auth/authorize`;
113
+ client._state.endpointMetadata?.credentialIssuerMetadata.authorization_endpoint = `${MOCK_URL}v1/auth/authorize`;
87
114
 
88
- expect(
115
+ await expect(
89
116
  client.createAuthorizationRequestUrl({
90
- codeChallengeMethod: CodeChallengeMethod.SHA256,
91
- codeChallenge: 'mE2kPHmIprOqtkaYmESWj35yz-PB5vzdiSu0tAZ8sqs',
92
- authorizationDetails: [
93
- {
94
- type: 'openid_credential',
95
- format: 'ldp_vc',
96
- credential_definition: {
97
- '@context': ['https://www.w3.org/2018/credentials/v1', 'https://www.w3.org/2018/credentials/examples/v1'],
98
- types: ['VerifiableCredential', 'UniversityDegreeCredential'],
117
+ pkce: {
118
+ codeChallengeMethod: CodeChallengeMethod.S256,
119
+ codeChallenge: 'mE2kPHmIprOqtkaYmESWj35yz-PB5vzdiSu0tAZ8sqs',
120
+ },
121
+ authorizationRequest: {
122
+ authorizationDetails: [
123
+ {
124
+ type: 'openid_credential',
125
+ format: 'ldp_vc',
126
+ credential_definition: {
127
+ '@context': ['https://www.w3.org/2018/credentials/v1', 'https://www.w3.org/2018/credentials/examples/v1'],
128
+ types: ['VerifiableCredential', 'UniversityDegreeCredential'],
129
+ },
99
130
  },
100
- },
101
- {
102
- type: 'openid_credential',
103
- format: 'mso_mdoc',
104
- doctype: 'org.iso.18013.5.1.mDL',
105
- },
106
- ],
107
- redirectUri: 'http://localhost:8881/cb',
131
+ {
132
+ type: 'openid_credential',
133
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
134
+ // @ts-ignore
135
+ format: 'mso_mdoc',
136
+ doctype: 'org.iso.18013.5.1.mDL',
137
+ },
138
+ ],
139
+ redirectUri: 'http://localhost:8881/cb',
140
+ },
108
141
  }),
109
- ).toEqual(
110
- 'https://server.example.com/v1/auth/authorize?response_type=code&code_challenge_method=S256&code_challenge=mE2kPHmIprOqtkaYmESWj35yz-PB5vzdiSu0tAZ8sqs&authorization_details=%5B%7B%22type%22%3A%22openid_credential%22%2C%22format%22%3A%22ldp_vc%22%2C%22credential_definition%22%3A%7B%22%40context%22%3A%5B%22https%3A%2F%2Fwww%2Ew3%2Eorg%2F2018%2Fcredentials%2Fv1%22%2C%22https%3A%2F%2Fwww%2Ew3%2Eorg%2F2018%2Fcredentials%2Fexamples%2Fv1%22%5D%2C%22types%22%3A%5B%22VerifiableCredential%22%2C%22UniversityDegreeCredential%22%5D%7D%2C%22locations%22%3A%22https%3A%2F%2Fserver%2Eexample%2Ecom%22%7D%2C%7B%22type%22%3A%22openid_credential%22%2C%22format%22%3A%22mso_mdoc%22%2C%22doctype%22%3A%22org%2Eiso%2E18013%2E5%2E1%2EmDL%22%2C%22locations%22%3A%22https%3A%2F%2Fserver%2Eexample%2Ecom%22%7D%5D&redirect_uri=http%3A%2F%2Flocalhost%3A8881%2Fcb&scope=openid&client_id=test-client',
142
+ ).resolves.toEqual(
143
+ 'https://server.example.com/v1/auth/authorize?response_type=code&code_challenge_method=S256&code_challenge=mE2kPHmIprOqtkaYmESWj35yz-PB5vzdiSu0tAZ8sqs&authorization_details=%5B%7B%22type%22%3A%22openid_credential%22%2C%22format%22%3A%22ldp_vc%22%2C%22credential_definition%22%3A%7B%22%40context%22%3A%5B%22https%3A%2F%2Fwww%2Ew3%2Eorg%2F2018%2Fcredentials%2Fv1%22%2C%22https%3A%2F%2Fwww%2Ew3%2Eorg%2F2018%2Fcredentials%2Fexamples%2Fv1%22%5D%2C%22types%22%3A%5B%22VerifiableCredential%22%2C%22UniversityDegreeCredential%22%5D%7D%2C%22locations%22%3A%5B%22https%3A%2F%2Fserver%2Eexample%2Ecom%22%5D%7D%2C%7B%22type%22%3A%22openid_credential%22%2C%22format%22%3A%22mso_mdoc%22%2C%22doctype%22%3A%22org%2Eiso%2E18013%2E5%2E1%2EmDL%22%2C%22locations%22%3A%5B%22https%3A%2F%2Fserver%2Eexample%2Ecom%22%5D%7D%5D&redirect_uri=http%3A%2F%2Flocalhost%3A8881%2Fcb&client_id=test-client&scope=openid',
111
144
  );
112
145
  });
113
146
  it('create an authorization request url with authorization_details object property', async () => {
114
147
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
115
148
  // @ts-ignore
116
- client._endpointMetadata?.credentialIssuerMetadata.authorization_endpoint = `${MOCK_URL}v1/auth/authorize`;
149
+ client._state.endpointMetadata?.credentialIssuerMetadata.authorization_endpoint = `${MOCK_URL}v1/auth/authorize`;
117
150
 
118
- expect(
151
+ await expect(
119
152
  client.createAuthorizationRequestUrl({
120
- codeChallengeMethod: CodeChallengeMethod.SHA256,
121
- codeChallenge: 'mE2kPHmIprOqtkaYmESWj35yz-PB5vzdiSu0tAZ8sqs',
122
- authorizationDetails: {
123
- type: 'openid_credential',
124
- format: 'ldp_vc',
125
- credential_definition: {
126
- '@context': ['https://www.w3.org/2018/credentials/v1', 'https://www.w3.org/2018/credentials/examples/v1'],
127
- types: ['VerifiableCredential', 'UniversityDegreeCredential'],
153
+ pkce: {
154
+ codeChallengeMethod: CodeChallengeMethod.S256,
155
+ codeChallenge: 'mE2kPHmIprOqtkaYmESWj35yz-PB5vzdiSu0tAZ8sqs',
156
+ },
157
+ authorizationRequest: {
158
+ authorizationDetails: {
159
+ type: 'openid_credential',
160
+ format: 'ldp_vc',
161
+ credential_definition: {
162
+ '@context': ['https://www.w3.org/2018/credentials/v1', 'https://www.w3.org/2018/credentials/examples/v1'],
163
+ types: ['VerifiableCredential', 'UniversityDegreeCredential'],
164
+ },
128
165
  },
166
+ redirectUri: 'http://localhost:8881/cb',
129
167
  },
130
- redirectUri: 'http://localhost:8881/cb',
131
168
  }),
132
- ).toEqual(
133
- 'https://server.example.com/v1/auth/authorize?response_type=code&code_challenge_method=S256&code_challenge=mE2kPHmIprOqtkaYmESWj35yz-PB5vzdiSu0tAZ8sqs&authorization_details=%7B%22type%22%3A%22openid_credential%22%2C%22format%22%3A%22ldp_vc%22%2C%22credential_definition%22%3A%7B%22%40context%22%3A%5B%22https%3A%2F%2Fwww%2Ew3%2Eorg%2F2018%2Fcredentials%2Fv1%22%2C%22https%3A%2F%2Fwww%2Ew3%2Eorg%2F2018%2Fcredentials%2Fexamples%2Fv1%22%5D%2C%22types%22%3A%5B%22VerifiableCredential%22%2C%22UniversityDegreeCredential%22%5D%7D%2C%22locations%22%3A%22https%3A%2F%2Fserver%2Eexample%2Ecom%22%7D&redirect_uri=http%3A%2F%2Flocalhost%3A8881%2Fcb&scope=openid&client_id=test-client',
169
+ ).resolves.toEqual(
170
+ 'https://server.example.com/v1/auth/authorize?response_type=code&code_challenge_method=S256&code_challenge=mE2kPHmIprOqtkaYmESWj35yz-PB5vzdiSu0tAZ8sqs&authorization_details=%7B%22type%22%3A%22openid_credential%22%2C%22format%22%3A%22ldp_vc%22%2C%22credential_definition%22%3A%7B%22%40context%22%3A%5B%22https%3A%2F%2Fwww%2Ew3%2Eorg%2F2018%2Fcredentials%2Fv1%22%2C%22https%3A%2F%2Fwww%2Ew3%2Eorg%2F2018%2Fcredentials%2Fexamples%2Fv1%22%5D%2C%22types%22%3A%5B%22VerifiableCredential%22%2C%22UniversityDegreeCredential%22%5D%7D%2C%22locations%22%3A%5B%22https%3A%2F%2Fserver%2Eexample%2Ecom%22%5D%7D&redirect_uri=http%3A%2F%2Flocalhost%3A8881%2Fcb&client_id=test-client&scope=openid',
134
171
  );
135
172
  });
136
173
  it('create an authorization request url with authorization_details and scope', async () => {
137
174
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
138
175
  // @ts-ignore
139
- client._endpointMetadata.credentialIssuerMetadata.authorization_endpoint = `${MOCK_URL}v1/auth/authorize`;
176
+ client._state.endpointMetadata.credentialIssuerMetadata.authorization_endpoint = `${MOCK_URL}v1/auth/authorize`;
140
177
 
141
- expect(
178
+ await expect(
142
179
  client.createAuthorizationRequestUrl({
143
- codeChallengeMethod: CodeChallengeMethod.SHA256,
144
- codeChallenge: 'mE2kPHmIprOqtkaYmESWj35yz-PB5vzdiSu0tAZ8sqs',
145
- authorizationDetails: {
146
- type: 'openid_credential',
147
- format: 'ldp_vc',
148
- locations: ['https://test.com'],
149
- credential_definition: {
150
- '@context': ['https://www.w3.org/2018/credentials/v1', 'https://www.w3.org/2018/credentials/examples/v1'],
151
- types: ['VerifiableCredential', 'UniversityDegreeCredential'],
180
+ pkce: {
181
+ codeChallengeMethod: CodeChallengeMethod.S256,
182
+ codeChallenge: 'mE2kPHmIprOqtkaYmESWj35yz-PB5vzdiSu0tAZ8sqs',
183
+ },
184
+ authorizationRequest: {
185
+ authorizationDetails: {
186
+ type: 'openid_credential',
187
+ format: 'ldp_vc',
188
+ locations: ['https://test.com'],
189
+ credential_definition: {
190
+ '@context': ['https://www.w3.org/2018/credentials/v1', 'https://www.w3.org/2018/credentials/examples/v1'],
191
+ types: ['VerifiableCredential', 'UniversityDegreeCredential'],
192
+ },
152
193
  },
194
+ scope: 'openid',
195
+ redirectUri: 'http://localhost:8881/cb',
153
196
  },
154
- scope: 'openid',
155
- redirectUri: 'http://localhost:8881/cb',
156
197
  }),
157
- ).toEqual(
158
- 'https://server.example.com/v1/auth/authorize?response_type=code&code_challenge_method=S256&code_challenge=mE2kPHmIprOqtkaYmESWj35yz-PB5vzdiSu0tAZ8sqs&authorization_details=%7B%22type%22%3A%22openid_credential%22%2C%22format%22%3A%22ldp_vc%22%2C%22locations%22%3A%5B%22https%3A%2F%2Ftest%2Ecom%22%2C%22https%3A%2F%2Fserver%2Eexample%2Ecom%22%5D%2C%22credential_definition%22%3A%7B%22%40context%22%3A%5B%22https%3A%2F%2Fwww%2Ew3%2Eorg%2F2018%2Fcredentials%2Fv1%22%2C%22https%3A%2F%2Fwww%2Ew3%2Eorg%2F2018%2Fcredentials%2Fexamples%2Fv1%22%5D%2C%22types%22%3A%5B%22VerifiableCredential%22%2C%22UniversityDegreeCredential%22%5D%7D%7D&redirect_uri=http%3A%2F%2Flocalhost%3A8881%2Fcb&scope=openid&client_id=test-client',
198
+ ).resolves.toEqual(
199
+ 'https://server.example.com/v1/auth/authorize?response_type=code&code_challenge_method=S256&code_challenge=mE2kPHmIprOqtkaYmESWj35yz-PB5vzdiSu0tAZ8sqs&authorization_details=%7B%22type%22%3A%22openid_credential%22%2C%22format%22%3A%22ldp_vc%22%2C%22locations%22%3A%5B%22https%3A%2F%2Ftest%2Ecom%22%2C%22https%3A%2F%2Fserver%2Eexample%2Ecom%22%5D%2C%22credential_definition%22%3A%7B%22%40context%22%3A%5B%22https%3A%2F%2Fwww%2Ew3%2Eorg%2F2018%2Fcredentials%2Fv1%22%2C%22https%3A%2F%2Fwww%2Ew3%2Eorg%2F2018%2Fcredentials%2Fexamples%2Fv1%22%5D%2C%22types%22%3A%5B%22VerifiableCredential%22%2C%22UniversityDegreeCredential%22%5D%7D%7D&redirect_uri=http%3A%2F%2Flocalhost%3A8881%2Fcb&client_id=test-client&scope=openid',
159
200
  );
160
201
  });
161
202
  });