@sphereon/oid4vci-client 0.8.2-next.4 → 0.8.2-next.46
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -8
- package/dist/AccessTokenClient.d.ts.map +1 -1
- package/dist/AccessTokenClient.js +9 -9
- package/dist/AccessTokenClient.js.map +1 -1
- package/dist/AuthorizationDetailsBuilder.d.ts.map +1 -1
- package/dist/AuthorizationDetailsBuilder.js.map +1 -1
- package/dist/CredentialOfferClient.js.map +1 -1
- package/dist/CredentialRequestClient.d.ts +13 -0
- package/dist/CredentialRequestClient.d.ts.map +1 -1
- package/dist/CredentialRequestClient.js +87 -41
- package/dist/CredentialRequestClient.js.map +1 -1
- package/dist/CredentialRequestClientBuilder.d.ts +19 -7
- package/dist/CredentialRequestClientBuilder.d.ts.map +1 -1
- package/dist/CredentialRequestClientBuilder.js +31 -1
- package/dist/CredentialRequestClientBuilder.js.map +1 -1
- package/dist/MetadataClient.d.ts.map +1 -1
- package/dist/MetadataClient.js +12 -1
- package/dist/MetadataClient.js.map +1 -1
- package/dist/OpenID4VCIClient.d.ts +19 -5
- package/dist/OpenID4VCIClient.d.ts.map +1 -1
- package/dist/OpenID4VCIClient.js +119 -41
- package/dist/OpenID4VCIClient.js.map +1 -1
- package/dist/ProofOfPossessionBuilder.d.ts +3 -1
- package/dist/ProofOfPossessionBuilder.d.ts.map +1 -1
- package/dist/ProofOfPossessionBuilder.js +5 -0
- package/dist/ProofOfPossessionBuilder.js.map +1 -1
- package/dist/functions/ProofUtil.d.ts +2 -1
- package/dist/functions/ProofUtil.d.ts.map +1 -1
- package/dist/functions/ProofUtil.js +6 -4
- package/dist/functions/ProofUtil.js.map +1 -1
- package/lib/AccessTokenClient.ts +11 -9
- package/lib/AuthorizationDetailsBuilder.ts +2 -2
- package/lib/CredentialRequestClient.ts +105 -43
- package/lib/CredentialRequestClientBuilder.ts +53 -8
- package/lib/MetadataClient.ts +13 -1
- package/lib/OpenID4VCIClient.ts +152 -42
- package/lib/ProofOfPossessionBuilder.ts +8 -0
- package/lib/__tests__/CredentialRequestClient.spec.ts +21 -9
- package/lib/__tests__/EBSIE2E.spec.test.ts +143 -0
- package/lib/__tests__/MetadataClient.spec.ts +4 -1
- package/lib/__tests__/OpenID4VCIClient.spec.ts +5 -0
- package/lib/__tests__/SdJwt.spec.ts +161 -0
- package/lib/__tests__/data/VciDataFixtures.ts +14 -13
- package/lib/functions/ProofUtil.ts +18 -4
- package/package.json +8 -6
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Jwt, ProofOfPossession, ProofOfPossessionCallbacks, Typ } from '@sphereon/oid4vci-common';
|
|
1
|
+
import { JWK, Jwt, ProofOfPossession, ProofOfPossessionCallbacks, Typ } from '@sphereon/oid4vci-common';
|
|
2
2
|
/**
|
|
3
3
|
*
|
|
4
4
|
* - proofOfPossessionCallback: JWTSignerCallback
|
|
@@ -20,6 +20,7 @@ export declare const createProofOfPossession: <DIDDoc>(callbacks: ProofOfPossess
|
|
|
20
20
|
export interface JwtProps {
|
|
21
21
|
typ?: Typ;
|
|
22
22
|
kid?: string;
|
|
23
|
+
jwk?: JWK;
|
|
23
24
|
issuer?: string;
|
|
24
25
|
clientId?: string;
|
|
25
26
|
alg?: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProofUtil.d.ts","sourceRoot":"","sources":["../../lib/functions/ProofUtil.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"ProofUtil.d.ts","sourceRoot":"","sources":["../../lib/functions/ProofUtil.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,GAAG,EAEH,GAAG,EAGH,iBAAiB,EACjB,0BAA0B,EAC1B,GAAG,EACJ,MAAM,0BAA0B,CAAC;AAKlC;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,uBAAuB,qEAEvB,QAAQ,gBACL,GAAG,KAChB,QAAQ,iBAAiB,CA0B3B,CAAC;AAQF,MAAM,WAAW,QAAQ;IACvB,GAAG,CAAC,EAAE,GAAG,CAAC;IACV,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,GAAG,CAAC;IACV,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB"}
|
|
@@ -66,7 +66,7 @@ const partiallyValidateJWS = (jws) => {
|
|
|
66
66
|
}
|
|
67
67
|
};
|
|
68
68
|
const createJWT = (jwtProps, existingJwt) => {
|
|
69
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
|
69
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
|
|
70
70
|
const aud = getJwtProperty('aud', true, jwtProps === null || jwtProps === void 0 ? void 0 : jwtProps.issuer, (_a = existingJwt === null || existingJwt === void 0 ? void 0 : existingJwt.payload) === null || _a === void 0 ? void 0 : _a.aud);
|
|
71
71
|
const iss = getJwtProperty('iss', false, jwtProps === null || jwtProps === void 0 ? void 0 : jwtProps.clientId, (_b = existingJwt === null || existingJwt === void 0 ? void 0 : existingJwt.payload) === null || _b === void 0 ? void 0 : _b.iss);
|
|
72
72
|
const jti = getJwtProperty('jti', false, jwtProps === null || jwtProps === void 0 ? void 0 : jwtProps.jti, (_c = existingJwt === null || existingJwt === void 0 ? void 0 : existingJwt.payload) === null || _c === void 0 ? void 0 : _c.jti);
|
|
@@ -74,14 +74,16 @@ const createJWT = (jwtProps, existingJwt) => {
|
|
|
74
74
|
const nonce = getJwtProperty('nonce', false, jwtProps === null || jwtProps === void 0 ? void 0 : jwtProps.nonce, (_e = existingJwt === null || existingJwt === void 0 ? void 0 : existingJwt.payload) === null || _e === void 0 ? void 0 : _e.nonce); // Officially this is required, but some implementations don't have it
|
|
75
75
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
76
76
|
const alg = getJwtProperty('alg', false, jwtProps === null || jwtProps === void 0 ? void 0 : jwtProps.alg, (_f = existingJwt === null || existingJwt === void 0 ? void 0 : existingJwt.header) === null || _f === void 0 ? void 0 : _f.alg, 'ES256');
|
|
77
|
-
const kid = getJwtProperty('kid',
|
|
77
|
+
const kid = getJwtProperty('kid', false, jwtProps === null || jwtProps === void 0 ? void 0 : jwtProps.kid, (_g = existingJwt === null || existingJwt === void 0 ? void 0 : existingJwt.header) === null || _g === void 0 ? void 0 : _g.kid);
|
|
78
|
+
const jwk = getJwtProperty('jwk', false, jwtProps === null || jwtProps === void 0 ? void 0 : jwtProps.jwk, (_h = existingJwt === null || existingJwt === void 0 ? void 0 : existingJwt.header) === null || _h === void 0 ? void 0 : _h.jwk);
|
|
78
79
|
const jwt = existingJwt ? existingJwt : {};
|
|
79
80
|
const now = +new Date();
|
|
80
|
-
const jwtPayload = Object.assign(Object.assign({ aud, iat: ((
|
|
81
|
+
const jwtPayload = Object.assign(Object.assign({ aud, iat: ((_j = jwt.payload) === null || _j === void 0 ? void 0 : _j.iat) ? jwt.payload.iat : now / 1000 - 60, exp: ((_k = jwt.payload) === null || _k === void 0 ? void 0 : _k.exp) ? jwt.payload.exp : now / 1000 + 10 * 60, nonce }, (iss ? { iss } : {})), (jti ? { jti } : {}));
|
|
81
82
|
const jwtHeader = {
|
|
82
83
|
typ,
|
|
83
84
|
alg,
|
|
84
85
|
kid,
|
|
86
|
+
jwk,
|
|
85
87
|
};
|
|
86
88
|
return {
|
|
87
89
|
payload: Object.assign(Object.assign({}, jwt.payload), jwtPayload),
|
|
@@ -89,7 +91,7 @@ const createJWT = (jwtProps, existingJwt) => {
|
|
|
89
91
|
};
|
|
90
92
|
};
|
|
91
93
|
const getJwtProperty = (propertyName, required, option, jwtProperty, defaultValue) => {
|
|
92
|
-
if (option && jwtProperty && option !== jwtProperty) {
|
|
94
|
+
if (typeof option === 'string' && option && jwtProperty && option !== jwtProperty) {
|
|
93
95
|
throw Error(`Cannot have a property '${propertyName}' with value '${option}' and different JWT value '${jwtProperty}' at the same time`);
|
|
94
96
|
}
|
|
95
97
|
let result = (jwtProperty ? jwtProperty : option);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProofUtil.js","sourceRoot":"","sources":["../../lib/functions/ProofUtil.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,
|
|
1
|
+
{"version":3,"file":"ProofUtil.js","sourceRoot":"","sources":["../../lib/functions/ProofUtil.ts"],"names":[],"mappings":"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"}
|
package/lib/AccessTokenClient.ts
CHANGED
|
@@ -27,9 +27,11 @@ export class AccessTokenClient {
|
|
|
27
27
|
public async acquireAccessToken(opts: AccessTokenRequestOpts): Promise<OpenIDResponse<AccessTokenResponse>> {
|
|
28
28
|
const { asOpts, pin, codeVerifier, code, redirectUri, metadata } = opts;
|
|
29
29
|
|
|
30
|
-
const credentialOffer = await assertedUniformCredentialOffer(opts.credentialOffer);
|
|
31
|
-
const isPinRequired = this.isPinRequiredValue(credentialOffer.credential_offer);
|
|
32
|
-
const issuer =
|
|
30
|
+
const credentialOffer = opts.credentialOffer ? await assertedUniformCredentialOffer(opts.credentialOffer) : undefined;
|
|
31
|
+
const isPinRequired = credentialOffer && this.isPinRequiredValue(credentialOffer.credential_offer);
|
|
32
|
+
const issuer =
|
|
33
|
+
opts.credentialIssuer ??
|
|
34
|
+
(credentialOffer ? getIssuerFromCredentialOfferPayload(credentialOffer.credential_offer) : (metadata?.issuer as string));
|
|
33
35
|
if (!issuer) {
|
|
34
36
|
throw Error('Issuer required at this point');
|
|
35
37
|
}
|
|
@@ -83,14 +85,14 @@ export class AccessTokenClient {
|
|
|
83
85
|
|
|
84
86
|
public async createAccessTokenRequest(opts: AccessTokenRequestOpts): Promise<AccessTokenRequest> {
|
|
85
87
|
const { asOpts, pin, codeVerifier, code, redirectUri } = opts;
|
|
86
|
-
const credentialOfferRequest = await toUniformCredentialOfferRequest(opts.credentialOffer);
|
|
88
|
+
const credentialOfferRequest = opts.credentialOffer ? await toUniformCredentialOfferRequest(opts.credentialOffer) : undefined;
|
|
87
89
|
const request: Partial<AccessTokenRequest> = {};
|
|
88
90
|
|
|
89
91
|
if (asOpts?.clientId) {
|
|
90
92
|
request.client_id = asOpts.clientId;
|
|
91
93
|
}
|
|
92
94
|
|
|
93
|
-
if (credentialOfferRequest
|
|
95
|
+
if (credentialOfferRequest?.supportedFlows.includes(AuthzFlowType.PRE_AUTHORIZED_CODE_FLOW)) {
|
|
94
96
|
this.assertNumericPin(this.isPinRequiredValue(credentialOfferRequest.credential_offer), pin);
|
|
95
97
|
request.user_pin = pin;
|
|
96
98
|
|
|
@@ -102,7 +104,7 @@ export class AccessTokenClient {
|
|
|
102
104
|
return request as AccessTokenRequest;
|
|
103
105
|
}
|
|
104
106
|
|
|
105
|
-
if (credentialOfferRequest.supportedFlows.includes(AuthzFlowType.AUTHORIZATION_CODE_FLOW)) {
|
|
107
|
+
if (!credentialOfferRequest || credentialOfferRequest.supportedFlows.includes(AuthzFlowType.AUTHORIZATION_CODE_FLOW)) {
|
|
106
108
|
request.grant_type = GrantTypes.AUTHORIZATION_CODE;
|
|
107
109
|
request.code = code;
|
|
108
110
|
request.redirect_uri = redirectUri;
|
|
@@ -193,7 +195,7 @@ export class AccessTokenClient {
|
|
|
193
195
|
this.assertNonEmptyCode(accessTokenRequest);
|
|
194
196
|
this.assertNonEmptyRedirectUri(accessTokenRequest);
|
|
195
197
|
} else {
|
|
196
|
-
this.throwNotSupportedFlow;
|
|
198
|
+
this.throwNotSupportedFlow();
|
|
197
199
|
}
|
|
198
200
|
}
|
|
199
201
|
|
|
@@ -243,7 +245,7 @@ export class AccessTokenClient {
|
|
|
243
245
|
}
|
|
244
246
|
|
|
245
247
|
private throwNotSupportedFlow(): void {
|
|
246
|
-
debug(`Only pre-authorized
|
|
247
|
-
throw new Error('Only pre-authorized-code
|
|
248
|
+
debug(`Only pre-authorized or authorization code flows supported.`);
|
|
249
|
+
throw new Error('Only pre-authorized-code or authorization code flows are supported');
|
|
248
250
|
}
|
|
249
251
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { AuthorizationDetailsJwtVcJson, OID4VCICredentialFormat } from '@sphereon/oid4vci-common';
|
|
1
|
+
import { AuthorizationDetails, AuthorizationDetailsJwtVcJson, OID4VCICredentialFormat } from '@sphereon/oid4vci-common';
|
|
2
2
|
|
|
3
3
|
//todo: refactor this builder to be able to create ldp details as well
|
|
4
4
|
export class AuthorizationDetailsBuilder {
|
|
5
|
-
private readonly authorizationDetails: Partial<
|
|
5
|
+
private readonly authorizationDetails: Partial<Exclude<AuthorizationDetails, string>>;
|
|
6
6
|
|
|
7
7
|
constructor() {
|
|
8
8
|
this.authorizationDetails = {};
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
2
|
+
acquireDeferredCredential,
|
|
3
3
|
CredentialResponse,
|
|
4
|
+
getCredentialRequestForVersion,
|
|
5
|
+
getUniformFormat,
|
|
6
|
+
isDeferredCredentialResponse,
|
|
4
7
|
OID4VCICredentialFormat,
|
|
5
8
|
OpenId4VCIVersion,
|
|
6
9
|
OpenIDResponse,
|
|
@@ -18,7 +21,10 @@ import { isValidURL, post } from './functions';
|
|
|
18
21
|
const debug = Debug('sphereon:oid4vci:credential');
|
|
19
22
|
|
|
20
23
|
export interface CredentialRequestOpts {
|
|
24
|
+
deferredCredentialAwait?: boolean;
|
|
25
|
+
deferredCredentialIntervalInMS?: number;
|
|
21
26
|
credentialEndpoint: string;
|
|
27
|
+
deferredCredentialEndpoint?: string;
|
|
22
28
|
credentialTypes: string[];
|
|
23
29
|
format?: CredentialFormat | OID4VCICredentialFormat;
|
|
24
30
|
proof: ProofOfPossession;
|
|
@@ -26,17 +32,45 @@ export interface CredentialRequestOpts {
|
|
|
26
32
|
version: OpenId4VCIVersion;
|
|
27
33
|
}
|
|
28
34
|
|
|
35
|
+
export async function buildProof<DIDDoc>(
|
|
36
|
+
proofInput: ProofOfPossessionBuilder<DIDDoc> | ProofOfPossession,
|
|
37
|
+
opts: {
|
|
38
|
+
version: OpenId4VCIVersion;
|
|
39
|
+
cNonce?: string;
|
|
40
|
+
},
|
|
41
|
+
) {
|
|
42
|
+
if ('proof_type' in proofInput) {
|
|
43
|
+
if (opts.cNonce) {
|
|
44
|
+
throw Error(`Cnonce param is only supported when using a Proof of Posession builder`);
|
|
45
|
+
}
|
|
46
|
+
return await ProofOfPossessionBuilder.fromProof(proofInput as ProofOfPossession, opts.version).build();
|
|
47
|
+
}
|
|
48
|
+
if (opts.cNonce) {
|
|
49
|
+
proofInput.withAccessTokenNonce(opts.cNonce);
|
|
50
|
+
}
|
|
51
|
+
return await proofInput.build();
|
|
52
|
+
}
|
|
53
|
+
|
|
29
54
|
export class CredentialRequestClient {
|
|
30
55
|
private readonly _credentialRequestOpts: Partial<CredentialRequestOpts>;
|
|
56
|
+
private _isDeferred = false;
|
|
31
57
|
|
|
32
58
|
get credentialRequestOpts(): CredentialRequestOpts {
|
|
33
59
|
return this._credentialRequestOpts as CredentialRequestOpts;
|
|
34
60
|
}
|
|
35
61
|
|
|
62
|
+
public isDeferred(): boolean {
|
|
63
|
+
return this._isDeferred;
|
|
64
|
+
}
|
|
65
|
+
|
|
36
66
|
public getCredentialEndpoint(): string {
|
|
37
67
|
return this.credentialRequestOpts.credentialEndpoint;
|
|
38
68
|
}
|
|
39
69
|
|
|
70
|
+
public getDeferredCredentialEndpoint(): string | undefined {
|
|
71
|
+
return this.credentialRequestOpts.deferredCredentialEndpoint;
|
|
72
|
+
}
|
|
73
|
+
|
|
40
74
|
public constructor(builder: CredentialRequestClientBuilder) {
|
|
41
75
|
this._credentialRequestOpts = { ...builder };
|
|
42
76
|
}
|
|
@@ -53,36 +87,49 @@ export class CredentialRequestClient {
|
|
|
53
87
|
}
|
|
54
88
|
|
|
55
89
|
public async acquireCredentialsUsingRequest(uniformRequest: UniformCredentialRequest): Promise<OpenIDResponse<CredentialResponse>> {
|
|
56
|
-
|
|
57
|
-
if (!this.isV11OrHigher()) {
|
|
58
|
-
let format: string = uniformRequest.format;
|
|
59
|
-
if (format === 'jwt_vc_json') {
|
|
60
|
-
format = 'jwt_vc';
|
|
61
|
-
} else if (format === 'jwt_vc_json-ld') {
|
|
62
|
-
format = 'ldp_vc';
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
request = {
|
|
66
|
-
format,
|
|
67
|
-
proof: uniformRequest.proof,
|
|
68
|
-
type:
|
|
69
|
-
'types' in uniformRequest
|
|
70
|
-
? uniformRequest.types.filter((t) => t !== 'VerifiableCredential')[0]
|
|
71
|
-
: uniformRequest.credential_definition.types[0],
|
|
72
|
-
} as CredentialRequestV1_0_08;
|
|
73
|
-
}
|
|
90
|
+
const request = getCredentialRequestForVersion(uniformRequest, this.version());
|
|
74
91
|
const credentialEndpoint: string = this.credentialRequestOpts.credentialEndpoint;
|
|
75
92
|
if (!isValidURL(credentialEndpoint)) {
|
|
76
93
|
debug(`Invalid credential endpoint: ${credentialEndpoint}`);
|
|
77
94
|
throw new Error(URL_NOT_VALID);
|
|
78
95
|
}
|
|
79
96
|
debug(`Acquiring credential(s) from: ${credentialEndpoint}`);
|
|
97
|
+
debug(`request\n: ${JSON.stringify(request, null, 2)}`);
|
|
80
98
|
const requestToken: string = this.credentialRequestOpts.token;
|
|
81
|
-
|
|
82
|
-
|
|
99
|
+
let response: OpenIDResponse<CredentialResponse> = await post(credentialEndpoint, JSON.stringify(request), { bearerToken: requestToken });
|
|
100
|
+
this._isDeferred = isDeferredCredentialResponse(response);
|
|
101
|
+
if (this.isDeferred() && this.credentialRequestOpts.deferredCredentialAwait && response.successBody) {
|
|
102
|
+
response = await this.acquireDeferredCredential(response.successBody, { bearerToken: this.credentialRequestOpts.token });
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
debug(`Credential endpoint ${credentialEndpoint} response:\r\n${JSON.stringify(response, null, 2)}`);
|
|
83
106
|
return response;
|
|
84
107
|
}
|
|
85
108
|
|
|
109
|
+
public async acquireDeferredCredential(
|
|
110
|
+
response: Pick<CredentialResponse, 'transaction_id' | 'acceptance_token' | 'c_nonce'>,
|
|
111
|
+
opts?: {
|
|
112
|
+
bearerToken?: string;
|
|
113
|
+
},
|
|
114
|
+
): Promise<OpenIDResponse<CredentialResponse>> {
|
|
115
|
+
const transactionId = response.transaction_id;
|
|
116
|
+
const bearerToken = response.acceptance_token ?? opts?.bearerToken;
|
|
117
|
+
const deferredCredentialEndpoint = this.getDeferredCredentialEndpoint();
|
|
118
|
+
if (!deferredCredentialEndpoint) {
|
|
119
|
+
throw Error(`No deferred credential endpoint supplied.`);
|
|
120
|
+
} else if (!bearerToken) {
|
|
121
|
+
throw Error(`No bearer token present and refresh for defered endpoint not supported yet`);
|
|
122
|
+
// todo updated bearer token with new c_nonce
|
|
123
|
+
}
|
|
124
|
+
return await acquireDeferredCredential({
|
|
125
|
+
bearerToken,
|
|
126
|
+
transactionId,
|
|
127
|
+
deferredCredentialEndpoint,
|
|
128
|
+
deferredCredentialAwait: this.credentialRequestOpts.deferredCredentialAwait,
|
|
129
|
+
deferredCredentialIntervalInMS: this.credentialRequestOpts.deferredCredentialIntervalInMS,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
86
133
|
public async createCredentialRequest<DIDDoc>(opts: {
|
|
87
134
|
proofInput: ProofOfPossessionBuilder<DIDDoc> | ProofOfPossession;
|
|
88
135
|
credentialTypes?: string | string[];
|
|
@@ -92,20 +139,10 @@ export class CredentialRequestClient {
|
|
|
92
139
|
const { proofInput } = opts;
|
|
93
140
|
const formatSelection = opts.format ?? this.credentialRequestOpts.format;
|
|
94
141
|
|
|
95
|
-
|
|
96
|
-
if (opts.version < OpenId4VCIVersion.VER_1_0_11) {
|
|
97
|
-
if (formatSelection === 'jwt_vc' || formatSelection === 'jwt') {
|
|
98
|
-
format = 'jwt_vc_json';
|
|
99
|
-
} else if (formatSelection === 'ldp_vc' || formatSelection === 'ldp') {
|
|
100
|
-
format = 'jwt_vc_json-ld';
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
if (!format) {
|
|
142
|
+
if (!formatSelection) {
|
|
105
143
|
throw Error(`Format of credential to be issued is missing`);
|
|
106
|
-
} else if (format !== 'jwt_vc_json-ld' && format !== 'jwt_vc_json' && format !== 'ldp_vc') {
|
|
107
|
-
throw Error(`Invalid format of credential to be issued: ${format}`);
|
|
108
144
|
}
|
|
145
|
+
const format = getUniformFormat(formatSelection);
|
|
109
146
|
const typesSelection =
|
|
110
147
|
opts?.credentialTypes && (typeof opts.credentialTypes === 'string' || opts.credentialTypes.length > 0)
|
|
111
148
|
? opts.credentialTypes
|
|
@@ -113,24 +150,49 @@ export class CredentialRequestClient {
|
|
|
113
150
|
const types = Array.isArray(typesSelection) ? typesSelection : [typesSelection];
|
|
114
151
|
if (types.length === 0) {
|
|
115
152
|
throw Error(`Credential type(s) need to be provided`);
|
|
116
|
-
}
|
|
153
|
+
}
|
|
154
|
+
// FIXME: this is mixing up the type (as id) from v8/v9 and the types (from the vc.type) from v11
|
|
155
|
+
else if (!this.isV11OrHigher() && types.length !== 1) {
|
|
117
156
|
throw Error('Only a single credential type is supported for V8/V9');
|
|
118
157
|
}
|
|
158
|
+
const proof = await buildProof(proofInput, opts);
|
|
119
159
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
160
|
+
// TODO: we should move format specific logic
|
|
161
|
+
if (format === 'jwt_vc_json' || format === 'jwt_vc') {
|
|
162
|
+
return {
|
|
163
|
+
types,
|
|
164
|
+
format,
|
|
165
|
+
proof,
|
|
166
|
+
};
|
|
167
|
+
} else if (format === 'jwt_vc_json-ld' || format === 'ldp_vc') {
|
|
168
|
+
return {
|
|
169
|
+
format,
|
|
170
|
+
proof,
|
|
171
|
+
credential_definition: {
|
|
172
|
+
types,
|
|
173
|
+
// FIXME: this was not included in the original code, but it is required
|
|
174
|
+
'@context': [],
|
|
175
|
+
},
|
|
176
|
+
};
|
|
177
|
+
} else if (format === 'vc+sd-jwt') {
|
|
178
|
+
if (types.length > 1) {
|
|
179
|
+
throw Error(`Only a single credential type is supported for ${format}`);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return {
|
|
183
|
+
format,
|
|
184
|
+
proof,
|
|
185
|
+
vct: types[0],
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
throw new Error(`Unsupported format: ${format}`);
|
|
129
190
|
}
|
|
130
191
|
|
|
131
192
|
private version(): OpenId4VCIVersion {
|
|
132
193
|
return this.credentialRequestOpts?.version ?? OpenId4VCIVersion.VER_1_0_11;
|
|
133
194
|
}
|
|
195
|
+
|
|
134
196
|
private isV11OrHigher(): boolean {
|
|
135
197
|
return this.version() >= OpenId4VCIVersion.VER_1_0_11;
|
|
136
198
|
}
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
determineSpecVersionFromOffer,
|
|
7
7
|
EndpointMetadata,
|
|
8
8
|
getIssuerFromCredentialOfferPayload,
|
|
9
|
+
getTypesFromOffer,
|
|
9
10
|
OID4VCICredentialFormat,
|
|
10
11
|
OpenId4VCIVersion,
|
|
11
12
|
UniformCredentialOfferRequest,
|
|
@@ -17,11 +18,36 @@ import { CredentialRequestClient } from './CredentialRequestClient';
|
|
|
17
18
|
|
|
18
19
|
export class CredentialRequestClientBuilder {
|
|
19
20
|
credentialEndpoint?: string;
|
|
21
|
+
deferredCredentialEndpoint?: string;
|
|
22
|
+
deferredCredentialAwait = false;
|
|
23
|
+
deferredCredentialIntervalInMS = 5000;
|
|
20
24
|
credentialTypes: string[] = [];
|
|
21
25
|
format?: CredentialFormat | OID4VCICredentialFormat;
|
|
22
26
|
token?: string;
|
|
23
27
|
version?: OpenId4VCIVersion;
|
|
24
28
|
|
|
29
|
+
public static fromCredentialIssuer({
|
|
30
|
+
credentialIssuer,
|
|
31
|
+
metadata,
|
|
32
|
+
version,
|
|
33
|
+
credentialTypes,
|
|
34
|
+
}: {
|
|
35
|
+
credentialIssuer: string;
|
|
36
|
+
metadata?: EndpointMetadata;
|
|
37
|
+
version?: OpenId4VCIVersion;
|
|
38
|
+
credentialTypes: string | string[];
|
|
39
|
+
}): CredentialRequestClientBuilder {
|
|
40
|
+
const issuer = credentialIssuer;
|
|
41
|
+
const builder = new CredentialRequestClientBuilder();
|
|
42
|
+
builder.withVersion(version ?? OpenId4VCIVersion.VER_1_0_11);
|
|
43
|
+
builder.withCredentialEndpoint(metadata?.credential_endpoint ?? (issuer.endsWith('/') ? `${issuer}credential` : `${issuer}/credential`));
|
|
44
|
+
if (metadata?.deferred_credential_endpoint) {
|
|
45
|
+
builder.withDeferredCredentialEndpoint(metadata.deferred_credential_endpoint);
|
|
46
|
+
}
|
|
47
|
+
builder.withCredentialType(credentialTypes);
|
|
48
|
+
return builder;
|
|
49
|
+
}
|
|
50
|
+
|
|
25
51
|
public static async fromURI({ uri, metadata }: { uri: string; metadata?: EndpointMetadata }): Promise<CredentialRequestClientBuilder> {
|
|
26
52
|
const offer = await CredentialOfferClient.fromURI(uri);
|
|
27
53
|
return CredentialRequestClientBuilder.fromCredentialOfferRequest({ request: offer, ...offer, metadata, version: offer.version });
|
|
@@ -40,13 +66,16 @@ export class CredentialRequestClientBuilder {
|
|
|
40
66
|
const issuer = getIssuerFromCredentialOfferPayload(request.credential_offer) ?? (metadata?.issuer as string);
|
|
41
67
|
builder.withVersion(version);
|
|
42
68
|
builder.withCredentialEndpoint(metadata?.credential_endpoint ?? (issuer.endsWith('/') ? `${issuer}credential` : `${issuer}/credential`));
|
|
69
|
+
if (metadata?.deferred_credential_endpoint) {
|
|
70
|
+
builder.withDeferredCredentialEndpoint(metadata.deferred_credential_endpoint);
|
|
71
|
+
}
|
|
43
72
|
|
|
44
73
|
if (version <= OpenId4VCIVersion.VER_1_0_08) {
|
|
45
74
|
//todo: This basically sets all types available during initiation. Probably the user only wants a subset. So do we want to do this?
|
|
46
75
|
builder.withCredentialType((request.original_credential_offer as CredentialOfferPayloadV1_0_08).credential_type);
|
|
47
76
|
} else {
|
|
48
77
|
// todo: look whether this is correct
|
|
49
|
-
builder.withCredentialType(request.credential_offer
|
|
78
|
+
builder.withCredentialType(getTypesFromOffer(request.credential_offer));
|
|
50
79
|
}
|
|
51
80
|
|
|
52
81
|
return builder;
|
|
@@ -66,37 +95,53 @@ export class CredentialRequestClientBuilder {
|
|
|
66
95
|
});
|
|
67
96
|
}
|
|
68
97
|
|
|
69
|
-
public withCredentialEndpointFromMetadata(metadata: CredentialIssuerMetadata):
|
|
98
|
+
public withCredentialEndpointFromMetadata(metadata: CredentialIssuerMetadata): this {
|
|
70
99
|
this.credentialEndpoint = metadata.credential_endpoint;
|
|
71
100
|
return this;
|
|
72
101
|
}
|
|
73
102
|
|
|
74
|
-
public withCredentialEndpoint(credentialEndpoint: string):
|
|
103
|
+
public withCredentialEndpoint(credentialEndpoint: string): this {
|
|
75
104
|
this.credentialEndpoint = credentialEndpoint;
|
|
76
105
|
return this;
|
|
77
106
|
}
|
|
78
107
|
|
|
79
|
-
public
|
|
108
|
+
public withDeferredCredentialEndpointFromMetadata(metadata: CredentialIssuerMetadata): this {
|
|
109
|
+
this.deferredCredentialEndpoint = metadata.deferred_credential_endpoint;
|
|
110
|
+
return this;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
public withDeferredCredentialEndpoint(deferredCredentialEndpoint: string): this {
|
|
114
|
+
this.deferredCredentialEndpoint = deferredCredentialEndpoint;
|
|
115
|
+
return this;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
public withDeferredCredentialAwait(deferredCredentialAwait: boolean, deferredCredentialIntervalInMS?: number): this {
|
|
119
|
+
this.deferredCredentialAwait = deferredCredentialAwait;
|
|
120
|
+
this.deferredCredentialIntervalInMS = deferredCredentialIntervalInMS ?? 5000;
|
|
121
|
+
return this;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
public withCredentialType(credentialTypes: string | string[]): this {
|
|
80
125
|
this.credentialTypes = Array.isArray(credentialTypes) ? credentialTypes : [credentialTypes];
|
|
81
126
|
return this;
|
|
82
127
|
}
|
|
83
128
|
|
|
84
|
-
public withFormat(format: CredentialFormat | OID4VCICredentialFormat):
|
|
129
|
+
public withFormat(format: CredentialFormat | OID4VCICredentialFormat): this {
|
|
85
130
|
this.format = format;
|
|
86
131
|
return this;
|
|
87
132
|
}
|
|
88
133
|
|
|
89
|
-
public withToken(accessToken: string):
|
|
134
|
+
public withToken(accessToken: string): this {
|
|
90
135
|
this.token = accessToken;
|
|
91
136
|
return this;
|
|
92
137
|
}
|
|
93
138
|
|
|
94
|
-
public withTokenFromResponse(response: AccessTokenResponse):
|
|
139
|
+
public withTokenFromResponse(response: AccessTokenResponse): this {
|
|
95
140
|
this.token = response.access_token;
|
|
96
141
|
return this;
|
|
97
142
|
}
|
|
98
143
|
|
|
99
|
-
public withVersion(version: OpenId4VCIVersion):
|
|
144
|
+
public withVersion(version: OpenId4VCIVersion): this {
|
|
100
145
|
this.version = version;
|
|
101
146
|
return this;
|
|
102
147
|
}
|
package/lib/MetadataClient.ts
CHANGED
|
@@ -45,6 +45,7 @@ export class MetadataClient {
|
|
|
45
45
|
public static async retrieveAllMetadata(issuer: string, opts?: { errorOnNotFound: boolean }): Promise<EndpointMetadataResult> {
|
|
46
46
|
let token_endpoint: string | undefined;
|
|
47
47
|
let credential_endpoint: string | undefined;
|
|
48
|
+
let deferred_credential_endpoint: string | undefined;
|
|
48
49
|
let authorization_endpoint: string | undefined;
|
|
49
50
|
let authorizationServerType: AuthorizationServerType = 'OID4VCI';
|
|
50
51
|
let authorization_server: string = issuer;
|
|
@@ -53,6 +54,7 @@ export class MetadataClient {
|
|
|
53
54
|
if (credentialIssuerMetadata) {
|
|
54
55
|
debug(`Issuer ${issuer} OID4VCI well-known server metadata\r\n${JSON.stringify(credentialIssuerMetadata)}`);
|
|
55
56
|
credential_endpoint = credentialIssuerMetadata.credential_endpoint;
|
|
57
|
+
deferred_credential_endpoint = credentialIssuerMetadata.deferred_credential_endpoint;
|
|
56
58
|
if (credentialIssuerMetadata.token_endpoint) {
|
|
57
59
|
token_endpoint = credentialIssuerMetadata.token_endpoint;
|
|
58
60
|
}
|
|
@@ -111,12 +113,21 @@ export class MetadataClient {
|
|
|
111
113
|
if (authMetadata.credential_endpoint) {
|
|
112
114
|
if (credential_endpoint && authMetadata.credential_endpoint !== credential_endpoint) {
|
|
113
115
|
debug(
|
|
114
|
-
`Credential issuer has a different credential_endpoint (${credential_endpoint}) from the Authorization Server (${authMetadata.
|
|
116
|
+
`Credential issuer has a different credential_endpoint (${credential_endpoint}) from the Authorization Server (${authMetadata.credential_endpoint}). Will use the issuer value`,
|
|
115
117
|
);
|
|
116
118
|
} else {
|
|
117
119
|
credential_endpoint = authMetadata.credential_endpoint;
|
|
118
120
|
}
|
|
119
121
|
}
|
|
122
|
+
if (authMetadata.deferred_credential_endpoint) {
|
|
123
|
+
if (deferred_credential_endpoint && authMetadata.deferred_credential_endpoint !== deferred_credential_endpoint) {
|
|
124
|
+
debug(
|
|
125
|
+
`Credential issuer has a different deferred_credential_endpoint (${deferred_credential_endpoint}) from the Authorization Server (${authMetadata.deferred_credential_endpoint}). Will use the issuer value`,
|
|
126
|
+
);
|
|
127
|
+
} else {
|
|
128
|
+
deferred_credential_endpoint = authMetadata.deferred_credential_endpoint;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
120
131
|
}
|
|
121
132
|
|
|
122
133
|
if (!authorization_endpoint) {
|
|
@@ -148,6 +159,7 @@ export class MetadataClient {
|
|
|
148
159
|
issuer,
|
|
149
160
|
token_endpoint,
|
|
150
161
|
credential_endpoint,
|
|
162
|
+
deferred_credential_endpoint,
|
|
151
163
|
authorization_server,
|
|
152
164
|
authorization_endpoint,
|
|
153
165
|
authorizationServerType,
|