Skip to content

Commit cc52d85

Browse files
authored
Merge branch 'Sphereon-Opensource:develop' into fix/tx_code
2 parents 3b0971d + 2759750 commit cc52d85

25 files changed

Lines changed: 1293 additions & 227 deletions

packages/callback-example/lib/__tests__/issuerCallback.spec.ts

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

3-
import { CredentialRequestClient, CredentialRequestClientBuilder, ProofOfPossessionBuilder } from '@sphereon/oid4vci-client'
3+
import { CredentialRequestClientBuilder, ProofOfPossessionBuilder } from '@sphereon/oid4vci-client'
44
import {
55
Alg,
66
CNonceState,
@@ -252,7 +252,7 @@ describe('issuerCallback', () => {
252252
.withKid(kid)
253253
.build()
254254

255-
const credentialRequestClient = new CredentialRequestClient(credReqClient)
255+
const credentialRequestClient = credReqClient.build()
256256
const credentialRequest: CredentialRequest = await credentialRequestClient.createCredentialRequest({
257257
credentialIdentifier: 'VerifiableCredential',
258258
// format: 'jwt_vc_json',

packages/client/lib/AccessTokenClient.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
import { ObjectUtils } from '@sphereon/ssi-types';
2323

2424
import { MetadataClientV1_0_13 } from './MetadataClientV1_0_13';
25+
import { createJwtBearerClientAssertion } from './functions';
2526
import { LOG } from './types';
2627

2728
export class AccessTokenClient {
@@ -90,11 +91,11 @@ export class AccessTokenClient {
9091
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
9192
// @ts-ignore
9293
const credentialOfferRequest = opts.credentialOffer ? await toUniformCredentialOfferRequest(opts.credentialOffer) : undefined;
93-
const request: Partial<AccessTokenRequest> = {};
94-
95-
if (asOpts?.clientId) {
96-
request.client_id = asOpts.clientId;
94+
const request: Partial<AccessTokenRequest> = { ...opts.additionalParams };
95+
if (asOpts?.clientOpts?.clientId) {
96+
request.client_id = asOpts.clientOpts.clientId;
9797
}
98+
await createJwtBearerClientAssertion(request, opts);
9899

99100
if (credentialOfferRequest?.supportedFlows.includes(AuthzFlowType.PRE_AUTHORIZED_CODE_FLOW)) {
100101
this.assertAlphanumericPin(opts.pinMetadata, pin);

packages/client/lib/AccessTokenClientV1_0_11.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
GrantTypes,
1515
IssuerOpts,
1616
JsonURIMode,
17+
OpenId4VCIVersion,
1718
OpenIDResponse,
1819
PRE_AUTH_CODE_LITERAL,
1920
TokenErrorResponse,
@@ -24,6 +25,7 @@ import { ObjectUtils } from '@sphereon/ssi-types';
2425
import Debug from 'debug';
2526

2627
import { MetadataClientV1_0_13 } from './MetadataClientV1_0_13';
28+
import { createJwtBearerClientAssertion } from './functions';
2729

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

@@ -92,11 +94,12 @@ export class AccessTokenClientV1_0_11 {
9294
const credentialOfferRequest = opts.credentialOffer
9395
? await toUniformCredentialOfferRequest(opts.credentialOffer as CredentialOfferV1_0_11 | CredentialOfferV1_0_13)
9496
: undefined;
95-
const request: Partial<AccessTokenRequest> = {};
97+
const request: Partial<AccessTokenRequest> = { ...opts.additionalParams };
9698

97-
if (asOpts?.clientId) {
98-
request.client_id = asOpts.clientId;
99+
if (asOpts?.clientOpts?.clientId) {
100+
request.client_id = asOpts.clientOpts.clientId;
99101
}
102+
await createJwtBearerClientAssertion(request, { ...opts, version: OpenId4VCIVersion.VER_1_0_11 });
100103

101104
if (credentialOfferRequest?.supportedFlows.includes(AuthzFlowType.PRE_AUTHORIZED_CODE_FLOW)) {
102105
this.assertNumericPin(this.isPinRequiredValue(credentialOfferRequest.credential_offer), pin);

packages/client/lib/CredentialRequestClient.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ import { ExperimentalSubjectIssuance } from '@sphereon/oid4vci-common/dist/exper
1919
import { CredentialFormat } from '@sphereon/ssi-types';
2020
import Debug from 'debug';
2121

22-
import { CredentialRequestClientBuilder } from './CredentialRequestClientBuilder';
22+
import { CredentialRequestClientBuilderV1_0_11 } from './CredentialRequestClientBuilderV1_0_11';
23+
import { CredentialRequestClientBuilderV1_0_13 } from './CredentialRequestClientBuilderV1_0_13';
2324
import { ProofOfPossessionBuilder } from './ProofOfPossessionBuilder';
2425

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

81-
public constructor(builder: CredentialRequestClientBuilder) {
82+
public constructor(builder: CredentialRequestClientBuilderV1_0_13 | CredentialRequestClientBuilderV1_0_11) {
8283
this._credentialRequestOpts = { ...builder };
8384
}
8485

Lines changed: 98 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,33 @@
11
import {
22
AccessTokenResponse,
3+
CredentialIssuerMetadata,
34
CredentialIssuerMetadataV1_0_13,
4-
CredentialOfferPayloadV1_0_13,
55
CredentialOfferRequestWithBaseUrl,
66
determineSpecVersionFromOffer,
77
EndpointMetadata,
88
ExperimentalSubjectIssuance,
9-
getIssuerFromCredentialOfferPayload,
109
OID4VCICredentialFormat,
1110
OpenId4VCIVersion,
1211
UniformCredentialOfferRequest,
1312
} from '@sphereon/oid4vci-common';
1413
import { CredentialFormat } from '@sphereon/ssi-types';
1514

1615
import { CredentialOfferClient } from './CredentialOfferClient';
17-
import { CredentialRequestClient } from './CredentialRequestClient';
16+
import { CredentialRequestClientBuilderV1_0_11 } from './CredentialRequestClientBuilderV1_0_11';
17+
import { CredentialRequestClientBuilderV1_0_13 } from './CredentialRequestClientBuilderV1_0_13';
18+
19+
type CredentialRequestClientBuilderVersionSpecific = CredentialRequestClientBuilderV1_0_11 | CredentialRequestClientBuilderV1_0_13;
20+
21+
function isV1_0_13(builder: CredentialRequestClientBuilderVersionSpecific): builder is CredentialRequestClientBuilderV1_0_13 {
22+
return (builder as CredentialRequestClientBuilderV1_0_13).withCredentialIdentifier !== undefined;
23+
}
1824

1925
export class CredentialRequestClientBuilder {
20-
credentialEndpoint?: string;
21-
deferredCredentialEndpoint?: string;
22-
deferredCredentialAwait = false;
23-
deferredCredentialIntervalInMS = 5000;
24-
credentialIdentifier?: string;
25-
credentialTypes?: string[] = [];
26-
format?: CredentialFormat | OID4VCICredentialFormat;
27-
token?: string;
28-
version?: OpenId4VCIVersion;
29-
subjectIssuance?: ExperimentalSubjectIssuance;
26+
private _builder: CredentialRequestClientBuilderVersionSpecific;
27+
28+
private constructor(builder: CredentialRequestClientBuilderVersionSpecific) {
29+
this._builder = builder;
30+
}
3031

3132
public static fromCredentialIssuer({
3233
credentialIssuer,
@@ -41,25 +42,40 @@ export class CredentialRequestClientBuilder {
4142
credentialIdentifier?: string;
4243
credentialTypes?: string | string[];
4344
}): CredentialRequestClientBuilder {
44-
const issuer = credentialIssuer;
45-
const builder = new CredentialRequestClientBuilder();
46-
builder.withVersion(version ?? OpenId4VCIVersion.VER_1_0_11);
47-
builder.withCredentialEndpoint(metadata?.credential_endpoint ?? (issuer.endsWith('/') ? `${issuer}credential` : `${issuer}/credential`));
48-
if (metadata?.deferred_credential_endpoint) {
49-
builder.withDeferredCredentialEndpoint(metadata.deferred_credential_endpoint);
50-
}
51-
if (credentialIdentifier) {
52-
builder.withCredentialIdentifier(credentialIdentifier);
53-
}
54-
if (credentialTypes) {
55-
builder.withCredentialType(credentialTypes);
45+
const specVersion = version ?? OpenId4VCIVersion.VER_1_0_13;
46+
let builder;
47+
48+
if (specVersion >= OpenId4VCIVersion.VER_1_0_13) {
49+
builder = CredentialRequestClientBuilderV1_0_13.fromCredentialIssuer({
50+
credentialIssuer,
51+
metadata,
52+
version,
53+
credentialIdentifier,
54+
credentialTypes,
55+
});
56+
} else {
57+
if (!credentialTypes || credentialTypes.length === 0) {
58+
throw new Error('CredentialTypes must be provided for v1_0_11');
59+
}
60+
builder = CredentialRequestClientBuilderV1_0_11.fromCredentialIssuer({
61+
credentialIssuer,
62+
metadata,
63+
version,
64+
credentialTypes,
65+
});
5666
}
57-
return builder;
67+
68+
return new CredentialRequestClientBuilder(builder);
5869
}
5970

6071
public static async fromURI({ uri, metadata }: { uri: string; metadata?: EndpointMetadata }): Promise<CredentialRequestClientBuilder> {
6172
const offer = await CredentialOfferClient.fromURI(uri);
62-
return CredentialRequestClientBuilder.fromCredentialOfferRequest({ request: offer, ...offer, metadata, version: offer.version });
73+
return CredentialRequestClientBuilder.fromCredentialOfferRequest({
74+
request: offer,
75+
...offer,
76+
metadata,
77+
version: offer.version,
78+
});
6379
}
6480

6581
public static fromCredentialOfferRequest(opts: {
@@ -69,24 +85,17 @@ export class CredentialRequestClientBuilder {
6985
version?: OpenId4VCIVersion;
7086
metadata?: EndpointMetadata;
7187
}): CredentialRequestClientBuilder {
72-
const { request, metadata } = opts;
88+
const { request } = opts;
7389
const version = opts.version ?? request.version ?? determineSpecVersionFromOffer(request.original_credential_offer);
90+
let builder;
91+
7492
if (version < OpenId4VCIVersion.VER_1_0_13) {
75-
throw new Error('Versions below v1.0.13 (draft 13) are not supported.');
76-
}
77-
const builder = new CredentialRequestClientBuilder();
78-
const issuer = getIssuerFromCredentialOfferPayload(request.credential_offer) ?? (metadata?.issuer as string);
79-
builder.withVersion(version);
80-
builder.withCredentialEndpoint(metadata?.credential_endpoint ?? (issuer.endsWith('/') ? `${issuer}credential` : `${issuer}/credential`));
81-
if (metadata?.deferred_credential_endpoint) {
82-
builder.withDeferredCredentialEndpoint(metadata.deferred_credential_endpoint);
83-
}
84-
const ids: string[] = (request.credential_offer as CredentialOfferPayloadV1_0_13).credential_configuration_ids;
85-
// if there's only one in the offer, we pre-select it. if not, you should provide the credentialType
86-
if (ids.length && ids.length === 1) {
87-
builder.withCredentialIdentifier(ids[0]);
93+
builder = CredentialRequestClientBuilderV1_0_11.fromCredentialOfferRequest(opts);
94+
} else {
95+
builder = CredentialRequestClientBuilderV1_0_13.fromCredentialOfferRequest(opts);
8896
}
89-
return builder;
97+
98+
return new CredentialRequestClientBuilder(builder);
9099
}
91100

92101
public static fromCredentialOffer({
@@ -96,78 +105,100 @@ export class CredentialRequestClientBuilder {
96105
credentialOffer: CredentialOfferRequestWithBaseUrl;
97106
metadata?: EndpointMetadata;
98107
}): CredentialRequestClientBuilder {
99-
return CredentialRequestClientBuilder.fromCredentialOfferRequest({
100-
request: credentialOffer,
101-
metadata,
102-
version: credentialOffer.version,
103-
});
108+
const version = determineSpecVersionFromOffer(credentialOffer.credential_offer);
109+
let builder;
110+
111+
if (version < OpenId4VCIVersion.VER_1_0_13) {
112+
builder = CredentialRequestClientBuilderV1_0_11.fromCredentialOffer({
113+
credentialOffer,
114+
metadata,
115+
});
116+
} else {
117+
builder = CredentialRequestClientBuilderV1_0_13.fromCredentialOffer({
118+
credentialOffer,
119+
metadata,
120+
});
121+
}
122+
123+
return new CredentialRequestClientBuilder(builder);
124+
}
125+
126+
public getVersion(): OpenId4VCIVersion | undefined {
127+
return this._builder.version;
104128
}
105129

106-
public withCredentialEndpointFromMetadata(metadata: CredentialIssuerMetadataV1_0_13): this {
107-
this.credentialEndpoint = metadata.credential_endpoint;
130+
public withCredentialEndpointFromMetadata(metadata: CredentialIssuerMetadata | CredentialIssuerMetadataV1_0_13): this {
131+
if (isV1_0_13(this._builder)) {
132+
this._builder.withCredentialEndpointFromMetadata(metadata as CredentialIssuerMetadataV1_0_13);
133+
} else {
134+
this._builder.withCredentialEndpointFromMetadata(metadata as CredentialIssuerMetadata);
135+
}
108136
return this;
109137
}
110138

111139
public withCredentialEndpoint(credentialEndpoint: string): this {
112-
this.credentialEndpoint = credentialEndpoint;
140+
this._builder.withCredentialEndpoint(credentialEndpoint);
113141
return this;
114142
}
115143

116-
public withDeferredCredentialEndpointFromMetadata(metadata: CredentialIssuerMetadataV1_0_13): this {
117-
this.deferredCredentialEndpoint = metadata.deferred_credential_endpoint;
144+
public withDeferredCredentialEndpointFromMetadata(metadata: CredentialIssuerMetadata | CredentialIssuerMetadataV1_0_13): this {
145+
if (isV1_0_13(this._builder)) {
146+
this._builder.withDeferredCredentialEndpointFromMetadata(metadata as CredentialIssuerMetadataV1_0_13);
147+
} else {
148+
this._builder.withDeferredCredentialEndpointFromMetadata(metadata as CredentialIssuerMetadata);
149+
}
118150
return this;
119151
}
120152

121153
public withDeferredCredentialEndpoint(deferredCredentialEndpoint: string): this {
122-
this.deferredCredentialEndpoint = deferredCredentialEndpoint;
154+
this._builder.withDeferredCredentialEndpoint(deferredCredentialEndpoint);
123155
return this;
124156
}
125157

126158
public withDeferredCredentialAwait(deferredCredentialAwait: boolean, deferredCredentialIntervalInMS?: number): this {
127-
this.deferredCredentialAwait = deferredCredentialAwait;
128-
this.deferredCredentialIntervalInMS = deferredCredentialIntervalInMS ?? 5000;
159+
this._builder.withDeferredCredentialAwait(deferredCredentialAwait, deferredCredentialIntervalInMS);
129160
return this;
130161
}
131162

132163
public withCredentialIdentifier(credentialIdentifier: string): this {
133-
this.credentialIdentifier = credentialIdentifier;
164+
if (this._builder.version === undefined || this._builder.version < OpenId4VCIVersion.VER_1_0_13) {
165+
throw new Error('Version of spec should be equal or higher than v1_0_13');
166+
}
167+
(this._builder as CredentialRequestClientBuilderV1_0_13).withCredentialIdentifier(credentialIdentifier);
134168
return this;
135169
}
136170

137171
public withCredentialType(credentialTypes: string | string[]): this {
138-
this.credentialTypes = Array.isArray(credentialTypes) ? credentialTypes : [credentialTypes];
172+
this._builder.withCredentialType(credentialTypes);
139173
return this;
140174
}
141175

142176
public withFormat(format: CredentialFormat | OID4VCICredentialFormat): this {
143-
this.format = format;
177+
this._builder.withFormat(format);
144178
return this;
145179
}
146180

147181
public withSubjectIssuance(subjectIssuance: ExperimentalSubjectIssuance): this {
148-
this.subjectIssuance = subjectIssuance;
182+
this._builder.withSubjectIssuance(subjectIssuance);
149183
return this;
150184
}
151185

152186
public withToken(accessToken: string): this {
153-
this.token = accessToken;
187+
this._builder.withToken(accessToken);
154188
return this;
155189
}
156190

157191
public withTokenFromResponse(response: AccessTokenResponse): this {
158-
this.token = response.access_token;
192+
this._builder.withTokenFromResponse(response);
159193
return this;
160194
}
161195

162196
public withVersion(version: OpenId4VCIVersion): this {
163-
this.version = version;
197+
this._builder.withVersion(version);
164198
return this;
165199
}
166200

167-
public build(): CredentialRequestClient {
168-
if (!this.version) {
169-
this.withVersion(OpenId4VCIVersion.VER_1_0_11);
170-
}
171-
return new CredentialRequestClient(this);
201+
public build() {
202+
return this._builder.build();
172203
}
173204
}

0 commit comments

Comments
 (0)