diff --git a/packages/client/lib/CredentialRequestClient.ts b/packages/client/lib/CredentialRequestClient.ts index f07309ad..eb573484 100644 --- a/packages/client/lib/CredentialRequestClient.ts +++ b/packages/client/lib/CredentialRequestClient.ts @@ -2,7 +2,7 @@ import { CredentialRequest, CredentialResponse, OpenIDResponse, ProofOfPossessio import { CredentialFormat } from '@sphereon/ssi-types'; import Debug from 'debug'; -import { CredentialRequestV1_0_09ClientBuilder } from './CredentialRequestV1_0_09ClientBuilder'; +import { CredentialRequestClientBuilderV1_0_09 } from './CredentialRequestClientBuilderV1_0_09'; import { ProofOfPossessionBuilder } from './ProofOfPossessionBuilder'; import { isValidURL, post } from './functions'; @@ -27,7 +27,7 @@ export class CredentialRequestClient { return this.credentialRequestOpts.credentialEndpoint; } - public constructor(builder: CredentialRequestV1_0_09ClientBuilder) { + public constructor(builder: CredentialRequestClientBuilderV1_0_09) { this._credentialRequestOpts = { ...builder }; } diff --git a/packages/client/lib/CredentialRequestV1_0_09ClientBuilder.ts b/packages/client/lib/CredentialRequestClientBuilderV1_0_09.ts similarity index 75% rename from packages/client/lib/CredentialRequestV1_0_09ClientBuilder.ts rename to packages/client/lib/CredentialRequestClientBuilderV1_0_09.ts index 03e62e94..7e1dbc24 100644 --- a/packages/client/lib/CredentialRequestV1_0_09ClientBuilder.ts +++ b/packages/client/lib/CredentialRequestClientBuilderV1_0_09.ts @@ -5,21 +5,21 @@ import { CredentialOfferV1_0_09, EndpointMetadata, getIssuerFromCredentialOfferPayload, - OpenID4VCIServerMetadata, + IssuerMetadata, } from '@sphereon/openid4vci-common'; import { CredentialFormat } from '@sphereon/ssi-types'; import { CredentialRequestClient } from './CredentialRequestClient'; import { convertURIToJsonObject } from './functions'; -export class CredentialRequestV1_0_09ClientBuilder { +export class CredentialRequestClientBuilderV1_0_09 { credentialEndpoint?: string; credentialType?: string | string[]; format?: CredentialFormat | CredentialFormat[]; token?: string; - public static fromURI({ uri, metadata }: { uri: string; metadata?: EndpointMetadata }): CredentialRequestV1_0_09ClientBuilder { - return CredentialRequestV1_0_09ClientBuilder.fromCredentialOfferRequest({ + public static fromURI({ uri, metadata }: { uri: string; metadata?: EndpointMetadata }): CredentialRequestClientBuilderV1_0_09 { + return CredentialRequestClientBuilderV1_0_09.fromCredentialOfferRequest({ request: convertURIToJsonObject(uri, { arrayTypeProperties: ['credential_type'], requiredProperties: ['issuer', 'credential_type'], @@ -34,8 +34,8 @@ export class CredentialRequestV1_0_09ClientBuilder { }: { request: CredentialOfferPayload; metadata?: EndpointMetadata; - }): CredentialRequestV1_0_09ClientBuilder { - const builder = new CredentialRequestV1_0_09ClientBuilder(); + }): CredentialRequestClientBuilderV1_0_09 { + const builder = new CredentialRequestClientBuilderV1_0_09(); const issuer = getIssuerFromCredentialOfferPayload(request); builder.withCredentialEndpoint( metadata?.credential_endpoint ? metadata.credential_endpoint : issuer.endsWith('/') ? `${issuer}credential` : `${issuer}/credential` @@ -54,39 +54,39 @@ export class CredentialRequestV1_0_09ClientBuilder { }: { credentialOffer: CredentialOfferRequestWithBaseUrl; metadata?: EndpointMetadata; - }): CredentialRequestV1_0_09ClientBuilder { - return CredentialRequestV1_0_09ClientBuilder.fromCredentialOfferRequest({ + }): CredentialRequestClientBuilderV1_0_09 { + return CredentialRequestClientBuilderV1_0_09.fromCredentialOfferRequest({ request: credentialOffer.request, metadata, }); } - public withCredentialEndpointFromMetadata(metadata: OpenID4VCIServerMetadata): CredentialRequestV1_0_09ClientBuilder { + public withCredentialEndpointFromMetadata(metadata: IssuerMetadata): CredentialRequestClientBuilderV1_0_09 { this.credentialEndpoint = metadata.credential_endpoint; return this; } - public withCredentialEndpoint(credentialEndpoint: string): CredentialRequestV1_0_09ClientBuilder { + public withCredentialEndpoint(credentialEndpoint: string): CredentialRequestClientBuilderV1_0_09 { this.credentialEndpoint = credentialEndpoint; return this; } - public withCredentialType(credentialType: string | string[]): CredentialRequestV1_0_09ClientBuilder { + public withCredentialType(credentialType: string | string[]): CredentialRequestClientBuilderV1_0_09 { this.credentialType = credentialType; return this; } - public withFormat(format: CredentialFormat | CredentialFormat[]): CredentialRequestV1_0_09ClientBuilder { + public withFormat(format: CredentialFormat | CredentialFormat[]): CredentialRequestClientBuilderV1_0_09 { this.format = format; return this; } - public withToken(accessToken: string): CredentialRequestV1_0_09ClientBuilder { + public withToken(accessToken: string): CredentialRequestClientBuilderV1_0_09 { this.token = accessToken; return this; } - public withTokenFromResponse(response: AccessTokenResponse): CredentialRequestV1_0_09ClientBuilder { + public withTokenFromResponse(response: AccessTokenResponse): CredentialRequestClientBuilderV1_0_09 { this.token = response.access_token; return this; } diff --git a/packages/client/lib/MetadataClient.ts b/packages/client/lib/MetadataClient.ts index 040895d2..63065285 100644 --- a/packages/client/lib/MetadataClient.ts +++ b/packages/client/lib/MetadataClient.ts @@ -3,9 +3,9 @@ import { CredentialOfferRequestWithBaseUrl, EndpointMetadata, getIssuerFromCredentialOfferPayload, + IssuerMetadata, OAuth2ASMetadata, Oauth2ASWithOID4VCIMetadata, - OpenID4VCIServerMetadata, OpenIDResponse, WellKnownEndpoints, } from '@sphereon/openid4vci-common'; @@ -115,7 +115,7 @@ export class MetadataClient { * * @param issuerHost The issuer hostname */ - public static async retrieveOpenID4VCIServerMetadata(issuerHost: string): Promise | undefined> { + public static async retrieveOpenID4VCIServerMetadata(issuerHost: string): Promise | undefined> { // Since the server metadata endpoint is optional we are not going to throw an error. return MetadataClient.retrieveWellknown(issuerHost, WellKnownEndpoints.OPENID4VCI_ISSUER, { errorOnNotFound: false }); } diff --git a/packages/client/lib/OpenID4VCIClient.ts b/packages/client/lib/OpenID4VCIClient.ts index defce3c1..adf15a80 100644 --- a/packages/client/lib/OpenID4VCIClient.ts +++ b/packages/client/lib/OpenID4VCIClient.ts @@ -7,18 +7,19 @@ import { CredentialOfferRequestWithBaseUrl, CredentialOfferV1_0_09, CredentialResponse, + CredentialSupported, EndpointMetadata, IssuerCredentialSubject, - IssuerCredentialSubjectDisplay, ProofOfPossessionCallbacks, ResponseType, } from '@sphereon/openid4vci-common'; +import { CredentialSupportedTypeV1_0_08, CredentialSupportedV1_0_08 } from '@sphereon/openid4vci-common/dist/types/v1_0_08.types'; import { CredentialFormat } from '@sphereon/ssi-types'; import Debug from 'debug'; import { AccessTokenClient } from './AccessTokenClient'; import { CredentialOffer } from './CredentialOffer'; -import { CredentialRequestV1_0_09ClientBuilder } from './CredentialRequestV1_0_09ClientBuilder'; +import { CredentialRequestClientBuilderV1_0_09 } from './CredentialRequestClientBuilderV1_0_09'; import { MetadataClient } from './MetadataClient'; import { ProofOfPossessionBuilder } from './ProofOfPossessionBuilder'; import { convertJsonToURI } from './functions'; @@ -109,8 +110,11 @@ export class OpenID4VCIClient { if (!scope && !authorizationDetails) { throw Error('Please provide a scope or authorization_details'); } - - if (!this._serverMetadata?.openid4vci_metadata?.authorization_endpoint) { + // todo: handling this because of the support for v1_0-08 + if (this._serverMetadata && this._serverMetadata.openid4vci_metadata && 'authorization_endpoint' in this._serverMetadata.openid4vci_metadata) { + this._serverMetadata.authorization_endpoint = this._serverMetadata.openid4vci_metadata.authorization_endpoint as string; + } + if (!this._serverMetadata?.authorization_endpoint) { throw Error('Server metadata does not contain authorization endpoint'); } @@ -131,7 +135,7 @@ export class OpenID4VCIClient { } as AuthorizationRequestV1_0_09; return convertJsonToURI(queryObj, { - baseUrl: this._serverMetadata.openid4vci_metadata.authorization_endpoint, + baseUrl: this._serverMetadata.authorization_endpoint, uriTypeProperties: ['redirect_uri', 'scope', 'authorization_details'], }); } @@ -147,10 +151,7 @@ export class OpenID4VCIClient { return authorizationDetails; } private handleLocations(authorizationDetails: AuthDetails) { - if ( - authorizationDetails && - (this.serverMetadata.openid4vci_metadata?.authorization_server || this.serverMetadata.openid4vci_metadata?.authorization_endpoint) - ) { + if (authorizationDetails && (this.serverMetadata.openid4vci_metadata?.authorization_server || this.serverMetadata.authorization_endpoint)) { if (authorizationDetails.locations) { if (Array.isArray(authorizationDetails.locations)) { (authorizationDetails.locations as string[]).push(this.serverMetadata.issuer); @@ -237,7 +238,7 @@ export class OpenID4VCIClient { this._kid = kid; } - const requestBuilder = CredentialRequestV1_0_09ClientBuilder.fromCredentialOffer({ + const requestBuilder = CredentialRequestClientBuilderV1_0_09.fromCredentialOffer({ credentialOffer: this.credentialOffer, metadata: this.serverMetadata, }); @@ -245,8 +246,23 @@ export class OpenID4VCIClient { if (this.serverMetadata?.openid4vci_metadata) { const metadata = this.serverMetadata.openid4vci_metadata; const types = Array.isArray(credentialType) ? credentialType : [credentialType]; - if (types.some((type) => !metadata.credentials_supported || !metadata.credentials_supported[type])) { - throw Error(`Not all credential types ${JSON.stringify(credentialType)} are supported by issuer ${this.getIssuer()}`); + if (metadata.credentials_supported && Array.isArray(metadata.credentials_supported)) { + for (const type of types) { + let typeSupported = false; + for (const credentialSupported of metadata.credentials_supported) { + if (credentialSupported.types.indexOf(type) != -1) { + typeSupported = true; + } + } + if (!typeSupported) { + throw Error(`Not all credential types ${JSON.stringify(credentialType)} are supported by issuer ${this.getIssuer()}`); + } + } + } else if (metadata.credentials_supported && !Array.isArray(metadata.credentials_supported)) { + const credentialsSupported = metadata.credentials_supported as CredentialSupportedTypeV1_0_08; + if (types.some((type) => !metadata.credentials_supported || !credentialsSupported[type])) { + throw Error(`Not all credential types ${JSON.stringify(credentialType)} are supported by issuer ${this.getIssuer()}`); + } } // todo: Format check? We might end up with some disjoint type / format combinations supported by the server } @@ -286,25 +302,53 @@ export class OpenID4VCIClient { return response.successBody; } - getCredentialsSupported(restrictToInitiationTypes: boolean): IssuerCredentialSubject { + getCredentialsSupported(restrictToInitiationTypes: boolean, supportedType?: string): CredentialSupported[] { const credentialsSupported = this.serverMetadata?.openid4vci_metadata?.credentials_supported; + /** + * the following (not array part is a legacy code from version 1_0-08 which jff implementors used) + */ + if (credentialsSupported && !Array.isArray(credentialsSupported)) { + if (!restrictToInitiationTypes) { + return credentialsSupported; + } + const credentialsSupportedV8: CredentialSupportedV1_0_08 = credentialsSupported as CredentialSupportedV1_0_08; + const initiationTypes = this.getCredentialTypes(); + const supported: IssuerCredentialSubject = {}; + for (const [key, value] of Object.entries(credentialsSupportedV8)) { + if (initiationTypes.includes(key)) { + supported[key] = value; + } + } + // todo: fix this later. we're returning CredentialSupportedV1_0_08 as a list of CredentialSupported (for v09 onward) + return supported as unknown as CredentialSupported[]; + } if (!credentialsSupported) { - return {}; - } else if (restrictToInitiationTypes === false) { + return []; + } else if (!restrictToInitiationTypes) { return credentialsSupported; } - const initiationTypes = this.getCredentialTypes(); - const supported: IssuerCredentialSubject = {}; - for (const [key, value] of Object.entries(credentialsSupported)) { - if (initiationTypes.includes(key)) { - supported[key] = value; + const initiationTypes = supportedType ? [supportedType] : this.getCredentialTypes(); + const credentialSupportedOverlap: CredentialSupported[] = []; + for (const supported of credentialsSupported) { + const supportedTypeOverlap: string[] = []; + for (const type of supported.types) { + initiationTypes.includes(type); + supportedTypeOverlap.push(type); + } + if (supportedTypeOverlap.length > 0) { + credentialSupportedOverlap.push({ + types: supportedTypeOverlap, + format: supported.format, + cryptographic_suites_supported: supported.cryptographic_suites_supported, + cryptographic_binding_methods_supported: supported.cryptographic_binding_methods_supported, + }); } } - return supported; + return credentialSupportedOverlap; } - getCredentialMetadata(type: string): IssuerCredentialSubjectDisplay { - return this.getCredentialsSupported(false)[type]; + getCredentialMetadata(type: string): CredentialSupported[] { + return this.getCredentialsSupported(false, type); } // todo https://sphereon.atlassian.net/browse/VDX-184 diff --git a/packages/client/lib/__tests__/CredentialRequestClient.spec.ts b/packages/client/lib/__tests__/CredentialRequestClient.spec.ts index 023162c0..a2cbda99 100644 --- a/packages/client/lib/__tests__/CredentialRequestClient.spec.ts +++ b/packages/client/lib/__tests__/CredentialRequestClient.spec.ts @@ -13,7 +13,7 @@ import { import * as jose from 'jose'; import nock from 'nock'; -import { CredentialRequestV1_0_09ClientBuilder, MetadataClient } from '..'; +import { CredentialRequestClientBuilderV1_0_09, MetadataClient } from '..'; import { ProofOfPossessionBuilder } from '..'; import { CredentialOffer } from '../CredentialOffer'; @@ -68,7 +68,7 @@ describe('Credential Request Client ', () => { error_description: 'This is a mock error message', }); - const credReqClient = CredentialRequestV1_0_09ClientBuilder.fromCredentialOffer({ credentialOffer: INITIATION_TEST }) + const credReqClient = CredentialRequestClientBuilderV1_0_09.fromCredentialOffer({ credentialOffer: INITIATION_TEST }) .withCredentialEndpoint(basePath + '/credential') .withFormat('ldp_vc') .withCredentialType('https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#OpenBadgeCredential') @@ -99,7 +99,7 @@ describe('Credential Request Client ', () => { format: 'jwt-vc', credential: mockedVC, }); - const credReqClient = CredentialRequestV1_0_09ClientBuilder.fromCredentialOfferRequest({ request: INITIATION_TEST.request }) + const credReqClient = CredentialRequestClientBuilderV1_0_09.fromCredentialOfferRequest({ request: INITIATION_TEST.request }) .withCredentialEndpoint('https://oidc4vci.demo.spruceid.com/credential') .withFormat('jwt_vc') .withCredentialType('https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#OpenBadgeCredential') @@ -122,7 +122,7 @@ describe('Credential Request Client ', () => { }); it('should fail with invalid url', async () => { - const credReqClient = CredentialRequestV1_0_09ClientBuilder.fromCredentialOfferRequest({ request: INITIATION_TEST.request }) + const credReqClient = CredentialRequestClientBuilderV1_0_09.fromCredentialOfferRequest({ request: INITIATION_TEST.request }) .withCredentialEndpoint('httpsf://oidc4vci.demo.spruceid.com/credential') .withFormat('jwt_vc') .withCredentialType('https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#OpenBadgeCredential') @@ -153,7 +153,7 @@ describe('Credential Request Client with Walt.id ', () => { expect(metadata.credential_endpoint).toEqual(WALT_OID4VCI_METADATA.credential_endpoint); expect(metadata.token_endpoint).toEqual(WALT_OID4VCI_METADATA.token_endpoint); - const credReqClient = CredentialRequestV1_0_09ClientBuilder.fromCredentialOffer({ + const credReqClient = CredentialRequestClientBuilderV1_0_09.fromCredentialOffer({ credentialOffer, metadata, }).build(); diff --git a/packages/client/lib/__tests__/CredentialRequestClientBuilder.spec.ts b/packages/client/lib/__tests__/CredentialRequestClientBuilder.spec.ts index f170210b..1470ace1 100644 --- a/packages/client/lib/__tests__/CredentialRequestClientBuilder.spec.ts +++ b/packages/client/lib/__tests__/CredentialRequestClientBuilder.spec.ts @@ -1,9 +1,9 @@ import { KeyObject } from 'crypto'; -import { Alg, CredentialRequest, Jwt, OpenID4VCIServerMetadata, ProofOfPossession, Typ } from '@sphereon/openid4vci-common'; +import { Alg, CredentialRequest, IssuerMetadata, Jwt, ProofOfPossession, Typ } from '@sphereon/openid4vci-common'; import * as jose from 'jose'; -import { CredentialRequestV1_0_09ClientBuilder, ProofOfPossessionBuilder } from '..'; +import { CredentialRequestClientBuilderV1_0_09, ProofOfPossessionBuilder } from '..'; import { IDENTIPROOF_ISSUER_URL, IDENTIPROOF_OID4VCI_METADATA, INITIATION_TEST_URI, WALT_ISSUER_URL, WALT_OID4VCI_METADATA } from './MetadataMocks'; @@ -49,7 +49,7 @@ async function proofOfPossessionVerifierCallbackFunction(args: { jwt: string; ki describe('Credential Request Client Builder', () => { it('should build correctly provided with correct params', function () { - const credReqClient = CredentialRequestV1_0_09ClientBuilder.fromURI({ uri: INITIATION_TEST_URI }) + const credReqClient = CredentialRequestClientBuilderV1_0_09.fromURI({ uri: INITIATION_TEST_URI }) .withCredentialEndpoint('https://oidc4vci.demo.spruceid.com/credential') .withFormat('jwt_vc') .withCredentialType('credentialType') @@ -62,7 +62,7 @@ describe('Credential Request Client Builder', () => { }); it('should build credential request correctly', async () => { - const credReqClient = CredentialRequestV1_0_09ClientBuilder.fromURI({ uri: INITIATION_TEST_URI }) + const credReqClient = CredentialRequestClientBuilderV1_0_09.fromURI({ uri: INITIATION_TEST_URI }) .withCredentialEndpoint('https://oidc4vci.demo.spruceid.com/credential') .withFormat('jwt_vc') .withCredentialType('https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#OpenBadgeCredential') @@ -84,7 +84,7 @@ describe('Credential Request Client Builder', () => { }); it('should build correctly from metadata', async () => { - const credReqClient = CredentialRequestV1_0_09ClientBuilder.fromURI({ + const credReqClient = CredentialRequestClientBuilderV1_0_09.fromURI({ uri: INITIATION_TEST_URI, metadata: WALT_OID4VCI_METADATA, }) @@ -94,9 +94,9 @@ describe('Credential Request Client Builder', () => { }); it('should build correctly with endpoint from metadata', async () => { - const credReqClient = CredentialRequestV1_0_09ClientBuilder.fromURI({ uri: INITIATION_TEST_URI }) + const credReqClient = CredentialRequestClientBuilderV1_0_09.fromURI({ uri: INITIATION_TEST_URI }) .withFormat('jwt_vc') - .withCredentialEndpointFromMetadata(IDENTIPROOF_OID4VCI_METADATA as unknown as OpenID4VCIServerMetadata) + .withCredentialEndpointFromMetadata(IDENTIPROOF_OID4VCI_METADATA as unknown as IssuerMetadata) .build(); expect(credReqClient.credentialRequestOpts.credentialEndpoint).toBe(`${IDENTIPROOF_ISSUER_URL}/credential`); }); diff --git a/packages/client/lib/__tests__/IT.spec.ts b/packages/client/lib/__tests__/IT.spec.ts index 86102181..52396401 100644 --- a/packages/client/lib/__tests__/IT.spec.ts +++ b/packages/client/lib/__tests__/IT.spec.ts @@ -1,7 +1,7 @@ import { AccessTokenResponse, Alg, AuthzFlowType, CredentialOfferRequestWithBaseUrl, Jwt, ProofOfPossession, Typ } from '@sphereon/openid4vci-common'; import nock from 'nock'; -import { AccessTokenClient, CredentialRequestV1_0_09ClientBuilder, OpenID4VCIClient, ProofOfPossessionBuilder } from '..'; +import { AccessTokenClient, CredentialRequestClientBuilderV1_0_09, OpenID4VCIClient, ProofOfPossessionBuilder } from '..'; import { CredentialOffer } from '../CredentialOffer'; import { IDENTIPROOF_AS_METADATA, IDENTIPROOF_AS_URL, IDENTIPROOF_ISSUER_URL, IDENTIPROOF_OID4VCI_METADATA } from './MetadataMocks'; @@ -124,7 +124,7 @@ describe('OID4VCI-Client should', () => { format: 'jwt-vc', credential: mockedVC, }); - const credReqClient = CredentialRequestV1_0_09ClientBuilder.fromCredentialOffer({ credentialOffer: credentialOffer }) + const credReqClient = CredentialRequestClientBuilderV1_0_09.fromCredentialOffer({ credentialOffer: credentialOffer }) .withFormat('jwt_vc') .withTokenFromResponse(accessTokenResponse.successBody!) diff --git a/packages/client/lib/index.ts b/packages/client/lib/index.ts index 1a20c72e..ccfed9e7 100644 --- a/packages/client/lib/index.ts +++ b/packages/client/lib/index.ts @@ -1,6 +1,6 @@ export * from './AccessTokenClient'; export * from './CredentialRequestClient'; -export * from './CredentialRequestV1_0_09ClientBuilder'; +export * from './CredentialRequestClientBuilderV1_0_09'; export * from './functions'; export * from './MetadataClient'; export * from './OpenID4VCIClient'; diff --git a/packages/common/lib/types/Generic.types.ts b/packages/common/lib/types/Generic.types.ts index cf707d56..8aea1e2f 100644 --- a/packages/common/lib/types/Generic.types.ts +++ b/packages/common/lib/types/Generic.types.ts @@ -1,7 +1,6 @@ import { ICredentialContextType, IVerifiableCredential, W3CVerifiableCredential } from '@sphereon/ssi-types'; import { ProofOfPossession } from './CredentialIssuance.types'; -import { OpenID4VCIServerMetadata } from './OpenID4VCIServerMetadata'; /** * Important Note: please be aware that these Common interfaces are based on versions v1_0.11 and v1_0.09 @@ -42,27 +41,29 @@ export interface IssuerMetadata { display?: Display[]; } -export interface CredentialSupported { - format: CredentialFormatEnum | string; - id?: string; - cryptographic_binding_methods_supported?: string[]; - cryptographic_suites_supported?: string[]; +export interface CredentialSupportedBrief { + types: string[]; // REQUIRED. JSON array designating the types a certain credential type supports + cryptographic_binding_methods_supported?: string[]; // OPTIONAL. Array of case sensitive strings that identify how the Credential is bound to the identifier of the End-User who possesses the Credential + cryptographic_suites_supported?: string[]; // OPTIONAL. Array of case sensitive strings that identify the cryptographic suites that are supported for the cryptographic_binding_methods_supported } +export type CommonCredentialSupported = CredentialSupportedBrief & { + format: CredentialFormatEnum | string; //REQUIRED. A JSON string identifying the format of this credential, e.g. jwt_vc_json or ldp_vc. + id?: string; // OPTIONAL. A JSON string identifying the respective object. The value MUST be unique across all credentials_supported entries in the Credential Issuer Metadata + display?: Display[]; // OPTIONAL. An array of objects, where each object contains the display properties of the supported credential for a certain language + /** + * following properties are non-mso_mdoc specific and we might wanna rethink them when we're going to support mso_mdoc + */ + credentialSubject?: IssuerCredentialSubject; // OPTIONAL. A JSON object containing a list of key value pairs, where the key identifies the claim offered in the Credential. The value MAY be a dictionary, which allows to represent the full (potentially deeply nested) structure of the verifiable credential to be issued. + order?: string[]; //An array of claims.display.name values that lists them in the order they should be displayed by the Wallet. +}; -export interface SupportedCredentialIssuerMetadataJwtVcJsonLdAndLdpVc extends CredentialSupported { - format: CredentialFormatEnum.ldp_vc; - '@context': ICredentialContextType[]; - types: string[]; - credentialSubject?: IssuerCredentialSubject; - display?: Display[]; +export interface CredentialSupportedJwtVcJsonLdAndLdpVc extends CommonCredentialSupported { + '@context': ICredentialContextType[]; // REQUIRED. JSON array as defined in [VC_DATA], Section 4.1. } -export interface SupportedCredentialIssuerMetadataJwtVcJson extends CredentialSupported { - types: string[]; - credentialSubject?: IssuerCredentialSubject; - display?: Display[]; - order?: string[]; //An array of claims.display.name values that lists them in the order they should be displayed by the Wallet. -} +export type CredentialSupportedJwtVcJson = CommonCredentialSupported; + +export type CredentialSupported = CredentialSupportedJwtVcJson | CredentialSupportedJwtVcJsonLdAndLdpVc; export interface CredentialOfferFormat { format: CredentialFormatEnum; @@ -207,5 +208,5 @@ export interface EndpointMetadata { token_endpoint: string; credential_endpoint: string; authorization_endpoint?: string; - openid4vci_metadata?: OpenID4VCIServerMetadata; + openid4vci_metadata?: IssuerMetadata; } diff --git a/packages/common/lib/types/OpenID4VCIServerMetadata.ts b/packages/common/lib/types/OpenID4VCIServerMetadata.ts index 0c23a75d..7ed2e3db 100644 --- a/packages/common/lib/types/OpenID4VCIServerMetadata.ts +++ b/packages/common/lib/types/OpenID4VCIServerMetadata.ts @@ -1,20 +1,9 @@ import { CredentialFormat } from '@sphereon/ssi-types'; -import { Display, IssuerCredentialSubject } from './Generic.types'; +import { Display, IssuerCredentialSubject, IssuerMetadata } from './Generic.types'; import { OAuth2ASMetadata } from './OAuth2ASMetadata'; -// https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#section-11.2 -export interface OpenID4VCIServerMetadata { - credential_endpoint: string; //REQUIRED. URL of the OP's Credential Endpoint. This URL MUST use the https scheme and MAY contain port, path and query parameter components. - credentials_supported: IssuerCredentialSubject; //REQUIRED. A JSON object containing a list of key value pairs, where the key is a string serving as an abstract identifier of the Credential. This identifier is RECOMMENDED to be collision resistant - it can be globally unique, but does not have to be when naming conflicts are unlikely to arise in a given use case. The value is a JSON object. The JSON object MUST conform to the structure of the Section 11.2.1. - credential_issuer?: CredentialIssuer; // A JSON object containing display properties for the Credential issuer. - token_endpoint?: string; //NON-SPEC compliant, but used by several issuers. URL of the OP's Token Endpoint. This URL MUST use the https scheme and MAY contain port, path and query parameter components. - authorization_server?: string; //NON-SPEC compliant, but used by some issuers. URL of the AS. This URL MUST use the https scheme and MAY contain port, path and query parameter components. - // TODO: The above authorization_server being used in the wild, serves roughly the same purpose as the below spec compliant endpoint. Look at how to use authorization_server as authorization_endpoint in case it is present - authorization_endpoint?: string; -} - -export type Oauth2ASWithOID4VCIMetadata = OAuth2ASMetadata & OpenID4VCIServerMetadata; +export type Oauth2ASWithOID4VCIMetadata = OAuth2ASMetadata & IssuerMetadata; export interface CredentialIssuer { display?: Display; //OPTIONAL. An array of objects, where each object contains display properties of a Credential issuer for a certain language. Below is a non-exhaustive list of valid parameters that MAY be included: diff --git a/packages/common/lib/types/v1_0_08.types.ts b/packages/common/lib/types/v1_0_08.types.ts new file mode 100644 index 00000000..98346546 --- /dev/null +++ b/packages/common/lib/types/v1_0_08.types.ts @@ -0,0 +1,21 @@ +import { CredentialSupportedBrief, Display, IssuerCredentialSubject } from './Generic.types'; +export interface IssuerMetadataV1_0_08 { + credential_endpoint: string; + credentials_supported: CredentialSupportedTypeV1_0_08; + credential_issuer: string; + authorization_server?: string; + token_endpoint?: string; + display?: Display[]; +} + +export interface CredentialSupportedTypeV1_0_08 { + [credentialType: string]: CredentialSupportedV1_0_08; +} + +export interface CredentialSupportedV1_0_08 { + display?: Display[]; + formats: { + [credentialFormat: string]: CredentialSupportedBrief; + }; + claims: IssuerCredentialSubject; +} diff --git a/packages/issuer/lib/builder/CredentialSupportedBuilderV1_11.ts b/packages/issuer/lib/builder/CredentialSupportedBuilderV1_11.ts index 1bd800e5..6f3859f7 100644 --- a/packages/issuer/lib/builder/CredentialSupportedBuilderV1_11.ts +++ b/packages/issuer/lib/builder/CredentialSupportedBuilderV1_11.ts @@ -4,8 +4,6 @@ import { Display, IssuerCredentialSubject, IssuerCredentialSubjectDisplay, - SupportedCredentialIssuerMetadataJwtVcJson, - SupportedCredentialIssuerMetadataJwtVcJsonLdAndLdpVc, TokenErrorResponse, } from '@sphereon/openid4vci-common' @@ -105,12 +103,11 @@ export class CredentialSupportedBuilderV1_11 { if (!this.format) { throw new Error(TokenErrorResponse.invalid_request) } - const credentialSupported: CredentialSupported = { + const credentialSupported: Partial = { format: this.format, } if (this.credentialSubject) { - ;(credentialSupported as SupportedCredentialIssuerMetadataJwtVcJsonLdAndLdpVc | SupportedCredentialIssuerMetadataJwtVcJson).credentialSubject = - this.credentialSubject + credentialSupported.credentialSubject = this.credentialSubject } if (this.cryptographicSuitesSupported) { credentialSupported.cryptographic_suites_supported = this.cryptographicSuitesSupported @@ -122,9 +119,8 @@ export class CredentialSupportedBuilderV1_11 { credentialSupported.id = this.id } if (this.display) { - ;(credentialSupported as SupportedCredentialIssuerMetadataJwtVcJsonLdAndLdpVc | SupportedCredentialIssuerMetadataJwtVcJson).display = - this.display + credentialSupported.display = this.display } - return credentialSupported + return credentialSupported as CredentialSupported } } diff --git a/packages/issuer/lib/functions/CredentialOfferUtils.ts b/packages/issuer/lib/functions/CredentialOfferUtils.ts index ab460bee..e14e88a6 100644 --- a/packages/issuer/lib/functions/CredentialOfferUtils.ts +++ b/packages/issuer/lib/functions/CredentialOfferUtils.ts @@ -3,6 +3,7 @@ import { v4 as uuidv4 } from 'uuid' export function createCredentialOfferURI( issuerMetadata?: IssuerMetadata, + // todo: probably it's wise to create another builder for CredentialOfferPayload that will generate different kinds of CredentialOfferPayload opts?: { state?: string; credentialOffer?: CredentialOfferPayload; preAuthorizedCode?: string; userPinRequired?: boolean } ): string { // openid-credential-offer://credential_offer=%7B%22credential_issuer%22:%22https://credential-issuer.example.com