@sphereon/oid4vci-client 0.10.3-next.8 → 0.10.4-next.14

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 (45) hide show
  1. package/README.md +18 -0
  2. package/dist/AccessTokenClient.d.ts.map +1 -1
  3. package/dist/AccessTokenClient.js +11 -16
  4. package/dist/AccessTokenClient.js.map +1 -1
  5. package/dist/CredentialOfferClient.d.ts.map +1 -1
  6. package/dist/CredentialOfferClient.js +8 -7
  7. package/dist/CredentialOfferClient.js.map +1 -1
  8. package/dist/CredentialRequestClient.d.ts +6 -1
  9. package/dist/CredentialRequestClient.d.ts.map +1 -1
  10. package/dist/CredentialRequestClient.js +42 -19
  11. package/dist/CredentialRequestClient.js.map +1 -1
  12. package/dist/MetadataClient.d.ts.map +1 -1
  13. package/dist/MetadataClient.js +1 -2
  14. package/dist/MetadataClient.js.map +1 -1
  15. package/dist/ProofOfPossessionBuilder.d.ts.map +1 -1
  16. package/dist/ProofOfPossessionBuilder.js +1 -2
  17. package/dist/ProofOfPossessionBuilder.js.map +1 -1
  18. package/dist/functions/index.d.ts +1 -3
  19. package/dist/functions/index.d.ts.map +1 -1
  20. package/dist/functions/index.js +1 -3
  21. package/dist/functions/index.js.map +1 -1
  22. package/dist/types/index.d.ts +2 -0
  23. package/dist/types/index.d.ts.map +1 -1
  24. package/dist/types/index.js +5 -0
  25. package/dist/types/index.js.map +1 -1
  26. package/lib/AccessTokenClient.ts +12 -13
  27. package/lib/CredentialOfferClient.ts +7 -5
  28. package/lib/CredentialRequestClient.ts +43 -3
  29. package/lib/MetadataClient.ts +1 -2
  30. package/lib/ProofOfPossessionBuilder.ts +1 -2
  31. package/lib/__tests__/CredentialRequestClient.spec.ts +5 -1
  32. package/lib/__tests__/HttpUtils.spec.ts +1 -1
  33. package/lib/__tests__/IT.spec.ts +42 -3
  34. package/lib/__tests__/IssuanceInitiation.spec.ts +22 -0
  35. package/lib/__tests__/ProofOfPossessionBuilder.spec.ts +1 -1
  36. package/lib/__tests__/SdJwt.spec.ts +2 -1
  37. package/lib/__tests__/SphereonE2E.spec.test.ts +2 -1
  38. package/lib/functions/index.ts +1 -3
  39. package/lib/types/index.ts +6 -0
  40. package/package.json +4 -4
  41. package/dist/functions/ProofUtil.d.ts +0 -30
  42. package/dist/functions/ProofUtil.d.ts.map +0 -1
  43. package/dist/functions/ProofUtil.js +0 -106
  44. package/dist/functions/ProofUtil.js.map +0 -1
  45. package/lib/functions/ProofUtil.ts +0 -128
