Skip to content

Commit 5597a97

Browse files
committed
chore: more fixes
1 parent a3a54e8 commit 5597a97

6 files changed

Lines changed: 128 additions & 64 deletions

File tree

packages/client/lib/OpenID4VCIClient.ts

Lines changed: 8 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -225,15 +225,15 @@ export class OpenID4VCIClient {
225225
endpointMetadata: this.endpointMetadata as EndpointMetadataResultV1_0_11,
226226
authorizationRequest: this._state.authorizationRequestOpts,
227227
credentialOffer: this.credentialOffer,
228-
credentialsSupported: Object.values(this.getCredentialsSupported()) as CredentialsSupportedLegacy[],
228+
credentialsSupported: Object.values(this.getCredentialsSupported(true)) as CredentialsSupportedLegacy[],
229229
});
230230
} else {
231231
this._state.authorizationURL = await createAuthorizationRequestUrl({
232232
pkce: this._state.pkce,
233233
endpointMetadata: this.endpointMetadata as EndpointMetadataResultV1_0_13,
234234
authorizationRequest: this._state.authorizationRequestOpts,
235235
credentialOffer: this.credentialOffer,
236-
credentialConfigurationSupported: this.getCredentialsSupported() as Record<string, CredentialConfigurationSupportedV1_0_13>,
236+
credentialConfigurationSupported: this.getCredentialsSupported(false) as Record<string, CredentialConfigurationSupportedV1_0_13>,
237237
});
238238
}
239239
}
@@ -452,31 +452,16 @@ export class OpenID4VCIClient {
452452
return JSON.stringify(this._state);
453453
}
454454

455-
// FIXME: We really should convert <v11 to v12 objects first. Right now the logic doesn't map nicely and is brittle.
456-
// We should resolve IDs to objects first in case of strings.
457-
// When < v11 convert into a v12 object. When v12 object retain it.
458-
// Then match the object array on server metadata
459-
getCredentialsSupportedV11(
460-
restrictToInitiationTypes: boolean,
461-
format?: (OID4VCICredentialFormat | string) | (OID4VCICredentialFormat | string)[],
462-
): Record<string, CredentialConfigurationSupported> {
463-
return getSupportedCredentials({
464-
issuerMetadata: this.endpointMetadata.credentialIssuerMetadata,
465-
version: this.version(),
466-
format: format,
467-
types: restrictToInitiationTypes ? this.getCredentialOfferTypes() : undefined,
468-
}) as Record<string, CredentialConfigurationSupported>;
469-
}
470-
471455
getCredentialsSupported(
456+
restrictToInitiationTypes?: boolean,
472457
format?: (OID4VCICredentialFormat | string) | (OID4VCICredentialFormat | string)[],
473-
): Record<string, CredentialConfigurationSupported> {
458+
): Record<string, CredentialConfigurationSupportedV1_0_13> | Array<CredentialConfigurationSupported> {
474459
return getSupportedCredentials({
475460
issuerMetadata: this.endpointMetadata.credentialIssuerMetadata,
476461
version: this.version(),
477462
format: format,
478-
types: undefined,
479-
}) as Record<string, CredentialConfigurationSupported>;
463+
types: restrictToInitiationTypes ? this.getCredentialOfferTypes() : undefined,
464+
});
480465
}
481466

482467
public async sendNotification(
@@ -487,7 +472,7 @@ export class OpenID4VCIClient {
487472
return sendNotification(credentialRequestOpts, request, accessToken ?? this._state.accessToken ?? this._state.accessTokenResponse?.access_token);
488473
}
489474

490-
getCredentialOfferTypes(): string[][] {
475+
getCredentialOfferTypes(): string[][] | undefined {
491476
if (!this.credentialOffer) {
492477
return [];
493478
} else if (this.credentialOffer.version < OpenId4VCIVersion.VER_1_0_11) {
@@ -510,7 +495,7 @@ export class OpenID4VCIClient {
510495
});
511496
}
512497
// we don't have this for v13. v13 only has credential_configuration_ids which is not translatable to type
513-
return [];
498+
return undefined;
514499
}
515500

516501
issuerSupportedFlowTypes(): AuthzFlowType[] {

packages/client/lib/OpenID4VCIClientV1_0_11.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -460,15 +460,13 @@ export class OpenID4VCIClientV1_0_11 {
460460
}) as Record<string, CredentialConfigurationSupported>;
461461
}
462462

