@sphereon/oid4vci-client 0.8.2-next.48 → 0.8.2-next.87

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/dist/AccessTokenClient.d.ts +0 -1
  2. package/dist/AccessTokenClient.d.ts.map +1 -1
  3. package/dist/AccessTokenClient.js +2 -9
  4. package/dist/AccessTokenClient.js.map +1 -1
  5. package/dist/AuthorizationCodeClient.d.ts +9 -0
  6. package/dist/AuthorizationCodeClient.d.ts.map +1 -0
  7. package/dist/AuthorizationCodeClient.js +124 -0
  8. package/dist/AuthorizationCodeClient.js.map +1 -0
  9. package/dist/CredentialOfferClient.d.ts.map +1 -1
  10. package/dist/CredentialOfferClient.js +3 -1
  11. package/dist/CredentialOfferClient.js.map +1 -1
  12. package/dist/CredentialRequestClient.d.ts +2 -0
  13. package/dist/CredentialRequestClient.d.ts.map +1 -1
  14. package/dist/CredentialRequestClient.js +9 -7
  15. package/dist/CredentialRequestClient.js.map +1 -1
  16. package/dist/OpenID4VCIClient.d.ts +50 -29
  17. package/dist/OpenID4VCIClient.d.ts.map +1 -1
  18. package/dist/OpenID4VCIClient.js +191 -190
  19. package/dist/OpenID4VCIClient.js.map +1 -1
  20. package/dist/functions/AuthorizationUtil.d.ts +3 -0
  21. package/dist/functions/AuthorizationUtil.d.ts.map +1 -0
  22. package/dist/functions/AuthorizationUtil.js +22 -0
  23. package/dist/functions/AuthorizationUtil.js.map +1 -0
  24. package/dist/index.d.ts +1 -0
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +1 -0
  27. package/dist/index.js.map +1 -1
  28. package/dist/types/index.d.ts +1 -0
  29. package/dist/types/index.d.ts.map +1 -0
  30. package/dist/types/index.js +2 -0
  31. package/dist/types/index.js.map +1 -0
  32. package/lib/AccessTokenClient.ts +5 -11
  33. package/lib/AuthorizationCodeClient.ts +151 -0
  34. package/lib/CredentialOfferClient.ts +4 -1
  35. package/lib/CredentialRequestClient.ts +13 -4
  36. package/lib/OpenID4VCIClient.ts +250 -228
  37. package/lib/__tests__/AccessTokenClient.spec.ts +2 -0
  38. package/lib/__tests__/CredentialRequestClient.spec.ts +10 -2
  39. package/lib/__tests__/EBSIE2E.spec.test.ts +8 -6
  40. package/lib/__tests__/OpenID4VCIClient.spec.ts +115 -79
  41. package/lib/__tests__/OpenID4VCIClientPAR.spec.ts +59 -49
  42. package/lib/__tests__/SdJwt.spec.ts +2 -0
  43. package/lib/__tests__/SphereonE2E.spec.test.ts +2 -2
  44. package/lib/functions/AuthorizationUtil.ts +18 -0
  45. package/lib/index.ts +1 -0
  46. package/lib/types/index.ts +0 -0
  47. package/package.json +3 -3
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.generateMissingPKCEOpts = void 0;
4
+ const oid4vci_common_1 = require("@sphereon/oid4vci-common");
5
+ const generateMissingPKCEOpts = (pkce) => {
6
+ if (pkce.disabled) {
7
+ return pkce;
8
+ }
9
+ if (!pkce.codeChallengeMethod) {
10
+ pkce.codeChallengeMethod = oid4vci_common_1.CodeChallengeMethod.S256;
11
+ }
12
+ if (!pkce.codeVerifier) {
13
+ pkce.codeVerifier = (0, oid4vci_common_1.generateCodeVerifier)();
14
+ }
15
+ (0, oid4vci_common_1.assertValidCodeVerifier)(pkce.codeVerifier);
16
+ if (!pkce.codeChallenge) {
17
+ pkce.codeChallenge = (0, oid4vci_common_1.createCodeChallenge)(pkce.codeVerifier, pkce.codeChallengeMethod);
18
+ }
19
+ return pkce;
20
+ };
21
+ exports.generateMissingPKCEOpts = generateMissingPKCEOpts;
22
+ //# sourceMappingURL=AuthorizationUtil.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AuthorizationUtil.js","sourceRoot":"","sources":["../../lib/functions/AuthorizationUtil.ts"],"names":[],"mappings":";;;AAAA,6DAA6I;AAEtI,MAAM,uBAAuB,GAAG,CAAC,IAAc,EAAE,EAAE;IACxD,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;QAClB,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IAAI,CAAC,IAAI,CAAC,mBAAmB,EAAE,CAAC;QAC9B,IAAI,CAAC,mBAAmB,GAAG,oCAAmB,CAAC,IAAI,CAAC;IACtD,CAAC;IACD,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,CAAC;QACvB,IAAI,CAAC,YAAY,GAAG,IAAA,qCAAoB,GAAE,CAAC;IAC7C,CAAC;IACD,IAAA,wCAAuB,EAAC,IAAI,CAAC,YAAY,CAAC,CAAC;IAC3C,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,CAAC;QACxB,IAAI,CAAC,aAAa,GAAG,IAAA,oCAAmB,EAAC,IAAI,CAAC,YAAY,EAAE,IAAI,CAAC,mBAAmB,CAAC,CAAC;IACxF,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC,CAAC;AAfW,QAAA,uBAAuB,2BAelC"}
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from './AccessTokenClient';
2
+ export * from './CredentialRequestClient';
2
3
  export * from './CredentialOfferClient';
