Skip to content

Commit

Permalink
feat: added a facade for CredentialRequestClientBuilder and adjusted …
Browse files Browse the repository at this point in the history
…the tests
  • Loading branch information
sksadjad committed Jun 21, 2024
1 parent 5439a02 commit 30cddd3
Show file tree
Hide file tree
Showing 12 changed files with 329 additions and 234 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { KeyObject } from 'crypto'

import { CredentialRequestClient, CredentialRequestClientBuilder, ProofOfPossessionBuilder } from '@sphereon/oid4vci-client'
import {
CredentialRequestClient,
CredentialRequestClientBuilder,
CredentialRequestClientBuilderV1_0_13,
ProofOfPossessionBuilder,
} from '@sphereon/oid4vci-client'
import {
Alg,
CNonceState,
Expand Down Expand Up @@ -219,7 +224,7 @@ describe('issuerCallback', () => {
})

it('Should pass requesting a verifiable credential using the client', async () => {
const credReqClient = (await CredentialRequestClientBuilder.fromURI({ uri: INITIATION_TEST_URI }))
const credReqClient = ((await CredentialRequestClientBuilder.fromURI({ uri: INITIATION_TEST_URI })) as CredentialRequestClientBuilderV1_0_13)
.withCredentialEndpoint('https://oidc4vci.demo.spruceid.com/credential')
.withCredentialEndpointFromMetadata({
credential_configurations_supported: { VeriCred: { format: 'jwt_vc_json' } },
Expand Down
11 changes: 7 additions & 4 deletions packages/client/lib/AuthorizationCodeClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,23 +35,26 @@ export async function createSignedAuthRequestWhenNeeded(requestObject: Record<st
} else if (!opts.kid) {
throw Error(`No kid found, whilst request object mode was set to ${opts.requestObjectMode}`);
}
let client_metadata: any
let client_metadata: any;
if (opts.clientMetadata || opts.jwksUri) {
client_metadata = opts.clientMetadata ?? {};
if (opts.jwksUri) {
client_metadata['jwks_uri'] = opts.jwksUri;
}
}
let authorization_details = requestObject['authorization_details']
let authorization_details = requestObject['authorization_details'];
if (typeof authorization_details === 'string') {
authorization_details = JSON.parse(requestObject.authorization_details);
}
if (!requestObject.aud && opts.aud) {
requestObject.aud = opts.aud;
}
const iss = requestObject.iss ?? opts.iss ?? requestObject.client_id
const iss = requestObject.iss ?? opts.iss ?? requestObject.client_id;

const jwt: Jwt = { header: { alg: 'ES256', kid: opts.kid, typ: 'jwt' }, payload: {...requestObject, iss, authorization_details, ...(client_metadata && {client_metadata})} };
const jwt: Jwt = {
header: { alg: 'ES256', kid: opts.kid, typ: 'jwt' },
payload: { ...requestObject, iss, authorization_details, ...(client_metadata && { client_metadata }) },
};
const pop = await ProofOfPossessionBuilder.fromJwt({
jwt,
callbacks: opts.signCallbacks,
Expand Down
5 changes: 3 additions & 2 deletions packages/client/lib/CredentialRequestClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ import { ExperimentalSubjectIssuance } from '@sphereon/oid4vci-common/dist/exper
import { CredentialFormat } from '@sphereon/ssi-types';
import Debug from 'debug';

import { CredentialRequestClientBuilder } from './CredentialRequestClientBuilder';
import { CredentialRequestClientBuilderV1_0_11 } from './CredentialRequestClientBuilderV1_0_11';
import { CredentialRequestClientBuilderV1_0_13 } from './CredentialRequestClientBuilderV1_0_13';
import { ProofOfPossessionBuilder } from './ProofOfPossessionBuilder';

const debug = Debug('sphereon:oid4vci:credential');
Expand Down Expand Up @@ -78,7 +79,7 @@ export class CredentialRequestClient {
return this.credentialRequestOpts.deferredCredentialEndpoint;
}

public constructor(builder: CredentialRequestClientBuilder) {
public constructor(builder: CredentialRequestClientBuilderV1_0_13 | CredentialRequestClientBuilderV1_0_11) {
this._credentialRequestOpts = { ...builder };
}

Expand Down
160 changes: 38 additions & 122 deletions packages/client/lib/CredentialRequestClientBuilder.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,16 @@
import {
AccessTokenResponse,
CredentialIssuerMetadataV1_0_13,
CredentialOfferPayloadV1_0_13,
CredentialOfferRequestWithBaseUrl,
determineSpecVersionFromOffer,
EndpointMetadata,
ExperimentalSubjectIssuance,
getIssuerFromCredentialOfferPayload,
OID4VCICredentialFormat,
OpenId4VCIVersion,
UniformCredentialOfferRequest,
} from '@sphereon/oid4vci-common';
import { CredentialFormat } from '@sphereon/ssi-types';

import { CredentialOfferClient } from './CredentialOfferClient';
import { CredentialRequestClient } from './CredentialRequestClient';
import { CredentialRequestClientBuilderV1_0_11 } from './CredentialRequestClientBuilderV1_0_11';
import { CredentialRequestClientBuilderV1_0_13 } from './CredentialRequestClientBuilderV1_0_13';

export class CredentialRequestClientBuilder {
credentialEndpoint?: string;
deferredCredentialEndpoint?: string;
deferredCredentialAwait = false;
deferredCredentialIntervalInMS = 5000;
credentialIdentifier?: string;
credentialTypes?: string[] = [];
format?: CredentialFormat | OID4VCICredentialFormat;
token?: string;
version?: OpenId4VCIVersion;
subjectIssuance?: ExperimentalSubjectIssuance;

public static fromCredentialIssuer({
credentialIssuer,
metadata,
Expand All @@ -40,24 +23,31 @@ export class CredentialRequestClientBuilder {
version?: OpenId4VCIVersion;
credentialIdentifier?: string;
credentialTypes?: string | string[];
}): CredentialRequestClientBuilder {
const issuer = credentialIssuer;
const builder = new CredentialRequestClientBuilder();
builder.withVersion(version ?? OpenId4VCIVersion.VER_1_0_11);
builder.withCredentialEndpoint(metadata?.credential_endpoint ?? (issuer.endsWith('/') ? `${issuer}credential` : `${issuer}/credential`));
if (metadata?.deferred_credential_endpoint) {
builder.withDeferredCredentialEndpoint(metadata.deferred_credential_endpoint);
}
if (credentialIdentifier) {
builder.withCredentialIdentifier(credentialIdentifier);
}
if (credentialTypes) {
builder.withCredentialType(credentialTypes);
}): CredentialRequestClientBuilderV1_0_13 | CredentialRequestClientBuilderV1_0_11 {
const specVersion = version ? version : OpenId4VCIVersion.VER_1_0_13;
if (specVersion >= OpenId4VCIVersion.VER_1_0_13) {
return CredentialRequestClientBuilderV1_0_13.fromCredentialIssuer({
credentialIssuer,
metadata,
version,
credentialIdentifier,
credentialTypes,
});
} else {
if (!credentialTypes || credentialTypes.length === 0) {
throw new Error('CredentialTypes must be provided for v1_0_11');
}
return CredentialRequestClientBuilderV1_0_11.fromCredentialIssuer({ credentialIssuer, metadata, version, credentialTypes });
}
return builder;
}

public static async fromURI({ uri, metadata }: { uri: string; metadata?: EndpointMetadata }): Promise<CredentialRequestClientBuilder> {
public static async fromURI({
uri,
metadata,
}: {
uri: string;
metadata?: EndpointMetadata;
}): Promise<CredentialRequestClientBuilderV1_0_11 | CredentialRequestClientBuilderV1_0_13> {
const offer = await CredentialOfferClient.fromURI(uri);
return CredentialRequestClientBuilder.fromCredentialOfferRequest({ request: offer, ...offer, metadata, version: offer.version });
}
Expand All @@ -68,25 +58,13 @@ export class CredentialRequestClientBuilder {
baseUrl?: string;
version?: OpenId4VCIVersion;
metadata?: EndpointMetadata;
}): CredentialRequestClientBuilder {
const { request, metadata } = opts;
}): CredentialRequestClientBuilderV1_0_11 | CredentialRequestClientBuilderV1_0_13 {
const { request } = opts;
const version = opts.version ?? request.version ?? determineSpecVersionFromOffer(request.original_credential_offer);
if (version < OpenId4VCIVersion.VER_1_0_13) {
throw new Error('Versions below v1.0.13 (draft 13) are not supported.');
}
const builder = new CredentialRequestClientBuilder();
const issuer = getIssuerFromCredentialOfferPayload(request.credential_offer) ?? (metadata?.issuer as string);
builder.withVersion(version);
builder.withCredentialEndpoint(metadata?.credential_endpoint ?? (issuer.endsWith('/') ? `${issuer}credential` : `${issuer}/credential`));
if (metadata?.deferred_credential_endpoint) {
builder.withDeferredCredentialEndpoint(metadata.deferred_credential_endpoint);
}
const ids: string[] = (request.credential_offer as CredentialOfferPayloadV1_0_13).credential_configuration_ids;
// if there's only one in the offer, we pre-select it. if not, you should provide the credentialType
if (ids.length && ids.length === 1) {
builder.withCredentialIdentifier(ids[0]);
return CredentialRequestClientBuilderV1_0_11.fromCredentialOfferRequest(opts);
}
return builder;
return CredentialRequestClientBuilderV1_0_13.fromCredentialOfferRequest(opts);
}

public static fromCredentialOffer({
Expand All @@ -95,79 +73,17 @@ export class CredentialRequestClientBuilder {
}: {
credentialOffer: CredentialOfferRequestWithBaseUrl;
metadata?: EndpointMetadata;
}): CredentialRequestClientBuilder {
return CredentialRequestClientBuilder.fromCredentialOfferRequest({
request: credentialOffer,
}): CredentialRequestClientBuilderV1_0_11 | CredentialRequestClientBuilderV1_0_13 {
const version = determineSpecVersionFromOffer(credentialOffer.credential_offer);
if (version < OpenId4VCIVersion.VER_1_0_13) {
return CredentialRequestClientBuilderV1_0_11.fromCredentialOffer({
credentialOffer,
metadata,
});
}
return CredentialRequestClientBuilderV1_0_13.fromCredentialOffer({
credentialOffer,
metadata,
version: credentialOffer.version,
});
}

public withCredentialEndpointFromMetadata(metadata: CredentialIssuerMetadataV1_0_13): this {
this.credentialEndpoint = metadata.credential_endpoint;
return this;
}

public withCredentialEndpoint(credentialEndpoint: string): this {
this.credentialEndpoint = credentialEndpoint;
return this;
}

public withDeferredCredentialEndpointFromMetadata(metadata: CredentialIssuerMetadataV1_0_13): this {
this.deferredCredentialEndpoint = metadata.deferred_credential_endpoint;
return this;
}

public withDeferredCredentialEndpoint(deferredCredentialEndpoint: string): this {
this.deferredCredentialEndpoint = deferredCredentialEndpoint;
return this;
}

public withDeferredCredentialAwait(deferredCredentialAwait: boolean, deferredCredentialIntervalInMS?: number): this {
this.deferredCredentialAwait = deferredCredentialAwait;
this.deferredCredentialIntervalInMS = deferredCredentialIntervalInMS ?? 5000;
return this;
}

public withCredentialIdentifier(credentialIdentifier: string): this {
this.credentialIdentifier = credentialIdentifier;
return this;
}

public withCredentialType(credentialTypes: string | string[]): this {
this.credentialTypes = Array.isArray(credentialTypes) ? credentialTypes : [credentialTypes];
return this;
}

public withFormat(format: CredentialFormat | OID4VCICredentialFormat): this {
this.format = format;
return this;
}

public withSubjectIssuance(subjectIssuance: ExperimentalSubjectIssuance): this {
this.subjectIssuance = subjectIssuance;
return this;
}

public withToken(accessToken: string): this {
this.token = accessToken;
return this;
}

public withTokenFromResponse(response: AccessTokenResponse): this {
this.token = response.access_token;
return this;
}

public withVersion(version: OpenId4VCIVersion): this {
this.version = version;
return this;
}

public build(): CredentialRequestClient {
if (!this.version) {
this.withVersion(OpenId4VCIVersion.VER_1_0_11);
}
return new CredentialRequestClient(this);
}
}
Loading

0 comments on commit 30cddd3

Please sign in to comment.