Skip to content

Commit

Permalink
fix: fixes for PAR. Several things were missing, wrong. Higly likely …
Browse files Browse the repository at this point in the history
…this is a problem for non PAR flows as well
  • Loading branch information
nklomp committed May 29, 2024
1 parent 86d480b commit 9ed5064
Show file tree
Hide file tree
Showing 13 changed files with 6,500 additions and 9,047 deletions.
1 change: 0 additions & 1 deletion packages/callback-example/lib/IssuerCallback.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ export const getIssuerCallbackV1_0_11 = (credential: CredentialIssuanceInput, ke
}
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const getIssuerCallbackV1_0_13 = (
credential: CredentialIssuanceInput,
credentialRequest: CredentialRequest,
Expand Down
74 changes: 56 additions & 18 deletions packages/client/lib/AuthorizationCodeClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ import {
CredentialConfigurationSupportedV1_0_13,
CredentialOfferPayloadV1_0_13,
CredentialOfferRequestWithBaseUrl,
CredentialConfigurationSupported,
determineSpecVersionFromOffer,
EndpointMetadataResultV1_0_13,
formPost,
JsonURIMode,
OID4VCICredentialFormat,
OpenId4VCIVersion,
PARMode,
PKCEOpts,
Expand All @@ -24,11 +24,15 @@ const debug = Debug('sphereon:oid4vci');
function filterSupportedCredentials(
credentialOffer: CredentialOfferPayloadV1_0_13,
credentialsSupported?: Record<string, CredentialConfigurationSupportedV1_0_13>,
): CredentialConfigurationSupportedV1_0_13[] {
): (CredentialConfigurationSupportedV1_0_13 & { configuration_id: string })[] {
if (!credentialOffer.credential_configuration_ids || !credentialsSupported) {
return [];
}
return credentialOffer.credential_configuration_ids.map((id) => credentialsSupported[id]).filter((cred) => cred !== undefined);
return Object.entries(credentialsSupported)
.filter((entry) => credentialOffer.credential_configuration_ids?.includes(entry[0]))
.map((entry) => {
return { ...entry[1], configuration_id: entry[0] };
});
}

export const createAuthorizationRequestUrl = async ({
Expand All @@ -37,13 +41,25 @@ export const createAuthorizationRequestUrl = async ({
authorizationRequest,
credentialOffer,
credentialConfigurationSupported,
version,
}: {
pkce: PKCEOpts;
endpointMetadata: EndpointMetadataResultV1_0_13;
authorizationRequest: AuthorizationRequestOpts;
credentialOffer?: CredentialOfferRequestWithBaseUrl;
credentialConfigurationSupported?: Record<string, CredentialConfigurationSupportedV1_0_13>;
version?: OpenId4VCIVersion;
}): Promise<string> => {
function removeDisplayAndValueTypes(obj: any): void {
for (const prop in obj) {
if (['display', 'value_type'].includes(prop)) {
delete obj[prop];
} else if (typeof obj[prop] === 'object') {
removeDisplayAndValueTypes(obj[prop]);
}
}
}

const { redirectUri, clientId } = authorizationRequest;
let { scope, authorizationDetails } = authorizationRequest;
const parMode = endpointMetadata?.credentialIssuerMetadata?.require_pushed_authorization_requests
Expand All @@ -58,28 +74,50 @@ export const createAuthorizationRequestUrl = async ({
if ('credentials' in credentialOffer.credential_offer) {
throw new Error('CredentialOffer format is wrong.');
}
const creds: CredentialConfigurationSupportedV1_0_13[] =
determineSpecVersionFromOffer(credentialOffer.credential_offer) === OpenId4VCIVersion.VER_1_0_13
const ver = version ?? determineSpecVersionFromOffer(credentialOffer.credential_offer) ?? OpenId4VCIVersion.VER_1_0_13;
const creds =
ver === OpenId4VCIVersion.VER_1_0_13
? filterSupportedCredentials(credentialOffer.credential_offer as CredentialOfferPayloadV1_0_13, credentialConfigurationSupported)
: [];

// FIXME: complains about VCT for sd-jwt
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
authorizationDetails = creds
.flatMap((cred) => cred as CredentialConfigurationSupported)
.filter((cred) => !!cred)
.map((cred) => {
return {
...cred,
type: 'openid_credential',
locations: [endpointMetadata.issuer],
authorizationDetails = creds.flatMap((cred) => {
const locations = [credentialOffer?.credential_offer.credential_issuer ?? endpointMetadata.issuer];
const credential_configuration_id: string | undefined = cred.configuration_id;
const vct: string | undefined = cred.vct;
let format: OID4VCICredentialFormat | undefined;

if (!credential_configuration_id) {
format = cred.format;
}
if (!credential_configuration_id && !cred.format) {
throw Error('format is required in authorization details');
}

const meta: any = {};
const credential_definition = cred.credential_definition;
if (credential_definition?.type && !format) {
// ype: OPTIONAL. Array as defined in Appendix A.1.1.2. This claim contains the type values the Wallet requests authorization for at the Credential Issuer. It MUST be present if the claim format is present in the root of the authorization details object. It MUST not be present otherwise.
// It meens we have a config_id, already mapping it to an explicit format and types
delete credential_definition.type;
}
if (credential_definition.credentialSubject) {
removeDisplayAndValueTypes(credential_definition.credentialSubject);
}

// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
format: cred!.format,
} satisfies AuthorizationDetails;
});
return {
type: 'openid_credential',
...meta,
locations,
...(credential_definition && { credential_definition }),
...(credential_configuration_id && { credential_configuration_id }),
...(format && { format }),
...(vct && { vct }),
...(cred.claims && { claims: removeDisplayAndValueTypes(JSON.parse(JSON.stringify(cred.claims))) }),
} as AuthorizationDetails;
});
if (!authorizationDetails || authorizationDetails.length === 0) {
throw Error(`Could not create authorization details from credential offer. Please pass in explicit details`);
}
Expand Down
2 changes: 1 addition & 1 deletion packages/client/lib/AuthorizationCodeClientV1_0_11.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ export const createAuthorizationRequestUrlV1_0_11 = async ({
format: cred!.format,
} satisfies AuthorizationDetails;
});
if (!authorizationDetails || authorizationDetails.length === 0) {
if (!authorizationDetails || (Array.isArray(authorizationDetails) && authorizationDetails.length === 0)) {
throw Error(`Could not create authorization details from credential offer. Please pass in explicit details`);
}
}
Expand Down
1 change: 1 addition & 0 deletions packages/client/lib/OpenID4VCIClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,7 @@ export class OpenID4VCIClient {
authorizationRequest: this._state.authorizationRequestOpts,
credentialOffer: this.credentialOffer,
credentialConfigurationSupported: this.getCredentialsSupported(),
version: this.version(),
});
}
return this._state.authorizationURL;
Expand Down
12 changes: 0 additions & 12 deletions packages/client/lib/__tests__/AuthorizationDetailsBuilder.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,16 +50,4 @@ describe('AuthorizationDetailsBuilder test', () => {
new AuthorizationDetailsBuilder().withType('openid_credential').withLocations(['test1']).buildJwtVcJson();
}).toThrow(Error('Type and format are required properties'));
});
it('should be able to add random field to the object', () => {
const actual = new AuthorizationDetailsBuilder()
.withFormats('jwt_vc' as OID4VCICredentialFormat)
.withType('openid_credential')
.buildJwtVcJson();
actual['random'] = 'test';
expect(actual).toEqual({
type: 'openid_credential',
format: 'jwt_vc',
random: 'test',
});
});
});
4 changes: 2 additions & 2 deletions packages/client/lib/__tests__/CredentialRequestClient.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ import {
CredentialRequestClientBuilder,
CredentialRequestClientBuilderV1_0_11,
MetadataClientV1_0_11,
ProofOfPossessionBuilder
} from '..'
ProofOfPossessionBuilder,
} from '..';

