Skip to content

Commit eadbba0

Browse files
committed
fix: Ensure we have a single client that handles both v13 and v11 and lower
1 parent ccbcaa7 commit eadbba0

22 files changed

Lines changed: 1330 additions & 246 deletions

packages/client/CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ Release with support for the pre-authorized code flow only!
166166
- Documentation updates/fixes
167167

168168
- Fixes:
169-
- The acquireCredential in the OpenID4VCIClient was not using the access token, resulting in auth issues.
169+
- The acquireCredential in the OpenID4VCIClientV1_0_13 was not using the access token, resulting in auth issues.
170170

171171
## v0.3.1 - 2022-11-20
172172

packages/client/README.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,10 @@ This initiates the client using a URI obtained from the Issuer using a link (URL
5252
already fetching the Server Metadata
5353

5454
```typescript
55-
import { OpenID4VCIClient } from '@sphereon/oid4vci-client';
55+
import { OpenID4VCIClientV1_0_13 } from '@sphereon/oid4vci-client';
5656

5757
// The client is initiated from a URI. This URI is provided by the Issuer, typically as a URL or QR code.
58-
const client = await OpenID4VCIClient.fromURI({
58+
const client = await OpenID4VCIClientV1_0_13.fromURI({
5959
uri: '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',
6060
kid: 'did:example:ebfeb1f712ebc6f1c276e12ec21#key-1', // Our DID. You can defer this also to when the acquireCredential method is called
6161
alg: Alg.ES256, // The signing Algorithm we will use. You can defer this also to when the acquireCredential method is called
@@ -71,10 +71,10 @@ console.log(client.getAccessTokenEndpoint()); // https://auth.research.identipro
7171
Using https scheme
7272

7373
```typescript
74-
import { OpenID4VCIClient } from '@sphereon/oid4vci-client';
74+
import { OpenID4VCIClientV1_0_13 } from '@sphereon/oid4vci-client';
7575

7676
// The client is initiated from a URI. This URI is provided by the Issuer, typically as a URL or QR code.
77-
const client = await OpenID4VCIClient.fromURI({
77+
const client = await OpenID4VCIClientV1_0_13.fromURI({
7878
uri: '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',
7979
kid: 'did:example:ebfeb1f712ebc6f1c276e12ec21#key-1', // Our DID. You can defer this also to when the acquireCredential method is called
8080
alg: Alg.ES256, // The signing Algorithm we will use. You can defer this also to when the acquireCredential method is called
@@ -206,15 +206,15 @@ The OpenID4VCI spec defines a server metadata object that contains information a
206206
support. Next to this predefined endpoint there are also the well-known locations for OpenID Connect Discovery
207207
configuration and
208208
Oauth2 Authorization Server configuration. These contain for instance the token endpoints.
209-
The MetadataClient checks the OpenID4VCI well-known location for the medata and existence of a token endpoint. If the
209+
The MetadataClientV1_0_13 checks the OpenID4VCI well-known location for the medata and existence of a token endpoint. If the
210210
OpenID4VCI well-known location is not found, the OIDC/OAuth2 well-known locations will be tried:
211211

212212
Example:
213213

214214
```typescript
215-
import { MetadataClient } from '@sphereon/oid4vci-client';
215+
import { MetadataClientV1_0_13 } from '@sphereon/oid4vci-client';
216216

217-
const metadata = await MetadataClient.retrieveAllMetadataFromCredentialOffer(initiationRequestWithUrl);
217+
const metadata = await MetadataClientV1_0_13.retrieveAllMetadataFromCredentialOffer(initiationRequestWithUrl);
218218

219219
console.log(metadata);
220220
/**

packages/client/lib/AccessTokenClient.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import {
2525
} from '@sphereon/oid4vci-common';
2626
import { ObjectUtils } from '@sphereon/ssi-types';
2727

28-
import { MetadataClient } from './MetadataClient';
28+
import { MetadataClientV1_0_13 } from './MetadataClientV1_0_13';
2929
import { LOG } from './types';
3030

3131
export class AccessTokenClient {
@@ -82,7 +82,7 @@ export class AccessTokenClient {
8282
metadata: metadata
8383
? metadata
8484
: issuerOpts?.fetchMetadata
85-
? await MetadataClient.retrieveAllMetadata(issuerOpts.issuer, { errorOnNotFound: false })
85+
? await MetadataClientV1_0_13.retrieveAllMetadata(issuerOpts.issuer, { errorOnNotFound: false })
8686
: undefined,
8787
});
8888

packages/client/lib/AccessTokenClientV1_0_11.ts

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,28 +6,24 @@ import {
66
AuthorizationServerOpts,
77
AuthzFlowType,
88
convertJsonToURI,
9-
CredentialOfferPayloadV1_0_11,
109
CredentialOfferV1_0_11,
1110
CredentialOfferV1_0_13,
12-
determineSpecVersionFromOffer,
1311
EndpointMetadata,
1412
formPost,
1513
getIssuerFromCredentialOfferPayload,
1614
GrantTypes,
1715
IssuerOpts,
1816
JsonURIMode,
19-
OpenId4VCIVersion,
2017
OpenIDResponse,
2118
PRE_AUTH_CODE_LITERAL,
2219
TokenErrorResponse,
2320
toUniformCredentialOfferRequest,
24-
toUniformCredentialOfferRequestV1_0_11,
2521
UniformCredentialOfferPayload,
2622
} from '@sphereon/oid4vci-common';
2723
import { ObjectUtils } from '@sphereon/ssi-types';
2824
import Debug from 'debug';
2925

30-
import { MetadataClient } from './MetadataClient';
26+
import { MetadataClientV1_0_13 } from './MetadataClientV1_0_13';
3127

3228
const debug = Debug('sphereon:oid4vci:token');
3329

@@ -84,7 +80,7 @@ export class AccessTokenClientV1_0_11 {
8480
metadata: metadata
8581
? metadata
8682
: issuerOpts?.fetchMetadata
87-
? await MetadataClient.retrieveAllMetadata(issuerOpts.issuer, { errorOnNotFound: false })
83+
? await MetadataClientV1_0_13.retrieveAllMetadata(issuerOpts.issuer, { errorOnNotFound: false })
8884
: undefined,
8985
});
9086

@@ -94,9 +90,7 @@ export class AccessTokenClientV1_0_11 {
9490
public async createAccessTokenRequest(opts: AccessTokenRequestOpts): Promise<AccessTokenRequest> {
9591
const { asOpts, pin, codeVerifier, code, redirectUri } = opts;
9692
const credentialOfferRequest = opts.credentialOffer
97-
? determineSpecVersionFromOffer(opts.credentialOffer as CredentialOfferPayloadV1_0_11).valueOf() <= OpenId4VCIVersion.VER_1_0_11.valueOf()
98-
? await toUniformCredentialOfferRequestV1_0_11(opts.credentialOffer as CredentialOfferV1_0_11)
99-
: await toUniformCredentialOfferRequest(opts.credentialOffer as CredentialOfferV1_0_13)
93+
? await toUniformCredentialOfferRequest(opts.credentialOffer as CredentialOfferV1_0_11 | CredentialOfferV1_0_13)
10094
: undefined;
10195
const request: Partial<AccessTokenRequest> = {};
10296

packages/client/lib/CredentialOfferClientV1_0_11.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
determineSpecVersionFromURI,
1111
getClientIdFromCredentialOfferPayload,
1212
OpenId4VCIVersion,
13-
toUniformCredentialOfferRequestV1_0_11,
13+
toUniformCredentialOfferRequest,
1414
} from '@sphereon/oid4vci-common';
1515
import Debug from 'debug';
1616

@@ -31,22 +31,22 @@ export class CredentialOfferClientV1_0_11 {
3131
if (version < OpenId4VCIVersion.VER_1_0_11) {
3232
credentialOfferPayload = convertURIToJsonObject(uri, {
3333
arrayTypeProperties: ['credential_type'],
34-
requiredProperties: uri.includes('credential_offer_uri=') ? ['credential_offer_uri'] : ['issuer', 'credential_type'],
34+
requiredProperties: uri.includes('credential_offer_uri=') ? ['credential_offer_uri='] : ['issuer', 'credential_type='],
3535
}) as CredentialOfferPayloadV1_0_09;
3636
credentialOffer = {
3737
credential_offer: credentialOfferPayload,
3838
};
3939
} else {
4040
credentialOffer = convertURIToJsonObject(uri, {
4141
arrayTypeProperties: ['credentials'],
42-
requiredProperties: uri.includes('credential_offer_uri=') ? ['credential_offer_uri'] : ['credential_offer'],
42+
requiredProperties: uri.includes('credential_offer_uri=') ? ['credential_offer_uri='] : ['credential_offer='],
4343
}) as CredentialOfferV1_0_11;
4444
if (credentialOffer?.credential_offer_uri === undefined && !credentialOffer?.credential_offer) {
4545
throw Error('Either a credential_offer or credential_offer_uri should be present in ' + uri);
4646
}
4747
}
4848

49-
const request = await toUniformCredentialOfferRequestV1_0_11(credentialOffer, {
49+
const request = await toUniformCredentialOfferRequest(credentialOffer, {
5050
...opts,
5151
version,
5252
});

packages/client/lib/MetadataClient.ts

Lines changed: 49 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,24 @@
11
import {
22
AuthorizationServerMetadata,
33
AuthorizationServerType,
4+
CredentialIssuerMetadataV1_0_11,
45
CredentialIssuerMetadataV1_0_13,
6+
CredentialOfferPayload,
57
CredentialOfferPayloadV1_0_13,
68
CredentialOfferRequestWithBaseUrl,
9+
determineSpecVersionFromOffer,
10+
EndpointMetadataResultV1_0_11,
711
EndpointMetadataResultV1_0_13,
812
getIssuerFromCredentialOfferPayload,
9-
IssuerMetadataV1_0_13,
13+
IssuerMetadataV1_0_08,
14+
OpenId4VCIVersion,
1015
OpenIDResponse,
1116
WellKnownEndpoints,
1217
} from '@sphereon/oid4vci-common';
1318
import Debug from 'debug';
1419

20+
import { MetadataClientV1_0_11 } from './MetadataClientV1_0_11';
21+
import { MetadataClientV1_0_13 } from './MetadataClientV1_0_13';
1522
import { retrieveWellknown } from './functions/OpenIDUtils';
1623

1724
const debug = Debug('sphereon:oid4vci:metadata');
@@ -24,18 +31,28 @@ export class MetadataClient {
2431
*/
2532
public static async retrieveAllMetadataFromCredentialOffer(
2633
credentialOffer: CredentialOfferRequestWithBaseUrl,
27-
): Promise<EndpointMetadataResultV1_0_13> {
28-
return MetadataClient.retrieveAllMetadataFromCredentialOfferRequest(credentialOffer.credential_offer as CredentialOfferPayloadV1_0_13);
34+
): Promise<EndpointMetadataResultV1_0_13 | EndpointMetadataResultV1_0_11> {
35+
if (determineSpecVersionFromOffer(credentialOffer.credential_offer) >= OpenId4VCIVersion.VER_1_0_13) {
36+
return await MetadataClientV1_0_13.retrieveAllMetadataFromCredentialOffer(credentialOffer);
37+
} else {
38+
return await MetadataClientV1_0_11.retrieveAllMetadataFromCredentialOffer(credentialOffer);
39+
}
2940
}
3041

3142
/**
3243
* Retrieve the metada using the initiation request obtained from a previous step
3344
* @param request
3445
*/
35-
public static async retrieveAllMetadataFromCredentialOfferRequest(request: CredentialOfferPayloadV1_0_13): Promise<EndpointMetadataResultV1_0_13> {
46+
public static async retrieveAllMetadataFromCredentialOfferRequest(
47+
request: CredentialOfferPayload,
48+
): Promise<EndpointMetadataResultV1_0_13 | EndpointMetadataResultV1_0_11> {
3649
const issuer = getIssuerFromCredentialOfferPayload(request);
3750
if (issuer) {
38-
return MetadataClient.retrieveAllMetadata(issuer);
51+
if (determineSpecVersionFromOffer(request) >= OpenId4VCIVersion.VER_1_0_13) {
52+
return MetadataClientV1_0_13.retrieveAllMetadataFromCredentialOfferRequest(request as CredentialOfferPayloadV1_0_13);
53+
} else {
54+
return MetadataClientV1_0_11.retrieveAllMetadataFromCredentialOfferRequest(request);
55+
}
3956
}
4057
throw new Error("can't retrieve metadata from CredentialOfferRequest. No issuer field is present");
4158
}
@@ -45,24 +62,33 @@ export class MetadataClient {
4562
* @param issuer The issuer URL
4663
* @param opts
4764
*/
48-
public static async retrieveAllMetadata(issuer: string, opts?: { errorOnNotFound: boolean }): Promise<EndpointMetadataResultV1_0_13> {
65+
public static async retrieveAllMetadata(
66+
issuer: string,
67+
opts?: { errorOnNotFound: boolean },
68+
): Promise<EndpointMetadataResultV1_0_13 | EndpointMetadataResultV1_0_11> {
4969
let token_endpoint: string | undefined;
5070
let credential_endpoint: string | undefined;
5171
let deferred_credential_endpoint: string | undefined;
5272
let authorization_endpoint: string | undefined;
5373
let authorizationServerType: AuthorizationServerType = 'OID4VCI';
54-
let authorization_servers: string[] = [issuer];
74+
let authorization_servers: string[] | undefined = [issuer];
75+
let authorization_server: string | undefined = undefined;
5576
const oid4vciResponse = await MetadataClient.retrieveOpenID4VCIServerMetadata(issuer, { errorOnNotFound: false }); // We will handle errors later, given we will also try other metadata locations
5677
let credentialIssuerMetadata = oid4vciResponse?.successBody;
5778
if (credentialIssuerMetadata) {
5879
debug(`Issuer ${issuer} OID4VCI well-known server metadata\r\n${JSON.stringify(credentialIssuerMetadata)}`);
5980
credential_endpoint = credentialIssuerMetadata.credential_endpoint;
60-
deferred_credential_endpoint = credentialIssuerMetadata.deferred_credential_endpoint;
81+
deferred_credential_endpoint = credentialIssuerMetadata.deferred_credential_endpoint
82+
? (credentialIssuerMetadata.deferred_credential_endpoint as string)
83+
: undefined;
6184
if (credentialIssuerMetadata.token_endpoint) {
6285
token_endpoint = credentialIssuerMetadata.token_endpoint;
6386
}
6487
if (credentialIssuerMetadata.authorization_servers) {
65-
authorization_servers = credentialIssuerMetadata.authorization_servers;
88+
authorization_servers = credentialIssuerMetadata.authorization_servers as string[];
89+
} else if (credentialIssuerMetadata.authorization_server) {
90+
authorization_server = credentialIssuerMetadata.authorization_server as string;
91+
authorization_servers = [authorization_server];
6692
}
6793
}
6894
// No specific OID4VCI endpoint. Either can be an OAuth2 AS or an OIDC IDP. Let's start with OIDC first
@@ -154,20 +180,24 @@ export class MetadataClient {
154180

155181
if (!credentialIssuerMetadata && authMetadata) {
156182
// Apparently everything worked out and the issuer is exposing everything in oAuth2/OIDC well-knowns. Spec is vague about this situation, but we can support it
157-
credentialIssuerMetadata = authMetadata as CredentialIssuerMetadataV1_0_13;
183+
credentialIssuerMetadata = authorization_server
184+
? (authMetadata as CredentialIssuerMetadataV1_0_11)
185+
: (authMetadata as CredentialIssuerMetadataV1_0_13);
158186
}
159187
debug(`Issuer ${issuer} token endpoint ${token_endpoint}, credential endpoint ${credential_endpoint}`);
160188
return {
161189
issuer,
162190
token_endpoint,
163191
credential_endpoint,
164192
deferred_credential_endpoint,
165-
authorization_server: authorization_servers[0],
193+
...(authorization_server ? { authorization_server } : { authorization_servers: authorization_servers }),
166194
authorization_endpoint,
167195
authorizationServerType,
168-
credentialIssuerMetadata: credentialIssuerMetadata,
196+
credentialIssuerMetadata: authorization_server
197+
? (credentialIssuerMetadata as IssuerMetadataV1_0_08 & Partial<AuthorizationServerMetadata>)
198+
: (credentialIssuerMetadata as CredentialIssuerMetadataV1_0_13),
169199
authorizationServerMetadata: authMetadata,
170-
};
200+
} as EndpointMetadataResultV1_0_13 | EndpointMetadataResultV1_0_11;
171201
}
172202

173203
/**
@@ -180,7 +210,12 @@ export class MetadataClient {
180210
opts?: {
181211
errorOnNotFound?: boolean;
182212
},
183-
): Promise<OpenIDResponse<IssuerMetadataV1_0_13> | undefined> {
213+
): Promise<
214+
| OpenIDResponse<
215+
CredentialIssuerMetadataV1_0_11 | CredentialIssuerMetadataV1_0_13 | (IssuerMetadataV1_0_08 & Partial<AuthorizationServerMetadata>)
216+
>
217+
| undefined
218+
> {
184219
return retrieveWellknown(issuerHost, WellKnownEndpoints.OPENID4VCI_ISSUER, {
185220
errorOnNotFound: opts?.errorOnNotFound === undefined ? true : opts.errorOnNotFound,
186221
});

0 commit comments

Comments
 (0)