Skip to content

Commit

Permalink
Merge pull request #130 from Sphereon-Opensource/develop
Browse files Browse the repository at this point in the history
New release
  • Loading branch information
nklomp authored Jul 23, 2024
2 parents ece73f7 + 0e6a6a8 commit b6ee59a
Show file tree
Hide file tree
Showing 26 changed files with 7,853 additions and 5,291 deletions.
2 changes: 1 addition & 1 deletion packages/callback-example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"@sphereon/oid4vci-client": "workspace:*",
"@sphereon/oid4vci-common": "workspace:*",
"@sphereon/oid4vci-issuer": "workspace:*",
"@sphereon/ssi-types": "0.26.1-next.6",
"@sphereon/ssi-types": "0.28.0",
"jose": "^4.10.0"
},
"devDependencies": {
Expand Down
59 changes: 32 additions & 27 deletions packages/client/lib/AuthorizationCodeClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,16 @@ import {
convertJsonToURI,
CreateRequestObjectMode,
CredentialConfigurationSupportedV1_0_13,
CredentialDefinitionJwtVcJsonLdAndLdpVcV1_0_13,
CredentialDefinitionJwtVcJsonV1_0_13,
CredentialOfferPayloadV1_0_13,
CredentialOfferRequestWithBaseUrl,
determineSpecVersionFromOffer,
EndpointMetadataResultV1_0_13,
formPost,
isW3cCredentialSupported,
JsonURIMode,
Jwt,
OID4VCICredentialFormat,
OpenId4VCIVersion,
PARMode,
PKCEOpts,
Expand Down Expand Up @@ -95,14 +97,17 @@ export const createAuthorizationRequestUrl = async ({
clientId?: string;
version?: OpenId4VCIVersion;
}): Promise<string> => {
function removeDisplayAndValueTypes(obj: any): void {
for (const prop in obj) {
function removeDisplayAndValueTypes(obj: any) {
const newObj = { ...obj };
for (const prop in newObj) {
if (['display', 'value_type'].includes(prop)) {
delete obj[prop];
} else if (typeof obj[prop] === 'object') {
removeDisplayAndValueTypes(obj[prop]);
delete newObj[prop];
} else if (typeof newObj[prop] === 'object') {
newObj[prop] = removeDisplayAndValueTypes(newObj[prop]);
}
}

return newObj;
}

const { redirectUri, requestObjectOpts = { requestObjectMode: CreateRequestObjectMode.NONE } } = authorizationRequest;
Expand All @@ -111,7 +116,7 @@ export const createAuthorizationRequestUrl = async ({
let { scope, authorizationDetails } = authorizationRequest;
const parMode = endpointMetadata?.credentialIssuerMetadata?.require_pushed_authorization_requests
? PARMode.REQUIRE
: (authorizationRequest.parMode ?? (client_id ? PARMode.AUTO : PARMode.NEVER));
: authorizationRequest.parMode ?? (client_id ? PARMode.AUTO : PARMode.NEVER);
// Scope and authorization_details can be used in the same authorization request
// https://datatracker.ietf.org/doc/html/draft-ietf-oauth-rar-23#name-relationship-to-scope-param
if (!scope && !authorizationDetails) {
Expand All @@ -127,42 +132,42 @@ export const createAuthorizationRequestUrl = async ({
? 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) => {
const locations = [credentialOffer?.credential_offer.credential_issuer ?? endpointMetadata.issuer];

// TODO: credential_configuration_id seems to always be defined?
const credential_configuration_id: string | undefined = cred.configuration_id;
const vct: string | undefined = cred.vct;
let format: OID4VCICredentialFormat | undefined;
const format = credential_configuration_id ? undefined : cred.format;

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);
// SD-JWT VC
const vct = cred.format === 'vc+sd-jwt' ? cred.vct : undefined;

// W3C credentials
let credential_definition: undefined | Partial<CredentialDefinitionJwtVcJsonV1_0_13 | CredentialDefinitionJwtVcJsonLdAndLdpVcV1_0_13> =
undefined;
if (isW3cCredentialSupported(cred)) {
credential_definition = {
...cred.credential_definition,
// type: 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
type: format ? cred.credential_definition.type : undefined,
credentialSubject: cred.credential_definition.credentialSubject
? removeDisplayAndValueTypes(cred.credential_definition.credentialSubject)
: undefined,
};
}

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))) }),
...(vct && { vct, claims: cred.claims ? removeDisplayAndValueTypes(cred.claims) : undefined }),
} as AuthorizationDetails;
});
if (!authorizationDetails || authorizationDetails.length === 0) {
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 @@ -40,7 +40,7 @@ export const createAuthorizationRequestUrlV1_0_11 = async ({

const parMode = endpointMetadata?.credentialIssuerMetadata?.require_pushed_authorization_requests
? PARMode.REQUIRE
: (authorizationRequest.parMode ?? PARMode.AUTO);
: authorizationRequest.parMode ?? PARMode.AUTO;
// Scope and authorization_details can be used in the same authorization request
// https://datatracker.ietf.org/doc/html/draft-ietf-oauth-rar-23#name-relationship-to-scope-param
if (!scope && !authorizationDetails) {
Expand Down
16 changes: 7 additions & 9 deletions packages/client/lib/CredentialRequestClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import {
getUniformFormat,
isDeferredCredentialResponse,
isValidURL,
JsonLdIssuerCredentialDefinition,
OID4VCICredentialFormat,
OpenId4VCIVersion,
OpenIDResponse,
Expand Down Expand Up @@ -203,7 +202,9 @@ export class CredentialRequestClient {
// TODO: we should move format specific logic
if (format === 'jwt_vc_json' || format === 'jwt_vc') {
return {
types,
credential_definition: {
type: types,
},
format,
proof,
...opts.subjectIssuance,
Expand All @@ -218,13 +219,10 @@ export class CredentialRequestClient {
proof,
...opts.subjectIssuance,

// Ignored because v11 does not have the context value, but it is required in v12
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
credential_definition: {
types,
...(opts.context && { '@context': opts.context }),
} as JsonLdIssuerCredentialDefinition,
type: types,
'@context': opts.context as string[],
},
};
} else if (format === 'vc+sd-jwt') {
if (types.length > 1) {
Expand All @@ -236,7 +234,7 @@ export class CredentialRequestClient {
proof,
vct: types[0],
...opts.subjectIssuance,
} as CredentialRequestV1_0_13;
};
}

throw new Error(`Unsupported format: ${format}`);
Expand Down
2 changes: 1 addition & 1 deletion packages/client/lib/OpenID4VCIClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -549,7 +549,7 @@ export class OpenID4VCIClient {
issuerSupportedFlowTypes(): AuthzFlowType[] {
return (
this.credentialOffer?.supportedFlows ??
((this._state.endpointMetadata?.credentialIssuerMetadata?.authorization_endpoint ?? this._state.endpointMetadata?.authorization_server)
(this._state.endpointMetadata?.credentialIssuerMetadata?.authorization_endpoint ?? this._state.endpointMetadata?.authorization_server
? [AuthzFlowType.AUTHORIZATION_CODE_FLOW]
: [])
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,6 @@
import { KeyObject } from 'crypto';

import {
Alg,
CredentialIssuerMetadataV1_0_13,
CredentialRequestV1_0_13,
Jwt,
JwtVerifyResult,
OpenId4VCIVersion,
ProofOfPossession,
} from '@sphereon/oid4vci-common';
import { Alg, CredentialIssuerMetadataV1_0_13, Jwt, JwtVerifyResult, OpenId4VCIVersion, ProofOfPossession } from '@sphereon/oid4vci-common';
import * as jose from 'jose';

import { CredentialRequestOpts, ProofOfPossessionBuilder } from '..';
Expand Down Expand Up @@ -112,7 +104,7 @@ describe('Credential Request Client Builder', () => {
.withKid(kid)
.build();
await proofOfPossessionVerifierCallbackFunction({ ...proof, kid });
const credentialRequest: CredentialRequestV1_0_13 = await credReqClient.createCredentialRequest({
const credentialRequest = await credReqClient.createCredentialRequest({
proofInput: proof,
credentialIdentifier: 'OpenBadgeCredential',
version: OpenId4VCIVersion.VER_1_0_13,
Expand Down Expand Up @@ -142,7 +134,7 @@ describe('Credential Request Client Builder', () => {
.withKid(kid_withoutDid)
.build();
await proofOfPossessionVerifierCallbackFunction({ ...proof, kid: kid_withoutDid });
const credentialRequest: CredentialRequestV1_0_13 = await credReqClient.createCredentialRequest({
const credentialRequest = await credReqClient.createCredentialRequest({
proofInput: proof,
credentialTypes: 'OpenBadgeCredential',
version: OpenId4VCIVersion.VER_1_0_13,
Expand Down
8 changes: 4 additions & 4 deletions packages/client/lib/__tests__/SdJwt.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {
AccessTokenRequest,
CredentialConfigurationSupportedSdJwtVcV1_0_13,
CredentialConfigurationSupportedV1_0_13,
CredentialRequestV1_0_13,
CredentialSupportedSdJwtVc,
} from '@sphereon/oid4vci-common';
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
Expand Down Expand Up @@ -109,7 +109,7 @@ describe('sd-jwt vc', () => {
const supported = client.getCredentialsSupported('vc+sd-jwt');
expect(supported).toEqual({ SdJwtCredentialId: { format: 'vc+sd-jwt', id: 'SdJwtCredentialId', vct: 'SdJwtCredentialId' } });

const offered = supported['SdJwtCredentialId'] as CredentialSupportedSdJwtVc;
const offered = supported['SdJwtCredentialId'] as CredentialConfigurationSupportedSdJwtVcV1_0_13;

nock(issuerMetadata.token_endpoint as string)
.post('/')
Expand All @@ -130,7 +130,7 @@ describe('sd-jwt vc', () => {
.post('/')
.reply(200, async (_, body) =>
vcIssuer.issueCredential({
credentialRequest: { ...(body as CredentialRequestV1_0_13), credential_identifier: offered.vct },
credentialRequest: { ...(body as any), credential_identifier: 'SdJwtCredentialId' },
credential: {
vct: 'Hello',
iss: 'did:example:123',
Expand Down Expand Up @@ -233,7 +233,7 @@ describe('sd-jwt vc', () => {
.post('/')
.reply(200, async (_, body) =>
vcIssuer.issueCredential({
credentialRequest: { ...(body as CredentialRequestV1_0_13), credential_identifier: offered.vct },
credentialRequest: { ...(body as any), credential_identifier: offered.vct },
credential: {
vct: 'Hello',
iss: 'example.com',
Expand Down
4 changes: 2 additions & 2 deletions packages/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@
},
"dependencies": {
"@sphereon/oid4vci-common": "workspace:*",
"@sphereon/ssi-types": "0.26.1-next.132",
"@sphereon/ssi-types": "0.28.0",
"cross-fetch": "^3.1.8",
"debug": "^4.3.5"
},
"devDependencies": {
"@sphereon/ssi-sdk-ext.key-utils": "^0.22.0",
"@sphereon/ssi-sdk-ext.key-utils": "^0.23.0",
"@transmute/did-key.js": "^0.3.0-unstable.10",
"@trust/keyto": "^2.0.0-alpha1",
"@types/jest": "^29.5.12",
Expand Down
35 changes: 22 additions & 13 deletions packages/common/lib/functions/CredentialRequestUtil.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,28 @@ export function getTypesFromRequest(credentialRequest: CredentialRequest, opts?:
let types: string[] = [];
if ('credential_identifier' in credentialRequest && credentialRequest.credential_identifier) {
throw Error(`Cannot get types from request when it contains a credential_identifier`);
} else if (credentialRequest.format === 'jwt_vc_json' || credentialRequest.format === 'jwt_vc') {
types = 'types' in credentialRequest ? credentialRequest.types : [];
} else if (credentialRequest.format === 'jwt_vc_json-ld' || credentialRequest.format === 'ldp_vc') {
types =
'credential_definition' in credentialRequest && credentialRequest.credential_definition
? credentialRequest.credential_definition.types
: // eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
'types' in credentialRequest.types
? (credentialRequest['types' as keyof CredentialRequest] as unknown as string[])
: [];
} else if (credentialRequest.format === 'vc+sd-jwt') {
types = 'vct' in credentialRequest ? [credentialRequest.vct as string] : [];
} else if (
credentialRequest.format === 'jwt_vc_json-ld' ||
credentialRequest.format === 'ldp_vc' ||
credentialRequest.format === 'jwt_vc' ||
credentialRequest.format === 'jwt_vc_json'
) {
if ('credential_definition' in credentialRequest && credentialRequest.credential_definition) {
types =
'types' in credentialRequest.credential_definition
? credentialRequest.credential_definition.types
: credentialRequest.credential_definition.type;
}

if ('type' in credentialRequest && Array.isArray(credentialRequest.type)) {
types = credentialRequest.type;
}

if ('types' in credentialRequest && Array.isArray(credentialRequest.types)) {
types = credentialRequest.types;
}
} else if (credentialRequest.format === 'vc+sd-jwt' && 'vct' in credentialRequest) {
types = [credentialRequest.vct];
}

if (!types || types.length === 0) {
Expand Down
16 changes: 7 additions & 9 deletions packages/common/lib/functions/IssuerMetadataUtils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { getTypesFromObject, VCI_LOG_COMMON } from '../index';
import { getTypesFromObject, isW3cCredentialSupported, VCI_LOG_COMMON } from '../index';
import {
AuthorizationServerMetadata,
CredentialConfigurationSupported,
Expand Down Expand Up @@ -112,13 +112,11 @@ export function getSupportedCredential(opts?: {
} else if (types) {
isTypeMatch = normalizedTypes.every((type) => types.includes(type));
} else {
if ('credential_definition' in config) {
isTypeMatch = normalizedTypes.every((type) => config.credential_definition.type?.includes(type));
} else if ('type' in config && Array.isArray(config.type)) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
isTypeMatch = normalizedTypes.every((type) => config.type.includes(type));
} else if ('types' in config) {
if (isW3cCredentialSupported(config) && 'credential_definition' in config) {
isTypeMatch = normalizedTypes.every((type) => config.credential_definition.type.includes(type));
} else if (isW3cCredentialSupported(config) && 'type' in config && Array.isArray(config.type)) {
isTypeMatch = normalizedTypes.every((type) => (config.type as string[]).includes(type));
} else if (isW3cCredentialSupported(config) && 'types' in config) {
isTypeMatch = normalizedTypes.every((type) => config.types?.includes(type));
}
}
Expand Down Expand Up @@ -183,7 +181,7 @@ export function getIssuerDisplays(metadata: CredentialIssuerMetadata | IssuerMet
metadata.display?.filter(
(item) => !opts?.prefLocales || opts.prefLocales.length === 0 || (item.locale && opts.prefLocales.includes(item.locale)) || !item.locale,
) ?? [];
return matchedDisplays.sort((item) => (item.locale ? (opts?.prefLocales.indexOf(item.locale) ?? 1) : Number.MAX_VALUE));
return matchedDisplays.sort((item) => (item.locale ? opts?.prefLocales.indexOf(item.locale) ?? 1 : Number.MAX_VALUE));
}

/**
Expand Down
Loading

0 comments on commit b6ee59a

Please sign in to comment.