Skip to content

Commit b6ee59a

Browse files
authored
Merge pull request #130 from Sphereon-Opensource/develop
New release
2 parents ece73f7 + 0e6a6a8 commit b6ee59a

26 files changed

Lines changed: 7853 additions & 5291 deletions

packages/callback-example/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
"@sphereon/oid4vci-client": "workspace:*",
1919
"@sphereon/oid4vci-common": "workspace:*",
2020
"@sphereon/oid4vci-issuer": "workspace:*",
21-
"@sphereon/ssi-types": "0.26.1-next.6",
21+
"@sphereon/ssi-types": "0.28.0",
2222
"jose": "^4.10.0"
2323
},
2424
"devDependencies": {

packages/client/lib/AuthorizationCodeClient.ts

Lines changed: 32 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,16 @@ import {
55
convertJsonToURI,
66
CreateRequestObjectMode,
77
CredentialConfigurationSupportedV1_0_13,
8+
CredentialDefinitionJwtVcJsonLdAndLdpVcV1_0_13,
9+
CredentialDefinitionJwtVcJsonV1_0_13,
810
CredentialOfferPayloadV1_0_13,
911
CredentialOfferRequestWithBaseUrl,
1012
determineSpecVersionFromOffer,
1113
EndpointMetadataResultV1_0_13,
1214
formPost,
15+
isW3cCredentialSupported,
1316
JsonURIMode,
1417
Jwt,
15-
OID4VCICredentialFormat,
1618
OpenId4VCIVersion,
1719
PARMode,
1820
PKCEOpts,
@@ -95,14 +97,17 @@ export const createAuthorizationRequestUrl = async ({
9597
clientId?: string;
9698
version?: OpenId4VCIVersion;
9799
}): Promise<string> => {
98-
function removeDisplayAndValueTypes(obj: any): void {
99-
for (const prop in obj) {
100+
function removeDisplayAndValueTypes(obj: any) {
101+
const newObj = { ...obj };
102+
for (const prop in newObj) {
100103
if (['display', 'value_type'].includes(prop)) {
101-
delete obj[prop];
102-
} else if (typeof obj[prop] === 'object') {
103-
removeDisplayAndValueTypes(obj[prop]);
104+
delete newObj[prop];
105+
} else if (typeof newObj[prop] === 'object') {
106+
newObj[prop] = removeDisplayAndValueTypes(newObj[prop]);
104107
}
105108
}
109+
110+
return newObj;
106111
}
107112

108113
const { redirectUri, requestObjectOpts = { requestObjectMode: CreateRequestObjectMode.NONE } } = authorizationRequest;
@@ -111,7 +116,7 @@ export const createAuthorizationRequestUrl = async ({
111116
let { scope, authorizationDetails } = authorizationRequest;
112117
const parMode = endpointMetadata?.credentialIssuerMetadata?.require_pushed_authorization_requests
113118
? PARMode.REQUIRE
114-
: (authorizationRequest.parMode ?? (client_id ? PARMode.AUTO : PARMode.NEVER));
119+
: authorizationRequest.parMode ?? (client_id ? PARMode.AUTO : PARMode.NEVER);
115120
// Scope and authorization_details can be used in the same authorization request
116121
// https://datatracker.ietf.org/doc/html/draft-ietf-oauth-rar-23#name-relationship-to-scope-param
117122
if (!scope && !authorizationDetails) {
@@ -127,42 +132,42 @@ export const createAuthorizationRequestUrl = async ({
127132
? filterSupportedCredentials(credentialOffer.credential_offer as CredentialOfferPayloadV1_0_13, credentialConfigurationSupported)
128133
: [];
129134

130-
// FIXME: complains about VCT for sd-jwt
131-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
132-
// @ts-ignore
133135
authorizationDetails = creds.flatMap((cred) => {
134136
const locations = [credentialOffer?.credential_offer.credential_issuer ?? endpointMetadata.issuer];
137+
138+
// TODO: credential_configuration_id seems to always be defined?
135139
const credential_configuration_id: string | undefined = cred.configuration_id;
136-
const vct: string | undefined = cred.vct;
137-
let format: OID4VCICredentialFormat | undefined;
140+
const format = credential_configuration_id ? undefined : cred.format;
138141

139-
if (!credential_configuration_id) {
140-
format = cred.format;
141-
}
142142
if (!credential_configuration_id && !cred.format) {
143143
throw Error('format is required in authorization details');
144144
}
145145

146-
const meta: any = {};
147-
const credential_definition = cred.credential_definition;
148-
if (credential_definition?.type && !format) {
149-
// 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.
150-
// It meens we have a config_id, already mapping it to an explicit format and types
151-
delete credential_definition.type;
152-
}
153-
if (credential_definition.credentialSubject) {
154-
removeDisplayAndValueTypes(credential_definition.credentialSubject);
146+
// SD-JWT VC
147+
const vct = cred.format === 'vc+sd-jwt' ? cred.vct : undefined;
148+
149+
// W3C credentials
150+
let credential_definition: undefined | Partial<CredentialDefinitionJwtVcJsonV1_0_13 | CredentialDefinitionJwtVcJsonLdAndLdpVcV1_0_13> =
151+
undefined;
152+
if (isW3cCredentialSupported(cred)) {
153+
credential_definition = {
154+
...cred.credential_definition,
155+
// 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.
156+
// It meens we have a config_id, already mapping it to an explicit format and types
157+
type: format ? cred.credential_definition.type : undefined,
158+
credentialSubject: cred.credential_definition.credentialSubject
159+
? removeDisplayAndValueTypes(cred.credential_definition.credentialSubject)
160+
: undefined,
161+
};
155162
}
156163

157164
return {
158165
type: 'openid_credential',
159-
...meta,
160166
locations,
161167
...(credential_definition && { credential_definition }),
162168
...(credential_configuration_id && { credential_configuration_id }),
163169
...(format && { format }),
164-
...(vct && { vct }),
165-
...(cred.claims && { claims: removeDisplayAndValueTypes(JSON.parse(JSON.stringify(cred.claims))) }),
170+
...(vct && { vct, claims: cred.claims ? removeDisplayAndValueTypes(cred.claims) : undefined }),
166171
} as AuthorizationDetails;
167172
});
168173
if (!authorizationDetails || authorizationDetails.length === 0) {

packages/client/lib/AuthorizationCodeClientV1_0_11.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ export const createAuthorizationRequestUrlV1_0_11 = async ({
4040

4141
const parMode = endpointMetadata?.credentialIssuerMetadata?.require_pushed_authorization_requests
4242
? PARMode.REQUIRE
43-
: (authorizationRequest.parMode ?? PARMode.AUTO);
43+
: authorizationRequest.parMode ?? PARMode.AUTO;
4444
// Scope and authorization_details can be used in the same authorization request
4545
// https://datatracker.ietf.org/doc/html/draft-ietf-oauth-rar-23#name-relationship-to-scope-param
4646
if (!scope && !authorizationDetails) {

packages/client/lib/CredentialRequestClient.ts

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import {
66
getUniformFormat,
77
isDeferredCredentialResponse,
88
isValidURL,
9-
JsonLdIssuerCredentialDefinition,
109
OID4VCICredentialFormat,
1110
OpenId4VCIVersion,
1211
OpenIDResponse,
@@ -203,7 +202,9 @@ export class CredentialRequestClient {
203202
// TODO: we should move format specific logic
204203
if (format === 'jwt_vc_json' || format === 'jwt_vc') {
205204
return {
206-
types,
205+
credential_definition: {
206+
type: types,
207+
},
207208
format,
208209
proof,
209210
...opts.subjectIssuance,
@@ -218,13 +219,10 @@ export class CredentialRequestClient {
218219
proof,
219220
...opts.subjectIssuance,
220221

221-
// Ignored because v11 does not have the context value, but it is required in v12
222-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
223-
// @ts-ignore
224222
credential_definition: {
225-
types,
226-
...(opts.context && { '@context': opts.context }),
227-
} as JsonLdIssuerCredentialDefinition,
223+
type: types,
224+
'@context': opts.context as string[],
225+
},
228226
};
229227
} else if (format === 'vc+sd-jwt') {
230228
if (types.length > 1) {
@@ -236,7 +234,7 @@ export class CredentialRequestClient {
236234
proof,
237235
vct: types[0],
238236
...opts.subjectIssuance,
239-
} as CredentialRequestV1_0_13;
237+
};
240238
}
241239

242240
throw new Error(`Unsupported format: ${format}`);

packages/client/lib/OpenID4VCIClient.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -549,7 +549,7 @@ export class OpenID4VCIClient {
549549
issuerSupportedFlowTypes(): AuthzFlowType[] {
550550
return (
551551
this.credentialOffer?.supportedFlows ??
552-
((this._state.endpointMetadata?.credentialIssuerMetadata?.authorization_endpoint ?? this._state.endpointMetadata?.authorization_server)
552+
(this._state.endpointMetadata?.credentialIssuerMetadata?.authorization_endpoint ?? this._state.endpointMetadata?.authorization_server
553553
? [AuthzFlowType.AUTHORIZATION_CODE_FLOW]
554554
: [])
555555
);

packages/client/lib/__tests__/CredentialRequestClientBuilder.spec.ts

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,6 @@
11
import { KeyObject } from 'crypto';
22

3-
import {
4-
Alg,
5-
CredentialIssuerMetadataV1_0_13,
6-
CredentialRequestV1_0_13,
7-
Jwt,
8-
JwtVerifyResult,
9-
OpenId4VCIVersion,
10-
ProofOfPossession,
11-
} from '@sphereon/oid4vci-common';
3+
import { Alg, CredentialIssuerMetadataV1_0_13, Jwt, JwtVerifyResult, OpenId4VCIVersion, ProofOfPossession } from '@sphereon/oid4vci-common';
124
import * as jose from 'jose';
135

146
import { CredentialRequestOpts, ProofOfPossessionBuilder } from '..';
@@ -112,7 +104,7 @@ describe('Credential Request Client Builder', () => {
112104
.withKid(kid)
113105
.build();
114106
await proofOfPossessionVerifierCallbackFunction({ ...proof, kid });
115-
const credentialRequest: CredentialRequestV1_0_13 = await credReqClient.createCredentialRequest({
107+
const credentialRequest = await credReqClient.createCredentialRequest({
116108
proofInput: proof,
117109
credentialIdentifier: 'OpenBadgeCredential',
118110
version: OpenId4VCIVersion.VER_1_0_13,
@@ -142,7 +134,7 @@ describe('Credential Request Client Builder', () => {
142134
.withKid(kid_withoutDid)
143135
.build();
144136
await proofOfPossessionVerifierCallbackFunction({ ...proof, kid: kid_withoutDid });
145-
const credentialRequest: CredentialRequestV1_0_13 = await credReqClient.createCredentialRequest({
137+
const credentialRequest = await credReqClient.createCredentialRequest({
146138
proofInput: proof,
147139
credentialTypes: 'OpenBadgeCredential',
148140
version: OpenId4VCIVersion.VER_1_0_13,

packages/client/lib/__tests__/SdJwt.spec.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import {
22
AccessTokenRequest,
3+
CredentialConfigurationSupportedSdJwtVcV1_0_13,
34
CredentialConfigurationSupportedV1_0_13,
4-
CredentialRequestV1_0_13,
55
CredentialSupportedSdJwtVc,
66
} from '@sphereon/oid4vci-common';
77
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
@@ -109,7 +109,7 @@ describe('sd-jwt vc', () => {
109109
const supported = client.getCredentialsSupported('vc+sd-jwt');
110110
expect(supported).toEqual({ SdJwtCredentialId: { format: 'vc+sd-jwt', id: 'SdJwtCredentialId', vct: 'SdJwtCredentialId' } });
111111

112-
const offered = supported['SdJwtCredentialId'] as CredentialSupportedSdJwtVc;
112+
const offered = supported['SdJwtCredentialId'] as CredentialConfigurationSupportedSdJwtVcV1_0_13;
113113

114114
nock(issuerMetadata.token_endpoint as string)
115115
.post('/')
@@ -130,7 +130,7 @@ describe('sd-jwt vc', () => {
130130
.post('/')
131131
.reply(200, async (_, body) =>
132132
vcIssuer.issueCredential({
133-
credentialRequest: { ...(body as CredentialRequestV1_0_13), credential_identifier: offered.vct },
133+
credentialRequest: { ...(body as any), credential_identifier: 'SdJwtCredentialId' },
134134
credential: {
135135
vct: 'Hello',
136136
iss: 'did:example:123',
@@ -233,7 +233,7 @@ describe('sd-jwt vc', () => {
233233
.post('/')
234234
.reply(200, async (_, body) =>
235235
vcIssuer.issueCredential({
236-
credentialRequest: { ...(body as CredentialRequestV1_0_13), credential_identifier: offered.vct },
236+
credentialRequest: { ...(body as any), credential_identifier: offered.vct },
237237
credential: {
238238
vct: 'Hello',
239239
iss: 'example.com',

packages/client/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,12 @@
1616
},
1717
"dependencies": {
1818
"@sphereon/oid4vci-common": "workspace:*",
19-
"@sphereon/ssi-types": "0.26.1-next.132",
19+
"@sphereon/ssi-types": "0.28.0",
2020
"cross-fetch": "^3.1.8",
2121
"debug": "^4.3.5"
2222
},
2323
"devDependencies": {
24-
"@sphereon/ssi-sdk-ext.key-utils": "^0.22.0",
24+
"@sphereon/ssi-sdk-ext.key-utils": "^0.23.0",
2525
"@transmute/did-key.js": "^0.3.0-unstable.10",
2626
"@trust/keyto": "^2.0.0-alpha1",
2727
"@types/jest": "^29.5.12",

packages/common/lib/functions/CredentialRequestUtil.ts

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,19 +13,28 @@ export function getTypesFromRequest(credentialRequest: CredentialRequest, opts?:
1313
let types: string[] = [];
1414
if ('credential_identifier' in credentialRequest && credentialRequest.credential_identifier) {
1515
throw Error(`Cannot get types from request when it contains a credential_identifier`);
16-
} else if (credentialRequest.format === 'jwt_vc_json' || credentialRequest.format === 'jwt_vc') {
17-
types = 'types' in credentialRequest ? credentialRequest.types : [];
18-
} else if (credentialRequest.format === 'jwt_vc_json-ld' || credentialRequest.format === 'ldp_vc') {
19-
types =
20-
'credential_definition' in credentialRequest && credentialRequest.credential_definition
21-
? credentialRequest.credential_definition.types
22-
: // eslint-disable-next-line @typescript-eslint/ban-ts-comment
23-
// @ts-ignore
24-
'types' in credentialRequest.types
25-
? (credentialRequest['types' as keyof CredentialRequest] as unknown as string[])
26-
: [];
27-
} else if (credentialRequest.format === 'vc+sd-jwt') {
28-
types = 'vct' in credentialRequest ? [credentialRequest.vct as string] : [];
16+
} else if (
17+
credentialRequest.format === 'jwt_vc_json-ld' ||
18+
credentialRequest.format === 'ldp_vc' ||
19+
credentialRequest.format === 'jwt_vc' ||
20+
credentialRequest.format === 'jwt_vc_json'
21+
) {
22+
if ('credential_definition' in credentialRequest && credentialRequest.credential_definition) {
23+
types =
24+
'types' in credentialRequest.credential_definition
25+
? credentialRequest.credential_definition.types
26+
: credentialRequest.credential_definition.type;
27+
}
28+
29+
if ('type' in credentialRequest && Array.isArray(credentialRequest.type)) {
30+
types = credentialRequest.type;
31+
}
32+
33+
if ('types' in credentialRequest && Array.isArray(credentialRequest.types)) {
34+
types = credentialRequest.types;
35+
}
36+
} else if (credentialRequest.format === 'vc+sd-jwt' && 'vct' in credentialRequest) {
37+
types = [credentialRequest.vct];
2938
}
3039

3140
if (!types || types.length === 0) {

packages/common/lib/functions/IssuerMetadataUtils.ts

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { getTypesFromObject, VCI_LOG_COMMON } from '../index';
1+
import { getTypesFromObject, isW3cCredentialSupported, VCI_LOG_COMMON } from '../index';
22
import {
33
AuthorizationServerMetadata,
44
CredentialConfigurationSupported,
@@ -112,13 +112,11 @@ export function getSupportedCredential(opts?: {
112112
} else if (types) {
113113
isTypeMatch = normalizedTypes.every((type) => types.includes(type));
114114
} else {
115-
if ('credential_definition' in config) {
116-
isTypeMatch = normalizedTypes.every((type) => config.credential_definition.type?.includes(type));
117-
} else if ('type' in config && Array.isArray(config.type)) {
118-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
119-
// @ts-ignore
120-
isTypeMatch = normalizedTypes.every((type) => config.type.includes(type));
121-
} else if ('types' in config) {
115+
if (isW3cCredentialSupported(config) && 'credential_definition' in config) {
116+
isTypeMatch = normalizedTypes.every((type) => config.credential_definition.type.includes(type));
117+
} else if (isW3cCredentialSupported(config) && 'type' in config && Array.isArray(config.type)) {
118+
isTypeMatch = normalizedTypes.every((type) => (config.type as string[]).includes(type));
119+
} else if (isW3cCredentialSupported(config) && 'types' in config) {
122120
isTypeMatch = normalizedTypes.every((type) => config.types?.includes(type));
123121
}
124122
}
@@ -183,7 +181,7 @@ export function getIssuerDisplays(metadata: CredentialIssuerMetadata | IssuerMet
183181
metadata.display?.filter(
184182
(item) => !opts?.prefLocales || opts.prefLocales.length === 0 || (item.locale && opts.prefLocales.includes(item.locale)) || !item.locale,
185183
) ?? [];
186-
return matchedDisplays.sort((item) => (item.locale ? (opts?.prefLocales.indexOf(item.locale) ?? 1) : Number.MAX_VALUE));
184+
return matchedDisplays.sort((item) => (item.locale ? opts?.prefLocales.indexOf(item.locale) ?? 1 : Number.MAX_VALUE));
187185
}
188186

189187
/**

0 commit comments

Comments
 (0)