@sphereon/oid4vci-client 0.2.0 → 0.4.1-unstable.247

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 (116) 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 +226 -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/CredentialOffer.d.ts +6 -0
  12. package/dist/CredentialOffer.d.ts.map +1 -0
  13. package/dist/CredentialOffer.js +49 -0
  14. package/dist/CredentialOffer.js.map +1 -0
  15. package/dist/CredentialRequestClient.d.ts +29 -0
  16. package/dist/CredentialRequestClient.d.ts.map +1 -0
  17. package/dist/CredentialRequestClient.js +63 -0
  18. package/dist/CredentialRequestClient.js.map +1 -0
  19. package/dist/CredentialRequestClientBuilderV1_0_09.d.ts +29 -0
  20. package/dist/CredentialRequestClientBuilderV1_0_09.d.ts.map +1 -0
  21. package/dist/CredentialRequestClientBuilderV1_0_09.js +63 -0
  22. package/dist/CredentialRequestClientBuilderV1_0_09.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 +72 -0
  28. package/dist/OpenID4VCIClient.d.ts.map +1 -0
  29. package/dist/OpenID4VCIClient.js +361 -0
  30. package/dist/OpenID4VCIClient.js.map +1 -0
  31. package/dist/ProofOfPossessionBuilder.d.ts +35 -0
  32. package/dist/ProofOfPossessionBuilder.d.ts.map +1 -0
  33. package/dist/ProofOfPossessionBuilder.js +120 -0
  34. package/dist/ProofOfPossessionBuilder.js.map +1 -0
  35. package/dist/{main/lib/functions → functions}/Encoding.d.ts +20 -17
  36. package/dist/functions/Encoding.d.ts.map +1 -0
  37. package/dist/functions/Encoding.js +144 -0
  38. package/dist/functions/Encoding.js.map +1 -0
  39. package/dist/functions/HttpUtils.d.ts +24 -0
  40. package/dist/functions/HttpUtils.d.ts.map +1 -0
  41. package/dist/functions/HttpUtils.js +93 -0
  42. package/dist/functions/HttpUtils.js.map +1 -0
  43. package/dist/functions/ProofUtil.d.ts +29 -0
  44. package/dist/functions/ProofUtil.d.ts.map +1 -0
  45. package/dist/functions/ProofUtil.js +103 -0
  46. package/dist/functions/ProofUtil.js.map +1 -0
  47. package/dist/functions/index.d.ts +4 -0
  48. package/dist/functions/index.d.ts.map +1 -0
  49. package/dist/{main/lib/functions → functions}/index.js +20 -20
  50. package/dist/functions/index.js.map +1 -0
  51. package/dist/index.d.ts +9 -0
  52. package/dist/index.d.ts.map +1 -0
  53. package/dist/{main/lib/index.js → index.js} +25 -24
  54. package/dist/index.js.map +1 -0
  55. package/lib/AccessTokenClient.ts +270 -0
  56. package/lib/AuthorizationDetailsBuilder.ts +46 -0
  57. package/lib/CredentialOffer.ts +55 -0
  58. package/lib/CredentialRequestClient.ts +77 -0
  59. package/lib/CredentialRequestClientBuilderV1_0_09.ts +99 -0
  60. package/lib/MetadataClient.ts +147 -0
  61. package/lib/OpenID4VCIClient.ts +477 -0
  62. package/lib/ProofOfPossessionBuilder.ts +156 -0
  63. package/lib/__tests__/AccessTokenClient.spec.ts +221 -0
  64. package/lib/__tests__/AuthorizationDetailsBuilder.spec.ts +65 -0
  65. package/lib/__tests__/AuthzFlowType.spec.ts +39 -0
  66. package/lib/__tests__/CredentialRequestClient.spec.ts +261 -0
  67. package/lib/__tests__/CredentialRequestClientBuilder.spec.ts +103 -0
  68. package/lib/__tests__/HttpUtils.spec.ts +37 -0
  69. package/lib/__tests__/IT.spec.ts +155 -0
  70. package/lib/__tests__/IssuanceInitiation.spec.ts +37 -0
  71. package/lib/__tests__/JsonURIConversions.spec.ts +86 -0
  72. package/lib/__tests__/MetadataClient.spec.ts +198 -0
  73. package/lib/__tests__/MetadataMocks.ts +428 -0
  74. package/lib/__tests__/OpenID4VCIClient.spec.ts +166 -0
  75. package/lib/__tests__/OpenID4VCIClientPAR.spec.ts +112 -0
  76. package/lib/__tests__/ProofOfPossessionBuilder.spec.ts +109 -0
  77. package/lib/__tests__/data/VciDataFixtures.ts +744 -0
  78. package/lib/functions/Encoding.ts +138 -0
  79. package/lib/functions/HttpUtils.ts +106 -0
  80. package/lib/functions/ProofUtil.ts +128 -0
  81. package/{dist/main/lib/functions/index.d.ts → lib/functions/index.ts} +3 -3
  82. package/lib/index.ts +8 -0
  83. package/package.json +68 -71
  84. package/CHANGELOG.md +0 -21
  85. package/dist/main/index.d.ts +0 -1
  86. package/dist/main/index.js +0 -18
  87. package/dist/main/lib/AccessTokenClient.d.ts +0 -20
  88. package/dist/main/lib/AccessTokenClient.js +0 -141
  89. package/dist/main/lib/CredentialRequestClient.d.ts +0 -31
  90. package/dist/main/lib/CredentialRequestClient.js +0 -66
  91. package/dist/main/lib/CredentialRequestClientBuilder.d.ts +0 -21
  92. package/dist/main/lib/CredentialRequestClientBuilder.js +0 -56
  93. package/dist/main/lib/IssuanceInitiation.d.ts +0 -5
  94. package/dist/main/lib/IssuanceInitiation.js +0 -29
  95. package/dist/main/lib/MetadataClient.js +0 -127
  96. package/dist/main/lib/functions/Encoding.js +0 -138
  97. package/dist/main/lib/functions/HttpUtils.d.ts +0 -17
  98. package/dist/main/lib/functions/HttpUtils.js +0 -133
  99. package/dist/main/lib/functions/ProofUtil.d.ts +0 -9
  100. package/dist/main/lib/functions/ProofUtil.js +0 -76
  101. package/dist/main/lib/index.d.ts +0 -7
  102. package/dist/main/lib/types/Authorization.types.d.ts +0 -66
  103. package/dist/main/lib/types/Authorization.types.js +0 -35
  104. package/dist/main/lib/types/CredentialIssuance.types.d.ts +0 -88
  105. package/dist/main/lib/types/CredentialIssuance.types.js +0 -8
  106. package/dist/main/lib/types/Generic.types.d.ts +0 -19
  107. package/dist/main/lib/types/Generic.types.js +0 -11
  108. package/dist/main/lib/types/OAuth2ASMetadata.d.ts +0 -37
  109. package/dist/main/lib/types/OAuth2ASMetadata.js +0 -3
  110. package/dist/main/lib/types/OID4VCIServerMetadata.d.ts +0 -65
  111. package/dist/main/lib/types/OID4VCIServerMetadata.js +0 -3
  112. package/dist/main/lib/types/Oidc4vciErrors.d.ts +0 -3
  113. package/dist/main/lib/types/Oidc4vciErrors.js +0 -7
  114. package/dist/main/lib/types/index.d.ts +0 -6
  115. package/dist/main/lib/types/index.js +0 -23
  116. package/dist/main/tsconfig.build.tsbuildinfo +0 -1