463-
getCredentialsSupported(
464-
format?: (OID4VCICredentialFormat | string) | (OID4VCICredentialFormat | string)[],
465-
): Record<string, CredentialConfigurationSupported> {
463+
getCredentialsSupported(format?: (OID4VCICredentialFormat | string) | (OID4VCICredentialFormat | string)[]): CredentialConfigurationSupported[] {
466464
return getSupportedCredentials({
467465
issuerMetadata: this.endpointMetadata.credentialIssuerMetadata,
468466
version: this.version(),
469467
format: format,
470468
types: undefined,
471-
}) as Record<string, CredentialConfigurationSupported>;
469+
}) as CredentialConfigurationSupported[];
472470
}
473471

474472
getCredentialOfferTypes(): string[][] {

packages/client/lib/OpenID4VCIClientV1_0_13.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -480,7 +480,7 @@ export class OpenID4VCIClientV1_0_13 {
480480
version: this.version(),
481481
format: format,
482482
types: undefined,
483-
});
483+
}) as Record<string, CredentialConfigurationSupportedV1_0_13>;
484484
}
485485

486486
public async sendNotification(
@@ -539,7 +539,7 @@ export class OpenID4VCIClientV1_0_13 {
539539
}
540540

541541
public version(): OpenId4VCIVersion {
542-
return this.credentialOffer?.version ?? OpenId4VCIVersion.VER_1_0_11;
542+
return this.credentialOffer?.version ?? OpenId4VCIVersion.VER_1_0_13;
543543
}
544544

545545
public get endpointMetadata(): EndpointMetadataResultV1_0_13 {

packages/client/lib/__tests__/OpenID4VCIClient.spec.ts

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,16 @@
1-
import { CodeChallengeMethod, WellKnownEndpoints } from '@sphereon/oid4vci-common';
1+
import {
2+
CodeChallengeMethod,
3+
CredentialOfferPayloadV1_0_13,
4+
determineSpecVersionFromOffer,
5+
determineSpecVersionFromURI,
6+
OpenId4VCIVersion,
7+
WellKnownEndpoints,
8+
} from '@sphereon/oid4vci-common';
29
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
310
// @ts-ignore
411
import nock from 'nock';
512

13+
import { createCredentialOfferURIFromObject } from '../../../issuer/lib';
614
import { OpenID4VCIClient } from '../OpenID4VCIClient';
715

816
const MOCK_URL = 'https://server.example.com/';
@@ -227,3 +235,24 @@ describe('should successfully handle isEbsi function', () => {
227235
expect(client.isEBSI()).toBe(true);
228236
});
229237
});
238+
239+
it('determine to be version 13', async () => {
240+
const offer = {
241+
grants: {
242+
'urn:ietf:params:oauth:grant-type:pre-authorized_code': {
243+
'pre-authorized_code': 'random',
244+
},
245+
},
246+
credential_configuration_ids: ['Omzetbelasting'],
247+
credential_issuer: 'https://example.com',
248+
} satisfies CredentialOfferPayloadV1_0_13;
249+
const offerUri = createCredentialOfferURIFromObject({ credential_offer: offer });
250+
251+
expect(determineSpecVersionFromOffer(offer)).toEqual(OpenId4VCIVersion.VER_1_0_13);
252+
expect(determineSpecVersionFromURI(offerUri)).toEqual(OpenId4VCIVersion.VER_1_0_13);
253+
});
254+
it('determine to be version 11', async () => {
255+
const offerUri =
256+
'openid-credential-offer://?credential_offer=%7B%22grants%22%3A%7B%22urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Apre-authorized_code%22%3A%7B%22pre-authorized_code%22%3A%22wN39X8fU4FCU2MaykNRkCr%22%2C%22user_pin_required%22%3Afalse%7D%7D%2C%22credentials%22%3A%5B%22dbc2023%22%5D%2C%22credential_issuer%22%3A%22https%3A%2F%2Fssi.dutchblockchaincoalition.org%2Fagent%22%7D';
257+
expect(determineSpecVersionFromURI(offerUri)).toEqual(OpenId4VCIVersion.VER_1_0_11);
258+
});

