@sphereon/oid4vci-client 0.2.0 → 0.4.1-unstable.247
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/LICENSE +201 -201
- package/README.md +494 -371
- package/dist/AccessTokenClient.d.ts +30 -0
- package/dist/AccessTokenClient.d.ts.map +1 -0
- package/dist/AccessTokenClient.js +226 -0
- package/dist/AccessTokenClient.js.map +1 -0
- package/dist/AuthorizationDetailsBuilder.d.ts +11 -0
- package/dist/AuthorizationDetailsBuilder.d.ts.map +1 -0
- package/dist/AuthorizationDetailsBuilder.js +44 -0
- package/dist/AuthorizationDetailsBuilder.js.map +1 -0
- package/dist/CredentialOffer.d.ts +6 -0
- package/dist/CredentialOffer.d.ts.map +1 -0
- package/dist/CredentialOffer.js +49 -0
- package/dist/CredentialOffer.js.map +1 -0
- package/dist/CredentialRequestClient.d.ts +29 -0
- package/dist/CredentialRequestClient.d.ts.map +1 -0
- package/dist/CredentialRequestClient.js +63 -0
- package/dist/CredentialRequestClient.js.map +1 -0
- package/dist/CredentialRequestClientBuilderV1_0_09.d.ts +29 -0
- package/dist/CredentialRequestClientBuilderV1_0_09.d.ts.map +1 -0
- package/dist/CredentialRequestClientBuilderV1_0_09.js +63 -0
- package/dist/CredentialRequestClientBuilderV1_0_09.js.map +1 -0
- package/dist/{main/lib/MetadataClient.d.ts → MetadataClient.d.ts} +39 -38
- package/dist/MetadataClient.d.ts.map +1 -0
- package/dist/MetadataClient.js +148 -0
- package/dist/MetadataClient.js.map +1 -0
- package/dist/OpenID4VCIClient.d.ts +72 -0
- package/dist/OpenID4VCIClient.d.ts.map +1 -0
- package/dist/OpenID4VCIClient.js +361 -0
- package/dist/OpenID4VCIClient.js.map +1 -0
- package/dist/ProofOfPossessionBuilder.d.ts +35 -0
- package/dist/ProofOfPossessionBuilder.d.ts.map +1 -0
- package/dist/ProofOfPossessionBuilder.js +120 -0
- package/dist/ProofOfPossessionBuilder.js.map +1 -0
- package/dist/{main/lib/functions → functions}/Encoding.d.ts +20 -17
- package/dist/functions/Encoding.d.ts.map +1 -0
- package/dist/functions/Encoding.js +144 -0
- package/dist/functions/Encoding.js.map +1 -0
- package/dist/functions/HttpUtils.d.ts +24 -0
- package/dist/functions/HttpUtils.d.ts.map +1 -0
- package/dist/functions/HttpUtils.js +93 -0
- package/dist/functions/HttpUtils.js.map +1 -0
- package/dist/functions/ProofUtil.d.ts +29 -0
- package/dist/functions/ProofUtil.d.ts.map +1 -0
- package/dist/functions/ProofUtil.js +103 -0
- package/dist/functions/ProofUtil.js.map +1 -0
- package/dist/functions/index.d.ts +4 -0
- package/dist/functions/index.d.ts.map +1 -0
- package/dist/{main/lib/functions → functions}/index.js +20 -20
- package/dist/functions/index.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/{main/lib/index.js → index.js} +25 -24
- package/dist/index.js.map +1 -0
- package/lib/AccessTokenClient.ts +270 -0
- package/lib/AuthorizationDetailsBuilder.ts +46 -0
- package/lib/CredentialOffer.ts +55 -0
- package/lib/CredentialRequestClient.ts +77 -0
- package/lib/CredentialRequestClientBuilderV1_0_09.ts +99 -0
- package/lib/MetadataClient.ts +147 -0
- package/lib/OpenID4VCIClient.ts +477 -0
- package/lib/ProofOfPossessionBuilder.ts +156 -0
- package/lib/__tests__/AccessTokenClient.spec.ts +221 -0
- package/lib/__tests__/AuthorizationDetailsBuilder.spec.ts +65 -0
- package/lib/__tests__/AuthzFlowType.spec.ts +39 -0
- package/lib/__tests__/CredentialRequestClient.spec.ts +261 -0
- package/lib/__tests__/CredentialRequestClientBuilder.spec.ts +103 -0
- package/lib/__tests__/HttpUtils.spec.ts +37 -0
- package/lib/__tests__/IT.spec.ts +155 -0
- package/lib/__tests__/IssuanceInitiation.spec.ts +37 -0
- package/lib/__tests__/JsonURIConversions.spec.ts +86 -0
- package/lib/__tests__/MetadataClient.spec.ts +198 -0
- package/lib/__tests__/MetadataMocks.ts +428 -0
- package/lib/__tests__/OpenID4VCIClient.spec.ts +166 -0
- package/lib/__tests__/OpenID4VCIClientPAR.spec.ts +112 -0
- package/lib/__tests__/ProofOfPossessionBuilder.spec.ts +109 -0
- package/lib/__tests__/data/VciDataFixtures.ts +744 -0
- package/lib/functions/Encoding.ts +138 -0
- package/lib/functions/HttpUtils.ts +106 -0
- package/lib/functions/ProofUtil.ts +128 -0
- package/{dist/main/lib/functions/index.d.ts → lib/functions/index.ts} +3 -3
- package/lib/index.ts +8 -0
- package/package.json +68 -71
- package/CHANGELOG.md +0 -21
- package/dist/main/index.d.ts +0 -1
- package/dist/main/index.js +0 -18
- package/dist/main/lib/AccessTokenClient.d.ts +0 -20
- package/dist/main/lib/AccessTokenClient.js +0 -141
- package/dist/main/lib/CredentialRequestClient.d.ts +0 -31
- package/dist/main/lib/CredentialRequestClient.js +0 -66
- package/dist/main/lib/CredentialRequestClientBuilder.d.ts +0 -21
- package/dist/main/lib/CredentialRequestClientBuilder.js +0 -56
- package/dist/main/lib/IssuanceInitiation.d.ts +0 -5
- package/dist/main/lib/IssuanceInitiation.js +0 -29
- package/dist/main/lib/MetadataClient.js +0 -127
- package/dist/main/lib/functions/Encoding.js +0 -138
- package/dist/main/lib/functions/HttpUtils.d.ts +0 -17
- package/dist/main/lib/functions/HttpUtils.js +0 -133
- package/dist/main/lib/functions/ProofUtil.d.ts +0 -9
- package/dist/main/lib/functions/ProofUtil.js +0 -76
- package/dist/main/lib/index.d.ts +0 -7
- package/dist/main/lib/types/Authorization.types.d.ts +0 -66
- package/dist/main/lib/types/Authorization.types.js +0 -35
- package/dist/main/lib/types/CredentialIssuance.types.d.ts +0 -88
- package/dist/main/lib/types/CredentialIssuance.types.js +0 -8
- package/dist/main/lib/types/Generic.types.d.ts +0 -19
- package/dist/main/lib/types/Generic.types.js +0 -11
- package/dist/main/lib/types/OAuth2ASMetadata.d.ts +0 -37
- package/dist/main/lib/types/OAuth2ASMetadata.js +0 -3
- package/dist/main/lib/types/OID4VCIServerMetadata.d.ts +0 -65
- package/dist/main/lib/types/OID4VCIServerMetadata.js +0 -3
- package/dist/main/lib/types/Oidc4vciErrors.d.ts +0 -3
- package/dist/main/lib/types/Oidc4vciErrors.js +0 -7
- package/dist/main/lib/types/index.d.ts +0 -6
- package/dist/main/lib/types/index.js +0 -23
- package/dist/main/tsconfig.build.tsbuildinfo +0 -1
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AccessTokenRequest,
|
|
3
|
+
AccessTokenRequestOpts,
|
|
4
|
+
AccessTokenResponse,
|
|
5
|
+
AuthorizationServerOpts,
|
|
6
|
+
CredentialOfferPayload,
|
|
7
|
+
CredentialOfferPayloadV1_0_09,
|
|
8
|
+
CredentialOfferPayloadV1_0_11,
|
|
9
|
+
EndpointMetadata,
|
|
10
|
+
getIssuerFromCredentialOfferPayload,
|
|
11
|
+
GrantTypes,
|
|
12
|
+
isCredentialOfferV1_0_09,
|
|
13
|
+
isCredentialOfferV1_0_11,
|
|
14
|
+
IssuerOpts,
|
|
15
|
+
OpenIDResponse,
|
|
16
|
+
PRE_AUTH_CODE_LITERAL,
|
|
17
|
+
TokenErrorResponse,
|
|
18
|
+
} from '@sphereon/oid4vci-common';
|
|
19
|
+
import { ObjectUtils } from '@sphereon/ssi-types';
|
|
20
|
+
import Debug from 'debug';
|
|
21
|
+
|
|
22
|
+
import { MetadataClient } from './MetadataClient';
|
|
23
|
+
import { convertJsonToURI, formPost } from './functions';
|
|
24
|
+
|
|
25
|
+
const debug = Debug('sphereon:openid4vci:token');
|
|
26
|
+
|
|
27
|
+
export class AccessTokenClient {
|
|
28
|
+
public async acquireAccessToken({
|
|
29
|
+
credentialOffer,
|
|
30
|
+
asOpts,
|
|
31
|
+
pin,
|
|
32
|
+
codeVerifier,
|
|
33
|
+
code,
|
|
34
|
+
redirectUri,
|
|
35
|
+
metadata,
|
|
36
|
+
}: AccessTokenRequestOpts): Promise<OpenIDResponse<AccessTokenResponse>> {
|
|
37
|
+
const { request } = credentialOffer;
|
|
38
|
+
|
|
39
|
+
const isPinRequired = this.isPinRequiredValue(request);
|
|
40
|
+
const issuerOpts = {
|
|
41
|
+
issuer: getIssuerFromCredentialOfferPayload(request) ? (getIssuerFromCredentialOfferPayload(request) as string) : (metadata?.issuer as string),
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
return await this.acquireAccessTokenUsingRequest({
|
|
45
|
+
accessTokenRequest: await this.createAccessTokenRequest({
|
|
46
|
+
credentialOffer,
|
|
47
|
+
asOpts,
|
|
48
|
+
codeVerifier,
|
|
49
|
+
code,
|
|
50
|
+
redirectUri,
|
|
51
|
+
pin,
|
|
52
|
+
}),
|
|
53
|
+
isPinRequired,
|
|
54
|
+
metadata,
|
|
55
|
+
asOpts,
|
|
56
|
+
issuerOpts,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
public async acquireAccessTokenUsingRequest({
|
|
61
|
+
accessTokenRequest,
|
|
62
|
+
isPinRequired,
|
|
63
|
+
metadata,
|
|
64
|
+
asOpts,
|
|
65
|
+
issuerOpts,
|
|
66
|
+
}: {
|
|
67
|
+
accessTokenRequest: AccessTokenRequest;
|
|
68
|
+
isPinRequired?: boolean;
|
|
69
|
+
metadata?: EndpointMetadata;
|
|
70
|
+
asOpts?: AuthorizationServerOpts;
|
|
71
|
+
issuerOpts?: IssuerOpts;
|
|
72
|
+
}): Promise<OpenIDResponse<AccessTokenResponse>> {
|
|
73
|
+
this.validate(accessTokenRequest, isPinRequired);
|
|
74
|
+
const requestTokenURL = AccessTokenClient.determineTokenURL({
|
|
75
|
+
asOpts,
|
|
76
|
+
issuerOpts,
|
|
77
|
+
metadata: metadata
|
|
78
|
+
? metadata
|
|
79
|
+
: issuerOpts?.fetchMetadata
|
|
80
|
+
? await MetadataClient.retrieveAllMetadata(issuerOpts.issuer, { errorOnNotFound: false })
|
|
81
|
+
: undefined,
|
|
82
|
+
});
|
|
83
|
+
return this.sendAuthCode(requestTokenURL, accessTokenRequest);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
public async createAccessTokenRequest({
|
|
87
|
+
credentialOffer,
|
|
88
|
+
asOpts,
|
|
89
|
+
pin,
|
|
90
|
+
codeVerifier,
|
|
91
|
+
code,
|
|
92
|
+
redirectUri,
|
|
93
|
+
}: AccessTokenRequestOpts): Promise<AccessTokenRequest> {
|
|
94
|
+
const credentialOfferRequest = credentialOffer.request;
|
|
95
|
+
const request: Partial<AccessTokenRequest> = {};
|
|
96
|
+
if (asOpts?.clientId) {
|
|
97
|
+
request.client_id = asOpts.clientId;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
this.assertNumericPin(this.isPinRequiredValue(credentialOfferRequest), pin);
|
|
101
|
+
request.user_pin = pin;
|
|
102
|
+
|
|
103
|
+
if (credentialOfferRequest[PRE_AUTH_CODE_LITERAL as keyof CredentialOfferPayload]) {
|
|
104
|
+
if (codeVerifier) {
|
|
105
|
+
throw new Error('Cannot pass a code_verifier when flow type is pre-authorized');
|
|
106
|
+
}
|
|
107
|
+
request.grant_type = GrantTypes.PRE_AUTHORIZED_CODE;
|
|
108
|
+
//todo: handle this for v11
|
|
109
|
+
request[PRE_AUTH_CODE_LITERAL] = (credentialOfferRequest as CredentialOfferPayloadV1_0_09)[PRE_AUTH_CODE_LITERAL];
|
|
110
|
+
}
|
|
111
|
+
if ('op_state' in credentialOfferRequest || 'issuer_state' in credentialOfferRequest) {
|
|
112
|
+
this.throwNotSupportedFlow();
|
|
113
|
+
request.grant_type = GrantTypes.AUTHORIZATION_CODE;
|
|
114
|
+
}
|
|
115
|
+
if (codeVerifier) {
|
|
116
|
+
request.code_verifier = codeVerifier;
|
|
117
|
+
request.code = code;
|
|
118
|
+
request.redirect_uri = redirectUri;
|
|
119
|
+
request.grant_type = GrantTypes.AUTHORIZATION_CODE;
|
|
120
|
+
}
|
|
121
|
+
//todo: handle this for v11
|
|
122
|
+
if (request.grant_type === GrantTypes.AUTHORIZATION_CODE && (credentialOfferRequest as CredentialOfferPayloadV1_0_09)[PRE_AUTH_CODE_LITERAL]) {
|
|
123
|
+
throw Error('A pre_authorized_code flow cannot have an op_state in the initiation request');
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return request as AccessTokenRequest;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
private assertPreAuthorizedGrantType(grantType: GrantTypes): void {
|
|
130
|
+
if (GrantTypes.PRE_AUTHORIZED_CODE !== grantType) {
|
|
131
|
+
throw new Error("grant type must be 'urn:ietf:params:oauth:grant-type:pre-authorized_code'");
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
private assertAuthorizationGrantType(grantType: GrantTypes): void {
|
|
136
|
+
if (GrantTypes.AUTHORIZATION_CODE !== grantType) {
|
|
137
|
+
throw new Error("grant type must be 'authorization_code'");
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
private isPinRequiredValue(requestPayload: CredentialOfferPayload): boolean {
|
|
142
|
+
let isPinRequired = false;
|
|
143
|
+
if (!requestPayload) {
|
|
144
|
+
throw new Error(TokenErrorResponse.invalid_request);
|
|
145
|
+
}
|
|
146
|
+
const issuer = getIssuerFromCredentialOfferPayload(requestPayload);
|
|
147
|
+
if (isCredentialOfferV1_0_09(requestPayload)) {
|
|
148
|
+
requestPayload = requestPayload as CredentialOfferPayloadV1_0_09;
|
|
149
|
+
if (typeof requestPayload.user_pin_required === 'string') {
|
|
150
|
+
isPinRequired = requestPayload.user_pin_required.toLowerCase() === 'true';
|
|
151
|
+
} else if (typeof requestPayload.user_pin_required === 'boolean') {
|
|
152
|
+
isPinRequired = requestPayload.user_pin_required;
|
|
153
|
+
}
|
|
154
|
+
} else if (isCredentialOfferV1_0_11(requestPayload)) {
|
|
155
|
+
requestPayload = requestPayload as CredentialOfferPayloadV1_0_11;
|
|
156
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
157
|
+
if ('grants' in requestPayload && 'urn:ietf:params:oauth:grant-type:pre-authorized_code' in requestPayload.grants!) {
|
|
158
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
159
|
+
isPinRequired = requestPayload!.grants!['urn:ietf:params:oauth:grant-type:pre-authorized_code']!.user_pin_required;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
debug(`Pin required for issuer ${issuer}: ${isPinRequired}`);
|
|
163
|
+
return isPinRequired;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
private assertNumericPin(isPinRequired?: boolean, pin?: string): void {
|
|
167
|
+
if (isPinRequired) {
|
|
168
|
+
if (!pin || !/^\d{1,8}$/.test(pin)) {
|
|
169
|
+
debug(`Pin is not 1 to 8 digits long`);
|
|
170
|
+
throw new Error('A valid pin consisting of maximal 8 numeric characters must be present.');
|
|
171
|
+
}
|
|
172
|
+
} else if (pin) {
|
|
173
|
+
debug(`Pin set, whilst not required`);
|
|
174
|
+
throw new Error('Cannot set a pin, when the pin is not required.');
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
private assertNonEmptyPreAuthorizedCode(accessTokenRequest: AccessTokenRequest): void {
|
|
179
|
+
if (!accessTokenRequest[PRE_AUTH_CODE_LITERAL]) {
|
|
180
|
+
debug(`No pre-authorized code present, whilst it is required`);
|
|
181
|
+
throw new Error('Pre-authorization must be proven by presenting the pre-authorized code. Code must be present.');
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
private assertNonEmptyCodeVerifier(accessTokenRequest: AccessTokenRequest): void {
|
|
186
|
+
if (!accessTokenRequest.code_verifier) {
|
|
187
|
+
debug('No code_verifier present, whilst it is required');
|
|
188
|
+
throw new Error('Authorization flow requires the code_verifier to be present');
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
private assertNonEmptyCode(accessTokenRequest: AccessTokenRequest): void {
|
|
193
|
+
if (!accessTokenRequest.code) {
|
|
194
|
+
debug('No code present, whilst it is required');
|
|
195
|
+
throw new Error('Authorization flow requires the code to be present');
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
private assertNonEmptyRedirectUri(accessTokenRequest: AccessTokenRequest): void {
|
|
200
|
+
if (!accessTokenRequest.redirect_uri) {
|
|
201
|
+
debug('No redirect_uri present, whilst it is required');
|
|
202
|
+
throw new Error('Authorization flow requires the redirect_uri to be present');
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
private validate(accessTokenRequest: AccessTokenRequest, isPinRequired?: boolean): void {
|
|
207
|
+
if (accessTokenRequest.grant_type === GrantTypes.PRE_AUTHORIZED_CODE) {
|
|
208
|
+
this.assertPreAuthorizedGrantType(accessTokenRequest.grant_type);
|
|
209
|
+
this.assertNonEmptyPreAuthorizedCode(accessTokenRequest);
|
|
210
|
+
this.assertNumericPin(isPinRequired, accessTokenRequest.user_pin);
|
|
211
|
+
} else if (accessTokenRequest.grant_type === GrantTypes.AUTHORIZATION_CODE) {
|
|
212
|
+
this.assertAuthorizationGrantType(accessTokenRequest.grant_type);
|
|
213
|
+
this.assertNonEmptyCodeVerifier(accessTokenRequest);
|
|
214
|
+
this.assertNonEmptyCode(accessTokenRequest);
|
|
215
|
+
this.assertNonEmptyRedirectUri(accessTokenRequest);
|
|
216
|
+
} else {
|
|
217
|
+
this.throwNotSupportedFlow;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
private async sendAuthCode(requestTokenURL: string, accessTokenRequest: AccessTokenRequest): Promise<OpenIDResponse<AccessTokenResponse>> {
|
|
222
|
+
return await formPost(requestTokenURL, convertJsonToURI(accessTokenRequest));
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
public static determineTokenURL({
|
|
226
|
+
asOpts,
|
|
227
|
+
issuerOpts,
|
|
228
|
+
metadata,
|
|
229
|
+
}: {
|
|
230
|
+
asOpts?: AuthorizationServerOpts;
|
|
231
|
+
issuerOpts?: IssuerOpts;
|
|
232
|
+
metadata?: EndpointMetadata;
|
|
233
|
+
}): string {
|
|
234
|
+
if (!asOpts && !metadata?.token_endpoint && !issuerOpts) {
|
|
235
|
+
throw new Error('Cannot determine token URL if no issuer, metadata and no Authorization Server values are present');
|
|
236
|
+
}
|
|
237
|
+
let url;
|
|
238
|
+
if (asOpts && asOpts.as) {
|
|
239
|
+
url = this.creatTokenURLFromURL(asOpts.as, asOpts?.allowInsecureEndpoints, asOpts.tokenEndpoint);
|
|
240
|
+
} else if (metadata?.token_endpoint) {
|
|
241
|
+
url = metadata.token_endpoint;
|
|
242
|
+
} else {
|
|
243
|
+
if (!issuerOpts) {
|
|
244
|
+
throw Error('Either authorization server options, a token endpoint or issuer options are required at this point');
|
|
245
|
+
}
|
|
246
|
+
url = this.creatTokenURLFromURL(issuerOpts.issuer, asOpts?.allowInsecureEndpoints, issuerOpts.tokenEndpoint);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (!url || !ObjectUtils.isString(url)) {
|
|
250
|
+
throw new Error('No authorization server token URL present. Cannot acquire access token');
|
|
251
|
+
}
|
|
252
|
+
debug(`Token endpoint determined to be ${url}`);
|
|
253
|
+
return url;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
private static creatTokenURLFromURL(url: string, allowInsecureEndpoints?: boolean, tokenEndpoint?: string): string {
|
|
257
|
+
if (allowInsecureEndpoints !== true && url.startsWith('http://')) {
|
|
258
|
+
throw Error(`Unprotected token endpoints are not allowed ${url}`);
|
|
259
|
+
}
|
|
260
|
+
const hostname = url.replace(/https?:\/\//, '').replace(/\/$/, '');
|
|
261
|
+
const endpoint = tokenEndpoint ? (tokenEndpoint.startsWith('/') ? tokenEndpoint : tokenEndpoint.substring(1)) : '/token';
|
|
262
|
+
// We always require https
|
|
263
|
+
return `https://${hostname}${endpoint}`;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
private throwNotSupportedFlow(): void {
|
|
267
|
+
debug(`Only pre-authorized flow supported.`);
|
|
268
|
+
throw new Error('Only pre-authorized-code flow is supported');
|
|
269
|
+
}
|
|
270
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { AuthorizationDetailsJwtVcJson, CredentialFormatEnum } from '@sphereon/oid4vci-common';
|
|
2
|
+
|
|
3
|
+
//todo: refactor this builder to be able to create ldp details as well
|
|
4
|
+
export class AuthorizationDetailsBuilder {
|
|
5
|
+
private readonly authorizationDetails: Partial<AuthorizationDetailsJwtVcJson>;
|
|
6
|
+
|
|
7
|
+
constructor() {
|
|
8
|
+
this.authorizationDetails = {};
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
withType(type: string): AuthorizationDetailsBuilder {
|
|
12
|
+
this.authorizationDetails.type = type;
|
|
13
|
+
return this;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
withFormats(format: CredentialFormatEnum): AuthorizationDetailsBuilder {
|
|
17
|
+
this.authorizationDetails.format = format;
|
|
18
|
+
return this;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
withLocations(locations: string[]): AuthorizationDetailsBuilder {
|
|
22
|
+
if (this.authorizationDetails.locations) {
|
|
23
|
+
this.authorizationDetails.locations.push(...locations);
|
|
24
|
+
} else {
|
|
25
|
+
this.authorizationDetails.locations = locations;
|
|
26
|
+
}
|
|
27
|
+
return this;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
addLocation(location: string): AuthorizationDetailsBuilder {
|
|
31
|
+
if (this.authorizationDetails.locations) {
|
|
32
|
+
this.authorizationDetails.locations.push(location);
|
|
33
|
+
} else {
|
|
34
|
+
this.authorizationDetails.locations = [location];
|
|
35
|
+
}
|
|
36
|
+
return this;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
//todo: we have to consider one thing, if this is a general purpose builder, we want to support ldp types here as well. and for that we need a few checks.
|
|
40
|
+
buildJwtVcJson(): AuthorizationDetailsJwtVcJson {
|
|
41
|
+
if (this.authorizationDetails.format && this.authorizationDetails.type) {
|
|
42
|
+
return this.authorizationDetails as AuthorizationDetailsJwtVcJson;
|
|
43
|
+
}
|
|
44
|
+
throw new Error('Type and format are required properties');
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CredentialOfferPayload,
|
|
3
|
+
CredentialOfferPayloadV1_0_09,
|
|
4
|
+
CredentialOfferPayloadV1_0_11,
|
|
5
|
+
CredentialOfferRequestWithBaseUrl,
|
|
6
|
+
OpenId4VCIVersion,
|
|
7
|
+
} from '@sphereon/oid4vci-common';
|
|
8
|
+
import { determineSpecVersionFromURI } from '@sphereon/oid4vci-common';
|
|
9
|
+
import Debug from 'debug';
|
|
10
|
+
|
|
11
|
+
import { convertJsonToURI, convertURIToJsonObject } from './functions';
|
|
12
|
+
|
|
13
|
+
const debug = Debug('sphereon:openid4vci:initiation');
|
|
14
|
+
|
|
15
|
+
export class CredentialOffer {
|
|
16
|
+
public static fromURI(uri: string): CredentialOfferRequestWithBaseUrl {
|
|
17
|
+
debug(`issuance initiation URI: ${uri}`);
|
|
18
|
+
if (!uri.includes('?')) {
|
|
19
|
+
debug(`Invalid issuance initiation URI: ${uri}`);
|
|
20
|
+
throw new Error('Invalid Issuance Initiation Request Payload');
|
|
21
|
+
}
|
|
22
|
+
const baseUrl = uri.split('?')[0];
|
|
23
|
+
const version = determineSpecVersionFromURI(uri);
|
|
24
|
+
const issuanceInitiationRequest: CredentialOfferPayload =
|
|
25
|
+
version < OpenId4VCIVersion.VER_1_0_11
|
|
26
|
+
? (convertURIToJsonObject(uri, {
|
|
27
|
+
arrayTypeProperties: ['credential_type'],
|
|
28
|
+
requiredProperties: ['issuer', 'credential_type'],
|
|
29
|
+
}) as CredentialOfferPayloadV1_0_09)
|
|
30
|
+
: (convertURIToJsonObject(uri, {
|
|
31
|
+
arrayTypeProperties: ['credentials'],
|
|
32
|
+
requiredProperties: ['credentials', 'credential_issuer'],
|
|
33
|
+
}) as CredentialOfferPayloadV1_0_11);
|
|
34
|
+
|
|
35
|
+
const request =
|
|
36
|
+
version < OpenId4VCIVersion.VER_1_0_11.valueOf()
|
|
37
|
+
? (issuanceInitiationRequest as CredentialOfferPayloadV1_0_09)
|
|
38
|
+
: (issuanceInitiationRequest as CredentialOfferPayloadV1_0_11);
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
baseUrl,
|
|
42
|
+
request,
|
|
43
|
+
version,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
public static toURI(uri: CredentialOfferRequestWithBaseUrl): string {
|
|
48
|
+
const request = uri.request;
|
|
49
|
+
return convertJsonToURI(request, {
|
|
50
|
+
baseUrl: uri.baseUrl,
|
|
51
|
+
arrayTypeProperties: ['credential_type'],
|
|
52
|
+
uriTypeProperties: ['issuer', 'credential_type'],
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { CredentialRequest, CredentialResponse, OpenIDResponse, ProofOfPossession, URL_NOT_VALID } from '@sphereon/oid4vci-common';
|
|
2
|
+
import { CredentialFormat } from '@sphereon/ssi-types';
|
|
3
|
+
import Debug from 'debug';
|
|
4
|
+
|
|
5
|
+
import { CredentialRequestClientBuilderV1_0_09 } from './CredentialRequestClientBuilderV1_0_09';
|
|
6
|
+
import { ProofOfPossessionBuilder } from './ProofOfPossessionBuilder';
|
|
7
|
+
import { isValidURL, post } from './functions';
|
|
8
|
+
|
|
9
|
+
const debug = Debug('sphereon:openid4vci:credential');
|
|
10
|
+
|
|
11
|
+
export interface CredentialRequestOpts {
|
|
12
|
+
credentialEndpoint: string;
|
|
13
|
+
credentialType: string | string[];
|
|
14
|
+
format: CredentialFormat | CredentialFormat[];
|
|
15
|
+
proof: ProofOfPossession;
|
|
16
|
+
token: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export class CredentialRequestClient {
|
|
20
|
+
private readonly _credentialRequestOpts: Partial<CredentialRequestOpts>;
|
|
21
|
+
|
|
22
|
+
get credentialRequestOpts(): CredentialRequestOpts {
|
|
23
|
+
return this._credentialRequestOpts as CredentialRequestOpts;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
public getCredentialEndpoint(): string {
|
|
27
|
+
return this.credentialRequestOpts.credentialEndpoint;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
public constructor(builder: CredentialRequestClientBuilderV1_0_09) {
|
|
31
|
+
this._credentialRequestOpts = { ...builder };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
public async acquireCredentialsUsingProof({
|
|
35
|
+
proofInput,
|
|
36
|
+
credentialType,
|
|
37
|
+
format,
|
|
38
|
+
}: {
|
|
39
|
+
proofInput: ProofOfPossessionBuilder | ProofOfPossession;
|
|
40
|
+
credentialType?: string | string[];
|
|
41
|
+
format?: CredentialFormat | CredentialFormat[];
|
|
42
|
+
}): Promise<OpenIDResponse<CredentialResponse>> {
|
|
43
|
+
const request = await this.createCredentialRequest({ proofInput, credentialType, format });
|
|
44
|
+
return await this.acquireCredentialsUsingRequest(request);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
public async acquireCredentialsUsingRequest(request: CredentialRequest): Promise<OpenIDResponse<CredentialResponse>> {
|
|
48
|
+
const credentialEndpoint: string = this.credentialRequestOpts.credentialEndpoint;
|
|
49
|
+
if (!isValidURL(credentialEndpoint)) {
|
|
50
|
+
debug(`Invalid credential endpoint: ${credentialEndpoint}`);
|
|
51
|
+
throw new Error(URL_NOT_VALID);
|
|
52
|
+
}
|
|
53
|
+
debug(`Acquiring credential(s) from: ${credentialEndpoint}`);
|
|
54
|
+
const requestToken: string = this.credentialRequestOpts.token;
|
|
55
|
+
const response: OpenIDResponse<CredentialResponse> = await post(credentialEndpoint, JSON.stringify(request), { bearerToken: requestToken });
|
|
56
|
+
debug(`Credential endpoint ${credentialEndpoint} response:\r\n${response}`);
|
|
57
|
+
return response;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
public async createCredentialRequest({
|
|
61
|
+
proofInput,
|
|
62
|
+
credentialType,
|
|
63
|
+
format,
|
|
64
|
+
}: {
|
|
65
|
+
proofInput: ProofOfPossessionBuilder | ProofOfPossession;
|
|
66
|
+
credentialType?: string | string[];
|
|
67
|
+
format?: CredentialFormat | CredentialFormat[];
|
|
68
|
+
}): Promise<CredentialRequest> {
|
|
69
|
+
const proof =
|
|
70
|
+
'proof_type' in proofInput ? await ProofOfPossessionBuilder.fromProof(proofInput as ProofOfPossession).build() : await proofInput.build();
|
|
71
|
+
return {
|
|
72
|
+
type: credentialType ? credentialType : this.credentialRequestOpts.credentialType,
|
|
73
|
+
format: format ? (format as string) : (this.credentialRequestOpts.format as string),
|
|
74
|
+
proof,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AccessTokenResponse,
|
|
3
|
+
CredentialOfferPayload,
|
|
4
|
+
CredentialOfferPayloadV1_0_09,
|
|
5
|
+
CredentialOfferRequestWithBaseUrl,
|
|
6
|
+
EndpointMetadata,
|
|
7
|
+
getIssuerFromCredentialOfferPayload,
|
|
8
|
+
IssuerMetadata,
|
|
9
|
+
} from '@sphereon/oid4vci-common';
|
|
10
|
+
import { CredentialFormat } from '@sphereon/ssi-types';
|
|
11
|
+
|
|
12
|
+
import { CredentialRequestClient } from './CredentialRequestClient';
|
|
13
|
+
import { convertURIToJsonObject } from './functions';
|
|
14
|
+
|
|
15
|
+
export class CredentialRequestClientBuilderV1_0_09 {
|
|
16
|
+
credentialEndpoint?: string;
|
|
17
|
+
credentialType?: string | string[];
|
|
18
|
+
format?: CredentialFormat | CredentialFormat[];
|
|
19
|
+
token?: string;
|
|
20
|
+
|
|
21
|
+
public static fromURI({ uri, metadata }: { uri: string; metadata?: EndpointMetadata }): CredentialRequestClientBuilderV1_0_09 {
|
|
22
|
+
return CredentialRequestClientBuilderV1_0_09.fromCredentialOfferRequest({
|
|
23
|
+
request: convertURIToJsonObject(uri, {
|
|
24
|
+
arrayTypeProperties: ['credential_type'],
|
|
25
|
+
requiredProperties: ['issuer', 'credential_type'],
|
|
26
|
+
}) as CredentialOfferPayload,
|
|
27
|
+
metadata,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
public static fromCredentialOfferRequest({
|
|
32
|
+
request,
|
|
33
|
+
metadata,
|
|
34
|
+
}: {
|
|
35
|
+
request: CredentialOfferPayload;
|
|
36
|
+
metadata?: EndpointMetadata;
|
|
37
|
+
}): CredentialRequestClientBuilderV1_0_09 {
|
|
38
|
+
const builder = new CredentialRequestClientBuilderV1_0_09();
|
|
39
|
+
const issuer = getIssuerFromCredentialOfferPayload(request)
|
|
40
|
+
? (getIssuerFromCredentialOfferPayload(request) as string)
|
|
41
|
+
: (metadata?.issuer as string);
|
|
42
|
+
builder.withCredentialEndpoint(
|
|
43
|
+
metadata?.credential_endpoint ? metadata.credential_endpoint : issuer.endsWith('/') ? `${issuer}credential` : `${issuer}/credential`
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
//todo: This basically sets all types available during initiation. Probably the user only wants a subset. So do we want to do this?
|
|
47
|
+
//todo: handle this for v11
|
|
48
|
+
builder.withCredentialType((request as CredentialOfferPayloadV1_0_09).credential_type);
|
|
49
|
+
|
|
50
|
+
return builder;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
public static fromCredentialOffer({
|
|
54
|
+
credentialOffer,
|
|
55
|
+
metadata,
|
|
56
|
+
}: {
|
|
57
|
+
credentialOffer: CredentialOfferRequestWithBaseUrl;
|
|
58
|
+
metadata?: EndpointMetadata;
|
|
59
|
+
}): CredentialRequestClientBuilderV1_0_09 {
|
|
60
|
+
return CredentialRequestClientBuilderV1_0_09.fromCredentialOfferRequest({
|
|
61
|
+
request: credentialOffer.request,
|
|
62
|
+
metadata,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
public withCredentialEndpointFromMetadata(metadata: IssuerMetadata): CredentialRequestClientBuilderV1_0_09 {
|
|
67
|
+
this.credentialEndpoint = metadata.credential_endpoint;
|
|
68
|
+
return this;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
public withCredentialEndpoint(credentialEndpoint: string): CredentialRequestClientBuilderV1_0_09 {
|
|
72
|
+
this.credentialEndpoint = credentialEndpoint;
|
|
73
|
+
return this;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
public withCredentialType(credentialType: string | string[]): CredentialRequestClientBuilderV1_0_09 {
|
|
77
|
+
this.credentialType = credentialType;
|
|
78
|
+
return this;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
public withFormat(format: CredentialFormat | CredentialFormat[]): CredentialRequestClientBuilderV1_0_09 {
|
|
82
|
+
this.format = format;
|
|
83
|
+
return this;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
public withToken(accessToken: string): CredentialRequestClientBuilderV1_0_09 {
|
|
87
|
+
this.token = accessToken;
|
|
88
|
+
return this;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
public withTokenFromResponse(response: AccessTokenResponse): CredentialRequestClientBuilderV1_0_09 {
|
|
92
|
+
this.token = response.access_token;
|
|
93
|
+
return this;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
public build(): CredentialRequestClient {
|
|
97
|
+
return new CredentialRequestClient(this);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CredentialOfferPayload,
|
|
3
|
+
CredentialOfferRequestWithBaseUrl,
|
|
4
|
+
EndpointMetadata,
|
|
5
|
+
getIssuerFromCredentialOfferPayload,
|
|
6
|
+
IssuerMetadata,
|
|
7
|
+
OAuth2ASMetadata,
|
|
8
|
+
Oauth2ASWithOID4VCIMetadata,
|
|
9
|
+
OpenIDResponse,
|
|
10
|
+
WellKnownEndpoints,
|
|
11
|
+
} from '@sphereon/oid4vci-common';
|
|
12
|
+
import Debug from 'debug';
|
|
13
|
+
|
|
14
|
+
import { getJson } from './functions';
|
|
15
|
+
|
|
16
|
+
const debug = Debug('sphereon:openid4vci:metadata');
|
|
17
|
+
|
|
18
|
+
export class MetadataClient {
|
|
19
|
+
/**
|
|
20
|
+
* Retrieve metadata using the Initiation obtained from a previous step
|
|
21
|
+
*
|
|
22
|
+
* @param credentialOffer
|
|
23
|
+
*/
|
|
24
|
+
public static async retrieveAllMetadataFromCredentialOffer(credentialOffer: CredentialOfferRequestWithBaseUrl): Promise<EndpointMetadata> {
|
|
25
|
+
return MetadataClient.retrieveAllMetadataFromCredentialOfferRequest(credentialOffer.request);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Retrieve the metada using the initiation request obtained from a previous step
|
|
30
|
+
* @param request
|
|
31
|
+
*/
|
|
32
|
+
public static async retrieveAllMetadataFromCredentialOfferRequest(request: CredentialOfferPayload): Promise<EndpointMetadata> {
|
|
33
|
+
if (getIssuerFromCredentialOfferPayload(request)) {
|
|
34
|
+
return MetadataClient.retrieveAllMetadata(getIssuerFromCredentialOfferPayload(request) as string);
|
|
35
|
+
}
|
|
36
|
+
throw new Error("can't retrieve metadata from CredentialOfferRequest. No issuer field is present");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Retrieve all metadata from an issuer
|
|
41
|
+
* @param issuer The issuer URL
|
|
42
|
+
* @param opts
|
|
43
|
+
*/
|
|
44
|
+
public static async retrieveAllMetadata(issuer: string, opts?: { errorOnNotFound: boolean }): Promise<EndpointMetadata> {
|
|
45
|
+
let token_endpoint;
|
|
46
|
+
let credential_endpoint;
|
|
47
|
+
const response = await MetadataClient.retrieveOpenID4VCIServerMetadata(issuer);
|
|
48
|
+
let issuerMetadata = response?.successBody;
|
|
49
|
+
if (issuerMetadata) {
|
|
50
|
+
debug(`Issuer ${issuer} OID4VCI well-known server metadata\r\n${issuerMetadata}`);
|
|
51
|
+
credential_endpoint = issuerMetadata.credential_endpoint;
|
|
52
|
+
token_endpoint = issuerMetadata.token_endpoint;
|
|
53
|
+
if (!token_endpoint && issuerMetadata.authorization_server) {
|
|
54
|
+
debug(
|
|
55
|
+
`Issuer ${issuer} OID4VCI metadata has separate authorization_server ${issuerMetadata.authorization_server} that contains the token endpoint`
|
|
56
|
+
);
|
|
57
|
+
// Crossword uses this to separate the AS metadata. We fail when not found, since we now have no way of getting the token endpoint
|
|
58
|
+
const response: OpenIDResponse<OAuth2ASMetadata> = await this.retrieveWellknown(
|
|
59
|
+
issuerMetadata.authorization_server,
|
|
60
|
+
WellKnownEndpoints.OAUTH_AS,
|
|
61
|
+
{
|
|
62
|
+
errorOnNotFound: true,
|
|
63
|
+
}
|
|
64
|
+
);
|
|
65
|
+
token_endpoint = response.successBody?.token_endpoint;
|
|
66
|
+
}
|
|
67
|
+
} else {
|
|
68
|
+
// No specific OID4VCI endpoint. Either can be an OAuth2 AS or an OpenID IDP. Let's start with OIDC first
|
|
69
|
+
let response: OpenIDResponse<Oauth2ASWithOID4VCIMetadata> = await MetadataClient.retrieveWellknown(
|
|
70
|
+
issuer,
|
|
71
|
+
WellKnownEndpoints.OPENID_CONFIGURATION,
|
|
72
|
+
{
|
|
73
|
+
errorOnNotFound: false,
|
|
74
|
+
}
|
|
75
|
+
);
|
|
76
|
+
let asConfig = response.successBody;
|
|
77
|
+
if (asConfig) {
|
|
78
|
+
debug(`Issuer ${issuer} has OpenID Connect Server metadata in well-known location`);
|
|
79
|
+
} else {
|
|
80
|
+
// Now oAuth2
|
|
81
|
+
response = await MetadataClient.retrieveWellknown(issuer, WellKnownEndpoints.OAUTH_AS, { errorOnNotFound: false });
|
|
82
|
+
asConfig = response.successBody;
|
|
83
|
+
}
|
|
84
|
+
if (asConfig) {
|
|
85
|
+
debug(`Issuer ${issuer} has oAuth2 Server metadata in well-known location`);
|
|
86
|
+
issuerMetadata = asConfig;
|
|
87
|
+
credential_endpoint = issuerMetadata.credential_endpoint;
|
|
88
|
+
token_endpoint = issuerMetadata.token_endpoint;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
if (!token_endpoint) {
|
|
92
|
+
debug(`Issuer ${issuer} does not have a token_endpoint listed in well-known locations!`);
|
|
93
|
+
if (opts?.errorOnNotFound) {
|
|
94
|
+
throw new Error(`Could not deduce the token endpoint for ${issuer}`);
|
|
95
|
+
} else {
|
|
96
|
+
token_endpoint = `${issuer}${issuer.endsWith('/') ? '' : '/'}token`;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (!credential_endpoint) {
|
|
100
|
+
debug(`Issuer ${issuer} does not have a credential_endpoint listed in well-known locations!`);
|
|
101
|
+
if (opts?.errorOnNotFound) {
|
|
102
|
+
throw new Error(`Could not deduce the credential endpoint for ${issuer}`);
|
|
103
|
+
} else {
|
|
104
|
+
credential_endpoint = `${issuer}${issuer.endsWith('/') ? '' : '/'}credential`;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
debug(`Issuer ${issuer} token endpoint ${token_endpoint}, credential endpoint ${credential_endpoint}`);
|
|
108
|
+
return {
|
|
109
|
+
issuer,
|
|
110
|
+
token_endpoint,
|
|
111
|
+
credential_endpoint,
|
|
112
|
+
issuerMetadata,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Retrieve only the OID4VCI metadata for the issuer. So no OIDC/OAuth2 metadata
|
|
118
|
+
*
|
|
119
|
+
* @param issuerHost The issuer hostname
|
|
120
|
+
*/
|
|
121
|
+
public static async retrieveOpenID4VCIServerMetadata(issuerHost: string): Promise<OpenIDResponse<IssuerMetadata> | undefined> {
|
|
122
|
+
// Since the server metadata endpoint is optional we are not going to throw an error.
|
|
123
|
+
return MetadataClient.retrieveWellknown(issuerHost, WellKnownEndpoints.OPENID4VCI_ISSUER, { errorOnNotFound: false });
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Allows to retrieve information from a well-known location
|
|
128
|
+
*
|
|
129
|
+
* @param host The host
|
|
130
|
+
* @param endpointType The endpoint type, currently supports OID4VCI, OIDC and OAuth2 endpoint types
|
|
131
|
+
* @param opts Options, like for instance whether an error should be thrown in case the endpoint doesn't exist
|
|
132
|
+
*/
|
|
133
|
+
public static async retrieveWellknown<T>(
|
|
134
|
+
host: string,
|
|
135
|
+
endpointType: WellKnownEndpoints,
|
|
136
|
+
opts?: { errorOnNotFound?: boolean }
|
|
137
|
+
): Promise<OpenIDResponse<T>> {
|
|
138
|
+
const result: OpenIDResponse<T> = await getJson(`${host.endsWith('/') ? host.slice(0, -1) : host}${endpointType}`, {
|
|
139
|
+
exceptionOnHttpErrorStatus: opts?.errorOnNotFound,
|
|
140
|
+
});
|
|
141
|
+
if (result.origResponse.status === 404) {
|
|
142
|
+
// We only get here when error on not found is false
|
|
143
|
+
debug(`host ${host} with endpoint type ${endpointType} was not found (404)`);
|
|
144
|
+
}
|
|
145
|
+
return result;
|
|
146
|
+
}
|
|
147
|
+
}
|