@@ -0,0 +1,221 @@
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
+ it(
17
+ 'get Access Token for with pre-authorized code without resulting in errors',
18
+ async () => {
19
+ const accessTokenClient: AccessTokenClient = new AccessTokenClient();
20
+
21
+ const accessTokenRequest: AccessTokenRequest = {
22
+ grant_type: GrantTypes.PRE_AUTHORIZED_CODE,
23
+ 'pre-authorized_code': '20221013',
24
+ client_id: 'sphereon',
25
+ } as AccessTokenRequest;
26
+
27
+ const body: AccessTokenResponse = {
28
+ access_token: 'ey6546.546654.64565',
29
+ authorization_pending: false,
30
+ c_nonce: 'c_nonce2022101300',
31
+ c_nonce_expires_in: 2022101300,
32
+ interval: 2022101300,
33
+ token_type: 'Bearer',
34
+ };
35
+ nock(MOCK_URL).post(/.*/).reply(200, JSON.stringify(body));
36
+
37
+ const accessTokenResponse: OpenIDResponse<AccessTokenResponse> = await accessTokenClient.acquireAccessTokenUsingRequest({
38
+ accessTokenRequest,
39
+ asOpts: { as: MOCK_URL },
40
+ });
41
+
42
+ expect(accessTokenResponse.successBody).toEqual(body);
43
+ },
44
+ UNIT_TEST_TIMEOUT
45
+ );
46
+
47
+ it(
48
+ 'get Access Token for authorization code without resulting in errors',
49
+ async () => {
50
+ const accessTokenClient: AccessTokenClient = new AccessTokenClient();
51
+
52
+ const accessTokenRequest: AccessTokenRequest = {
53
+ client_id: 'test-client',
54
+ code_verifier: 'F0Y2OGARX2ppIERYdSVuLCV3Zi95Ci5yWzAYNU8QQC0',
55
+ code: '9mq3kwIuNZ88czRjJ2-UDxtaNXulOfxHSXo-kM01MLV',
56
+ redirect_uri: 'http://test.com/cb',
57
+ grant_type: GrantTypes.AUTHORIZATION_CODE,
58
+ } as AccessTokenRequest;
59
+
60
+ const body: AccessTokenResponse = {
61
+ access_token: '6W-kZopGNBq8e-5KvnGf2u0p0iGSxWZ7jIGV86nO1Dn',
62
+ expires_in: 3600,
63
+ scope: 'TestCredential',
64
+ token_type: 'Bearer',
65
+ };
66
+ nock(MOCK_URL).post(/.*/).reply(200, JSON.stringify(body));
67
+
68
+ const accessTokenResponse: OpenIDResponse<AccessTokenResponse> = await accessTokenClient.acquireAccessTokenUsingRequest({
69
+ accessTokenRequest,
70
+ asOpts: { as: MOCK_URL },
71
+ });
72
+
73
+ expect(accessTokenResponse.successBody).toEqual(body);
74
+ },
75
+ UNIT_TEST_TIMEOUT
76
+ );
77
+
78
+ it(
79
+ 'get error for incorrect code',
80
+ async () => {
81
+ const accessTokenClient: AccessTokenClient = new AccessTokenClient();
82
+
83
+ const accessTokenRequest: AccessTokenRequest = {
84
+ grant_type: GrantTypes.PRE_AUTHORIZED_CODE,
85
+ 'pre-authorized_code': '',
86
+ user_pin: '1.0',
87
+ } as AccessTokenRequest;
88
+
89
+ nock(MOCK_URL).post(/.*/).reply(200, {});
90
+
91
+ await expect(
92
+ accessTokenClient.acquireAccessTokenUsingRequest({
93
+ accessTokenRequest,
94
+ asOpts: { as: MOCK_URL },
95
+ })
96
+ ).rejects.toThrow('Pre-authorization must be proven by presenting the pre-authorized code. Code must be present.');
97
+ },
98
+ UNIT_TEST_TIMEOUT
99
+ );
100
+
101
+ it(
102
+ 'get error for incorrect pin',
103
+ async () => {
104
+ const accessTokenClient: AccessTokenClient = new AccessTokenClient();
105
+
106
+ const accessTokenRequest: AccessTokenRequest = {
107
+ grant_type: GrantTypes.PRE_AUTHORIZED_CODE,
108
+ 'pre-authorized_code': '20221013',
109
+ } as AccessTokenRequest;
110
+
111
+ nock(MOCK_URL).post(/.*/).reply(200, {});
112
+
113
+ await expect(
114
+ accessTokenClient.acquireAccessTokenUsingRequest({
115
+ accessTokenRequest,
116
+ isPinRequired: true,
117
+ asOpts: { as: MOCK_URL },
118
+ })
119
+ ).rejects.toThrow('A valid pin consisting of maximal 8 numeric characters must be present.');
120
+ },
121
+ UNIT_TEST_TIMEOUT
122
+ );
123
+
124
+ it(
125
+ 'get error for incorrectly long pin',
126
+ async () => {
127
+ const accessTokenClient: AccessTokenClient = new AccessTokenClient();
128
+
129
+ const accessTokenRequest: AccessTokenRequest = {
130
+ grant_type: GrantTypes.PRE_AUTHORIZED_CODE,
131
+ 'pre-authorized_code': '20221013',
132
+ client_id: 'sphereon.com',
133
+ user_pin: '123456789',
134
+ } as AccessTokenRequest;
135
+
136
+ nock(MOCK_URL).post(/.*/).reply(200, {});
137
+
138
+ await expect(
139
+ accessTokenClient.acquireAccessTokenUsingRequest({
140
+ accessTokenRequest,
141
+ isPinRequired: true,
142
+ asOpts: { as: MOCK_URL },
143
+ })
144
+ ).rejects.toThrow(Error('A valid pin consisting of maximal 8 numeric characters must be present.'));
145
+ },
146
+ UNIT_TEST_TIMEOUT
147
+ );
148
+
149
+ it(
150
+ 'get success for correct length of pin',
151
+ async () => {
152
+ const accessTokenClient: AccessTokenClient = new AccessTokenClient();
153
+
154
+ const accessTokenRequest: AccessTokenRequest = {
155
+ grant_type: GrantTypes.PRE_AUTHORIZED_CODE,
156
+ 'pre-authorized_code': '20221013',
157
+ client_id: 'sphereon.com',
158
+ user_pin: '12345678',
159
+ } as AccessTokenRequest;
160
+
161
+ const body: AccessTokenResponse = {
162
+ access_token: 'ey6546.546654.64565',
163
+ authorization_pending: false,
164
+ c_nonce: 'c_nonce2022101300',
165
+ c_nonce_expires_in: 2022101300,
166
+ interval: 2022101300,
167
+ token_type: 'Bearer',
168
+ };
169
+ nock(MOCK_URL).post(/.*/).reply(200, body);
170
+
171
+ const response = await accessTokenClient.acquireAccessTokenUsingRequest({
172
+ accessTokenRequest,
173
+ isPinRequired: true,
174
+ asOpts: { as: MOCK_URL },
175
+ });
176
+ expect(response.successBody).toEqual(body);
177
+ },
178
+ UNIT_TEST_TIMEOUT
179
+ );
180
+
181
+ it('get error for using a pin when not requested', async () => {
182
+ const accessTokenClient: AccessTokenClient = new AccessTokenClient();
183
+
184
+ nock(MOCK_URL).post(/.*/).reply(200, {});
185
+
186
+ await expect(() =>
187
+ accessTokenClient.acquireAccessToken({
188
+ credentialOffer: INITIATION_TEST,
189
+ pin: '1234',
190
+ })
191
+ ).rejects.toThrow(Error('Cannot set a pin, when the pin is not required.'));
192
+ });
193
+
194
+ it('get error if code_verifier is present when flow type is pre-authorized', async () => {
195
+ const accessTokenClient: AccessTokenClient = new AccessTokenClient();
196
+
197
+ nock(MOCK_URL).post(/.*/).reply(200, {});
198
+
199
+ const requestOpts: AccessTokenRequestOpts = {
200
+ credentialOffer: INITIATION_TEST,
201
+ pin: undefined,
202
+ codeVerifier: 'RylyWGQ-dzpObnEcoMBDIH9cTAwZXk1wYzktKxsOFgA',
203
+ code: 'LWCt225yj7gzT2cWeMP4hXj4B4oIYkEiGs4T6pfez91',
204
+ redirectUri: 'http://example.com/cb',
205
+ };
206
+
207
+ await expect(() => accessTokenClient.acquireAccessToken(requestOpts)).rejects.toThrow(
208
+ Error('Cannot pass a code_verifier when flow type is pre-authorized')
209
+ );
210
+ });
211
+
212
+ it('get error if no as, issuer and metadata values are present', async () => {
213
+ await expect(() =>
214
+ AccessTokenClient.determineTokenURL({
215
+ asOpts: undefined,
216
+ issuerOpts: undefined,
217
+ metadata: undefined,
218
+ })
219
+ ).toThrow(Error('Cannot determine token URL if no issuer, metadata and no Authorization Server values are present'));
220
+ });
221
+ });
@@ -0,0 +1,65 @@
1
+ import { CredentialFormatEnum } 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 CredentialFormatEnum)
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 CredentialFormatEnum)
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 CredentialFormatEnum)
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 CredentialFormatEnum)
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 CredentialFormatEnum)
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, CredentialOfferPayloadV1_0_09 } 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 CredentialOfferPayloadV1_0_09)
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 CredentialOfferPayloadV1_0_09)
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 CredentialOfferPayloadV1_0_09)
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 CredentialOfferPayloadV1_0_09)
37
+ ).toEqual(AuthzFlowType.PRE_AUTHORIZED_CODE_FLOW);
38
+ });
39
+ });
@@ -0,0 +1,261 @@
1
+ import { KeyObject } from 'crypto';
2
+
3
+ import {
4
+ Alg,
5
+ CredentialRequest,
6
+ EndpointMetadata,
7
+ getIssuerFromCredentialOfferPayload,
8
+ Jwt,
9
+ ProofOfPossession,
10
+ ProofType,
11
+ Typ,
12
+ URL_NOT_VALID,
13
+ WellKnownEndpoints,
14
+ } from '@sphereon/oid4vci-common';
15
+ import * as jose from 'jose';
16
+ import nock from 'nock';
17
+
18
+ import { CredentialRequestClientBuilderV1_0_09, MetadataClient, ProofOfPossessionBuilder } from '..';
19
+ import { CredentialOffer } from '../CredentialOffer';
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: 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
+ describe('Credential Request Client ', () => {
66
+ it('should get a failed credential response with an unsupported format', async function () {
67
+ const basePath = 'https://sphereonjunit2022101301.com/';
68
+ nock(basePath).post(/.*/).reply(500, {
69
+ error: 'unsupported_format',
70
+ error_description: 'This is a mock error message',
71
+ });
72
+
73
+ const credReqClient = CredentialRequestClientBuilderV1_0_09.fromCredentialOffer({ credentialOffer: INITIATION_TEST })
74
+ .withCredentialEndpoint(basePath + '/credential')
75
+ .withFormat('ldp_vc')
76
+ .withCredentialType('https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#OpenBadgeCredential')
77
+ .build();
78
+ const proof: ProofOfPossession = await ProofOfPossessionBuilder.fromJwt({
79
+ jwt,
80
+ callbacks: {
81
+ signCallback: proofOfPossessionCallbackFunction,
82
+ },
83
+ })
84
+ // .withEndpointMetadata(metadata)
85
+ .withClientId('sphereon:wallet')
86
+ .withKid(kid)
87
+ .build();
88
+ expect(credReqClient.getCredentialEndpoint()).toEqual(basePath + '/credential');
89
+ const credentialRequest: CredentialRequest = await credReqClient.createCredentialRequest({ proofInput: proof });
90
+ expect(credentialRequest.proof.jwt.includes(partialJWT)).toBeTruthy();
91
+ const result = await credReqClient.acquireCredentialsUsingRequest(credentialRequest);
92
+ expect(result?.errorBody?.error).toBe('unsupported_format');
93
+ });
94
+
95
+ it('should get success credential response', async function () {
96
+ const mockedVC =
97
+ 'eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJ2YyI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSIsImh0dHBzOi8vd3d3LnczLm9yZy8yMDE4L2NyZWRlbnRpYWxzL2V4YW1wbGVzL3YxIl0sImlkIjoiaHR0cDovL2V4YW1wbGUuZWR1L2NyZWRlbnRpYWxzLzM3MzIiLCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIiwiVW5pdmVyc2l0eURlZ3JlZUNyZWRlbnRpYWwiXSwiaXNzdWVyIjoiaHR0cHM6Ly9leGFtcGxlLmVkdS9pc3N1ZXJzLzU2NTA0OSIsImlzc3VhbmNlRGF0ZSI6IjIwMTAtMDEtMDFUMDA6MDA6MDBaIiwiY3JlZGVudGlhbFN1YmplY3QiOnsiaWQiOiJkaWQ6ZXhhbXBsZTplYmZlYjFmNzEyZWJjNmYxYzI3NmUxMmVjMjEiLCJkZWdyZWUiOnsidHlwZSI6IkJhY2hlbG9yRGVncmVlIiwibmFtZSI6IkJhY2hlbG9yIG9mIFNjaWVuY2UgYW5kIEFydHMifX19LCJpc3MiOiJodHRwczovL2V4YW1wbGUuZWR1L2lzc3VlcnMvNTY1MDQ5IiwibmJmIjoxMjYyMzA0MDAwLCJqdGkiOiJodHRwOi8vZXhhbXBsZS5lZHUvY3JlZGVudGlhbHMvMzczMiIsInN1YiI6ImRpZDpleGFtcGxlOmViZmViMWY3MTJlYmM2ZjFjMjc2ZTEyZWMyMSJ9.z5vgMTK1nfizNCg5N-niCOL3WUIAL7nXy-nGhDZYO_-PNGeE-0djCpWAMH8fD8eWSID5PfkPBYkx_dfLJnQ7NA';
98
+ nock('https://oidc4vci.demo.spruceid.com')
99
+ .post(/credential/)
100
+ .reply(200, {
101
+ format: 'jwt-vc',
102
+ credential: mockedVC,
103
+ });
104
+ const credReqClient = CredentialRequestClientBuilderV1_0_09.fromCredentialOfferRequest({ request: INITIATION_TEST.request })
105
+ .withCredentialEndpoint('https://oidc4vci.demo.spruceid.com/credential')
106
+ .withFormat('jwt_vc')
107
+ .withCredentialType('https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#OpenBadgeCredential')
108
+ .build();
109
+ const proof: ProofOfPossession = await ProofOfPossessionBuilder.fromJwt({
110
+ jwt,
111
+ callbacks: {
112
+ signCallback: proofOfPossessionCallbackFunction,
113
+ },
114
+ })
115
+ // .withEndpointMetadata(metadata)
116
+ .withKid(kid)
117
+ .withClientId('sphereon:wallet')
118
+ .build();
119
+ const credentialRequest: CredentialRequest = await credReqClient.createCredentialRequest({ proofInput: proof, format: 'jwt' });
120
+ expect(credentialRequest.proof.jwt.includes(partialJWT)).toBeTruthy();
121
+ expect(credentialRequest.format).toEqual('jwt');
122
+ const result = await credReqClient.acquireCredentialsUsingRequest(credentialRequest);
123
+ expect(result?.successBody?.credential).toEqual(mockedVC);
124
+ });
125
+
126
+ it('should fail with invalid url', async () => {
127
+ const credReqClient = CredentialRequestClientBuilderV1_0_09.fromCredentialOfferRequest({ request: INITIATION_TEST.request })
128
+ .withCredentialEndpoint('httpsf://oidc4vci.demo.spruceid.com/credential')
129
+ .withFormat('jwt_vc')
130
+ .withCredentialType('https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#OpenBadgeCredential')
131
+ .build();
132
+ const proof: ProofOfPossession = await ProofOfPossessionBuilder.fromJwt({
133
+ jwt,
134
+ callbacks: {
135
+ signCallback: proofOfPossessionCallbackFunction,
136
+ },
137
+ })
138
+ // .withEndpointMetadata(metadata)
139
+ .withKid(kid)
140
+ .withClientId('sphereon:wallet')
141
+ .build();
142
+ await expect(credReqClient.acquireCredentialsUsingRequest({ format: 'jwt_vc', type: 'random', proof })).rejects.toThrow(Error(URL_NOT_VALID));
143
+ });
144
+ });
145
+
146
+ describe('Credential Request Client with Walt.id ', () => {
147
+ it('should have correct metadata endpoints', async function () {
148
+ nock.cleanAll();
149
+ const WALT_IRR_URI =
150
+ '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';
151
+ const credentialOffer = CredentialOffer.fromURI(WALT_IRR_URI);
152
+
153
+ const request = credentialOffer.request;
154
+ const metadata = await MetadataClient.retrieveAllMetadata(getIssuerFromCredentialOfferPayload(request) as string);
155
+ expect(metadata.credential_endpoint).toEqual(WALT_OID4VCI_METADATA.credential_endpoint);
156
+ expect(metadata.token_endpoint).toEqual(WALT_OID4VCI_METADATA.token_endpoint);
157
+
158
+ const credReqClient = CredentialRequestClientBuilderV1_0_09.fromCredentialOffer({
159
+ credentialOffer,
160
+ metadata,
161
+ }).build();
162
+ expect(credReqClient.credentialRequestOpts.credentialEndpoint).toBe(WALT_OID4VCI_METADATA.credential_endpoint);
163
+ });
164
+ });
165
+
166
+ describe('Credential Request Client with different issuers ', () => {
167
+ it('should create correct CredentialRequest for Spruce', async () => {
168
+ const IRR_URI =
169
+ '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';
170
+ const credentialOffer = await CredentialRequestClientBuilderV1_0_09.fromURI({
171
+ uri: IRR_URI,
172
+ metadata: getMockData('spruce')?.metadata as unknown as EndpointMetadata,
173
+ })
174
+ .build()
175
+ .createCredentialRequest({
176
+ proofInput: {
177
+ proof_type: ProofType.JWT,
178
+ jwt: getMockData('spruce')?.credential.request.proof.jwt as string,
179
+ },
180
+ credentialType: 'OpenBadgeCredential',
181
+ format: 'jwt_vc',
182
+ });
183
+ expect(credentialOffer).toEqual(getMockData('spruce')?.credential.request);
184
+ });
185
+
186
+ it('should create correct CredentialRequest for Walt', async () => {
187
+ const IRR_URI =
188
+ 'openid-initiate-issuance://?issuer=https%3A%2F%2Fjff.walt.id%2Fissuer-api%2Fdefault%2Foidc%2F&amp;credential_type=OpenBadgeCredential&amp;pre-authorized_code=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIwMTc4OTNjYy04ZTY3LTQxNzItYWZlOS1lODcyYmYxNDBlNWMiLCJwcmUtYXV0aG9yaXplZCI6dHJ1ZX0.ODfq2AIhOcB61dAb3zMrXBJjPJaf53zkeHh_AssYyYA&amp;user_pin_required=false';
189
+ const credentialOffer = await CredentialRequestClientBuilderV1_0_09.fromURI({
190
+ uri: IRR_URI,
191
+ metadata: getMockData('walt')?.metadata as unknown as EndpointMetadata,
192
+ })
193
+ .build()
194
+ .createCredentialRequest({
195
+ proofInput: {
196
+ proof_type: ProofType.JWT,
197
+ jwt: getMockData('walt')?.credential.request.proof.jwt as string,
198
+ },
199
+ credentialType: 'OpenBadgeCredential',
200
+ format: 'jwt_vc',
201
+ });
202
+ expect(credentialOffer).toEqual(getMockData('walt')?.credential.request);
203
+ });
204
+
205
+ it('should create correct CredentialRequest for uniissuer', async () => {
206
+ const IRR_URI =
207
+ 'https://oidc4vc.uniissuer.io/&credential_type=OpenBadgeCredential&pre-authorized_code=0ApoI8rxVmdQ44RIpuDbFIURIIkOhyek&user_pin_required=false';
208
+ const credentialOffer = await CredentialRequestClientBuilderV1_0_09.fromURI({
209
+ uri: IRR_URI,
210
+ metadata: getMockData('uniissuer')?.metadata as unknown as EndpointMetadata,
211
+ })
212
+ .build()
213
+ .createCredentialRequest({
214
+ proofInput: {
215
+ proof_type: ProofType.JWT,
216
+ jwt: getMockData('uniissuer')?.credential.request.proof.jwt as string,
217
+ },
218
+ credentialType: 'OpenBadgeCredential',
219
+ format: 'jwt_vc',
220
+ });
221
+ expect(credentialOffer).toEqual(getMockData('uniissuer')?.credential.request);
222
+ });
223
+
224
+ it('should create correct CredentialRequest for mattr', async () => {
225
+ const IRR_URI =
226
+ 'openid-initiate-issuance://?issuer=https://launchpad.mattrlabs.com&credential_type=OpenBadgeCredential&pre-authorized_code=g0UCOj6RAN5AwHU6gczm_GzB4_lH6GW39Z0Dl2DOOiO';
227
+ const credentialOffer = await CredentialRequestClientBuilderV1_0_09.fromURI({
228
+ uri: IRR_URI,
229
+ metadata: getMockData('mattr')?.metadata as unknown as EndpointMetadata,
230
+ })
231
+ .build()
232
+ .createCredentialRequest({
233
+ proofInput: {
234
+ proof_type: ProofType.JWT,
235
+ jwt: getMockData('mattr')?.credential.request.proof.jwt as string,
236
+ },
237
+ credentialType: 'OpenBadgeCredential',
238
+ format: 'ldp_vc',
239
+ });
240
+ expect(credentialOffer).toEqual(getMockData('mattr')?.credential.request);
241
+ });
242
+
243
+ it('should create correct CredentialRequest for diwala', async () => {
244
+ const IRR_URI =
245
+ 'openid-initiate-issuance://?issuer=https://oidc4vc.diwala.io&amp;credential_type=OpenBadgeCredential&amp;pre-authorized_code=eyJhbGciOiJIUzI1NiJ9.eyJjcmVkZW50aWFsX3R5cGUiOiJPcGVuQmFkZ2VDcmVkZW50aWFsIiwiZXhwIjoxNjgxOTg0NDY3fQ.fEAHKz2nuWfiYHw406iNxr-81pWkNkbi31bWsYSf6Ng';
246
+ const credentialOffer = await CredentialRequestClientBuilderV1_0_09.fromURI({
247
+ uri: IRR_URI,
248
+ metadata: getMockData('diwala')?.metadata as unknown as EndpointMetadata,
249
+ })
250
+ .build()
251
+ .createCredentialRequest({
252
+ proofInput: {
253
+ proof_type: ProofType.JWT,
254
+ jwt: getMockData('diwala')?.credential.request.proof.jwt as string,
255
+ },
256
+ credentialType: 'OpenBadgeCredential',
257
+ format: 'ldp_vc',
258
+ });
259
+ expect(credentialOffer).toEqual(getMockData('diwala')?.credential.request);
260
+ });
261
+ });