3
4
  export * from './CredentialRequestClient';
4
5
  export * from './CredentialRequestClientBuilder';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../lib/index.ts"],"names":[],"mappings":"AAAA,cAAc,qBAAqB,CAAC;AACpC,cAAc,yBAAyB,CAAC;AACxC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,kCAAkC,CAAC;AACjD,cAAc,aAAa,CAAC;AAC5B,cAAc,kBAAkB,CAAC;AACjC,cAAc,oBAAoB,CAAC;AACnC,cAAc,4BAA4B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../lib/index.ts"],"names":[],"mappings":"AAAA,cAAc,qBAAqB,CAAC;AACpC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,yBAAyB,CAAC;AACxC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,kCAAkC,CAAC;AACjD,cAAc,aAAa,CAAC;AAC5B,cAAc,kBAAkB,CAAC;AACjC,cAAc,oBAAoB,CAAC;AACnC,cAAc,4BAA4B,CAAC"}
package/dist/index.js CHANGED
@@ -15,6 +15,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./AccessTokenClient"), exports);
18
+ __exportStar(require("./CredentialRequestClient"), exports);
18
19
  __exportStar(require("./CredentialOfferClient"), exports);
19
20
  __exportStar(require("./CredentialRequestClient"), exports);
20
21
  __exportStar(require("./CredentialRequestClientBuilder"), exports);
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../lib/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,sDAAoC;AACpC,0DAAwC;AACxC,4DAA0C;AAC1C,mEAAiD;AACjD,8CAA4B;AAC5B,mDAAiC;AACjC,qDAAmC;AACnC,6DAA2C"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../lib/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,sDAAoC;AACpC,4DAA0C;AAC1C,0DAAwC;AACxC,4DAA0C;AAC1C,mEAAiD;AACjD,8CAA4B;AAC5B,mDAAiC;AACjC,qDAAmC;AACnC,6DAA2C"}
@@ -0,0 +1 @@
1
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../lib/types/index.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../lib/types/index.ts"],"names":[],"mappings":""}
@@ -9,6 +9,7 @@ import {
9
9
  getIssuerFromCredentialOfferPayload,
10
10
  GrantTypes,
11
11
  IssuerOpts,
12
+ JsonURIMode,
12
13
  OpenIDResponse,
13
14
  PRE_AUTH_CODE_LITERAL,
14
15
  TokenErrorResponse,
@@ -176,14 +177,6 @@ export class AccessTokenClient {
176
177
  throw new Error('Authorization flow requires the code to be present');
177
178
  }
178
179
  }