import { IDENTIPROOF_ISSUER_URL, IDENTIPROOF_OID4VCI_METADATA, INITIATION_TEST, WALT_OID4VCI_METADATA } from './MetadataMocks';
import { getMockData } from './data/VciDataFixtures';
Expand Down
2 changes: 1 addition & 1 deletion packages/client/lib/__tests__/IssuanceInitiation.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { OpenId4VCIVersion } from '@sphereon/oid4vci-common';
import nock from 'nock';

import { CredentialOfferClient } from '../CredentialOfferClient';
import { CredentialOfferClientV1_0_11 } from '../CredentialOfferClientV1_0_11'
import { CredentialOfferClientV1_0_11 } from '../CredentialOfferClientV1_0_11';

import { INITIATION_TEST, INITIATION_TEST_HTTPS_URI, INITIATION_TEST_URI } from './MetadataMocks';

Expand Down
4 changes: 2 additions & 2 deletions packages/common/lib/functions/CredentialRequestUtil.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import { getFormatForVersion } from './FormatUtils';

export function getTypesFromRequest(credentialRequest: CredentialRequest, opts?: { filterVerifiableCredential: boolean }) {
let types: string[] = [];
if('credential_identifier' in credentialRequest && credentialRequest.credential_identifier) {
types = [credentialRequest.credential_identifier]
if ('credential_identifier' in credentialRequest && credentialRequest.credential_identifier) {
types = [credentialRequest.credential_identifier];
} else if (credentialRequest.format === 'jwt_vc_json' || credentialRequest.format === 'jwt_vc') {
types =
'types' in credentialRequest
Expand Down
9 changes: 1 addition & 8 deletions packages/common/lib/functions/Encoding.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,4 @@
import {
BAD_PARAMS,
DecodeURIAsJsonOpts,
EncodeJsonAsURIOpts,
JsonURIMode,
OpenId4VCIVersion,
SearchValue
} from '../types'
import { BAD_PARAMS, DecodeURIAsJsonOpts, EncodeJsonAsURIOpts, JsonURIMode, OpenId4VCIVersion, SearchValue } from '../types';

/**
* @type {(json: {[s:string]: never} | ArrayLike<never> | string | object, opts?: EncodeJsonAsURIOpts)} encodes a Json object into a URI
Expand Down
8 changes: 4 additions & 4 deletions packages/common/lib/functions/IssuerMetadataUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export function getSupportedCredential(opts?: {

return Object.entries(credentialConfigurations).reduce(
(filteredConfigs, [id, config]) => {
const isTypeMatch = normalizedTypes.length === 0 || normalizedTypes.some((type) => config.credential_definition.type.includes(type));
const isTypeMatch = normalizedTypes.length === 0 || normalizedTypes.some((type) => config.credential_definition.type?.includes(type));
const isFormatMatch = normalizedFormats.length === 0 || normalizedFormats.includes(config.format);

if (isTypeMatch && isFormatMatch) {
Expand All @@ -77,11 +77,11 @@ export function getTypesFromCredentialSupported(
credentialSupported.format === 'jwt_vc_json-ld' ||
credentialSupported.format === 'ldp_vc'
) {
types = credentialSupported.types
types = (credentialSupported.types
? (credentialSupported.types as string[])
: 'credential_definition' in credentialSupported
? credentialSupported.credential_definition.type
: [];
? credentialSupported.credential_definition?.type
: []) ?? [];
} else if (credentialSupported.format === 'vc+sd-jwt') {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
Expand Down
4 changes: 2 additions & 2 deletions packages/common/lib/types/Authorization.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,8 +124,8 @@ export interface CommonAuthorizationDetails {
*/
locations?: string[];

// eslint-disable-next-line @typescript-eslint/no-explicit-any
[key: string]: any;
/* // eslint-disable-next-line @typescript-eslint/no-explicit-any
// [key: string]: any;*/
}

export interface AuthorizationDetailsJwtVcJson extends CommonAuthorizationDetails {
Expand Down
3 changes: 2 additions & 1 deletion packages/common/lib/types/v1_0_13.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export interface IssuerMetadataV1_0_13 {
}

export type CredentialDefinitionV1_0_13 = {
type: string[];
type?: string[];
credentialSubject?: IssuerCredentialSubject;
};

Expand All @@ -44,6 +44,7 @@ export type CredentialConfigurationSupportedV1_0_13 = {
*/
vct?: string;
id?: string;
claims?: IssuerCredentialSubject;
format: OID4VCICredentialFormat; //REQUIRED. A JSON string identifying the format of this credential, e.g. jwt_vc_json or ldp_vc.
scope?: string; // OPTIONAL. A JSON string identifying the scope value that this Credential Issuer supports for this particular Credential. The value can be the same across multiple credential_configurations_supported objects. The Authorization Server MUST be able to uniquely identify the Credential Issuer based on the scope value. The Wallet can use this value in the Authorization Request as defined in Section 5.1.2. Scope values in this Credential Issuer metadata MAY duplicate those in the scopes_supported parameter of the Authorization Server.
cryptographic_binding_methods_supported?: string[];
Expand Down
Loading

0 comments on commit 9ed5064

Please sign in to comment.