@sphereon/oid4vci-client 0.2.0 → 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 (107) hide show
  1. package/LICENSE +201 -201
  2. package/README.md +494 -371
  3. package/dist/AccessTokenClient.d.ts +30 -0
  4. package/dist/AccessTokenClient.d.ts.map +1 -0
  5. package/dist/AccessTokenClient.js +222 -0
  6. package/dist/AccessTokenClient.js.map +1 -0
  7. package/dist/AuthorizationDetailsBuilder.d.ts +11 -0
  8. package/dist/AuthorizationDetailsBuilder.d.ts.map +1 -0
  9. package/dist/AuthorizationDetailsBuilder.js +44 -0
  10. package/dist/AuthorizationDetailsBuilder.js.map +1 -0
  11. package/dist/CredentialOfferClient.d.ts +10 -0
  12. package/dist/CredentialOfferClient.d.ts.map +1 -0
  13. package/dist/CredentialOfferClient.js +101 -0
  14. package/dist/CredentialOfferClient.js.map +1 -0
  15. package/dist/CredentialRequestClient.d.ts +33 -0
  16. package/dist/CredentialRequestClient.d.ts.map +1 -0
  17. package/dist/CredentialRequestClient.js +118 -0
  18. package/dist/CredentialRequestClient.js.map +1 -0
  19. package/dist/CredentialRequestClientBuilder.d.ts +34 -0
  20. package/dist/CredentialRequestClientBuilder.d.ts.map +1 -0
  21. package/dist/CredentialRequestClientBuilder.js +87 -0
  22. package/dist/CredentialRequestClientBuilder.js.map +1 -0
  23. package/dist/{main/lib/MetadataClient.d.ts → MetadataClient.d.ts} +39 -38
  24. package/dist/MetadataClient.d.ts.map +1 -0
  25. package/dist/MetadataClient.js +148 -0
  26. package/dist/MetadataClient.js.map +1 -0
  27. package/dist/OpenID4VCIClient.d.ts +75 -0
  28. package/dist/OpenID4VCIClient.d.ts.map +1 -0
  29. package/dist/OpenID4VCIClient.js +403 -0
  30. package/dist/OpenID4VCIClient.js.map +1 -0
  31. package/dist/ProofOfPossessionBuilder.d.ts +38 -0
  32. package/dist/ProofOfPossessionBuilder.d.ts.map +1 -0
  33. package/dist/ProofOfPossessionBuilder.js +129 -0
  34. package/dist/ProofOfPossessionBuilder.js.map +1 -0
  35. package/dist/functions/ProofUtil.d.ts +29 -0
  36. package/dist/functions/ProofUtil.d.ts.map +1 -0
  37. package/dist/functions/ProofUtil.js +104 -0
  38. package/dist/functions/ProofUtil.js.map +1 -0
  39. package/dist/functions/index.d.ts +4 -0
  40. package/dist/functions/index.d.ts.map +1 -0
  41. package/dist/{main → functions}/index.js +20 -18
  42. package/dist/functions/index.js.map +1 -0
  43. package/dist/index.d.ts +9 -0
  44. package/dist/index.d.ts.map +1 -0
  45. package/dist/{main/lib/index.js → index.js} +25 -24
  46. package/dist/index.js.map +1 -0
  47. package/lib/AccessTokenClient.ts +249 -0
  48. package/lib/AuthorizationDetailsBuilder.ts +46 -0
  49. package/lib/CredentialOfferClient.ts +108 -0
  50. package/lib/CredentialRequestClient.ts +137 -0
  51. package/lib/CredentialRequestClientBuilder.ts +110 -0
  52. package/lib/MetadataClient.ts +147 -0
  53. package/lib/OpenID4VCIClient.ts +523 -0
  54. package/lib/ProofOfPossessionBuilder.ts +181 -0
  55. package/lib/__tests__/AccessTokenClient.spec.ts +225 -0
  56. package/lib/__tests__/AuthorizationDetailsBuilder.spec.ts +65 -0
  57. package/lib/__tests__/AuthzFlowType.spec.ts +39 -0
  58. package/lib/__tests__/CredentialRequestClient.spec.ts +291 -0
  59. package/lib/__tests__/CredentialRequestClientBuilder.spec.ts +121 -0
  60. package/lib/__tests__/HttpUtils.spec.ts +37 -0
  61. package/lib/__tests__/IT.spec.ts +173 -0
  62. package/lib/__tests__/IssuanceInitiation.spec.ts +48 -0
  63. package/lib/__tests__/JsonURIConversions.spec.ts +146 -0
  64. package/lib/__tests__/MetadataClient.spec.ts +203 -0
  65. package/lib/__tests__/MetadataMocks.ts +444 -0
  66. package/lib/__tests__/OpenID4VCIClient.spec.ts +166 -0
  67. package/lib/__tests__/OpenID4VCIClientPAR.spec.ts +112 -0
  68. package/lib/__tests__/ProofOfPossessionBuilder.spec.ts +110 -0
  69. package/lib/__tests__/data/VciDataFixtures.ts +744 -0
  70. package/lib/functions/ProofUtil.ts +120 -0
  71. package/lib/functions/index.ts +3 -0
  72. package/{dist/main/lib/index.d.ts → lib/index.ts} +8 -7
  73. package/package.json +68 -71
  74. package/CHANGELOG.md +0 -21
  75. package/dist/main/index.d.ts +0 -1
  76. package/dist/main/lib/AccessTokenClient.d.ts +0 -20
  77. package/dist/main/lib/AccessTokenClient.js +0 -141
  78. package/dist/main/lib/CredentialRequestClient.d.ts +0 -31
  79. package/dist/main/lib/CredentialRequestClient.js +0 -66
  80. package/dist/main/lib/CredentialRequestClientBuilder.d.ts +0 -21
  81. package/dist/main/lib/CredentialRequestClientBuilder.js +0 -56
  82. package/dist/main/lib/IssuanceInitiation.d.ts +0 -5
  83. package/dist/main/lib/IssuanceInitiation.js +0 -29
  84. package/dist/main/lib/MetadataClient.js +0 -127
  85. package/dist/main/lib/functions/Encoding.d.ts +0 -17
  86. package/dist/main/lib/functions/Encoding.js +0 -138
  87. package/dist/main/lib/functions/HttpUtils.d.ts +0 -17
  88. package/dist/main/lib/functions/HttpUtils.js +0 -133
  89. package/dist/main/lib/functions/ProofUtil.d.ts +0 -9
  90. package/dist/main/lib/functions/ProofUtil.js +0 -76
  91. package/dist/main/lib/functions/index.d.ts +0 -3
  92. package/dist/main/lib/functions/index.js +0 -20
  93. package/dist/main/lib/types/Authorization.types.d.ts +0 -66
  94. package/dist/main/lib/types/Authorization.types.js +0 -35
  95. package/dist/main/lib/types/CredentialIssuance.types.d.ts +0 -88
  96. package/dist/main/lib/types/CredentialIssuance.types.js +0 -8
  97. package/dist/main/lib/types/Generic.types.d.ts +0 -19
  98. package/dist/main/lib/types/Generic.types.js +0 -11
  99. package/dist/main/lib/types/OAuth2ASMetadata.d.ts +0 -37
  100. package/dist/main/lib/types/OAuth2ASMetadata.js +0 -3
  101. package/dist/main/lib/types/OID4VCIServerMetadata.d.ts +0 -65
  102. package/dist/main/lib/types/OID4VCIServerMetadata.js +0 -3
  103. package/dist/main/lib/types/Oidc4vciErrors.d.ts +0 -3
  104. package/dist/main/lib/types/Oidc4vciErrors.js +0 -7
  105. package/dist/main/lib/types/index.d.ts +0 -6
  106. package/dist/main/lib/types/index.js +0 -23
  107. package/dist/main/tsconfig.build.tsbuildinfo +0 -1