packages/common/lib/__tests__/CredentialOfferUtil.spec.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { determineSpecVersionFromURI, getClientIdFromCredentialOfferPayload } from '../functions';
1+
import { determineSpecVersionFromOffer, determineSpecVersionFromURI, getClientIdFromCredentialOfferPayload } from '../functions';
22
import { CredentialOfferPayload, CredentialOfferPayloadV1_0_11, OpenId4VCIVersion } from '../types';
33

44
export const UNIT_TEST_TIMEOUT = 30000;
@@ -46,13 +46,27 @@ describe('CredentialOfferUtil should', () => {
4646
);
4747

4848
it(
49-
'get version 11 as default value',
49+
'get version 13 as default value',
5050
async () => {
5151
expect(determineSpecVersionFromURI('test://uri')).toEqual(OpenId4VCIVersion.VER_1_0_13);
5252
},
5353
UNIT_TEST_TIMEOUT,
5454
);
5555

56+
it('determine to be version 13', async () => {
57+
const offer = {
58+
grants: {
59+
'urn:ietf:params:oauth:grant-type:pre-authorized_code': {
60+
'pre-authorized_code': 'random',
61+
},
62+
},
63+
credential_configuration_ids: ['Omzetbelasting'],
64+
credential_issuer: 'https://example.com',
65+
};
66+
67+
expect(determineSpecVersionFromOffer(offer)).toEqual(OpenId4VCIVersion.VER_1_0_13);
68+
});
69+
5670
it('get client_id from JWT pre-auth code offer', () => {
5771
const offer: CredentialOfferPayload = {
5872
credential_issuer: 'https://conformance-test.ebsi.eu/conformance/v3/issuer-mock',

packages/common/lib/functions/IssuerMetadataUtils.ts

Lines changed: 70 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { VCI_LOG_COMMON } from '../index';
12
import {
23
AuthorizationServerMetadata,
34
CredentialConfigurationSupported,
@@ -11,59 +12,96 @@ import {
1112
OpenId4VCIVersion,
1213
} from '../types';
1314

14-
export function getSupportedCredentials(options?: {
15+
export function getSupportedCredentials(opts?: {
1516
issuerMetadata?: CredentialIssuerMetadata | IssuerMetadata;
1617
version: OpenId4VCIVersion;
1718
types?: string[][];
1819
format?: OID4VCICredentialFormat | string | (OID4VCICredentialFormat | string)[];
19-
}): Record<string, CredentialConfigurationSupportedV1_0_13> {
20-
if (options?.types && Array.isArray(options.types)) {
21-
return options.types
22-
.map((typeSet) => {
23-
return getSupportedCredential({ ...options, types: typeSet });
24-
})
25-
.reduce(
26-
(acc, result) => {
27-
Object.assign(acc, result);
28-
return acc;
29-
},
30-
{} as Record<string, CredentialConfigurationSupportedV1_0_13>,
31-
);
20+
}): Record<string, CredentialConfigurationSupportedV1_0_13> | Array<CredentialConfigurationSupported> {
21+
const {version = OpenId4VCIVersion.VER_1_0_13, types} = opts ?? {}
22+
if (types && Array.isArray(types)) {
23+
if (version < OpenId4VCIVersion.VER_1_0_13) {
24+
return types.flatMap(typeSet => getSupportedCredential({ ...opts, version, types: typeSet }) as Array<CredentialConfigurationSupported>);
25+
} else {
26+
return types
27+
.map((typeSet) => {
28+
return getSupportedCredential({ ...opts, version, types: typeSet });
29+
})
30+
.reduce(
31+
(acc, result) => {
32+
Object.assign(acc, result);
33+
return acc;
34+
},
35+
{} as Record<string, CredentialConfigurationSupportedV1_0_13>,
36+
);
37+
}
3238
}
3339

34-
return getSupportedCredential(options ? { ...options, types: undefined } : undefined);
40+
return getSupportedCredential(opts ? { ...opts, types: undefined } : undefined);
3541
}
3642

3743
export function getSupportedCredential(opts?: {
3844
issuerMetadata?: CredentialIssuerMetadata | IssuerMetadata;
3945
version: OpenId4VCIVersion;
4046
types?: string | string[];
4147
format?: OID4VCICredentialFormat | string | (OID4VCICredentialFormat | string)[];
42-
}): Record<string, CredentialConfigurationSupportedV1_0_13> {
43-
const { issuerMetadata, types, format } = opts ?? {};
48+
}): Record<string, CredentialConfigurationSupportedV1_0_13> | Array<CredentialConfigurationSupported> {
49+
const { issuerMetadata, types, format, version = OpenId4VCIVersion.VER_1_0_13 } = opts ?? {};
4450

45-
if (!issuerMetadata || !issuerMetadata.credential_configurations_supported) {
46-
return {};
51+
let credentialConfigurationsV11: Array<CredentialConfigurationSupported> | undefined = undefined;
52+
let credentialConfigurationsV13: Record<string, CredentialConfigurationSupportedV1_0_13> | undefined = undefined;
53+
if (version < OpenId4VCIVersion.VER_1_0_12 || issuerMetadata?.credentials_supported) {
54+
credentialConfigurationsV11 = issuerMetadata?.credential_supported ?? [];
55+
} else {
56+
credentialConfigurationsV13 =
57+
(issuerMetadata?.credential_configurations_supported as Record<string, CredentialConfigurationSupportedV1_0_13>) ?? {};
58+
}
59+
if (!issuerMetadata || (!issuerMetadata.credential_configurations_supported && !issuerMetadata.credentials_supported)) {
60+
VCI_LOG_COMMON.warning(`No credential issuer metadata or supported credentials found for issuer}`);
61+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
62+
return version < OpenId4VCIVersion.VER_1_0_13 ? credentialConfigurationsV11! : credentialConfigurationsV13!;
4763
}
4864

49-
const credentialConfigurations: Record<string, CredentialConfigurationSupportedV1_0_13> =
50-
issuerMetadata.credential_configurations_supported as Record<string, CredentialConfigurationSupportedV1_0_13>;
5165
const normalizedTypes: string[] = Array.isArray(types) ? types : types ? [types] : [];
5266
const normalizedFormats: string[] = Array.isArray(format) ? format : format ? [format] : [];
5367

54-
return Object.entries(credentialConfigurations).reduce(
55-
(filteredConfigs, [id, config]) => {
56-
const isTypeMatch = normalizedTypes.length === 0 || normalizedTypes.some((type) => config.credential_definition.type?.includes(type));
57-
const isFormatMatch = normalizedFormats.length === 0 || normalizedFormats.includes(config.format);
58-
59-
if (isTypeMatch && isFormatMatch) {
60-
filteredConfigs[id] = config;
68+
function filterMatchingConfig(config: CredentialConfigurationSupported): CredentialConfigurationSupported | undefined {
69+
let isTypeMatch = normalizedTypes.length === 0;
70+
if (!isTypeMatch) {
71+
if ('credential_definition' in config) {
72+
isTypeMatch = normalizedTypes.some((type) => config.credential_definition.type?.includes(type));
73+
} else if ('type' in config && Array.isArray(config.type)) {
74+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
75+
// @ts-ignore
76+
isTypeMatch = normalizedTypes.some((type) => config.type.includes(type));
77+
} else if ('types' in config) {
78+
isTypeMatch = normalizedTypes.some((type) => config.types?.includes(type));
6179
}
80+
}
6281

63-
return filteredConfigs;
64-
},
65-
{} as Record<string, CredentialConfigurationSupportedV1_0_13>,
66-
);
82+
const isFormatMatch = normalizedFormats.length === 0 || normalizedFormats.includes(config.format);
83+
84+
return isTypeMatch && isFormatMatch ? config : undefined;
85+
}
86+
87+
if (credentialConfigurationsV13) {
88+
return Object.entries(credentialConfigurationsV13).reduce(
89+
(filteredConfigs, [id, config]) => {
90+
if (filterMatchingConfig(config)) {
91+
filteredConfigs[id] = config;
92+
// Added to enable support < 13. We basically assign the
93+
if (!config.id) {
94+
config.id = id;
95+
}
96+
}
97+
return filteredConfigs;
98+
},
99+
{} as Record<string, CredentialConfigurationSupportedV1_0_13>,
100+
);
101+
} else if (credentialConfigurationsV11) {
102+
return credentialConfigurationsV11.filter((config) => filterMatchingConfig(config));
103+
}
104+
throw Error(`Either < v11 configurations or V13 configurations should have been filtered at this point`);
67105
}
68106

69107
export function getTypesFromCredentialSupported(

0 commit comments

Comments
 (0)