179
-
180
- private assertNonEmptyRedirectUri(accessTokenRequest: AccessTokenRequest): void {
181
- if (!accessTokenRequest.redirect_uri) {
182
- debug('No redirect_uri present, whilst it is required');
183
- throw new Error('Authorization flow requires the redirect_uri to be present');
184
- }
185
- }
186
-
187
180
  private validate(accessTokenRequest: AccessTokenRequest, isPinRequired?: boolean): void {
188
181
  if (accessTokenRequest.grant_type === GrantTypes.PRE_AUTHORIZED_CODE) {
189
182
  this.assertPreAuthorizedGrantType(accessTokenRequest.grant_type);
@@ -193,14 +186,13 @@ export class AccessTokenClient {
193
186
  this.assertAuthorizationGrantType(accessTokenRequest.grant_type);
194
187
  this.assertNonEmptyCodeVerifier(accessTokenRequest);
195
188
  this.assertNonEmptyCode(accessTokenRequest);
196
- this.assertNonEmptyRedirectUri(accessTokenRequest);
197
189
  } else {
198
190
  this.throwNotSupportedFlow();
199
191
  }
200
192
  }
201
193
 
202
194
  private async sendAuthCode(requestTokenURL: string, accessTokenRequest: AccessTokenRequest): Promise<OpenIDResponse<AccessTokenResponse>> {
203
- return await formPost(requestTokenURL, convertJsonToURI(accessTokenRequest));
195
+ return await formPost(requestTokenURL, convertJsonToURI(accessTokenRequest, { mode: JsonURIMode.X_FORM_WWW_URLENCODED }));
204
196
  }
205
197
 
206
198
  public static determineTokenURL({
@@ -236,7 +228,9 @@ export class AccessTokenClient {
236
228
 
237
229
  private static creatTokenURLFromURL(url: string, allowInsecureEndpoints?: boolean, tokenEndpoint?: string): string {
238
230
  if (allowInsecureEndpoints !== true && url.startsWith('http:')) {
239
- throw Error(`Unprotected token endpoints are not allowed ${url}. Adjust settings if you really need this (dev/test settings only!!)`);
231
+ throw Error(
232
+ `Unprotected token endpoints are not allowed ${url}. Use the 'allowInsecureEndpoints' param if you really need this for dev/testing!`,
233
+ );
240
234
  }
241
235
  const hostname = url.replace(/https?:\/\//, '').replace(/\/$/, '');
242
236
  const endpoint = tokenEndpoint ? (tokenEndpoint.startsWith('/') ? tokenEndpoint : tokenEndpoint.substring(1)) : '/token';
@@ -0,0 +1,151 @@
1
+ import {
2
+ AuthorizationDetails,
3
+ AuthorizationRequestOpts,
4
+ CodeChallengeMethod,
5
+ convertJsonToURI,
6
+ CredentialOfferRequestWithBaseUrl,
7
+ CredentialSupported,
8
+ EndpointMetadataResult,
9
+ JsonURIMode,
10
+ PARMode,
11
+ PKCEOpts,
12
+ PushedAuthorizationResponse,
13
+ ResponseType,
14
+ } from '@sphereon/oid4vci-common';
15
+ import { formPost } from '@sphereon/oid4vci-common';
16
+ import Debug from 'debug';
17
+
18
+ const debug = Debug('sphereon:oid4vci');
19
+
20
+ export const createAuthorizationRequestUrl = async ({
21
+ pkce,
22
+ endpointMetadata,
23
+ authorizationRequest,
24
+ credentialOffer,
25
+ credentialsSupported,
26
+ }: {
27
+ pkce: PKCEOpts;
28
+ endpointMetadata: EndpointMetadataResult;
29
+ authorizationRequest: AuthorizationRequestOpts;
30
+ credentialOffer?: CredentialOfferRequestWithBaseUrl;
31
+ credentialsSupported?: CredentialSupported[];
32
+ }): Promise<string> => {
33
+ const { redirectUri, clientId } = authorizationRequest;
34
+ let { scope, authorizationDetails } = authorizationRequest;
35
+ const parMode = endpointMetadata?.credentialIssuerMetadata?.require_pushed_authorization_requests
36
+ ? PARMode.REQUIRE
37
+ : authorizationRequest.parMode ?? PARMode.AUTO;
38
+ // Scope and authorization_details can be used in the same authorization request
39
+ // https://datatracker.ietf.org/doc/html/draft-ietf-oauth-rar-23#name-relationship-to-scope-param
40
+ if (!scope && !authorizationDetails) {
41
+ if (!credentialOffer) {
42
+ throw Error('Please provide a scope or authorization_details if no credential offer is present');
43
+ }
44
+ const creds = credentialOffer.credential_offer.credentials;
45
+
46
+ // FIXME: complains about VCT for sd-jwt
47
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
48
+ // @ts-ignore
49
+ authorizationDetails = creds
50
+ .flatMap((cred) => (typeof cred === 'string' ? credentialsSupported : (cred as CredentialSupported)))
51
+ .filter((cred) => !!cred)
52
+ .map((cred) => {
53
+ return {
54
+ ...cred,
55
+ type: 'openid_credential',
56
+ locations: [endpointMetadata.issuer],
57
+
58
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
59
+ // @ts-ignore
60
+ format: cred!.format,
61
+ } satisfies AuthorizationDetails;
62
+ });
63
+ if (!authorizationDetails || authorizationDetails.length === 0) {
64
+ throw Error(`Could not create authorization details from credential offer. Please pass in explicit details`);
65
+ }
66
+ }
67
+ if (!endpointMetadata?.authorization_endpoint) {
68
+ throw Error('Server metadata does not contain authorization endpoint');
69
+ }
70
+ const parEndpoint = endpointMetadata.credentialIssuerMetadata?.pushed_authorization_request_endpoint;
71
+
72
+ // add 'openid' scope if not present
73
+ if (!scope?.includes('openid')) {
74
+ scope = ['openid', scope].filter((s) => !!s).join(' ');
75
+ }
76
+
77
+ let queryObj: { [key: string]: string } | PushedAuthorizationResponse = {
78
+ response_type: ResponseType.AUTH_CODE,
79
+ ...(!pkce.disabled && {
80
+ code_challenge_method: pkce.codeChallengeMethod ?? CodeChallengeMethod.S256,
81
+ code_challenge: pkce.codeChallenge,
82
+ }),
83
+ authorization_details: JSON.stringify(handleAuthorizationDetails(endpointMetadata, authorizationDetails)),
84
+ ...(redirectUri && { redirect_uri: redirectUri }),
85
+ ...(clientId && { client_id: clientId }),
86
+ ...(credentialOffer?.issuerState && { issuer_state: credentialOffer.issuerState }),
87
+ scope,
88
+ };
89
+
90
+ if (!parEndpoint && parMode === PARMode.REQUIRE) {
91
+ throw Error(`PAR mode is set to required by Authorization Server does not support PAR!`);
92
+ } else if (parEndpoint && parMode !== PARMode.NEVER) {
93
+ debug(`USING PAR with endpoint ${parEndpoint}`);
94
+ const parResponse = await formPost<PushedAuthorizationResponse>(parEndpoint, new URLSearchParams(queryObj));
95
+ if (parResponse.errorBody || !parResponse.successBody) {
96
+ throw Error(`PAR error`);
97
+ }
98
+ debug(`PAR response: ${(parResponse.successBody, null, 2)}`);
99
+ queryObj = { request_uri: parResponse.successBody.request_uri };
100
+ }
101
+
102
+ debug(`Object that will become query params: ` + JSON.stringify(queryObj, null, 2));
103
+ const url = convertJsonToURI(queryObj, {
104
+ baseUrl: endpointMetadata.authorization_endpoint,
105
+ uriTypeProperties: ['client_id', 'request_uri', 'redirect_uri', 'scope', 'authorization_details', 'issuer_state'],
106
+ // arrayTypeProperties: ['authorization_details'],
107
+ mode: JsonURIMode.X_FORM_WWW_URLENCODED,
108
+ // We do not add the version here, as this always needs to be form encoded
109
+ });
110
+ debug(`Authorization Request URL: ${url}`);
111
+ return url;
112
+ };
113
+
114
+ const handleAuthorizationDetails = (
115
+ endpointMetadata: EndpointMetadataResult,
116
+ authorizationDetails?: AuthorizationDetails | AuthorizationDetails[],
117
+ ): AuthorizationDetails | AuthorizationDetails[] | undefined => {
118
+ if (authorizationDetails) {
119
+ if (typeof authorizationDetails === 'string') {
120
+ // backwards compat for older versions of the lib
121
+ return authorizationDetails;
122
+ }
123
+ if (Array.isArray(authorizationDetails)) {
124
+ return authorizationDetails
125
+ .filter((value) => typeof value !== 'string')
126
+ .map((value) => handleLocations(endpointMetadata, typeof value === 'string' ? value : { ...value }));
127
+ } else {
128
+ return handleLocations(endpointMetadata, { ...authorizationDetails });
129
+ }
130
+ }
131
+ return authorizationDetails;
132
+ };
133
+
134
+ const handleLocations = (endpointMetadata: EndpointMetadataResult, authorizationDetails: AuthorizationDetails) => {
135
+ if (typeof authorizationDetails === 'string') {
136
+ // backwards compat for older versions of the lib
137
+ return authorizationDetails;
138
+ }
139
+ if (authorizationDetails && (endpointMetadata.credentialIssuerMetadata?.authorization_server || endpointMetadata.authorization_endpoint)) {
140
+ if (authorizationDetails.locations) {
141
+ if (Array.isArray(authorizationDetails.locations)) {
142
+ authorizationDetails.locations.push(endpointMetadata.issuer);
143
+ } else {
144
+ authorizationDetails.locations = [authorizationDetails.locations as string, endpointMetadata.issuer];
145
+ }
146
+ } else {
147
+ authorizationDetails.locations = [endpointMetadata.issuer];
148
+ }
149
+ }
150
+ return authorizationDetails;
151
+ };
@@ -5,6 +5,7 @@ import {
5
5
  CredentialOfferRequestWithBaseUrl,
6
6
  CredentialOfferV1_0_11,
7
7
  determineSpecVersionFromURI,
8
+ getClientIdFromCredentialOfferPayload,
8
9
  OpenId4VCIVersion,
9
10
  toUniformCredentialOfferRequest,
10
11
  } from '@sphereon/oid4vci-common';
@@ -43,16 +44,18 @@ export class CredentialOfferClient {
43
44
  throw Error('Either a credential_offer or credential_offer_uri should be present in ' + uri);
44
45
  }
45
46
  }
47
+
46
48
  const request = await toUniformCredentialOfferRequest(credentialOffer, {
47
49
  ...opts,
48
50
  version,
49
51
  });
50
-
52
+ const clientId = getClientIdFromCredentialOfferPayload(request.credential_offer);
51
53
  const grants = request.credential_offer?.grants;
52
54
 
53
55
  return {
54
56
  scheme,
55
57
  baseUrl,
58
+ clientId,
56
59
  ...request,
57
60
  ...(grants?.authorization_code?.issuer_state && { issuerState: grants.authorization_code.issuer_state }),
58
61
  ...(grants?.['urn:ietf:params:oauth:grant-type:pre-authorized_code']?.['pre-authorized_code'] && {
@@ -78,11 +78,12 @@ export class CredentialRequestClient {
78
78
  public async acquireCredentialsUsingProof<DIDDoc>(opts: {
79
79
  proofInput: ProofOfPossessionBuilder<DIDDoc> | ProofOfPossession;
80
80
  credentialTypes?: string | string[];
81
+ context?: string[];
81
82
  format?: CredentialFormat | OID4VCICredentialFormat;
82
83
  }): Promise<OpenIDResponse<CredentialResponse>> {
83
- const { credentialTypes, proofInput, format } = opts;
84
+ const { credentialTypes, proofInput, format, context } = opts;
84
85
 
85
- const request = await this.createCredentialRequest({ proofInput, credentialTypes, format, version: this.version() });
86
+ const request = await this.createCredentialRequest({ proofInput, credentialTypes, context, format, version: this.version() });
86
87
  return await this.acquireCredentialsUsingRequest(request);
87
88
  }
88
89
 
@@ -133,6 +134,7 @@ export class CredentialRequestClient {
133
134
  public async createCredentialRequest<DIDDoc>(opts: {
134
135
  proofInput: ProofOfPossessionBuilder<DIDDoc> | ProofOfPossession;
135
136
  credentialTypes?: string | string[];
137
+ context?: string[];
136
138
  format?: CredentialFormat | OID4VCICredentialFormat;
137
139
  version: OpenId4VCIVersion;
138
140
  }): Promise<UniformCredentialRequest> {
@@ -165,13 +167,20 @@ export class CredentialRequestClient {
165
167
  proof,
166
168
  };
167
169
  } else if (format === 'jwt_vc_json-ld' || format === 'ldp_vc') {
170
+ if (this.version() >= OpenId4VCIVersion.VER_1_0_12 && !opts.context) {
171
+ throw Error('No @context value present, but it is required');
172
+ }
173
+
168
174
  return {
169
175
  format,
170
176
  proof,
177
+
178
+ // Ignored because v11 does not have the context value, but it is required in v12
179
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
180
+ // @ts-ignore
171
181
  credential_definition: {
172
182
  types,
173
- // FIXME: this was not included in the original code, but it is required
174
- '@context': [],
183
+ ...(opts.context && { '@context': opts.context }),
175
184
  },
176
185
  };
177
186
  } else if (format === 'vc+sd-jwt') {