Skip to content

Commit

Permalink
feat: added support for v8 in our types (partially) to make old logic…
Browse files Browse the repository at this point in the history
…s work

Signed-off-by: sksadjad <[email protected]>
  • Loading branch information
sksadjad committed Apr 6, 2023
1 parent 9a9a074 commit 4b5abf1
Show file tree
Hide file tree
Showing 13 changed files with 148 additions and 96 deletions.
4 changes: 2 additions & 2 deletions packages/client/lib/CredentialRequestClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -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 };
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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'],
Expand All @@ -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`
Expand All @@ -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;
}
Expand Down
4 changes: 2 additions & 2 deletions packages/client/lib/MetadataClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import {
CredentialOfferRequestWithBaseUrl,
EndpointMetadata,
getIssuerFromCredentialOfferPayload,
IssuerMetadata,
OAuth2ASMetadata,
Oauth2ASWithOID4VCIMetadata,
OpenID4VCIServerMetadata,
OpenIDResponse,
WellKnownEndpoints,
} from '@sphereon/openid4vci-common';
Expand Down Expand Up @@ -115,7 +115,7 @@ export class MetadataClient {
*
* @param issuerHost The issuer hostname
*/
public static async retrieveOpenID4VCIServerMetadata(issuerHost: string): Promise<OpenIDResponse<OpenID4VCIServerMetadata> | undefined> {
public static async retrieveOpenID4VCIServerMetadata(issuerHost: string): Promise<OpenIDResponse<IssuerMetadata> | 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 });
}
Expand Down
90 changes: 67 additions & 23 deletions packages/client/lib/OpenID4VCIClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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');
}

Expand All @@ -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'],
});
}
Expand All @@ -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);
Expand Down Expand Up @@ -237,16 +238,31 @@ export class OpenID4VCIClient {
this._kid = kid;
}

const requestBuilder = CredentialRequestV1_0_09ClientBuilder.fromCredentialOffer({
const requestBuilder = CredentialRequestClientBuilderV1_0_09.fromCredentialOffer({
credentialOffer: this.credentialOffer,
metadata: this.serverMetadata,
});
requestBuilder.withToken(this.accessTokenResponse.access_token);
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
}
Expand Down Expand Up @@ -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
Expand Down
10 changes: 5 additions & 5 deletions packages/client/lib/__tests__/CredentialRequestClient.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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')
Expand Down Expand Up @@ -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')
Expand All @@ -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')
Expand Down Expand Up @@ -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();
Expand Down
Original file line number Diff line number Diff line change
@@ -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';

Expand Down Expand Up @@ -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')
Expand All @@ -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')
Expand All @@ -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,
})
Expand All @@ -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`);
});
Expand Down
4 changes: 2 additions & 2 deletions packages/client/lib/__tests__/IT.spec.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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!)
Expand Down
Loading

0 comments on commit 4b5abf1

Please sign in to comment.