@@ -0,0 +1,225 @@
1
+ import { AccessTokenRequest, AccessTokenRequestOpts, AccessTokenResponse, GrantTypes, OpenIDResponse } from '@sphereon/oid4vci-common';
2
+ import nock from 'nock';
3
+
4
+ import { AccessTokenClient } from '../AccessTokenClient';
5
+
6
+ import { UNIT_TEST_TIMEOUT } from './IT.spec';
7
+ import { INITIATION_TEST } from './MetadataMocks';
8
+
9
+ const MOCK_URL = 'https://sphereonjunit20221013.com/';
10
+
11
+ describe('AccessTokenClient should', () => {
12
+ beforeEach(() => {
13
+ nock.cleanAll();
14
+ });
15
+
16
+ afterEach(() => {
17
+ nock.cleanAll();
18
+ });
19
+
20
+ it(
21
+ 'get Access Token for with pre-authorized code without resulting in errors',
22
+ async () => {
23
+ const accessTokenClient: AccessTokenClient = new AccessTokenClient();
24
+
25
+ const accessTokenRequest: AccessTokenRequest = {
26
+ grant_type: GrantTypes.PRE_AUTHORIZED_CODE,
27
+ 'pre-authorized_code': '20221013',
28
+ client_id: 'sphereon',
29
+ } as AccessTokenRequest;
30
+
31
+ const body: AccessTokenResponse = {
32
+ access_token: 'ey6546.546654.64565',
33
+ authorization_pending: false,
34
+ c_nonce: 'c_nonce2022101300',
35
+ c_nonce_expires_in: 2022101300,
36
+ interval: 2022101300,
37
+ token_type: 'Bearer',
38
+ };
39
+ nock(MOCK_URL).post(/.*/).reply(200, JSON.stringify(body));
40
+
41
+ const accessTokenResponse: OpenIDResponse<AccessTokenResponse> = await accessTokenClient.acquireAccessTokenUsingRequest({
42
+ accessTokenRequest,
43
+ asOpts: { as: MOCK_URL },
44
+ });
45
+
46
+ expect(accessTokenResponse.successBody).toEqual(body);
47
+ },
48
+ UNIT_TEST_TIMEOUT
49
+ );
50
+
51
+ it(
52
+ 'get Access Token for authorization code without resulting in errors',
53
+ async () => {
54
+ const accessTokenClient: AccessTokenClient = new AccessTokenClient();
55
+
56
+ const accessTokenRequest: AccessTokenRequest = {
57
+ client_id: 'test-client',
58
+ code_verifier: 'F0Y2OGARX2ppIERYdSVuLCV3Zi95Ci5yWzAYNU8QQC0',
59
+ code: '9mq3kwIuNZ88czRjJ2-UDxtaNXulOfxHSXo-kM01MLV',
60
+ redirect_uri: 'http://test.com/cb',
61
+ grant_type: GrantTypes.AUTHORIZATION_CODE,
62
+ } as AccessTokenRequest;
63
+
64
+ const body: AccessTokenResponse = {
65
+ access_token: '6W-kZopGNBq8e-5KvnGf2u0p0iGSxWZ7jIGV86nO1Dn',
66
+ expires_in: 3600,
67
+ scope: 'TestCredential',
68
+ token_type: 'Bearer',
69
+ };
70
+ nock(MOCK_URL).post(/.*/).reply(200, JSON.stringify(body));
71
+
72
+ const accessTokenResponse: OpenIDResponse<AccessTokenResponse> = await accessTokenClient.acquireAccessTokenUsingRequest({
73
+ accessTokenRequest,
74
+ asOpts: { as: MOCK_URL },
75
+ });
76
+
77
+ expect(accessTokenResponse.successBody).toEqual(body);
78
+ },
79
+ UNIT_TEST_TIMEOUT
80
+ );
81
+
82
+ it(
83
+ 'get error for incorrect code',
84
+ async () => {
85
+ const accessTokenClient: AccessTokenClient = new AccessTokenClient();
86
+
87
+ const accessTokenRequest: AccessTokenRequest = {
88
+ grant_type: GrantTypes.PRE_AUTHORIZED_CODE,
89
+ 'pre-authorized_code': '',
90
+ user_pin: '1.0',
91
+ } as AccessTokenRequest;
92
+
93
+ nock(MOCK_URL).post(/.*/).reply(200, {});
94
+
95
+ await expect(
96
+ accessTokenClient.acquireAccessTokenUsingRequest({
97
+ accessTokenRequest,
98
+ asOpts: { as: MOCK_URL },
99
+ })
100
+ ).rejects.toThrow('Pre-authorization must be proven by presenting the pre-authorized code. Code must be present.');
101
+ },
102
+ UNIT_TEST_TIMEOUT
103
+ );
104
+
105
+ it(
106
+ 'get error for incorrect pin',
107
+ async () => {
108
+ const accessTokenClient: AccessTokenClient = new AccessTokenClient();
109
+
110
+ const accessTokenRequest: AccessTokenRequest = {
111
+ grant_type: GrantTypes.PRE_AUTHORIZED_CODE,
112
+ 'pre-authorized_code': '20221013',
113
+ } as AccessTokenRequest;
114
+
115
+ nock(MOCK_URL).post(/.*/).reply(200, {});
116
+
117
+ await expect(
118
+ accessTokenClient.acquireAccessTokenUsingRequest({
119
+ accessTokenRequest,
120
+ isPinRequired: true,
121
+ asOpts: { as: MOCK_URL },
122
+ })
123
+ ).rejects.toThrow('A valid pin consisting of maximal 8 numeric characters must be present.');
124
+ },
125
+ UNIT_TEST_TIMEOUT
126
+ );
127
+
128
+ it(
129
+ 'get error for incorrectly long pin',
130
+ async () => {
131
+ const accessTokenClient: AccessTokenClient = new AccessTokenClient();
132
+
133
+ const accessTokenRequest: AccessTokenRequest = {
134
+ grant_type: GrantTypes.PRE_AUTHORIZED_CODE,
135
+ 'pre-authorized_code': '20221013',
136
+ client_id: 'sphereon.com',
137
+ user_pin: '123456789',
138
+ } as AccessTokenRequest;
139
+
140
+ nock(MOCK_URL).post(/.*/).reply(200, {});
141
+
142
+ await expect(
143
+ accessTokenClient.acquireAccessTokenUsingRequest({
144
+ accessTokenRequest,
145
+ isPinRequired: true,
146
+ asOpts: { as: MOCK_URL },
147
+ })
148
+ ).rejects.toThrow(Error('A valid pin consisting of maximal 8 numeric characters must be present.'));
149
+ },
150
+ UNIT_TEST_TIMEOUT
151
+ );
152
+
153
+ it(
154
+ 'get success for correct length of pin',
155
+ async () => {
156
+ const accessTokenClient: AccessTokenClient = new AccessTokenClient();
157
+
158
+ const accessTokenRequest: AccessTokenRequest = {
159
+ grant_type: GrantTypes.PRE_AUTHORIZED_CODE,
160
+ 'pre-authorized_code': '20221013',
161
+ client_id: 'sphereon.com',
162
+ user_pin: '12345678',
163
+ } as AccessTokenRequest;
164
+
165
+ const body: AccessTokenResponse = {
166
+ access_token: 'ey6546.546654.64565',
167
+ authorization_pending: false,
168
+ c_nonce: 'c_nonce2022101300',
169
+ c_nonce_expires_in: 2022101300,
170
+ interval: 2022101300,
171
+ token_type: 'Bearer',
172
+ };
173
+ nock(MOCK_URL).post(/.*/).reply(200, body);
174
+
175
+ const response = await accessTokenClient.acquireAccessTokenUsingRequest({
176
+ accessTokenRequest,
177
+ isPinRequired: true,
178
+ asOpts: { as: MOCK_URL },
179
+ });
180
+ expect(response.successBody).toEqual(body);
181
+ },
182
+ UNIT_TEST_TIMEOUT
183
+ );
184
+
185
+ it('get error for using a pin when not requested', async () => {
186
+ const accessTokenClient: AccessTokenClient = new AccessTokenClient();
187
+
188
+ nock(MOCK_URL).post(/.*/).reply(200, {});
189
+
190
+ await expect(() =>
191
+ accessTokenClient.acquireAccessToken({
192
+ credentialOffer: INITIATION_TEST,
193
+ pin: '1234',
194
+ })
195
+ ).rejects.toThrow(Error('Cannot set a pin, when the pin is not required.'));
196
+ });
197
+
198
+ it('get error if code_verifier is present when flow type is pre-authorized', async () => {
199
+ const accessTokenClient: AccessTokenClient = new AccessTokenClient();
200
+
201
+ nock(MOCK_URL).post(/.*/).reply(200, {});
202
+
203
+ const requestOpts: AccessTokenRequestOpts = {
204
+ credentialOffer: INITIATION_TEST,
205
+ pin: undefined,
206
+ codeVerifier: 'RylyWGQ-dzpObnEcoMBDIH9cTAwZXk1wYzktKxsOFgA',
207
+ code: 'LWCt225yj7gzT2cWeMP4hXj4B4oIYkEiGs4T6pfez91',
208
+ redirectUri: 'http://example.com/cb',
209
+ };
210
+
211
+ await expect(() => accessTokenClient.acquireAccessToken(requestOpts)).rejects.toThrow(
212
+ Error('Cannot pass a code_verifier when flow type is pre-authorized')
213
+ );
214
+ });
215
+
216
+ it('get error if no as, issuer and metadata values are present', async () => {
217
+ await expect(() =>
218
+ AccessTokenClient.determineTokenURL({
219
+ asOpts: undefined,
220
+ issuerOpts: undefined,
221
+ metadata: undefined,
222
+ })
223
+ ).toThrow(Error('Cannot determine token URL if no issuer, metadata and no Authorization Server values are present'));
224
+ });
225
+ });
@@ -0,0 +1,65 @@
1
+ import { OID4VCICredentialFormat } from '@sphereon/oid4vci-common';
2
+
3
+ import { AuthorizationDetailsBuilder } from '../AuthorizationDetailsBuilder';
4
+
5
+ describe('AuthorizationDetailsBuilder test', () => {
6
+ it('should create AuthorizationDetails object from arrays', () => {
7
+ const actual = new AuthorizationDetailsBuilder()
8
+ .withFormats('jwt_vc' as OID4VCICredentialFormat)
9
+ .withLocations(['test1', 'test2'])
10
+ .withType('openid_credential')
11
+ .buildJwtVcJson();
12
+ expect(actual).toEqual({
13
+ type: 'openid_credential',
14
+ format: 'jwt_vc',
15
+ locations: ['test1', 'test2'],
16
+ });
17
+ });
18
+ it('should create AuthorizationDetails object from single objects', () => {
19
+ const actual = new AuthorizationDetailsBuilder()
20
+ .withFormats('jwt_vc' as OID4VCICredentialFormat)
21
+ .withLocations(['test1'])
22
+ .withType('openid_credential')
23
+ .buildJwtVcJson();
24
+ expect(actual).toEqual({
25
+ type: 'openid_credential',
26
+ format: 'jwt_vc',
27
+ locations: ['test1'],
28
+ });
29
+ });
30
+ it('should create AuthorizationDetails object if locations is missing', () => {
31
+ const actual = new AuthorizationDetailsBuilder()
32
+ .withFormats('jwt_vc' as OID4VCICredentialFormat)
33
+ .withType('openid_credential')
34
+ .buildJwtVcJson();
35
+ expect(actual).toEqual({
36
+ type: 'openid_credential',
37
+ format: 'jwt_vc',
38
+ });
39
+ });
40
+ it('should fail if type is missing', () => {
41
+ expect(() => {
42
+ new AuthorizationDetailsBuilder()
43
+ .withFormats('jwt_vc' as OID4VCICredentialFormat)
44
+ .withLocations(['test1'])
45
+ .buildJwtVcJson();
46
+ }).toThrow(Error('Type and format are required properties'));
47
+ });
48
+ it('should fail if format is missing', () => {
49
+ expect(() => {
50
+ new AuthorizationDetailsBuilder().withType('openid_credential').withLocations(['test1']).buildJwtVcJson();
51
+ }).toThrow(Error('Type and format are required properties'));
52
+ });
53
+ it('should be able to add random field to the object', () => {
54
+ const actual = new AuthorizationDetailsBuilder()
55
+ .withFormats('jwt_vc' as OID4VCICredentialFormat)
56
+ .withType('openid_credential')
57
+ .buildJwtVcJson();
58
+ actual['random'] = 'test';
59
+ expect(actual).toEqual({
60
+ type: 'openid_credential',
61
+ format: 'jwt_vc',
62
+ random: 'test',
63
+ });
64
+ });
65
+ });
@@ -0,0 +1,39 @@
1
+ import { AuthzFlowType, CredentialOfferPayload } from '@sphereon/oid4vci-common';
2
+
3
+ //todo: this file is just testing v9, we probably want to add v11 tests here as well
4
+ describe('Authorization Flow Type determination', () => {
5
+ it('should return authorization code flow type with a single credential_type', () => {
6
+ expect(
7
+ AuthzFlowType.valueOf({
8
+ issuer: 'test',
9
+ credential_type: 'test',
10
+ } as CredentialOfferPayload)
11
+ ).toEqual(AuthzFlowType.AUTHORIZATION_CODE_FLOW);
12
+ });
13
+ it('should return authorization code flow type with a credential_type array', () => {
14
+ expect(
15
+ AuthzFlowType.valueOf({
16
+ issuer: 'test',
17
+ credential_type: ['test', 'test1'],
18
+ } as CredentialOfferPayload)
19
+ ).toEqual(AuthzFlowType.AUTHORIZATION_CODE_FLOW);
20
+ });
21
+ it('should return pre-authorized code flow with a single credential_type', () => {
22
+ expect(
23
+ AuthzFlowType.valueOf({
24
+ issuer: 'test',
25
+ credential_type: 'test',
26
+ 'pre-authorized_code': 'test',
27
+ } as CredentialOfferPayload)
28
+ ).toEqual(AuthzFlowType.PRE_AUTHORIZED_CODE_FLOW);
29
+ });
30
+ it('should return pre-authorized code flow with a credential_type array', () => {
31
+ expect(
32
+ AuthzFlowType.valueOf({
33
+ issuer: 'test',
34
+ credential_type: ['test', 'test1'],
35
+ 'pre-authorized_code': 'test',
36
+ } as CredentialOfferPayload)
37
+ ).toEqual(AuthzFlowType.PRE_AUTHORIZED_CODE_FLOW);
38
+ });
39
+ });
@@ -0,0 +1,291 @@
1
+ import { KeyObject } from 'crypto';
2
+
3
+ import {
4
+ Alg,
5
+ EndpointMetadata,
6
+ getIssuerFromCredentialOfferPayload,
7
+ Jwt,
8
+ OpenId4VCIVersion,
9
+ ProofOfPossession,
10
+ URL_NOT_VALID,
11
+ WellKnownEndpoints,
12
+ } from '@sphereon/oid4vci-common';
13
+ import * as jose from 'jose';
14
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
15
+ // @ts-ignore
16
+ import nock from 'nock';
17
+
18
+ import { CredentialRequestClientBuilder, MetadataClient, ProofOfPossessionBuilder } from '..';
19
+ import { CredentialOfferClient } from '../CredentialOfferClient';
20
+
21
+ import { IDENTIPROOF_ISSUER_URL, IDENTIPROOF_OID4VCI_METADATA, INITIATION_TEST, WALT_OID4VCI_METADATA } from './MetadataMocks';
22
+ import { getMockData } from './data/VciDataFixtures';
23
+
24
+ const partialJWT = 'eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJkaWQ6ZXhhbXBsZTplYmZlYjFmN';
25
+
26
+ const jwt: Jwt = {
27
+ header: { alg: Alg.ES256, kid: 'did:example:ebfeb1f712ebc6f1c276e12ec21/keys/1', typ: 'jwt' },
28
+ payload: { iss: 'sphereon:wallet', nonce: 'tZignsnFbp', jti: 'tZignsnFbp223', aud: IDENTIPROOF_ISSUER_URL },
29
+ };
30
+
31
+ const kid = 'did:example:ebfeb1f712ebc6f1c276e12ec21/keys/1';
32
+
33
+ let keypair: KeyPair;
34
+
35
+ async function proofOfPossessionCallbackFunction(args: Jwt, kid?: string): Promise<string> {
36
+ if (!args.payload.aud) {
37
+ throw Error('aud required');
38
+ } else if (!kid) {
39
+ throw Error('kid required');
40
+ }
41
+ return await new jose.SignJWT({ ...args.payload })
42
+ .setProtectedHeader({ alg: 'ES256' })
43
+ .setIssuedAt()
44
+ .setIssuer(kid)
45
+ .setAudience(args.payload.aud)
46
+ .setExpirationTime('2h')
47
+ .sign(keypair.privateKey);
48
+ }
49
+
50
+ interface KeyPair {
51
+ publicKey: KeyObject;
52
+ privateKey: KeyObject;
53
+ }
54
+
55
+ beforeAll(async () => {
56
+ const { privateKey, publicKey } = await jose.generateKeyPair('ES256');
57
+ keypair = { publicKey: publicKey as KeyObject, privateKey: privateKey as KeyObject };
58
+ });
59
+
60
+ beforeEach(async () => {
61
+ nock.cleanAll();
62
+ nock(IDENTIPROOF_ISSUER_URL).get(WellKnownEndpoints.OPENID4VCI_ISSUER).reply(200, JSON.stringify(IDENTIPROOF_OID4VCI_METADATA));
63
+ });
64
+
65
+ afterEach(async () => {
66
+ nock.cleanAll();
67
+ });
68
+ describe('Credential Request Client ', () => {
69
+ it('should get a failed credential response with an unsupported format', async function () {
70
+ const basePath = 'https://sphereonjunit2022101301.com/';
71
+ nock(basePath).post(/.*/).reply(500, {
72
+ error: 'unsupported_format',
73
+ error_description: 'This is a mock error message',
74
+ });
75
+
76
+ const credReqClient = CredentialRequestClientBuilder.fromCredentialOffer({ credentialOffer: INITIATION_TEST })
77
+ .withCredentialEndpoint(basePath + '/credential')
78
+ .withFormat('ldp_vc')
79
+ .withCredentialType('https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#OpenBadgeCredential')
80
+ .build();
81
+ const proof: ProofOfPossession = await ProofOfPossessionBuilder.fromJwt({
82
+ jwt,
83
+ callbacks: {
84
+ signCallback: proofOfPossessionCallbackFunction,
85
+ },
86
+ version: OpenId4VCIVersion.VER_1_0_08,
87
+ })
88
+ // .withEndpointMetadata(metadata)
89
+ .withClientId('sphereon:wallet')
90
+ .withKid(kid)
91
+ .build();
92
+ expect(credReqClient.getCredentialEndpoint()).toEqual(basePath + '/credential');
93
+ const credentialRequest = await credReqClient.createCredentialRequest({ proofInput: proof, version: OpenId4VCIVersion.VER_1_0_08 });
94
+ expect(credentialRequest.proof?.jwt?.includes(partialJWT)).toBeTruthy();
95
+ const result = await credReqClient.acquireCredentialsUsingRequest(credentialRequest);
96
+ expect(result?.errorBody?.error).toBe('unsupported_format');
97
+ });
98
+
99
+ it('should get success credential response', async function () {
100
+ const mockedVC =
101
+ 'eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJ2YyI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSIsImh0dHBzOi8vd3d3LnczLm9yZy8yMDE4L2NyZWRlbnRpYWxzL2V4YW1wbGVzL3YxIl0sImlkIjoiaHR0cDovL2V4YW1wbGUuZWR1L2NyZWRlbnRpYWxzLzM3MzIiLCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIiwiVW5pdmVyc2l0eURlZ3JlZUNyZWRlbnRpYWwiXSwiaXNzdWVyIjoiaHR0cHM6Ly9leGFtcGxlLmVkdS9pc3N1ZXJzLzU2NTA0OSIsImlzc3VhbmNlRGF0ZSI6IjIwMTAtMDEtMDFUMDA6MDA6MDBaIiwiY3JlZGVudGlhbFN1YmplY3QiOnsiaWQiOiJkaWQ6ZXhhbXBsZTplYmZlYjFmNzEyZWJjNmYxYzI3NmUxMmVjMjEiLCJkZWdyZWUiOnsidHlwZSI6IkJhY2hlbG9yRGVncmVlIiwibmFtZSI6IkJhY2hlbG9yIG9mIFNjaWVuY2UgYW5kIEFydHMifX19LCJpc3MiOiJodHRwczovL2V4YW1wbGUuZWR1L2lzc3VlcnMvNTY1MDQ5IiwibmJmIjoxMjYyMzA0MDAwLCJqdGkiOiJodHRwOi8vZXhhbXBsZS5lZHUvY3JlZGVudGlhbHMvMzczMiIsInN1YiI6ImRpZDpleGFtcGxlOmViZmViMWY3MTJlYmM2ZjFjMjc2ZTEyZWMyMSJ9.z5vgMTK1nfizNCg5N-niCOL3WUIAL7nXy-nGhDZYO_-PNGeE-0djCpWAMH8fD8eWSID5PfkPBYkx_dfLJnQ7NA';
102
+ nock('https://oidc4vci.demo.spruceid.com')
103
+ .post(/credential/)
104
+ .reply(200, {
105
+ format: 'jwt-vc',
106
+ credential: mockedVC,
107
+ });
108
+ const credReqClient = CredentialRequestClientBuilder.fromCredentialOfferRequest({ request: INITIATION_TEST })
109
+ .withCredentialEndpoint('https://oidc4vci.demo.spruceid.com/credential')
110
+ .withFormat('jwt_vc')
111
+ .withCredentialType('https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#OpenBadgeCredential')
112
+ .build();
113
+ const proof: ProofOfPossession = await ProofOfPossessionBuilder.fromJwt({
114
+ jwt,
115
+ callbacks: {
116
+ signCallback: proofOfPossessionCallbackFunction,
117
+ },
118
+ version: OpenId4VCIVersion.VER_1_0_08,
119
+ })
120
+ // .withEndpointMetadata(metadata)
121
+ .withKid(kid)
122
+ .withClientId('sphereon:wallet')
123
+ .build();
124
+ const credentialRequest = await credReqClient.createCredentialRequest({
125
+ proofInput: proof,
126
+ format: 'jwt',
127
+ version: OpenId4VCIVersion.VER_1_0_08,
128
+ });
129
+ expect(credentialRequest.proof?.jwt?.includes(partialJWT)).toBeTruthy();
130
+ expect(credentialRequest.format).toEqual('jwt_vc_json');
131
+ const result = await credReqClient.acquireCredentialsUsingRequest(credentialRequest);
132
+ expect(result?.successBody?.credential).toEqual(mockedVC);
133
+ });
134
+
135
+ it('should fail with invalid url', async () => {
136
+ const credReqClient = CredentialRequestClientBuilder.fromCredentialOfferRequest({ request: INITIATION_TEST })
137
+ .withCredentialEndpoint('httpsf://oidc4vci.demo.spruceid.com/credential')
138
+ .withFormat('jwt_vc')
139
+ .withCredentialType('https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#OpenBadgeCredential')
140
+ .build();
141
+ const proof: ProofOfPossession = await ProofOfPossessionBuilder.fromJwt({
142
+ jwt,
143
+ callbacks: {
144
+ signCallback: proofOfPossessionCallbackFunction,
145
+ },
146
+ version: OpenId4VCIVersion.VER_1_0_08,
147
+ })
148
+ // .withEndpointMetadata(metadata)
149
+ .withKid(kid)
150
+ .withClientId('sphereon:wallet')
151
+ .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(
155
+ Error(URL_NOT_VALID)
156
+ );
157
+ });
158
+ });
159
+
160
+ describe('Credential Request Client with Walt.id ', () => {
161
+ it('should have correct metadata endpoints', async function () {
162
+ nock.cleanAll();
163
+ const WALT_IRR_URI =
164
+ 'openid-initiate-issuance://?issuer=https%3A%2F%2Fjff.walt.id%2Fissuer-api%2Foidc%2F&credential_type=OpenBadgeCredential&pre-authorized_code=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhOTUyZjUxNi1jYWVmLTQ4YjMtODIxYy00OTRkYzgyNjljZjAiLCJwcmUtYXV0aG9yaXplZCI6dHJ1ZX0.YE5DlalcLC2ChGEg47CQDaN1gTxbaQqSclIVqsSAUHE&user_pin_required=false';
165
+ const credentialOffer = await CredentialOfferClient.fromURI(WALT_IRR_URI);
166
+
167
+ const request = credentialOffer.credential_offer;
168
+ const metadata = await MetadataClient.retrieveAllMetadata(getIssuerFromCredentialOfferPayload(request) as string);
169
+ expect(metadata.credential_endpoint).toEqual(WALT_OID4VCI_METADATA.credential_endpoint);
170
+ expect(metadata.token_endpoint).toEqual(WALT_OID4VCI_METADATA.token_endpoint);
171
+
172
+ const credReqClient = CredentialRequestClientBuilder.fromCredentialOffer({
173
+ credentialOffer,
174
+ metadata,
175
+ }).build();
176
+ expect(credReqClient.credentialRequestOpts.credentialEndpoint).toBe(WALT_OID4VCI_METADATA.credential_endpoint);
177
+ });
178
+ });
179
+
180
+ describe('Credential Request Client with different issuers ', () => {
181
+ it('should create correct CredentialRequest for Spruce', async () => {
182
+ const IRR_URI =
183
+ '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';
184
+ const credentialRequest = await (
185
+ await CredentialRequestClientBuilder.fromURI({
186
+ uri: IRR_URI,
187
+ metadata: getMockData('spruce')?.metadata as unknown as EndpointMetadata,
188
+ })
189
+ )
190
+ .build()
191
+ .createCredentialRequest({
192
+ proofInput: {
193
+ proof_type: 'jwt',
194
+ jwt: getMockData('spruce')?.credential.request.proof.jwt as string,
195
+ },
196
+ credentialTypes: ['OpenBadgeCredential'],
197
+ format: 'jwt_vc_json-ld',
198
+ version: OpenId4VCIVersion.VER_1_0_08,
199
+ });
200
+ expect(credentialRequest).toEqual(getMockData('spruce')?.credential.request);
201
+ });
202
+
203
+ it('should create correct CredentialRequest for Walt', async () => {
204
+ const IRR_URI =
205
+ '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
+ const credentialOffer = await (
207
+ await CredentialRequestClientBuilder.fromURI({
208
+ uri: IRR_URI,
209
+ metadata: getMockData('walt')?.metadata as unknown as EndpointMetadata,
210
+ })
211
+ )
212
+ .build()
213
+ .createCredentialRequest({
214
+ proofInput: {
215
+ proof_type: 'jwt',
216
+ jwt: getMockData('walt')?.credential.request.proof.jwt as string,
217
+ },
218
+ credentialTypes: ['OpenBadgeCredential'],
219
+ format: 'jwt_vc',
220
+ version: OpenId4VCIVersion.VER_1_0_08,
221
+ });
222
+ expect(credentialOffer).toEqual(getMockData('walt')?.credential.request);
223
+ });
224
+
225
+ // Missing the issuer required property
226
+ xit('should create correct CredentialRequest for uniissuer', async () => {
227
+ const IRR_URI =
228
+ 'https://oidc4vc.uniissuer.io/?credential_type=OpenBadgeCredential&pre-authorized_code=0ApoI8rxVmdQ44RIpuDbFIURIIkOhyek&user_pin_required=false';
229
+ const credentialOffer = await (
230
+ await CredentialRequestClientBuilder.fromURI({
231
+ uri: IRR_URI,
232
+ metadata: getMockData('uniissuer')?.metadata as unknown as EndpointMetadata,
233
+ })
234
+ )
235
+ .build()
236
+ .createCredentialRequest({
237
+ proofInput: {
238
+ proof_type: 'jwt',
239
+ jwt: getMockData('uniissuer')?.credential.request.proof.jwt as string,
240
+ },
241
+ credentialTypes: ['OpenBadgeCredential'],
242
+ format: 'jwt_vc',
243
+ version: OpenId4VCIVersion.VER_1_0_08,
244
+ });
245
+ expect(credentialOffer).toEqual(getMockData('uniissuer')?.credential.request);
246
+ });
247
+
248
+ it('should create correct CredentialRequest for mattr', async () => {
249
+ const IRR_URI =
250
+ 'openid-initiate-issuance://?issuer=https://launchpad.mattrlabs.com&credential_type=OpenBadgeCredential&pre-authorized_code=g0UCOj6RAN5AwHU6gczm_GzB4_lH6GW39Z0Dl2DOOiO';
251
+ const credentialOffer = await (
252
+ await CredentialRequestClientBuilder.fromURI({
253
+ uri: IRR_URI,
254
+ metadata: getMockData('mattr')?.metadata as unknown as EndpointMetadata,
255
+ })
256
+ )
257
+ .build()
258
+ .createCredentialRequest({
259
+ proofInput: {
260
+ proof_type: 'jwt',
261
+ jwt: getMockData('mattr')?.credential.request.proof.jwt as string,
262
+ },
263
+ credentialTypes: ['OpenBadgeCredential'],
264
+ format: 'ldp_vc',
265
+ version: OpenId4VCIVersion.VER_1_0_08,
266
+ });
267
+ expect(credentialOffer).toEqual(getMockData('mattr')?.credential.request);
268
+ });
269
+
270
+ it('should create correct CredentialRequest for diwala', async () => {
271
+ const IRR_URI =
272
+ 'openid-initiate-issuance://?issuer=https://oidc4vc.diwala.io&credential_type=OpenBadgeCredential&pre-authorized_code=eyJhbGciOiJIUzI1NiJ9.eyJjcmVkZW50aWFsX3R5cGUiOiJPcGVuQmFkZ2VDcmVkZW50aWFsIiwiZXhwIjoxNjgxOTg0NDY3fQ.fEAHKz2nuWfiYHw406iNxr-81pWkNkbi31bWsYSf6Ng';
273
+ const credentialOffer = await (
274
+ await CredentialRequestClientBuilder.fromURI({
275
+ uri: IRR_URI,
276
+ metadata: getMockData('diwala')?.metadata as unknown as EndpointMetadata,
277
+ })
278
+ )
279
+ .build()
280
+ .createCredentialRequest({
281
+ proofInput: {
282
+ proof_type: 'jwt',
283
+ jwt: getMockData('diwala')?.credential.request.proof.jwt as string,
284
+ },
285
+ credentialTypes: ['OpenBadgeCredential'],
286
+ format: 'ldp_vc',
287
+ version: OpenId4VCIVersion.VER_1_0_08,
288
+ });
289
+ expect(credentialOffer).toEqual(getMockData('diwala')?.credential.request);
290
+ });
291
+ });