@sphereon/oid4vci-client 0.10.3 → 0.10.4-next.119
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 +24 -5
- package/dist/AccessTokenClient.d.ts +5 -5
- package/dist/AccessTokenClient.d.ts.map +1 -1
- package/dist/AccessTokenClient.js +51 -37
- package/dist/AccessTokenClient.js.map +1 -1
- package/dist/AccessTokenClientV1_0_11.d.ts +29 -0
- package/dist/AccessTokenClientV1_0_11.d.ts.map +1 -0
- package/dist/AccessTokenClientV1_0_11.js +209 -0
- package/dist/AccessTokenClientV1_0_11.js.map +1 -0
- package/dist/AuthorizationCodeClient.d.ts +9 -4
- package/dist/AuthorizationCodeClient.d.ts.map +1 -1
- package/dist/AuthorizationCodeClient.js +102 -18
- package/dist/AuthorizationCodeClient.js.map +1 -1
- package/dist/AuthorizationCodeClientV1_0_11.d.ts +9 -0
- package/dist/AuthorizationCodeClientV1_0_11.d.ts.map +1 -0
- package/dist/AuthorizationCodeClientV1_0_11.js +134 -0
- package/dist/AuthorizationCodeClientV1_0_11.js.map +1 -0
- package/dist/CredentialOfferClient.d.ts.map +1 -1
- package/dist/CredentialOfferClient.js +18 -13
- package/dist/CredentialOfferClient.js.map +1 -1
- package/dist/CredentialOfferClientV1_0_11.d.ts +10 -0
- package/dist/CredentialOfferClientV1_0_11.d.ts.map +1 -0
- package/dist/CredentialOfferClientV1_0_11.js +101 -0
- package/dist/CredentialOfferClientV1_0_11.js.map +1 -0
- package/dist/CredentialOfferClientV1_0_13.d.ts +10 -0
- package/dist/CredentialOfferClientV1_0_13.d.ts.map +1 -0
- package/dist/CredentialOfferClientV1_0_13.js +94 -0
- package/dist/CredentialOfferClientV1_0_13.js.map +1 -0
- package/dist/CredentialRequestClient.d.ts +20 -7
- package/dist/CredentialRequestClient.d.ts.map +1 -1
- package/dist/CredentialRequestClient.js +46 -30
- package/dist/CredentialRequestClient.js.map +1 -1
- package/dist/CredentialRequestClientBuilder.d.ts +11 -6
- package/dist/CredentialRequestClientBuilder.d.ts.map +1 -1
- package/dist/CredentialRequestClientBuilder.js +22 -9
- package/dist/CredentialRequestClientBuilder.js.map +1 -1
- package/dist/CredentialRequestClientBuilderV1_0_11.d.ts +48 -0
- package/dist/CredentialRequestClientBuilderV1_0_11.d.ts.map +1 -0
- package/dist/CredentialRequestClientBuilderV1_0_11.js +121 -0
- package/dist/CredentialRequestClientBuilderV1_0_11.js.map +1 -0
- package/dist/CredentialRequestClientV1_0_11.d.ts +50 -0
- package/dist/CredentialRequestClientV1_0_11.d.ts.map +1 -0
- package/dist/CredentialRequestClientV1_0_11.js +151 -0
- package/dist/CredentialRequestClientV1_0_11.js.map +1 -0
- package/dist/MetadataClient.d.ts +5 -15
- package/dist/MetadataClient.d.ts.map +1 -1
- package/dist/MetadataClient.js +41 -44
- package/dist/MetadataClient.js.map +1 -1
- package/dist/MetadataClientV1_0_11.d.ts +31 -0
- package/dist/MetadataClientV1_0_11.d.ts.map +1 -0
- package/dist/MetadataClientV1_0_11.js +182 -0
- package/dist/MetadataClientV1_0_11.js.map +1 -0
- package/dist/MetadataClientV1_0_13.d.ts +31 -0
- package/dist/MetadataClientV1_0_13.d.ts.map +1 -0
- package/dist/MetadataClientV1_0_13.js +181 -0
- package/dist/MetadataClientV1_0_13.js.map +1 -0
- package/dist/OpenID4VCIClient.d.ts +14 -19
- package/dist/OpenID4VCIClient.d.ts.map +1 -1
- package/dist/OpenID4VCIClient.js +111 -61
- package/dist/OpenID4VCIClient.js.map +1 -1
- package/dist/OpenID4VCIClientV1_0_11.d.ts +108 -0
- package/dist/OpenID4VCIClientV1_0_11.d.ts.map +1 -0
- package/dist/OpenID4VCIClientV1_0_11.js +449 -0
- package/dist/OpenID4VCIClientV1_0_11.js.map +1 -0
- package/dist/OpenID4VCIClientV1_0_13.d.ts +112 -0
- package/dist/OpenID4VCIClientV1_0_13.d.ts.map +1 -0
- package/dist/OpenID4VCIClientV1_0_13.js +478 -0
- package/dist/OpenID4VCIClientV1_0_13.js.map +1 -0
- package/dist/ProofOfPossessionBuilder.d.ts +14 -3
- package/dist/ProofOfPossessionBuilder.d.ts.map +1 -1
- package/dist/ProofOfPossessionBuilder.js +20 -21
- package/dist/ProofOfPossessionBuilder.js.map +1 -1
- package/dist/functions/OpenIDUtils.d.ts +12 -0
- package/dist/functions/OpenIDUtils.d.ts.map +1 -0
- package/dist/functions/OpenIDUtils.js +37 -0
- package/dist/functions/OpenIDUtils.js.map +1 -0
- package/dist/functions/index.d.ts +2 -3
- package/dist/functions/index.d.ts.map +1 -1
- package/dist/functions/index.js +2 -3
- package/dist/functions/index.js.map +1 -1
- package/dist/functions/notifications.d.ts +4 -0
- package/dist/functions/notifications.d.ts.map +1 -0
- package/dist/functions/notifications.js +39 -0
- package/dist/functions/notifications.js.map +1 -0
- package/dist/index.d.ts +13 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -1
- package/dist/index.js.map +1 -1
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +5 -0
- package/dist/types/index.js.map +1 -1
- package/lib/AccessTokenClient.ts +59 -34
- package/lib/AccessTokenClientV1_0_11.ts +250 -0
- package/lib/AuthorizationCodeClient.ts +131 -28
- package/lib/AuthorizationCodeClientV1_0_11.ts +170 -0
- package/lib/CredentialOfferClient.ts +21 -8
- package/lib/CredentialOfferClientV1_0_11.ts +112 -0
- package/lib/CredentialOfferClientV1_0_13.ts +103 -0
- package/lib/CredentialRequestClient.ts +65 -26
- package/lib/CredentialRequestClientBuilder.ts +34 -16
- package/lib/CredentialRequestClientBuilderV1_0_11.ts +163 -0
- package/lib/CredentialRequestClientV1_0_11.ts +197 -0
- package/lib/MetadataClient.ts +64 -49
- package/lib/MetadataClientV1_0_11.ts +189 -0
- package/lib/MetadataClientV1_0_13.ts +188 -0
- package/lib/OpenID4VCIClient.ts +132 -68
- package/lib/OpenID4VCIClientV1_0_11.ts +635 -0
- package/lib/OpenID4VCIClientV1_0_13.ts +677 -0
- package/lib/ProofOfPossessionBuilder.ts +41 -11
- package/lib/__tests__/AccessTokenClient.spec.ts +40 -12
- package/lib/__tests__/AuthorizationDetailsBuilder.spec.ts +0 -12
- package/lib/__tests__/CredentialRequestClient.spec.ts +87 -50
- package/lib/__tests__/CredentialRequestClientBuilder.spec.ts +18 -12
- package/lib/__tests__/CredentialRequestClientV1_0_11.spec.ts +317 -0
- package/lib/__tests__/EBSIE2E.spec.test.ts +2 -2
- package/lib/__tests__/HttpUtils.spec.ts +1 -1
- package/lib/__tests__/IT.spec.ts +264 -14
- package/lib/__tests__/IssuanceInitiation.spec.ts +59 -4
- package/lib/__tests__/IssuanceInitiationV1_0_11.spec.ts +62 -0
- package/lib/__tests__/MattrE2E.spec.test.ts +2 -2
- package/lib/__tests__/MetadataClient.spec.ts +53 -3
- package/lib/__tests__/MetadataMocks.ts +42 -2
- package/lib/__tests__/OpenID4VCIClient.spec.ts +58 -2
- package/lib/__tests__/{OpenID4VCIClientPAR.spec.ts → OpenID4VCIClientPARV1_0_11.spec.ts} +5 -5
- package/lib/__tests__/OpenID4VCIClientV1_0_11.spec.ts +226 -0
- package/lib/__tests__/OpenID4VCIClientV1_0_13.spec.ts +204 -0
- package/lib/__tests__/ProofOfPossessionBuilder.spec.ts +1 -1
- package/lib/__tests__/SdJwt.spec.ts +36 -30
- package/lib/__tests__/SphereonE2E.spec.test.ts +10 -7
- package/lib/__tests__/data/VciDataFixtures.ts +712 -27
- package/lib/functions/OpenIDUtils.ts +25 -0
- package/lib/functions/index.ts +2 -3
- package/lib/functions/notifications.ts +32 -0
- package/lib/index.ts +16 -1
- package/lib/types/index.ts +6 -0
- package/package.json +4 -4
- package/dist/functions/ProofUtil.d.ts +0 -30
- package/dist/functions/ProofUtil.d.ts.map +0 -1
- package/dist/functions/ProofUtil.js +0 -106
- package/dist/functions/ProofUtil.js.map +0 -1
- package/lib/functions/ProofUtil.ts +0 -128
|
@@ -0,0 +1,635 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AccessTokenResponse,
|
|
3
|
+
Alg,
|
|
4
|
+
AuthorizationRequestOpts,
|
|
5
|
+
AuthorizationResponse,
|
|
6
|
+
AuthzFlowType,
|
|
7
|
+
CodeChallengeMethod,
|
|
8
|
+
CredentialConfigurationSupported,
|
|
9
|
+
CredentialOfferPayloadV1_0_08,
|
|
10
|
+
CredentialOfferPayloadV1_0_11,
|
|
11
|
+
CredentialOfferRequestWithBaseUrl,
|
|
12
|
+
CredentialResponse,
|
|
13
|
+
CredentialsSupportedLegacy,
|
|
14
|
+
DefaultURISchemes,
|
|
15
|
+
EndpointMetadataResultV1_0_11,
|
|
16
|
+
getClientIdFromCredentialOfferPayload,
|
|
17
|
+
getIssuerFromCredentialOfferPayload,
|
|
18
|
+
getSupportedCredentials,
|
|
19
|
+
getTypesFromCredentialSupported,
|
|
20
|
+
getTypesFromObject,
|
|
21
|
+
JWK,
|
|
22
|
+
KID_JWK_X5C_ERROR,
|
|
23
|
+
OID4VCICredentialFormat,
|
|
24
|
+
OpenId4VCIVersion,
|
|
25
|
+
PKCEOpts,
|
|
26
|
+
ProofOfPossessionCallbacks,
|
|
27
|
+
toAuthorizationResponsePayload,
|
|
28
|
+
} from '@sphereon/oid4vci-common';
|
|
29
|
+
import { CredentialFormat } from '@sphereon/ssi-types';
|
|
30
|
+
import Debug from 'debug';
|
|
31
|
+
|
|
32
|
+
import { AccessTokenClientV1_0_11 } from './AccessTokenClientV1_0_11';
|
|
33
|
+
import { createAuthorizationRequestUrlV1_0_11 } from './AuthorizationCodeClientV1_0_11';
|
|
34
|
+
import { CredentialOfferClientV1_0_11 } from './CredentialOfferClientV1_0_11';
|
|
35
|
+
import { CredentialRequestClientBuilderV1_0_11 } from './CredentialRequestClientBuilderV1_0_11';
|
|
36
|
+
import { MetadataClientV1_0_11 } from './MetadataClientV1_0_11';
|
|
37
|
+
import { ProofOfPossessionBuilder } from './ProofOfPossessionBuilder';
|
|
38
|
+
import { generateMissingPKCEOpts } from './functions/AuthorizationUtil';
|
|
39
|
+
|
|
40
|
+
const debug = Debug('sphereon:oid4vci');
|
|
41
|
+
|
|
42
|
+
export interface OpenID4VCIClientStateV1_0_11 {
|
|
43
|
+
credentialIssuer: string;
|
|
44
|
+
credentialOffer?: CredentialOfferRequestWithBaseUrl;
|
|
45
|
+
clientId?: string;
|
|
46
|
+
kid?: string;
|
|
47
|
+
jwk?: JWK;
|
|
48
|
+
alg?: Alg | string;
|
|
49
|
+
endpointMetadata?: EndpointMetadataResultV1_0_11;
|
|
50
|
+
accessTokenResponse?: AccessTokenResponse;
|
|
51
|
+
authorizationRequestOpts?: AuthorizationRequestOpts;
|
|
52
|
+
authorizationCodeResponse?: AuthorizationResponse;
|
|
53
|
+
pkce: PKCEOpts;
|
|
54
|
+
accessToken?: string;
|
|
55
|
+
authorizationURL?: string;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export class OpenID4VCIClientV1_0_11 {
|
|
59
|
+
private readonly _state: OpenID4VCIClientStateV1_0_11;
|
|
60
|
+
|
|
61
|
+
private constructor({
|
|
62
|
+
credentialOffer,
|
|
63
|
+
clientId,
|
|
64
|
+
kid,
|
|
65
|
+
alg,
|
|
66
|
+
credentialIssuer,
|
|
67
|
+
pkce,
|
|
68
|
+
authorizationRequest,
|
|
69
|
+
jwk,
|
|
70
|
+
endpointMetadata,
|
|
71
|
+
accessTokenResponse,
|
|
72
|
+
authorizationRequestOpts,
|
|
73
|
+
authorizationCodeResponse,
|
|
74
|
+
authorizationURL,
|
|
75
|
+
}: {
|
|
76
|
+
credentialOffer?: CredentialOfferRequestWithBaseUrl;
|
|
77
|
+
kid?: string;
|
|
78
|
+
alg?: Alg | string;
|
|
79
|
+
clientId?: string;
|
|
80
|
+
credentialIssuer?: string;
|
|
81
|
+
pkce?: PKCEOpts;
|
|
82
|
+
authorizationRequest?: AuthorizationRequestOpts; // Can be provided here, or when manually calling createAuthorizationUrl
|
|
83
|
+
jwk?: JWK;
|
|
84
|
+
endpointMetadata?: EndpointMetadataResultV1_0_11;
|
|
85
|
+
accessTokenResponse?: AccessTokenResponse;
|
|
86
|
+
authorizationRequestOpts?: AuthorizationRequestOpts;
|
|
87
|
+
authorizationCodeResponse?: AuthorizationResponse;
|
|
88
|
+
authorizationURL?: string;
|
|
89
|
+
}) {
|
|
90
|
+
const issuer = credentialIssuer ?? (credentialOffer ? getIssuerFromCredentialOfferPayload(credentialOffer.credential_offer) : undefined);
|
|
91
|
+
if (!issuer) {
|
|
92
|
+
throw Error('No credential issuer supplied or deduced from offer');
|
|
93
|
+
}
|
|
94
|
+
this._state = {
|
|
95
|
+
credentialOffer,
|
|
96
|
+
credentialIssuer: issuer,
|
|
97
|
+
kid,
|
|
98
|
+
alg,
|
|
99
|
+
// TODO: We need to refactor this and always explicitly call createAuthorizationRequestUrl, so we can have a credential selection first and use the kid as a default for the client id
|
|
100
|
+
clientId: clientId ?? (credentialOffer && getClientIdFromCredentialOfferPayload(credentialOffer.credential_offer)) ?? kid?.split('#')[0],
|
|
101
|
+
pkce: { disabled: false, codeChallengeMethod: CodeChallengeMethod.S256, ...pkce },
|
|
102
|
+
authorizationRequestOpts,
|
|
103
|
+
authorizationCodeResponse,
|
|
104
|
+
jwk,
|
|
105
|
+
endpointMetadata,
|
|
106
|
+
accessTokenResponse,
|
|
107
|
+
authorizationURL,
|
|
108
|
+
};
|
|
109
|
+
// Running syncAuthorizationRequestOpts later as it is using the state
|
|
110
|
+
if (!this._state.authorizationRequestOpts) {
|
|
111
|
+
this._state.authorizationRequestOpts = this.syncAuthorizationRequestOpts(authorizationRequest);
|
|
112
|
+
}
|
|
113
|
+
debug(`Authorization req options: ${JSON.stringify(this._state.authorizationRequestOpts, null, 2)}`);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
public static async fromCredentialIssuer({
|
|
117
|
+
kid,
|
|
118
|
+
alg,
|
|
119
|
+
retrieveServerMetadata,
|
|
120
|
+
clientId,
|
|
121
|
+
credentialIssuer,
|
|
122
|
+
pkce,
|
|
123
|
+
authorizationRequest,
|
|
124
|
+
createAuthorizationRequestURL,
|
|
125
|
+
}: {
|
|
126
|
+
credentialIssuer: string;
|
|
127
|
+
kid?: string;
|
|
128
|
+
alg?: Alg | string;
|
|
129
|
+
retrieveServerMetadata?: boolean;
|
|
130
|
+
clientId?: string;
|
|
131
|
+
createAuthorizationRequestURL?: boolean;
|
|
132
|
+
authorizationRequest?: AuthorizationRequestOpts; // Can be provided here, or when manually calling createAuthorizationUrl
|
|
133
|
+
pkce?: PKCEOpts;
|
|
134
|
+
}) {
|
|
135
|
+
const client = new OpenID4VCIClientV1_0_11({
|
|
136
|
+
kid,
|
|
137
|
+
alg,
|
|
138
|
+
clientId: clientId ?? authorizationRequest?.clientId,
|
|
139
|
+
credentialIssuer,
|
|
140
|
+
pkce,
|
|
141
|
+
authorizationRequest,
|
|
142
|
+
});
|
|
143
|
+
if (retrieveServerMetadata === undefined || retrieveServerMetadata) {
|
|
144
|
+
await client.retrieveServerMetadata();
|
|
145
|
+
}
|
|
146
|
+
if (createAuthorizationRequestURL === undefined || createAuthorizationRequestURL) {
|
|
147
|
+
await client.createAuthorizationRequestUrl({ authorizationRequest, pkce });
|
|
148
|
+
}
|
|
149
|
+
return client;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
public static async fromState({ state }: { state: OpenID4VCIClientStateV1_0_11 | string }): Promise<OpenID4VCIClientV1_0_11> {
|
|
153
|
+
const clientState = typeof state === 'string' ? JSON.parse(state) : state;
|
|
154
|
+
|
|
155
|
+
return new OpenID4VCIClientV1_0_11(clientState);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
public static async fromURI({
|
|
159
|
+
uri,
|
|
160
|
+
kid,
|
|
161
|
+
alg,
|
|
162
|
+
retrieveServerMetadata,
|
|
163
|
+
clientId,
|
|
164
|
+
pkce,
|
|
165
|
+
createAuthorizationRequestURL,
|
|
166
|
+
authorizationRequest,
|
|
167
|
+
resolveOfferUri,
|
|
168
|
+
}: {
|
|
169
|
+
uri: string;
|
|
170
|
+
kid?: string;
|
|
171
|
+
alg?: Alg | string;
|
|
172
|
+
retrieveServerMetadata?: boolean;
|
|
173
|
+
createAuthorizationRequestURL?: boolean;
|
|
174
|
+
resolveOfferUri?: boolean;
|
|
175
|
+
pkce?: PKCEOpts;
|
|
176
|
+
clientId?: string;
|
|
177
|
+
authorizationRequest?: AuthorizationRequestOpts; // Can be provided here, or when manually calling createAuthorizationUrl
|
|
178
|
+
}): Promise<OpenID4VCIClientV1_0_11> {
|
|
179
|
+
const credentialOfferClient = await CredentialOfferClientV1_0_11.fromURI(uri, { resolve: resolveOfferUri });
|
|
180
|
+
const client = new OpenID4VCIClientV1_0_11({
|
|
181
|
+
credentialOffer: credentialOfferClient,
|
|
182
|
+
kid,
|
|
183
|
+
alg,
|
|
184
|
+
clientId: clientId ?? authorizationRequest?.clientId ?? credentialOfferClient.clientId,
|
|
185
|
+
pkce,
|
|
186
|
+
authorizationRequest,
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
if (retrieveServerMetadata === undefined || retrieveServerMetadata) {
|
|
190
|
+
await client.retrieveServerMetadata();
|
|
191
|
+
}
|
|
192
|
+
if (
|
|
193
|
+
credentialOfferClient.supportedFlows.includes(AuthzFlowType.AUTHORIZATION_CODE_FLOW) &&
|
|
194
|
+
(createAuthorizationRequestURL === undefined || createAuthorizationRequestURL)
|
|
195
|
+
) {
|
|
196
|
+
await client.createAuthorizationRequestUrl({ authorizationRequest, pkce });
|
|
197
|
+
debug(`Authorization Request URL: ${client._state.authorizationURL}`);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return client;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Allows you to create an Authorization Request URL when using an Authorization Code flow. This URL needs to be accessed using the front channel (browser)
|
|
205
|
+
*
|
|
206
|
+
* The Identity provider would present a login screen typically; after you authenticated, it would redirect to the provided redirectUri; which can be same device or cross-device
|
|
207
|
+
* @param opts
|
|
208
|
+
*/
|
|
209
|
+
public async createAuthorizationRequestUrl(opts?: { authorizationRequest?: AuthorizationRequestOpts; pkce?: PKCEOpts }): Promise<string> {
|
|
210
|
+
if (!this._state.authorizationURL) {
|
|
211
|
+
this.calculatePKCEOpts(opts?.pkce);
|
|
212
|
+
this._state.authorizationRequestOpts = this.syncAuthorizationRequestOpts(opts?.authorizationRequest);
|
|
213
|
+
if (!this._state.authorizationRequestOpts) {
|
|
214
|
+
throw Error(`No Authorization Request options present or provided in this call`);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// todo: Probably can go with current logic in MetadataClientV1_0_13 who will always set the authorization_endpoint when found
|
|
218
|
+
// handling this because of the support for v1_0-08
|
|
219
|
+
if (
|
|
220
|
+
this._state.endpointMetadata?.credentialIssuerMetadata &&
|
|
221
|
+
'authorization_endpoint' in this._state.endpointMetadata.credentialIssuerMetadata
|
|
222
|
+
) {
|
|
223
|
+
this._state.endpointMetadata.authorization_endpoint = this._state.endpointMetadata.credentialIssuerMetadata.authorization_endpoint as string;
|
|
224
|
+
}
|
|
225
|
+
this._state.authorizationURL = await createAuthorizationRequestUrlV1_0_11({
|
|
226
|
+
pkce: this._state.pkce,
|
|
227
|
+
endpointMetadata: this.endpointMetadata,
|
|
228
|
+
authorizationRequest: this._state.authorizationRequestOpts,
|
|
229
|
+
credentialOffer: this.credentialOffer,
|
|
230
|
+
credentialsSupported: Object.values(this.getCredentialsSupported()) as CredentialsSupportedLegacy[],
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
return this._state.authorizationURL;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
public async retrieveServerMetadata(): Promise<EndpointMetadataResultV1_0_11> {
|
|
237
|
+
this.assertIssuerData();
|
|
238
|
+
if (!this._state.endpointMetadata) {
|
|
239
|
+
if (this.credentialOffer) {
|
|
240
|
+
this._state.endpointMetadata = await MetadataClientV1_0_11.retrieveAllMetadataFromCredentialOffer(this.credentialOffer);
|
|
241
|
+
} else if (this._state.credentialIssuer) {
|
|
242
|
+
this._state.endpointMetadata = await MetadataClientV1_0_11.retrieveAllMetadata(this._state.credentialIssuer);
|
|
243
|
+
} else {
|
|
244
|
+
throw Error(`Cannot retrieve issuer metadata without either a credential offer, or issuer value`);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
return this.endpointMetadata;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
private calculatePKCEOpts(pkce?: PKCEOpts) {
|
|
252
|
+
this._state.pkce = generateMissingPKCEOpts({ ...this._state.pkce, ...pkce });
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
public async acquireAccessToken(opts?: {
|
|
256
|
+
pin?: string;
|
|
257
|
+
clientId?: string;
|
|
258
|
+
codeVerifier?: string;
|
|
259
|
+
authorizationResponse?: string | AuthorizationResponse; // Pass in an auth response, either as URI/redirect, or object
|
|
260
|
+
code?: string; // Directly pass in a code from an auth response
|
|
261
|
+
redirectUri?: string;
|
|
262
|
+
}): Promise<AccessTokenResponse> {
|
|
263
|
+
const { pin, clientId } = opts ?? {};
|
|
264
|
+
let { redirectUri } = opts ?? {};
|
|
265
|
+
if (opts?.authorizationResponse) {
|
|
266
|
+
this._state.authorizationCodeResponse = { ...toAuthorizationResponsePayload(opts.authorizationResponse) };
|
|
267
|
+
} else if (opts?.code) {
|
|
268
|
+
this._state.authorizationCodeResponse = { code: opts.code };
|
|
269
|
+
}
|
|
270
|
+
const code = this._state.authorizationCodeResponse?.code;
|
|
271
|
+
|
|
272
|
+
if (opts?.codeVerifier) {
|
|
273
|
+
this._state.pkce.codeVerifier = opts.codeVerifier;
|
|
274
|
+
}
|
|
275
|
+
this.assertIssuerData();
|
|
276
|
+
|
|
277
|
+
if (clientId) {
|
|
278
|
+
this._state.clientId = clientId;
|
|
279
|
+
}
|
|
280
|
+
if (!this._state.accessTokenResponse) {
|
|
281
|
+
const accessTokenClient = new AccessTokenClientV1_0_11();
|
|
282
|
+
|
|
283
|
+
if (redirectUri && redirectUri !== this._state.authorizationRequestOpts?.redirectUri) {
|
|
284
|
+
console.log(
|
|
285
|
+
`Redirect URI mismatch between access-token (${redirectUri}) and authorization request (${this._state.authorizationRequestOpts?.redirectUri}). According to the specification that is not allowed.`,
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
if (this._state.authorizationRequestOpts?.redirectUri && !redirectUri) {
|
|
289
|
+
redirectUri = this._state.authorizationRequestOpts.redirectUri;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const response = await accessTokenClient.acquireAccessToken({
|
|
293
|
+
credentialOffer: this.credentialOffer,
|
|
294
|
+
metadata: this.endpointMetadata,
|
|
295
|
+
credentialIssuer: this.getIssuer(),
|
|
296
|
+
pin,
|
|
297
|
+
...(!this._state.pkce.disabled && { codeVerifier: this._state.pkce.codeVerifier }),
|
|
298
|
+
code,
|
|
299
|
+
redirectUri,
|
|
300
|
+
asOpts: { clientId: this.clientId },
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
if (response.errorBody) {
|
|
304
|
+
debug(`Access token error:\r\n${JSON.stringify(response.errorBody)}`);
|
|
305
|
+
throw Error(
|
|
306
|
+
`Retrieving an access token from ${this._state.endpointMetadata?.token_endpoint} for issuer ${this.getIssuer()} failed with status: ${
|
|
307
|
+
response.origResponse.status
|
|
308
|
+
}`,
|
|
309
|
+
);
|
|
310
|
+
} else if (!response.successBody) {
|
|
311
|
+
debug(`Access token error. No success body`);
|
|
312
|
+
throw Error(
|
|
313
|
+
`Retrieving an access token from ${
|
|
314
|
+
this._state.endpointMetadata?.token_endpoint
|
|
315
|
+
} for issuer ${this.getIssuer()} failed as there was no success response body`,
|
|
316
|
+
);
|
|
317
|
+
}
|
|
318
|
+
this._state.accessTokenResponse = response.successBody;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
return this.accessTokenResponse;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
public async acquireCredentials({
|
|
325
|
+
credentialTypes,
|
|
326
|
+
context,
|
|
327
|
+
proofCallbacks,
|
|
328
|
+
format,
|
|
329
|
+
kid,
|
|
330
|
+
jwk,
|
|
331
|
+
alg,
|
|
332
|
+
jti,
|
|
333
|
+
deferredCredentialAwait,
|
|
334
|
+
deferredCredentialIntervalInMS,
|
|
335
|
+
}: {
|
|
336
|
+
credentialTypes: string | string[];
|
|
337
|
+
context?: string[];
|
|
338
|
+
proofCallbacks: ProofOfPossessionCallbacks<any>;
|
|
339
|
+
format?: CredentialFormat | OID4VCICredentialFormat;
|
|
340
|
+
kid?: string;
|
|
341
|
+
jwk?: JWK;
|
|
342
|
+
alg?: Alg | string;
|
|
343
|
+
jti?: string;
|
|
344
|
+
deferredCredentialAwait?: boolean;
|
|
345
|
+
deferredCredentialIntervalInMS?: number;
|
|
346
|
+
}): Promise<CredentialResponse> {
|
|
347
|
+
if ([jwk, kid].filter((v) => v !== undefined).length > 1) {
|
|
348
|
+
throw new Error(KID_JWK_X5C_ERROR + `. jwk: ${jwk !== undefined}, kid: ${kid !== undefined}`);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
if (alg) this._state.alg = alg;
|
|
352
|
+
if (jwk) this._state.jwk = jwk;
|
|
353
|
+
if (kid) this._state.kid = kid;
|
|
354
|
+
|
|
355
|
+
const requestBuilder = this.credentialOffer
|
|
356
|
+
? CredentialRequestClientBuilderV1_0_11.fromCredentialOffer({
|
|
357
|
+
credentialOffer: this.credentialOffer,
|
|
358
|
+
metadata: this.endpointMetadata,
|
|
359
|
+
})
|
|
360
|
+
: CredentialRequestClientBuilderV1_0_11.fromCredentialIssuer({
|
|
361
|
+
credentialIssuer: this.getIssuer(),
|
|
362
|
+
credentialTypes,
|
|
363
|
+
metadata: this.endpointMetadata,
|
|
364
|
+
version: this.version(),
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
requestBuilder.withTokenFromResponse(this.accessTokenResponse);
|
|
368
|
+
requestBuilder.withDeferredCredentialAwait(deferredCredentialAwait ?? false, deferredCredentialIntervalInMS);
|
|
369
|
+
if (this.endpointMetadata?.credentialIssuerMetadata) {
|
|
370
|
+
const metadata = this.endpointMetadata.credentialIssuerMetadata;
|
|
371
|
+
const types = Array.isArray(credentialTypes) ? credentialTypes : [credentialTypes];
|
|
372
|
+
|
|
373
|
+
if (metadata.credentials_supported && Array.isArray(metadata.credentials_supported)) {
|
|
374
|
+
let typeSupported = false;
|
|
375
|
+
|
|
376
|
+
metadata.credentials_supported.forEach((supportedCredential) => {
|
|
377
|
+
const subTypes = getTypesFromCredentialSupported(supportedCredential);
|
|
378
|
+
if (
|
|
379
|
+
subTypes.every((t, i) => types[i] === t) ||
|
|
380
|
+
(types.length === 1 && (types[0] === supportedCredential.id || subTypes.includes(types[0])))
|
|
381
|
+
) {
|
|
382
|
+
typeSupported = true;
|
|
383
|
+
}
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
if (!typeSupported) {
|
|
387
|
+
console.log(`Not all credential types ${JSON.stringify(credentialTypes)} are present in metadata for ${this.getIssuer()}`);
|
|
388
|
+
// throw Error(`Not all credential types ${JSON.stringify(credentialTypes)} are supported by issuer ${this.getIssuer()}`);
|
|
389
|
+
}
|
|
390
|
+
} else if (metadata.credentials_supported && !Array.isArray(metadata.credentials_supported)) {
|
|
391
|
+
const credentialsSupported = metadata.credentials_supported;
|
|
392
|
+
if (types.some((type) => !metadata.credentials_supported || !credentialsSupported[type])) {
|
|
393
|
+
throw Error(`Not all credential types ${JSON.stringify(credentialTypes)} are supported by issuer ${this.getIssuer()}`);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
// todo: Format check? We might end up with some disjoint type / format combinations supported by the server
|
|
397
|
+
}
|
|
398
|
+
const credentialRequestClient = requestBuilder.build();
|
|
399
|
+
const proofBuilder = ProofOfPossessionBuilder.fromAccessTokenResponse({
|
|
400
|
+
accessTokenResponse: this.accessTokenResponse,
|
|
401
|
+
callbacks: proofCallbacks,
|
|
402
|
+
version: this.version(),
|
|
403
|
+
})
|
|
404
|
+
.withIssuer(this.getIssuer())
|
|
405
|
+
.withAlg(this.alg);
|
|
406
|
+
|
|
407
|
+
if (this._state.jwk) {
|
|
408
|
+
proofBuilder.withJWK(this._state.jwk);
|
|
409
|
+
}
|
|
410
|
+
if (this._state.kid) {
|
|
411
|
+
proofBuilder.withKid(this._state.kid);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
if (this.clientId) {
|
|
415
|
+
proofBuilder.withClientId(this.clientId);
|
|
416
|
+
}
|
|
417
|
+
if (jti) {
|
|
418
|
+
proofBuilder.withJti(jti);
|
|
419
|
+
}
|
|
420
|
+
const response = await credentialRequestClient.acquireCredentialsUsingProof({
|
|
421
|
+
proofInput: proofBuilder,
|
|
422
|
+
credentialTypes,
|
|
423
|
+
context,
|
|
424
|
+
format,
|
|
425
|
+
});
|
|
426
|
+
if (response.errorBody) {
|
|
427
|
+
debug(`Credential request error:\r\n${JSON.stringify(response.errorBody)}`);
|
|
428
|
+
throw Error(
|
|
429
|
+
`Retrieving a credential from ${this._state.endpointMetadata?.credential_endpoint} for issuer ${this.getIssuer()} failed with status: ${
|
|
430
|
+
response.origResponse.status
|
|
431
|
+
}`,
|
|
432
|
+
);
|
|
433
|
+
} else if (!response.successBody) {
|
|
434
|
+
debug(`Credential request error. No success body`);
|
|
435
|
+
throw Error(
|
|
436
|
+
`Retrieving a credential from ${
|
|
437
|
+
this._state.endpointMetadata?.credential_endpoint
|
|
438
|
+
} for issuer ${this.getIssuer()} failed as there was no success response body`,
|
|
439
|
+
);
|
|
440
|
+
}
|
|
441
|
+
return response.successBody;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
public async exportState(): Promise<string> {
|
|
445
|
+
return JSON.stringify(this._state);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// FIXME: We really should convert <v11 to v12 objects first. Right now the logic doesn't map nicely and is brittle.
|
|
449
|
+
// We should resolve IDs to objects first in case of strings.
|
|
450
|
+
// When < v11 convert into a v12 object. When v12 object retain it.
|
|
451
|
+
// Then match the object array on server metadata
|
|
452
|
+
getCredentialsSupportedV11(
|
|
453
|
+
restrictToInitiationTypes: boolean,
|
|
454
|
+
format?: (OID4VCICredentialFormat | string) | (OID4VCICredentialFormat | string)[],
|
|
455
|
+
): Record<string, CredentialConfigurationSupported> {
|
|
456
|
+
return getSupportedCredentials({
|
|
457
|
+
issuerMetadata: this.endpointMetadata.credentialIssuerMetadata,
|
|
458
|
+
version: this.version(),
|
|
459
|
+
format: format,
|
|
460
|
+
types: restrictToInitiationTypes ? this.getCredentialOfferTypes() : undefined,
|
|
461
|
+
}) as Record<string, CredentialConfigurationSupported>;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
getCredentialsSupported(format?: (OID4VCICredentialFormat | string) | (OID4VCICredentialFormat | string)[]): CredentialConfigurationSupported[] {
|
|
465
|
+
return getSupportedCredentials({
|
|
466
|
+
issuerMetadata: this.endpointMetadata.credentialIssuerMetadata,
|
|
467
|
+
version: this.version(),
|
|
468
|
+
format: format,
|
|
469
|
+
types: undefined,
|
|
470
|
+
}) as CredentialConfigurationSupported[];
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
getCredentialOfferTypes(): string[][] {
|
|
474
|
+
if (!this.credentialOffer) {
|
|
475
|
+
return [];
|
|
476
|
+
} else if (this.credentialOffer.version < OpenId4VCIVersion.VER_1_0_11) {
|
|
477
|
+
const orig = this.credentialOffer.original_credential_offer as CredentialOfferPayloadV1_0_08;
|
|
478
|
+
const types: string[] = typeof orig.credential_type === 'string' ? [orig.credential_type] : orig.credential_type;
|
|
479
|
+
const result: string[][] = [];
|
|
480
|
+
result[0] = types;
|
|
481
|
+
return result;
|
|
482
|
+
} else if (this.credentialOffer.version < OpenId4VCIVersion.VER_1_0_13) {
|
|
483
|
+
return (this.credentialOffer.credential_offer as CredentialOfferPayloadV1_0_11).credentials.map((c) => getTypesFromObject(c) ?? []);
|
|
484
|
+
}
|
|
485
|
+
// we don't support > V11
|
|
486
|
+
throw Error(`This class only supports version 11 and lower! Version: ${this.version()}`);
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
issuerSupportedFlowTypes(): AuthzFlowType[] {
|
|
490
|
+
return (
|
|
491
|
+
this.credentialOffer?.supportedFlows ??
|
|
492
|
+
(this._state.endpointMetadata?.credentialIssuerMetadata?.authorization_endpoint ? [AuthzFlowType.AUTHORIZATION_CODE_FLOW] : [])
|
|
493
|
+
);
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
isFlowTypeSupported(flowType: AuthzFlowType): boolean {
|
|
497
|
+
return this.issuerSupportedFlowTypes().includes(flowType);
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
get authorizationURL(): string | undefined {
|
|
501
|
+
return this._state.authorizationURL;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
public hasAuthorizationURL(): boolean {
|
|
505
|
+
return !!this.authorizationURL;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
get credentialOffer(): CredentialOfferRequestWithBaseUrl | undefined {
|
|
509
|
+
return this._state.credentialOffer;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
public version(): OpenId4VCIVersion {
|
|
513
|
+
return this.credentialOffer?.version ?? OpenId4VCIVersion.VER_1_0_11;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
public get endpointMetadata(): EndpointMetadataResultV1_0_11 {
|
|
517
|
+
this.assertServerMetadata();
|
|
518
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
519
|
+
return this._state.endpointMetadata!;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
get kid(): string {
|
|
523
|
+
this.assertIssuerData();
|
|
524
|
+
if (!this._state.kid) {
|
|
525
|
+
throw new Error('No value for kid is supplied');
|
|
526
|
+
}
|
|
527
|
+
return this._state.kid;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
get alg(): string {
|
|
531
|
+
this.assertIssuerData();
|
|
532
|
+
if (!this._state.alg) {
|
|
533
|
+
throw new Error('No value for alg is supplied');
|
|
534
|
+
}
|
|
535
|
+
return this._state.alg;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
set clientId(value: string | undefined) {
|
|
539
|
+
this._state.clientId = value;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
get clientId(): string | undefined {
|
|
543
|
+
return this._state.clientId;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
public hasAccessTokenResponse(): boolean {
|
|
547
|
+
return !!this._state.accessTokenResponse;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
get accessTokenResponse(): AccessTokenResponse {
|
|
551
|
+
this.assertAccessToken();
|
|
552
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
553
|
+
return this._state.accessTokenResponse!;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
public getIssuer(): string {
|
|
557
|
+
this.assertIssuerData();
|
|
558
|
+
return this._state.credentialIssuer;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
public getAccessTokenEndpoint(): string {
|
|
562
|
+
this.assertIssuerData();
|
|
563
|
+
return this.endpointMetadata
|
|
564
|
+
? this.endpointMetadata.token_endpoint
|
|
565
|
+
: AccessTokenClientV1_0_11.determineTokenURL({ issuerOpts: { issuer: this.getIssuer() } });
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
public getCredentialEndpoint(): string {
|
|
569
|
+
this.assertIssuerData();
|
|
570
|
+
return this.endpointMetadata ? this.endpointMetadata.credential_endpoint : `${this.getIssuer()}/credential`;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
public hasDeferredCredentialEndpoint(): boolean {
|
|
574
|
+
return !!this.getAccessTokenEndpoint();
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
public getDeferredCredentialEndpoint(): string {
|
|
578
|
+
this.assertIssuerData();
|
|
579
|
+
return this.endpointMetadata ? this.endpointMetadata.credential_endpoint : `${this.getIssuer()}/credential`;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
/**
|
|
583
|
+
* Too bad we need a method like this, but EBSI is not exposing metadata
|
|
584
|
+
*/
|
|
585
|
+
public isEBSI() {
|
|
586
|
+
if (
|
|
587
|
+
(this.credentialOffer?.credential_offer as CredentialOfferPayloadV1_0_11)['credentials'] &&
|
|
588
|
+
(this.credentialOffer?.credential_offer as CredentialOfferPayloadV1_0_11).credentials.find(
|
|
589
|
+
(cred) =>
|
|
590
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
591
|
+
// @ts-ignore
|
|
592
|
+
typeof cred !== 'string' && 'trust_framework' in cred && 'name' in cred.trust_framework && cred.trust_framework.name.includes('ebsi'),
|
|
593
|
+
)
|
|
594
|
+
) {
|
|
595
|
+
return true;
|
|
596
|
+
}
|
|
597
|
+
this.assertIssuerData();
|
|
598
|
+
return this.endpointMetadata.credentialIssuerMetadata?.authorization_endpoint?.includes('ebsi.eu') === true;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
private assertIssuerData(): void {
|
|
602
|
+
if (!this._state.credentialIssuer) {
|
|
603
|
+
throw Error(`No credential issuer value present`);
|
|
604
|
+
} else if (!this._state.credentialOffer && this._state.endpointMetadata && this.issuerSupportedFlowTypes().length === 0) {
|
|
605
|
+
throw Error(`No issuance initiation or credential offer present`);
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
private assertServerMetadata(): void {
|
|
610
|
+
if (!this._state.endpointMetadata) {
|
|
611
|
+
throw Error('No server metadata');
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
private assertAccessToken(): void {
|
|
616
|
+
if (!this._state.accessTokenResponse) {
|
|
617
|
+
throw Error(`No access token present`);
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
private syncAuthorizationRequestOpts(opts?: AuthorizationRequestOpts): AuthorizationRequestOpts {
|
|
622
|
+
let authorizationRequestOpts = { ...this._state?.authorizationRequestOpts, ...opts } as AuthorizationRequestOpts;
|
|
623
|
+
if (!authorizationRequestOpts) {
|
|
624
|
+
// We only set a redirectUri if no options are provided.
|
|
625
|
+
// Note that this only works for mobile apps, that can handle a code query param on the default openid-credential-offer deeplink.
|
|
626
|
+
// Provide your own options if that is not desired!
|
|
627
|
+
authorizationRequestOpts = { redirectUri: `${DefaultURISchemes.CREDENTIAL_OFFER}://` };
|
|
628
|
+
}
|
|
629
|
+
const clientId = authorizationRequestOpts.clientId ?? this._state.clientId;
|
|
630
|
+
// sync clientId
|
|
631
|
+
this._state.clientId = clientId;
|
|
632
|
+
authorizationRequestOpts.clientId = clientId;
|
|
633
|
+
return authorizationRequestOpts;
|
|
634
|
+
}
|
|
635
|
+
}
|