@@ -5,7 +5,9 @@ import {
5
5
  assertedUniformCredentialOffer,
6
6
  AuthorizationServerOpts,
7
7
  AuthzFlowType,
8
+ convertJsonToURI,
8
9
  EndpointMetadata,
10
+ formPost,
9
11
  getIssuerFromCredentialOfferPayload,
10
12
  GrantTypes,
11
13
  IssuerOpts,
@@ -17,12 +19,9 @@ import {
17
19
  UniformCredentialOfferPayload,
18
20
  } from '@sphereon/oid4vci-common';
19
21
  import { ObjectUtils } from '@sphereon/ssi-types';
20
- import Debug from 'debug';
21
22
 
22
23
  import { MetadataClient } from './MetadataClient';
23
- import { convertJsonToURI, formPost } from './functions';
24
-
25
- const debug = Debug('sphereon:oid4vci:token');
24
+ import { LOG } from './types';
26
25
 
27
26
  export class AccessTokenClient {
28
27
  public async acquireAccessToken(opts: AccessTokenRequestOpts): Promise<OpenIDResponse<AccessTokenResponse>> {
@@ -117,7 +116,7 @@ export class AccessTokenClient {
117
116
  return request as AccessTokenRequest;
118
117
  }
119
118
 
120
- throw new Error('Credential offer request does not follow neither pre-authorized code nor authorization code flow requirements.');
119
+ throw new Error('Credential offer request follows neither pre-authorized code nor authorization code flow requirements.');
121
120
  }
122
121
 
123
122
  private assertPreAuthorizedGrantType(grantType: GrantTypes): void {
@@ -141,39 +140,39 @@ export class AccessTokenClient {
141
140
  if (requestPayload.grants?.['urn:ietf:params:oauth:grant-type:pre-authorized_code']) {
142
141
  isPinRequired = requestPayload.grants['urn:ietf:params:oauth:grant-type:pre-authorized_code']?.user_pin_required ?? false;
143
142
  }
144
- debug(`Pin required for issuer ${issuer}: ${isPinRequired}`);
143
+ LOG.warning(`Pin required for issuer ${issuer}: ${isPinRequired}`);
145
144
  return isPinRequired;
146
145
  }
147
146
 
148
147
  private assertNumericPin(isPinRequired?: boolean, pin?: string): void {
149
148
  if (isPinRequired) {
150
149
  if (!pin || !/^\d{1,8}$/.test(pin)) {
151
- debug(`Pin is not 1 to 8 digits long`);
150
+ LOG.warning(`Pin is not 1 to 8 digits long`);
152
151
  throw new Error('A valid pin consisting of maximal 8 numeric characters must be present.');
153
152
  }
154
153
  } else if (pin) {
155
- debug(`Pin set, whilst not required`);
154
+ LOG.warning(`Pin set, whilst not required`);
156
155
  throw new Error('Cannot set a pin, when the pin is not required.');
157
156
  }
158
157
  }
159
158
 
160
159
  private assertNonEmptyPreAuthorizedCode(accessTokenRequest: AccessTokenRequest): void {
161
160
  if (!accessTokenRequest[PRE_AUTH_CODE_LITERAL]) {
162
- debug(`No pre-authorized code present, whilst it is required`);
161
+ LOG.warning(`No pre-authorized code present, whilst it is required`, accessTokenRequest);
163
162
  throw new Error('Pre-authorization must be proven by presenting the pre-authorized code. Code must be present.');
164
163
  }
165
164
  }
166
165
 
167
166
  private assertNonEmptyCodeVerifier(accessTokenRequest: AccessTokenRequest): void {
168
167
  if (!accessTokenRequest.code_verifier) {
169
- debug('No code_verifier present, whilst it is required');
168
+ LOG.warning('No code_verifier present, whilst it is required', accessTokenRequest);
170
169
  throw new Error('Authorization flow requires the code_verifier to be present');
171
170
  }
172
171
  }
173
172
 
174
173
  private assertNonEmptyCode(accessTokenRequest: AccessTokenRequest): void {
175
174
  if (!accessTokenRequest.code) {
176
- debug('No code present, whilst it is required');
175
+ LOG.warning('No code present, whilst it is required');
177
176
  throw new Error('Authorization flow requires the code to be present');
178
177
  }
179
178
  }
@@ -222,7 +221,7 @@ export class AccessTokenClient {
222
221
  if (!url || !ObjectUtils.isString(url)) {
223
222
  throw new Error('No authorization server token URL present. Cannot acquire access token');
224
223
  }
225
- debug(`Token endpoint determined to be ${url}`);
224
+ LOG.debug(`Token endpoint determined to be ${url}`);
226
225
  return url;
227
226
  }
228
227
 
@@ -239,7 +238,7 @@ export class AccessTokenClient {
239
238
  }
240
239
 
241
240
  private throwNotSupportedFlow(): void {
242
- debug(`Only pre-authorized or authorization code flows supported.`);
241
+ LOG.warning(`Only pre-authorized or authorization code flows supported.`);
243
242
  throw new Error('Only pre-authorized-code or authorization code flows are supported');
244
243
  }
245
244
  }
@@ -1,4 +1,6 @@
1
1
  import {
2
+ convertJsonToURI,
3
+ convertURIToJsonObject,
2
4
  CredentialOffer,
3
5
  CredentialOfferPayload,
4
6
  CredentialOfferPayloadV1_0_09,
@@ -11,8 +13,6 @@ import {
11
13
  } from '@sphereon/oid4vci-common';
12
14
  import Debug from 'debug';
13
15
 
14
- import { convertJsonToURI, convertURIToJsonObject } from './functions';
15
-
16
16
  const debug = Debug('sphereon:oid4vci:offer');
17
17
 
18
18
  export class CredentialOfferClient {
@@ -27,18 +27,20 @@ export class CredentialOfferClient {
27
27
  const version = determineSpecVersionFromURI(uri);
28
28
  let credentialOffer: CredentialOffer;
29
29
  let credentialOfferPayload: CredentialOfferPayload;
30
+ // credential offer was introduced in draft 9 and credential_offer_uri in draft 11
30
31
  if (version < OpenId4VCIVersion.VER_1_0_11) {
31
32
  credentialOfferPayload = convertURIToJsonObject(uri, {
32
33
  arrayTypeProperties: ['credential_type'],
33
- requiredProperties: uri.includes('credential_offer_uri=') ? ['credential_offer_uri'] : ['issuer', 'credential_type'],
34
+ requiredProperties: uri.includes('credential_offer=') ? ['credential_offer'] : ['issuer', 'credential_type'],
34
35
  }) as CredentialOfferPayloadV1_0_09;
35
36
  credentialOffer = {
36
37
  credential_offer: credentialOfferPayload,
37
38
  };
38
39
  } else {
39
40
  credentialOffer = convertURIToJsonObject(uri, {
40
- arrayTypeProperties: ['credentials'],
41
- requiredProperties: uri.includes('credential_offer_uri=') ? ['credential_offer_uri'] : ['credential_offer'],
41
+ // It must have the '=' sign after credential_offer otherwise the uri will get split at openid_credential_offer
42
+ arrayTypeProperties: uri.includes('credential_offer_uri=') ? ['credential_offer_uri='] : ['credential_offer='],
43
+ requiredProperties: uri.includes('credential_offer_uri=') ? ['credential_offer_uri='] : ['credential_offer='],
42
44
  }) as CredentialOfferV1_0_11;
43
45
  if (credentialOffer?.credential_offer_uri === undefined && !credentialOffer?.credential_offer) {
44
46
  throw Error('Either a credential_offer or credential_offer_uri should be present in ' + uri);
@@ -4,19 +4,25 @@ import {
4
4
  getCredentialRequestForVersion,
5
5
  getUniformFormat,
6
6
  isDeferredCredentialResponse,
7
+ isValidURL,
8
+ NotificationErrorResponse,
9
+ NotificationRequest,
10
+ NotificationResult,
7
11
  OID4VCICredentialFormat,
8
12
  OpenId4VCIVersion,
9
13
  OpenIDResponse,
14
+ post,
10
15
  ProofOfPossession,
11
16
  UniformCredentialRequest,
12
17
  URL_NOT_VALID,
13
18
  } from '@sphereon/oid4vci-common';
19
+ import { ExperimentalSubjectIssuance } from '@sphereon/oid4vci-common/dist/experimental/holder-vci';
14
20
  import { CredentialFormat } from '@sphereon/ssi-types';
15
21
  import Debug from 'debug';
16
22
 
17
23
  import { CredentialRequestClientBuilder } from './CredentialRequestClientBuilder';
18
24
  import { ProofOfPossessionBuilder } from './ProofOfPossessionBuilder';
19
- import { isValidURL, post } from './functions';
25
+ import { LOG } from './types';
20
26
 
21
27
  const debug = Debug('sphereon:oid4vci:credential');
22
28
 
@@ -24,6 +30,7 @@ export interface CredentialRequestOpts {
24
30
  deferredCredentialAwait?: boolean;
25
31
  deferredCredentialIntervalInMS?: number;
26
32
  credentialEndpoint: string;
33
+ notificationEndpoint?: string;
27
34
  deferredCredentialEndpoint?: string;
28
35
  credentialTypes: string[];
29
36
  format?: CredentialFormat | OID4VCICredentialFormat;
@@ -80,10 +87,11 @@ export class CredentialRequestClient {
80
87
  credentialTypes?: string | string[];
81
88
  context?: string[];
82
89
  format?: CredentialFormat | OID4VCICredentialFormat;
90
+ subjectIssuance?: ExperimentalSubjectIssuance;
83
91
  }): Promise<OpenIDResponse<CredentialResponse>> {
84
- const { credentialTypes, proofInput, format, context } = opts;
92
+ const { credentialTypes, proofInput, format, context, subjectIssuance } = opts;
85
93
 
86
- const request = await this.createCredentialRequest({ proofInput, credentialTypes, context, format, version: this.version() });
94
+ const request = await this.createCredentialRequest({ proofInput, credentialTypes, context, format, version: this.version(), subjectIssuance });
87
95
  return await this.acquireCredentialsUsingRequest(request);
88
96
  }
89
97
 
@@ -103,6 +111,11 @@ export class CredentialRequestClient {
103
111
  response = await this.acquireDeferredCredential(response.successBody, { bearerToken: this.credentialRequestOpts.token });
104
112
  }
105
113
 
114
+ if ((uniformRequest.credential_subject_issuance && response.successBody) || response.successBody?.credential_subject_issuance) {
115
+ if (uniformRequest.credential_subject_issuance !== response.successBody?.credential_subject_issuance) {
116
+ throw Error('Subject signing was requested, but issuer did not provide the options in its response');
117
+ }
118
+ }
106
119
  debug(`Credential endpoint ${credentialEndpoint} response:\r\n${JSON.stringify(response, null, 2)}`);
107
120
  return response;
108
121
  }
@@ -136,6 +149,7 @@ export class CredentialRequestClient {
136
149
  credentialTypes?: string | string[];
137
150
  context?: string[];
138
151
  format?: CredentialFormat | OID4VCICredentialFormat;
152
+ subjectIssuance?: ExperimentalSubjectIssuance;
139
153
  version: OpenId4VCIVersion;
140
154
  }): Promise<UniformCredentialRequest> {
141
155
  const { proofInput } = opts;
@@ -165,6 +179,7 @@ export class CredentialRequestClient {
165
179
  types,
166
180
  format,
167
181
  proof,
182
+ ...opts.subjectIssuance,
168
183
  };
169
184
  } else if (format === 'jwt_vc_json-ld' || format === 'ldp_vc') {
170
185
  if (this.version() >= OpenId4VCIVersion.VER_1_0_12 && !opts.context) {
@@ -174,6 +189,7 @@ export class CredentialRequestClient {
174
189
  return {
175
190
  format,
176
191
  proof,
192
+ ...opts.subjectIssuance,
177
193
 
178
194
  // Ignored because v11 does not have the context value, but it is required in v12
179
195
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
@@ -192,12 +208,36 @@ export class CredentialRequestClient {
192
208
  format,
193
209
  proof,
194
210
  vct: types[0],
211
+ ...opts.subjectIssuance,
195
212
  };
196
213
  }
197
214
 
198
215
  throw new Error(`Unsupported format: ${format}`);
199
216
  }
200
217
 
218
+ public async sendNotification(request: NotificationRequest, accessToken: string): Promise<NotificationResult> {
219
+ LOG.info(`Sending status notification event '${request.event}' for id ${request.notification_id}`);
220
+ if (!this.credentialRequestOpts.notificationEndpoint) {
221
+ throw Error(`Cannot send notification when no notification endpoint is provided`);
222
+ }
223
+ const response = await post<NotificationErrorResponse>(this.credentialRequestOpts.notificationEndpoint, JSON.stringify(request), {
224
+ bearerToken: accessToken,
225
+ });
226
+ const error = response.errorBody?.error !== undefined;
227
+ const result = {
228
+ error,
229
+ response: error ? await response.errorBody?.json() : undefined,
230
+ };
231
+ if (error) {
232
+ LOG.warning(
233
+ `Notification endpoint returned an error for event '${request.event}' and id ${request.notification_id}: ${await response.errorBody?.json()}`,
234
+ );
235
+ } else {
236
+ LOG.debug(`Notification endpoint returned success for event '${request.event}' and id ${request.notification_id}`);
237
+ }
238
+ return result;
239
+ }
240
+
201
241
  private version(): OpenId4VCIVersion {
202
242
  return this.credentialRequestOpts?.version ?? OpenId4VCIVersion.VER_1_0_11;
203
243
  }
@@ -6,13 +6,12 @@ import {
6
6
  CredentialOfferRequestWithBaseUrl,
7
7
  EndpointMetadataResult,
8
8
  getIssuerFromCredentialOfferPayload,
9
+ getJson,
9
10
  OpenIDResponse,
10
11
  WellKnownEndpoints,
11
12
  } from '@sphereon/oid4vci-common';
12
13
  import Debug from 'debug';
13
14
 
14
- import { getJson } from './functions';
15
-
16
15
  const debug = Debug('sphereon:oid4vci:metadata');
17
16
 
18
17
  export class MetadataClient {
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  AccessTokenResponse,
3
3
  Alg,
4
+ createProofOfPossession,
4
5
  EndpointMetadata,
5
6
  JWK,
6
7
  Jwt,
@@ -12,8 +13,6 @@ import {
12
13
  Typ,
13
14
  } from '@sphereon/oid4vci-common';
14
15
 
15
- import { createProofOfPossession } from './functions';
16
-
17
16
  export class ProofOfPossessionBuilder<DIDDoc> {
18
17
  private readonly proof?: ProofOfPossession;
19
18
  private readonly callbacks?: ProofOfPossessionCallbacks<DIDDoc>;
@@ -1,3 +1,6 @@
1
+ // Walt uses a self signed cert
2
+ process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
3
+
1
4
  import { KeyObject } from 'crypto';
2
5
 
3
6
  import {
@@ -164,7 +167,8 @@ describe('Credential Request Client with Walt.id ', () => {
164
167
  afterEach(() => {
165
168
  nock.cleanAll();
166
169
  });
167
- it('should have correct metadata endpoints', async function () {
170
+ // Walt id has cert issue
171
+ it.skip('should have correct metadata endpoints', async function () {
168
172
  nock.cleanAll();
169
173
  const WALT_IRR_URI =
170
174
  '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';
@@ -1,4 +1,4 @@
1
- import { isValidURL } from '../functions';
1
+ import { isValidURL } from '@sphereon/oid4vci-common';
2
2
 
3
3
  describe('httputils.isValidURL', () => {
4
4
  it('Should return true for http://localhost', () => {
@@ -50,7 +50,13 @@ describe('OID4VCI-Client should', () => {
50
50
  const INITIATE_QR =
51
51
  'openid-initiate-issuance://?issuer=https%3A%2F%2Fissuer.research.identiproof.io&credential_type=OpenBadgeCredentialUrl&pre-authorized_code=4jLs9xZHEfqcoow0kHE7d1a8hUk6Sy-5bVSV2MqBUGUgiFFQi-ImL62T-FmLIo8hKA1UdMPH0lM1xAgcFkJfxIw9L-lI3mVs0hRT8YVwsEM1ma6N3wzuCdwtMU4bcwKp&user_pin_required=true';
52
52
  const OFFER_QR =
53
- 'openid-credential-offer://credential_offer=%7B%22credential_issuer%22:%22https://credential-issuer.example.com%22,%22credentials%22:%5B%7B%22format%22:%22jwt_vc_json%22,%22types%22:%5B%22VerifiableCredential%22,%22UniversityDegreeCredential%22%5D%7D%5D,%22issuer_state%22:%22eyJhbGciOiJSU0Et...FYUaBy%22%7D';
53
+ 'openid-credential-offer://?credential_offer=%7B%22credential_issuer%22%3A%22https%3A%2F%2Fissuer.research.identiproof.io%22%2C%22credentials%22%3A%5B%7B%22format%22%3A%22jwt_vc_json%22%2C%22types%22%3A%5B%22VerifiableCredential%22%2C%22UniversityDegreeCredential%22%5D%7D%5D%2C%22grants%22%3A%7B%22urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Apre-authorized_code%22%3A%7B%22pre-authorized_code%22%3A%22adhjhdjajkdkhjhdj%22%2C%22user_pin_required%22%3Atrue%7D%7D%7D';
54
+ const HTTPS_INITIATE_QR =
55
+ 'https://issuer.research.identiproof.io?issuer=https%3A%2F%2Fissuer.research.identiproof.io&credential_type=OpenBadgeCredentialUrl&pre-authorized_code=4jLs9xZHEfqcoow0kHE7d1a8hUk6Sy-5bVSV2MqBUGUgiFFQi-ImL62T-FmLIo8hKA1UdMPH0lM1xAgcFkJfxIw9L-lI3mVs0hRT8YVwsEM1ma6N3wzuCdwtMU4bcwKp&user_pin_required=true';
56
+ const HTTPS_OFFER_QR_AUTHORIZATION_CODE =
57
+ 'https://issuer.research.identiproof.io?credential_offer=%7B%22credential_issuer%22%3A%22https%3A%2F%2Fissuer.research.identiproof.io%22%2C%22credentials%22%3A%5B%7B%22format%22%3A%22jwt_vc_json%22%2C%22types%22%3A%5B%22VerifiableCredential%22%2C%22UniversityDegreeCredential%22%5D%7D%5D%2C%22grants%22%3A%7B%22authorization_code%22%3A%7B%22issuer_state%22%3A%22eyJhbGciOiJSU0Et...FYUaBy%22%7D%7D%7D';
58
+ const HTTPS_OFFER_QR_PRE_AUTHORIZED =
59
+ 'https://issuer.research.identiproof.io?credential_offer=%7B%22credential_issuer%22%3A%22https%3A%2F%2Fissuer.research.identiproof.io%22%2C%22credentials%22%3A%5B%7B%22format%22%3A%22jwt_vc_json%22%2C%22types%22%3A%5B%22VerifiableCredential%22%2C%22UniversityDegreeCredential%22%5D%7D%5D%2C%22grants%22%3A%7B%22urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Apre-authorized_code%22%3A%7B%22pre-authorized_code%22%3A%22adhjhdjajkdkhjhdj%22%2C%22user_pin_required%22%3Atrue%7D%7D%7D';
54
60
 
55
61
  function succeedWithAFullFlowWithClientSetup() {
56
62
  nock(IDENTIPROOF_ISSUER_URL).get('/.well-known/openid-credential-issuer').reply(200, JSON.stringify(IDENTIPROOF_OID4VCI_METADATA));
@@ -78,7 +84,7 @@ describe('OID4VCI-Client should', () => {
78
84
  await assertionOfsucceedWithAFullFlowWithClient(client);
79
85
  });
80
86
 
81
- test.skip('succeed with a full flow wit the client using OpenID4VCI version 11', async () => {
87
+ it('succeed with a full flow with the client using OpenID4VCI version 11 and deeplink', async () => {
82
88
  succeedWithAFullFlowWithClientSetup();
83
89
  const client = await OpenID4VCIClient.fromURI({
84
90
  uri: OFFER_QR,
@@ -89,6 +95,39 @@ describe('OID4VCI-Client should', () => {
89
95
  await assertionOfsucceedWithAFullFlowWithClient(client);
90
96
  });
91
97
 
98
+ it('succeed with a full flow with the client using OpenID4VCI draft < 9 and https', async () => {
99
+ succeedWithAFullFlowWithClientSetup();
100
+ const client = await OpenID4VCIClient.fromURI({
101
+ uri: HTTPS_INITIATE_QR,
102
+ kid: 'did:example:ebfeb1f712ebc6f1c276e12ec21/keys/1',
103
+ alg: Alg.ES256,
104
+ clientId: 'test-clientId',
105
+ });
106
+ await assertionOfsucceedWithAFullFlowWithClient(client);
107
+ });
108
+
109
+ it('should succeed with a full flow with the client using OpenID4VCI draft > 11, https and authorization_code flow', async () => {
110
+ succeedWithAFullFlowWithClientSetup();
111
+ const client = await OpenID4VCIClient.fromURI({
112
+ uri: HTTPS_OFFER_QR_AUTHORIZATION_CODE,
113
+ kid: 'did:example:ebfeb1f712ebc6f1c276e12ec21/keys/1',
114
+ alg: Alg.ES256,
115
+ clientId: 'test-clientId',
116
+ });
117
+ await assertionOfsucceedWithAFullFlowWithClient(client);
118
+ });
119
+
120
+ it('should succeed with a full flow with the client using OpenID4VCI draft > 11, https and preauthorized_code flow', async () => {
121
+ succeedWithAFullFlowWithClientSetup();
122
+ const client = await OpenID4VCIClient.fromURI({
123
+ uri: HTTPS_OFFER_QR_PRE_AUTHORIZED,
124
+ kid: 'did:example:ebfeb1f712ebc6f1c276e12ec21/keys/1',
125
+ alg: Alg.ES256,
126
+ clientId: 'test-clientId',
127
+ });
128
+ await assertionOfsucceedWithAFullFlowWithClient(client);
129
+ });
130
+
92
131
  async function assertionOfsucceedWithAFullFlowWithClient(client: OpenID4VCIClient) {
93
132
  expect(client.credentialOffer).toBeDefined();
94
133
  expect(client.endpointMetadata).toBeDefined();
@@ -96,7 +135,7 @@ describe('OID4VCI-Client should', () => {
96
135
  expect(client.getCredentialEndpoint()).toEqual('https://issuer.research.identiproof.io/credential');
97
136
  expect(client.getAccessTokenEndpoint()).toEqual('https://auth.research.identiproof.io/oauth2/token');
98
137
 
99
- const accessToken = await client.acquireAccessToken({ pin: '1234' });
138
+ const accessToken = await client.acquireAccessToken({ pin: '1234', code: 'ABCD' });
100
139
  expect(accessToken).toEqual(mockedAccessTokenResponse);
101
140
 
102
141
  const credentialResponse = await client.acquireCredentials({
@@ -58,4 +58,26 @@ describe('Issuance Initiation', () => {
58
58
  expect(client.credential_offer.credential_issuer).toEqual('https://launchpad.vii.electron.mattrlabs.io');
59
59
  expect(client.preAuthorizedCode).toEqual('UPZohaodPlLBnGsqB02n2tIupCIg8nKRRUEUHWA665X');
60
60
  });
61
+
62
+ it('Should take an https url as input and return a Credential Offer', async () => {
63
+ const client = await CredentialOfferClient.fromURI(
64
+ 'https://launchpad.vii.electron.mattrlabs.io?credential_offer=%7B%22credential_issuer%22%3A%22https%3A%2F%2Flaunchpad.vii.electron.mattrlabs.io%22%2C%22credentials%22%3A%5B%7B%22format%22%3A%22ldp_vc%22%2C%22types%22%3A%5B%22OpenBadgeCredential%22%5D%7D%5D%2C%22grants%22%3A%7B%22urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Apre-authorized_code%22%3A%7B%22pre-authorized_code%22%3A%22UPZohaodPlLBnGsqB02n2tIupCIg8nKRRUEUHWA665X%22%7D%7D%7D',
65
+ );
66
+ expect(client.version).toEqual(OpenId4VCIVersion.VER_1_0_11);
67
+ expect(client.baseUrl).toEqual('https://launchpad.vii.electron.mattrlabs.io');
68
+ expect(client.scheme).toEqual('https');
69
+ expect(client.credential_offer.credential_issuer).toEqual('https://launchpad.vii.electron.mattrlabs.io');
70
+ expect(client.preAuthorizedCode).toEqual('UPZohaodPlLBnGsqB02n2tIupCIg8nKRRUEUHWA665X');
71
+ })
72
+
73
+ it('Should take an http url as input and return a Credential Offer', async () => {
74
+ const client = await CredentialOfferClient.fromURI(
75
+ 'http://launchpad.vii.electron.mattrlabs.io?credential_offer=%7B%22credential_issuer%22%3A%22http%3A%2F%2Flaunchpad.vii.electron.mattrlabs.io%22%2C%22credentials%22%3A%5B%7B%22format%22%3A%22ldp_vc%22%2C%22types%22%3A%5B%22OpenBadgeCredential%22%5D%7D%5D%2C%22grants%22%3A%7B%22urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Apre-authorized_code%22%3A%7B%22pre-authorized_code%22%3A%22UPZohaodPlLBnGsqB02n2tIupCIg8nKRRUEUHWA665X%22%7D%7D%7D',
76
+ );
77
+ expect(client.version).toEqual(OpenId4VCIVersion.VER_1_0_11);
78
+ expect(client.baseUrl).toEqual('http://launchpad.vii.electron.mattrlabs.io');
79
+ expect(client.scheme).toEqual('http');
80
+ expect(client.credential_offer.credential_issuer).toEqual('http://launchpad.vii.electron.mattrlabs.io');
81
+ expect(client.preAuthorizedCode).toEqual('UPZohaodPlLBnGsqB02n2tIupCIg8nKRRUEUHWA665X');
82
+ })
61
83
  });
@@ -9,7 +9,7 @@ import { IDENTIPROOF_ISSUER_URL } from './MetadataMocks';
9
9
 
10
10
  const jwt: Jwt = {
11
11
  header: { alg: Alg.ES256, kid: 'did:example:ebfeb1f712ebc6f1c276e12ec21/keys/1', typ: 'jwt' },
12
- payload: { iss: 'sphereon:wallet', nonce: 'tZignsnFbp', jti: 'tZignsnFbp223', aud: IDENTIPROOF_ISSUER_URL, iat: Date.now()/1000 },
12
+ payload: { iss: 'sphereon:wallet', nonce: 'tZignsnFbp', jti: 'tZignsnFbp223', aud: IDENTIPROOF_ISSUER_URL, iat: Date.now() / 1000 },
13
13
  };
14
14
 
15
15
  const kid = 'did:example:ebfeb1f712ebc6f1c276e12ec21/keys/1';
@@ -43,7 +43,7 @@ const vcIssuer = new VcIssuerBuilder()
43
43
  },
44
44
  payload: {
45
45
  aud: issuerMetadata.credential_issuer,
46
- iat: +new Date()/1000,
46
+ iat: +new Date() / 1000,
47
47
  nonce: 'a-c-nonce',
48
48
  },
49
49
  },
@@ -152,6 +152,7 @@ describe('sd-jwt vc', () => {
152
152
  });
153
153
 
154
154
  expect(credentials).toEqual({
155
+ notification_id: expect.any(String),
155
156
  c_nonce: 'new-c-nonce',
156
157
  c_nonce_expires_in: 300,
157
158
  credential: 'sd-jwt',
@@ -115,7 +115,8 @@ async function proofOfPossessionCallbackFunction(args: Jwt, kid?: string): Promi
115
115
  }
116
116
 
117
117
  describe('ismapolis bug report #63, https://github.com/Sphereon-Opensource/OID4VC-demo/issues/63, should', () => {
118
- it('work as expected provided a correct JWT is supplied', async () => {
118
+ // Sphereon infra is not working currently
119
+ it.skip('work as expected provided a correct JWT is supplied', async () => {
119
120
  debug.enable('*');
120
121
  const { uri } = await getCredentialOffer('jwt_vc_json');
121
122
  const client = await OpenID4VCIClient.fromURI({ uri: uri, clientId: 'test-clientID' });
@@ -1,3 +1 @@
1
- export * from '@sphereon/oid4vci-common/dist/functions/Encoding';
2
- export * from '@sphereon/oid4vci-common/dist/functions/HttpUtils';
3
- export * from './ProofUtil';
1
+ export * from './AuthorizationUtil';
@@ -0,0 +1,6 @@
1
+ import { VCI_LOGGERS } from '@sphereon/oid4vci-common';
2
+ import { ISimpleLogger, LogMethod } from '@sphereon/ssi-types';
3
+
4
+ export const LOG: ISimpleLogger<string> = VCI_LOGGERS.options('sphereon:oid4vci:client', { methods: [LogMethod.EVENT, LogMethod.DEBUG_PKG] }).get(
5
+ 'sphereon:oid4vci:client',
6
+ );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sphereon/oid4vci-client",
3
- "version": "0.10.3-next.8+afc2a8a",
3
+ "version": "0.10.4-next.14+68c89f4",
4
4
  "description": "OpenID for Verifiable Credential Issuance (OpenID4VCI) client",
5
5
  "source": "lib/index.ts",
6
6
  "main": "dist/index.js",
@@ -15,8 +15,8 @@
15
15
  "build": "tsc"
16
16
  },
17
17
  "dependencies": {
18
- "@sphereon/oid4vci-common": "0.10.3-next.8+afc2a8a",
19
- "@sphereon/ssi-types": "^0.23.0",
18
+ "@sphereon/oid4vci-common": "0.10.4-next.14+68c89f4",
19
+ "@sphereon/ssi-types": "0.24.1-unstable.112",
20
20
  "cross-fetch": "^3.1.8",
21
21
  "debug": "^4.3.4"
22
22
  },
@@ -69,5 +69,5 @@
69
69
  "OIDC4VCI",
70
70
  "OID4VCI"
71
71
  ],
72
- "gitHead": "afc2a8a9171bae7e30ed7c7d9bd094d8cbd49b80"
72
+ "gitHead": "68c89f45e627577608d1b10ab260f6810a46fe88"
73
73
  }
@@ -1,30 +0,0 @@
1
- import { JWK, Jwt, ProofOfPossession, ProofOfPossessionCallbacks, Typ } from '@sphereon/oid4vci-common';
2
- /**
3
- *
4
- * - proofOfPossessionCallback: JWTSignerCallback
5
- * Mandatory if you want to create (sign) ProofOfPossession
6
- * - proofOfPossessionVerifierCallback?: JWTVerifyCallback
7
- * If exists, verifies the ProofOfPossession
8
- * - proofOfPossessionCallbackArgs: ProofOfPossessionCallbackArgs
9
- * arguments needed for signing ProofOfPossession
10
- * @param callbacks:
11
- * - proofOfPossessionCallback: JWTSignerCallback
12
- * Mandatory to create (sign) ProofOfPossession
13
- * - proofOfPossessionVerifierCallback?: JWTVerifyCallback
14
- * If exists, verifies the ProofOfPossession
15
- * @param jwtProps
16
- * @param existingJwt
17
- * - Optional, clientId of the party requesting the credential
18
- */
19
- export declare const createProofOfPossession: <DIDDoc>(callbacks: ProofOfPossessionCallbacks<DIDDoc>, jwtProps?: JwtProps, existingJwt?: Jwt) => Promise<ProofOfPossession>;
20
- export interface JwtProps {
21
- typ?: Typ;
22
- kid?: string;
23
- jwk?: JWK;
24
- issuer?: string;
25
- clientId?: string;
26
- alg?: string;
27
- jti?: string;
28
- nonce?: string;
29
- }
30
- //# sourceMappingURL=ProofUtil.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"ProofUtil.d.ts","sourceRoot":"","sources":["../../lib/functions/ProofUtil.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,GAAG,EAEH,GAAG,EAGH,iBAAiB,EACjB,0BAA0B,EAC1B,GAAG,EACJ,MAAM,0BAA0B,CAAC;AAKlC;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,uBAAuB,qEAEvB,QAAQ,gBACL,GAAG,KAChB,QAAQ,iBAAiB,CA0B3B,CAAC;AAQF,MAAM,WAAW,QAAQ;IACvB,GAAG,CAAC,EAAE,GAAG,CAAC;IACV,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,GAAG,CAAC;IACV,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB"}
@@ -1,106 +0,0 @@
1
- "use strict";
2
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
- return new (P || (P = Promise))(function (resolve, reject) {
5
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
- step((generator = generator.apply(thisArg, _arguments || [])).next());
9
- });
10
- };
11
- var __importDefault = (this && this.__importDefault) || function (mod) {
12
- return (mod && mod.__esModule) ? mod : { "default": mod };
13
- };
14
- Object.defineProperty(exports, "__esModule", { value: true });
15
- exports.createProofOfPossession = void 0;
16
- const oid4vci_common_1 = require("@sphereon/oid4vci-common");
17
- const debug_1 = __importDefault(require("debug"));
18
- const debug = (0, debug_1.default)('sphereon:openid4vci:token');
19
- /**
20
- *
21
- * - proofOfPossessionCallback: JWTSignerCallback
22
- * Mandatory if you want to create (sign) ProofOfPossession
23
- * - proofOfPossessionVerifierCallback?: JWTVerifyCallback
24
- * If exists, verifies the ProofOfPossession
25
- * - proofOfPossessionCallbackArgs: ProofOfPossessionCallbackArgs
26
- * arguments needed for signing ProofOfPossession
27
- * @param callbacks:
28
- * - proofOfPossessionCallback: JWTSignerCallback
29
- * Mandatory to create (sign) ProofOfPossession
30
- * - proofOfPossessionVerifierCallback?: JWTVerifyCallback
31
- * If exists, verifies the ProofOfPossession
32
- * @param jwtProps
33
- * @param existingJwt
34
- * - Optional, clientId of the party requesting the credential
35
- */
36
- const createProofOfPossession = (callbacks, jwtProps, existingJwt) => __awaiter(void 0, void 0, void 0, function* () {
37
- if (!callbacks.signCallback) {
38
- debug(`no jwt signer callback or arguments supplied!`);
39
- throw new Error(oid4vci_common_1.BAD_PARAMS);
40
- }
41
- const signerArgs = createJWT(jwtProps, existingJwt);
42
- const jwt = yield callbacks.signCallback(signerArgs, signerArgs.header.kid);
43
- const proof = {
44
- proof_type: 'jwt',
45
- jwt,
46
- };
47
- try {
48
- partiallyValidateJWS(jwt);
49
- if (callbacks.verifyCallback) {
50
- debug(`Calling supplied verify callback....`);
51
- yield callbacks.verifyCallback({ jwt, kid: signerArgs.header.kid });
52
- debug(`Supplied verify callback return success result`);
53
- }
54
- }
55
- catch (_a) {
56
- debug(`JWS was not valid`);
57
- throw new Error(oid4vci_common_1.JWS_NOT_VALID);
58
- }
59
- debug(`Proof of Possession JWT:\r\n${jwt}`);
60
- return proof;
61
- });
62
- exports.createProofOfPossession = createProofOfPossession;
63
- const partiallyValidateJWS = (jws) => {
64
- if (jws.split('.').length !== 3 || !jws.startsWith('ey')) {
65
- throw new Error(oid4vci_common_1.JWS_NOT_VALID);
66
- }
67
- };
68
- const createJWT = (jwtProps, existingJwt) => {
69
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
70
- const aud = getJwtProperty('aud', true, jwtProps === null || jwtProps === void 0 ? void 0 : jwtProps.issuer, (_a = existingJwt === null || existingJwt === void 0 ? void 0 : existingJwt.payload) === null || _a === void 0 ? void 0 : _a.aud);
71
- const iss = getJwtProperty('iss', false, jwtProps === null || jwtProps === void 0 ? void 0 : jwtProps.clientId, (_b = existingJwt === null || existingJwt === void 0 ? void 0 : existingJwt.payload) === null || _b === void 0 ? void 0 : _b.iss);
72
- const jti = getJwtProperty('jti', false, jwtProps === null || jwtProps === void 0 ? void 0 : jwtProps.jti, (_c = existingJwt === null || existingJwt === void 0 ? void 0 : existingJwt.payload) === null || _c === void 0 ? void 0 : _c.jti);
73
- const typ = getJwtProperty('typ', true, jwtProps === null || jwtProps === void 0 ? void 0 : jwtProps.typ, (_d = existingJwt === null || existingJwt === void 0 ? void 0 : existingJwt.header) === null || _d === void 0 ? void 0 : _d.typ, 'jwt');
74
- const nonce = getJwtProperty('nonce', false, jwtProps === null || jwtProps === void 0 ? void 0 : jwtProps.nonce, (_e = existingJwt === null || existingJwt === void 0 ? void 0 : existingJwt.payload) === null || _e === void 0 ? void 0 : _e.nonce); // Officially this is required, but some implementations don't have it
75
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
76
- const alg = getJwtProperty('alg', false, jwtProps === null || jwtProps === void 0 ? void 0 : jwtProps.alg, (_f = existingJwt === null || existingJwt === void 0 ? void 0 : existingJwt.header) === null || _f === void 0 ? void 0 : _f.alg, 'ES256');
77
- const kid = getJwtProperty('kid', false, jwtProps === null || jwtProps === void 0 ? void 0 : jwtProps.kid, (_g = existingJwt === null || existingJwt === void 0 ? void 0 : existingJwt.header) === null || _g === void 0 ? void 0 : _g.kid);
78
- const jwk = getJwtProperty('jwk', false, jwtProps === null || jwtProps === void 0 ? void 0 : jwtProps.jwk, (_h = existingJwt === null || existingJwt === void 0 ? void 0 : existingJwt.header) === null || _h === void 0 ? void 0 : _h.jwk);
79
- const jwt = existingJwt ? existingJwt : {};
80
- const now = +new Date();
81
- const jwtPayload = Object.assign(Object.assign({ aud, iat: (_k = (_j = jwt.payload) === null || _j === void 0 ? void 0 : _j.iat) !== null && _k !== void 0 ? _k : Math.round(now / 1000 - 60), exp: (_m = (_l = jwt.payload) === null || _l === void 0 ? void 0 : _l.exp) !== null && _m !== void 0 ? _m : Math.round(now / 1000 + 10 * 60), nonce }, (iss ? { iss } : {})), (jti ? { jti } : {}));
82
- const jwtHeader = {
83
- typ,
84
- alg,
85
- kid,
86
- jwk,
87
- };
88
- return {
89
- payload: Object.assign(Object.assign({}, jwt.payload), jwtPayload),
90
- header: Object.assign(Object.assign({}, jwt.header), jwtHeader),
91
- };
92
- };
93
- const getJwtProperty = (propertyName, required, option, jwtProperty, defaultValue) => {
94
- if (typeof option === 'string' && option && jwtProperty && option !== jwtProperty) {
95
- throw Error(`Cannot have a property '${propertyName}' with value '${option}' and different JWT value '${jwtProperty}' at the same time`);
96
- }
97
- let result = (jwtProperty ? jwtProperty : option);
98
- if (!result) {
99
- if (required) {
100
- throw Error(`No ${propertyName} property provided either in a JWT or as option`);
101
- }
102
- result = defaultValue;
103
- }
104
- return result;
105
- };
106
- //# sourceMappingURL=ProofUtil.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"ProofUtil.js","sourceRoot":"","sources":["../../lib/functions/ProofUtil.ts"],"names":[],"mappings":"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"}