Skip to content

Commit

Permalink
fix: (WIP) refactored and fixed build. still have to fix 8 test cases…
Browse files Browse the repository at this point in the history
… that are failing
  • Loading branch information
sksadjad committed May 11, 2024
1 parent ff88a64 commit d8c2c4f
Show file tree
Hide file tree
Showing 26 changed files with 1,168 additions and 175 deletions.
58 changes: 30 additions & 28 deletions packages/callback-example/lib/__tests__/issuerCallback.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ import { CredentialRequestClient, CredentialRequestClientBuilder, ProofOfPossess
import {
Alg,
CNonceState,
CredentialConfigurationSupported,
CredentialConfigurationSupported, CredentialIssuerMetadata,
IssuerCredentialSubjectDisplay,
IssueStatus,
Jwt,
JwtVerifyResult,
OpenId4VCIVersion,
ProofOfPossession,
ProofOfPossession
} from '@sphereon/oid4vci-common'
import { CredentialOfferSession } from '@sphereon/oid4vci-common/dist'
import { CredentialSupportedBuilderV1_13, VcIssuer, VcIssuerBuilder } from '@sphereon/oid4vci-issuer'
Expand All @@ -23,7 +23,7 @@ import * as jose from 'jose'
import { generateDid, getIssuerCallback, verifyCredential } from '../IssuerCallback'

const INITIATION_TEST_URI =
'openid-initiate-issuance://?credential_type=OpenBadgeCredential&issuer=https%3A%2F%2Fjff%2Ewalt%2Eid%2Fissuer-api%2Foidc%2F&pre-authorized_code=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhOTUyZjUxNi1jYWVmLTQ4YjMtODIxYy00OTRkYzgyNjljZjAiLCJwcmUtYXV0aG9yaXplZCI6dHJ1ZX0.YE5DlalcLC2ChGEg47CQDaN1gTxbaQqSclIVqsSAUHE&user_pin_required=false'
'openid-credential-offer://?credential_offer=%7B%22credential_issuer%22:%22https://credential-issuer.example.com%22,%22credential_configuration_ids%22:%5B%22UniversityDegreeCredential%22%5D,%22grants%22:%7B%22urn:ietf:params:oauth:grant-type:pre-authorized_code%22:%7B%22pre-authorized_code%22:%22oaKazRN8I0IbtZ0C7JuMn5%22,%22tx_code%22:%7B%22input_mode%22:%22text%22,%22description%22:%22Please%20enter%20the%20serial%20number%20of%20your%20physical%20drivers%20license%22%7D%7D%7D%7D'
const IDENTIPROOF_ISSUER_URL = 'https://example.com/credential'
const kid = 'did:example:ebfeb1f712ebc6f1c276e12ec21#keys-1'
let keypair: KeyPair // Proof of Possession JWT
Expand Down Expand Up @@ -85,28 +85,27 @@ describe('issuerCallback', () => {
const clientId = 'sphereon:wallet'

beforeAll(async () => {
const credentialsSupported: Record<string, CredentialConfigurationSupported> =
new CredentialSupportedBuilderV1_13()
.withCryptographicSuitesSupported('ES256K')
.withCryptographicBindingMethod('did')
.withFormat('jwt_vc_json')
.withTypes('VerifiableCredential')
.withId('UniversityDegree_JWT')
.withCredentialSupportedDisplay({
name: 'University Credential',
locale: 'en-US',
logo: {
url: 'https://exampleuniversity.com/public/logo.png',
alt_text: 'a square logo of a university',
},
background_color: '#12107c',
text_color: '#FFFFFF',
})
.addCredentialSubjectPropertyDisplay('given_name', {
name: 'given name',
locale: 'en-US',
} as IssuerCredentialSubjectDisplay)
.build()
const credentialsSupported: Record<string, CredentialConfigurationSupported> = new CredentialSupportedBuilderV1_13()
.withCryptographicSuitesSupported('ES256K')
.withCryptographicBindingMethod('did')
.withFormat('jwt_vc_json')
.withTypes('VerifiableCredential')
.withId('UniversityDegree_JWT')
.withCredentialSupportedDisplay({
name: 'University Credential',
locale: 'en-US',
logo: {
url: 'https://exampleuniversity.com/public/logo.png',
alt_text: 'a square logo of a university',
},
background_color: '#12107c',
text_color: '#FFFFFF',
})
.addCredentialSubjectPropertyDisplay('given_name', {
name: 'given name',
locale: 'en-US',
} as IssuerCredentialSubjectDisplay)
.build()
const stateManager = new MemoryStates<CredentialOfferSession>()
await stateManager.set('existing-state', {
issuerState: 'existing-state',
Expand Down Expand Up @@ -215,7 +214,10 @@ describe('issuerCallback', () => {
it('Should pass requesting a verifiable credential using the client', async () => {
const credReqClient = (await CredentialRequestClientBuilder.fromURI({ uri: INITIATION_TEST_URI }))
.withCredentialEndpoint('https://oidc4vci.demo.spruceid.com/credential')
.withFormat('jwt_vc_json')
.withCredentialEndpointFromMetadata({
credential_configurations_supported: {"VeriCred":{format: 'jwt_vc_json' } as CredentialConfigurationSupported}
} as unknown as CredentialIssuerMetadata)
.withFormat('jwt_vc_json')
.withCredentialType('credentialType')
.withToken('token')

Expand All @@ -237,7 +239,7 @@ describe('issuerCallback', () => {
callbacks: {
signCallback: proofOfPossessionCallbackFunction,
},
version: OpenId4VCIVersion.VER_1_0_11,
version: OpenId4VCIVersion.VER_1_0_13,
})
.withClientId(clientId)
.withKid(kid)
Expand All @@ -248,7 +250,7 @@ describe('issuerCallback', () => {
credentialTypes: ['VerifiableCredential'],
format: 'jwt_vc_json',
proofInput: proof,
version: OpenId4VCIVersion.VER_1_0_11,
version: OpenId4VCIVersion.VER_1_0_13,
})
expect(credentialRequest).toEqual({
format: 'jwt_vc_json',
Expand Down
4 changes: 2 additions & 2 deletions packages/client/lib/AccessTokenClientV1_0_11.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import { convertJsonToURI, formPost } from './functions';

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

export class AccessTokenClient {
export class AccessTokenClientV1_0_11 {
public async acquireAccessToken(opts: AccessTokenRequestOpts): Promise<OpenIDResponse<AccessTokenResponse>> {
const { asOpts, pin, codeVerifier, code, redirectUri, metadata } = opts;

Expand Down Expand Up @@ -77,7 +77,7 @@ export class AccessTokenClient {
}): Promise<OpenIDResponse<AccessTokenResponse>> {
this.validate(accessTokenRequest, isPinRequired);

const requestTokenURL = AccessTokenClient.determineTokenURL({
const requestTokenURL = AccessTokenClientV1_0_11.determineTokenURL({
asOpts,
issuerOpts,
metadata: metadata
Expand Down
7 changes: 4 additions & 3 deletions packages/client/lib/CredentialOfferClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,10 @@ export class CredentialOfferClient {
...(grants?.['urn:ietf:params:oauth:grant-type:pre-authorized_code']?.['pre-authorized_code'] && {
preAuthorizedCode: grants['urn:ietf:params:oauth:grant-type:pre-authorized_code']['pre-authorized_code'],
}),
...(request.credential_offer?.grants?.['urn:ietf:params:oauth:grant-type:pre-authorized_code']?.tx_code && {
// txCode: request.credential_offer?.grants?.['urn:ietf:params:oauth:grant-type:pre-authorized_code']?.tx_code,
}),
...(request.credential_offer?.grants?.['urn:ietf:params:oauth:grant-type:pre-authorized_code']?.tx_code &&
{
// txCode: request.credential_offer?.grants?.['urn:ietf:params:oauth:grant-type:pre-authorized_code']?.tx_code,
}),
};
}

Expand Down
31 changes: 17 additions & 14 deletions packages/client/lib/CredentialRequestClient.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {
acquireDeferredCredential,
acquireDeferredCredential, CredentialRequestV1_0_13,
CredentialResponse,
getCredentialRequestForVersion,
getUniformFormat,
Expand All @@ -9,14 +9,14 @@ import {
OpenIDResponse,
ProofOfPossession,
UniformCredentialRequest,
URL_NOT_VALID,
} from '@sphereon/oid4vci-common';
import { CredentialFormat } from '@sphereon/ssi-types';
import Debug from 'debug';
URL_NOT_VALID
} from '@sphereon/oid4vci-common'
import { CredentialFormat } from '@sphereon/ssi-types'
import Debug from 'debug'

import { CredentialRequestClientBuilder } from './CredentialRequestClientBuilder';
import { ProofOfPossessionBuilder } from './ProofOfPossessionBuilder';
import { isValidURL, post } from './functions';
import { CredentialRequestClientBuilder } from './CredentialRequestClientBuilder'
import { isValidURL, post } from './functions'
import { ProofOfPossessionBuilder } from './ProofOfPossessionBuilder'

const debug = Debug('sphereon:oid4vci:credential');

Expand All @@ -41,7 +41,7 @@ export async function buildProof<DIDDoc>(
) {
if ('proof_type' in proofInput) {
if (opts.cNonce) {
throw Error(`Cnonce param is only supported when using a Proof of Posession builder`);
throw Error(`Cnonce param is only supported when using a Proof of possession builder`);
}
return await ProofOfPossessionBuilder.fromProof(proofInput as ProofOfPossession, opts.version).build();
}
Expand Down Expand Up @@ -88,7 +88,10 @@ export class CredentialRequestClient {
}

public async acquireCredentialsUsingRequest(uniformRequest: UniformCredentialRequest): Promise<OpenIDResponse<CredentialResponse>> {
const request = getCredentialRequestForVersion(uniformRequest, this.version());
if (this.version() < OpenId4VCIVersion.VER_1_0_13) {
throw new Error('Versions below v1.0.13 (draft 13) are not supported.')
}
const request: CredentialRequestV1_0_13 = getCredentialRequestForVersion(uniformRequest, this.version()) as CredentialRequestV1_0_13;
const credentialEndpoint: string = this.credentialRequestOpts.credentialEndpoint;
if (!isValidURL(credentialEndpoint)) {
debug(`Invalid credential endpoint: ${credentialEndpoint}`);
Expand Down Expand Up @@ -154,7 +157,7 @@ export class CredentialRequestClient {
throw Error(`Credential type(s) need to be provided`);
}
// FIXME: this is mixing up the type (as id) from v8/v9 and the types (from the vc.type) from v11
else if (!this.isV11OrHigher() && types.length !== 1) {
else if (!this.isV13OrHigher() && types.length !== 1) {
throw Error('Only a single credential type is supported for V8/V9');
}
const proof = await buildProof(proofInput, opts);
Expand Down Expand Up @@ -199,10 +202,10 @@ export class CredentialRequestClient {
}

private version(): OpenId4VCIVersion {
return this.credentialRequestOpts?.version ?? OpenId4VCIVersion.VER_1_0_11;
return this.credentialRequestOpts?.version ?? OpenId4VCIVersion.VER_1_0_13;
}

private isV11OrHigher(): boolean {
return this.version() >= OpenId4VCIVersion.VER_1_0_11;
private isV13OrHigher(): boolean {
return this.version() >= OpenId4VCIVersion.VER_1_0_13;
}
}
156 changes: 156 additions & 0 deletions packages/client/lib/CredentialRequestClientBuilderV1_0_11.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
import {
AccessTokenResponse,
CredentialIssuerMetadata,
CredentialOfferPayloadV1_0_08,
CredentialOfferPayloadV1_0_11,
CredentialOfferRequestWithBaseUrl,
determineSpecVersionFromOffer,
EndpointMetadata,
getIssuerFromCredentialOfferPayload,
getTypesFromOfferV1_0_11,
OID4VCICredentialFormat,
OpenId4VCIVersion,
UniformCredentialOfferRequest,
} from '@sphereon/oid4vci-common';
import { CredentialFormat } from '@sphereon/ssi-types';

import { CredentialOfferClientV1_0_11 } from './CredentialOfferClientV1_0_11'
import { CredentialRequestClientV1_0_11 } from './CredentialRequestClientV1_0_11'

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

public static fromCredentialIssuer({
credentialIssuer,
metadata,
version,
credentialTypes,
}: {
credentialIssuer: string;
metadata?: EndpointMetadata;
version?: OpenId4VCIVersion;
credentialTypes: string | string[];
}): CredentialRequestClientBuilderV1_0_11 {
const issuer = credentialIssuer;
const builder = new CredentialRequestClientBuilderV1_0_11();
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);
}
builder.withCredentialType(credentialTypes);
return builder;
}

public static async fromURI({ uri, metadata }: { uri: string; metadata?: EndpointMetadata }): Promise<CredentialRequestClientBuilderV1_0_11> {
const offer = await CredentialOfferClientV1_0_11.fromURI(uri);
return CredentialRequestClientBuilderV1_0_11.fromCredentialOfferRequest({ request: offer, ...offer, metadata, version: offer.version });
}

public static fromCredentialOfferRequest(opts: {
request: UniformCredentialOfferRequest;
scheme?: string;
baseUrl?: string;
version?: OpenId4VCIVersion;
metadata?: EndpointMetadata;
}): CredentialRequestClientBuilderV1_0_11 {
const { request, metadata } = opts;
const version = opts.version ?? request.version ?? determineSpecVersionFromOffer(request.original_credential_offer);
const builder = new CredentialRequestClientBuilderV1_0_11();
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);
}

if (version <= OpenId4VCIVersion.VER_1_0_08) {
//todo: This basically sets all types available during initiation. Probably the user only wants a subset. So do we want to do this?
builder.withCredentialType((request.original_credential_offer as CredentialOfferPayloadV1_0_08).credential_type);
} else if (version <= OpenId4VCIVersion.VER_1_0_11) {
// todo: look whether this is correct
builder.withCredentialType(getTypesFromOfferV1_0_11(request.credential_offer as CredentialOfferPayloadV1_0_11));
}

return builder;
}

public static fromCredentialOffer({
credentialOffer,
metadata,
}: {
credentialOffer: CredentialOfferRequestWithBaseUrl;
metadata?: EndpointMetadata;
}): CredentialRequestClientBuilderV1_0_11 {
return CredentialRequestClientBuilderV1_0_11.fromCredentialOfferRequest({
request: credentialOffer,
metadata,
version: credentialOffer.version,
});
}

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

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

public withDeferredCredentialEndpointFromMetadata(metadata: CredentialIssuerMetadata): 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 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 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(): CredentialRequestClientV1_0_11 {
if (!this.version) {
this.withVersion(OpenId4VCIVersion.VER_1_0_11);
}
return new CredentialRequestClientV1_0_11(this);
}
}
Loading

0 comments on commit d8c2c4f

Please sign in to comment.