@sphereon/oid4vci-client 0.10.4-unstable.98 → 0.12.0
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 +7 -7
- package/dist/AccessTokenClient.d.ts.map +1 -1
- package/dist/AccessTokenClient.js +5 -6
- package/dist/AccessTokenClient.js.map +1 -1
- package/dist/AccessTokenClientV1_0_11.d.ts.map +1 -1
- package/dist/AccessTokenClientV1_0_11.js +3 -5
- package/dist/AccessTokenClientV1_0_11.js.map +1 -1
- package/dist/AuthorizationCodeClient.d.ts +4 -1
- package/dist/AuthorizationCodeClient.d.ts.map +1 -1
- package/dist/AuthorizationCodeClient.js +47 -8
- package/dist/AuthorizationCodeClient.js.map +1 -1
- package/dist/AuthorizationCodeClientV1_0_11.d.ts.map +1 -1
- package/dist/AuthorizationCodeClientV1_0_11.js +5 -3
- package/dist/AuthorizationCodeClientV1_0_11.js.map +1 -1
- package/dist/CredentialOfferClient.d.ts.map +1 -1
- package/dist/CredentialOfferClient.js +26 -12
- package/dist/CredentialOfferClient.js.map +1 -1
- package/dist/CredentialOfferClientV1_0_11.js +6 -7
- package/dist/CredentialOfferClientV1_0_11.js.map +1 -1
- 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.js +1 -1
- package/dist/CredentialRequestClient.js.map +1 -1
- package/dist/CredentialRequestClientBuilderV1_0_11.d.ts +3 -1
- package/dist/CredentialRequestClientBuilderV1_0_11.d.ts.map +1 -1
- package/dist/CredentialRequestClientBuilderV1_0_11.js +4 -0
- package/dist/CredentialRequestClientBuilderV1_0_11.js.map +1 -1
- package/dist/CredentialRequestClientV1_0_11.d.ts +9 -3
- package/dist/CredentialRequestClientV1_0_11.d.ts.map +1 -1
- package/dist/CredentialRequestClientV1_0_11.js +2 -1
- package/dist/CredentialRequestClientV1_0_11.js.map +1 -1
- package/dist/MetadataClient.d.ts +5 -5
- package/dist/MetadataClient.d.ts.map +1 -1
- package/dist/MetadataClient.js +30 -13
- package/dist/MetadataClient.js.map +1 -1
- 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 +13 -24
- package/dist/OpenID4VCIClient.d.ts.map +1 -1
- package/dist/OpenID4VCIClient.js +126 -103
- package/dist/OpenID4VCIClient.js.map +1 -1
- package/dist/OpenID4VCIClientV1_0_11.d.ts +3 -2
- package/dist/OpenID4VCIClientV1_0_11.d.ts.map +1 -1
- package/dist/OpenID4VCIClientV1_0_11.js +5 -18
- package/dist/OpenID4VCIClientV1_0_11.js.map +1 -1
- 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 -20
- package/dist/ProofOfPossessionBuilder.js.map +1 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/lib/AccessTokenClient.ts +5 -11
- package/lib/AccessTokenClientV1_0_11.ts +3 -9
- package/lib/AuthorizationCodeClient.ts +47 -8
- package/lib/AuthorizationCodeClientV1_0_11.ts +8 -6
- package/lib/CredentialOfferClient.ts +31 -9
- package/lib/CredentialOfferClientV1_0_11.ts +6 -6
- package/lib/CredentialOfferClientV1_0_13.ts +103 -0
- package/lib/CredentialRequestClient.ts +1 -1
- package/lib/CredentialRequestClientBuilderV1_0_11.ts +7 -0
- package/lib/CredentialRequestClientV1_0_11.ts +9 -4
- package/lib/MetadataClient.ts +49 -14
- package/lib/MetadataClientV1_0_13.ts +188 -0
- package/lib/OpenID4VCIClient.ts +131 -115
- package/lib/OpenID4VCIClientV1_0_11.ts +9 -19
- package/lib/OpenID4VCIClientV1_0_13.ts +677 -0
- package/lib/ProofOfPossessionBuilder.ts +40 -9
- package/lib/__tests__/CredentialRequestClientV1_0_11.spec.ts +2 -2
- package/lib/__tests__/MetadataClient.spec.ts +3 -4
- package/lib/__tests__/MetadataMocks.ts +1 -0
- package/lib/__tests__/OpenID4VCIClient.spec.ts +42 -9
- package/lib/__tests__/OpenID4VCIClientV1_0_11.spec.ts +24 -0
- package/lib/__tests__/OpenID4VCIClientV1_0_13.spec.ts +204 -0
- package/lib/__tests__/SdJwt.spec.ts +2 -2
- package/lib/__tests__/SphereonE2E.spec.test.ts +4 -3
- package/lib/index.ts +8 -0
- package/package.json +4 -4
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import {
|
|
2
|
+
convertJsonToURI,
|
|
3
|
+
convertURIToJsonObject,
|
|
4
|
+
CredentialOfferRequestWithBaseUrl,
|
|
5
|
+
CredentialOfferV1_0_13,
|
|
6
|
+
determineSpecVersionFromURI,
|
|
7
|
+
getClientIdFromCredentialOfferPayload,
|
|
8
|
+
OpenId4VCIVersion,
|
|
9
|
+
toUniformCredentialOfferRequest,
|
|
10
|
+
} from '@sphereon/oid4vci-common';
|
|
11
|
+
import Debug from 'debug';
|
|
12
|
+
|
|
13
|
+
const debug = Debug('sphereon:oid4vci:offer');
|
|
14
|
+
|
|
15
|
+
export class CredentialOfferClientV1_0_13 {
|
|
16
|
+
public static async fromURI(uri: string, opts?: { resolve?: boolean }): Promise<CredentialOfferRequestWithBaseUrl> {
|
|
17
|
+
debug(`Credential Offer URI: ${uri}`);
|
|
18
|
+
if (!uri.includes('?') || !uri.includes('://')) {
|
|
19
|
+
debug(`Invalid Credential Offer URI: ${uri}`);
|
|
20
|
+
throw Error(`Invalid Credential Offer Request`);
|
|
21
|
+
}
|
|
22
|
+
const scheme = uri.split('://')[0];
|
|
23
|
+
const baseUrl = uri.split('?')[0];
|
|
24
|
+
const version = determineSpecVersionFromURI(uri);
|
|
25
|
+
const credentialOffer = convertURIToJsonObject(uri, {
|
|
26
|
+
// It must have the '=' sign after credential_offer otherwise the uri will get split at openid_credential_offer
|
|
27
|
+
arrayTypeProperties: uri.includes('credential_offer_uri=')
|
|
28
|
+
? ['credential_configuration_ids', 'credential_offer_uri=']
|
|
29
|
+
: ['credential_configuration_ids', 'credential_offer='],
|
|
30
|
+
requiredProperties: uri.includes('credential_offer_uri=') ? ['credential_offer_uri='] : ['credential_offer='],
|
|
31
|
+
}) as CredentialOfferV1_0_13;
|
|
32
|
+
if (credentialOffer?.credential_offer_uri === undefined && !credentialOffer?.credential_offer) {
|
|
33
|
+
throw Error('Either a credential_offer or credential_offer_uri should be present in ' + uri);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const request = await toUniformCredentialOfferRequest(credentialOffer, {
|
|
37
|
+
...opts,
|
|
38
|
+
version,
|
|
39
|
+
});
|
|
40
|
+
const clientId = getClientIdFromCredentialOfferPayload(request.credential_offer);
|
|
41
|
+
const grants = request.credential_offer?.grants;
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
scheme,
|
|
45
|
+
baseUrl,
|
|
46
|
+
...(clientId && { clientId }),
|
|
47
|
+
...request,
|
|
48
|
+
...(grants?.authorization_code?.issuer_state && { issuerState: grants.authorization_code.issuer_state }),
|
|
49
|
+
...(grants?.['urn:ietf:params:oauth:grant-type:pre-authorized_code']?.['pre-authorized_code'] && {
|
|
50
|
+
preAuthorizedCode: grants['urn:ietf:params:oauth:grant-type:pre-authorized_code']['pre-authorized_code'],
|
|
51
|
+
}),
|
|
52
|
+
userPinRequired: !!request.credential_offer?.grants?.['urn:ietf:params:oauth:grant-type:pre-authorized_code']?.tx_code ?? false,
|
|
53
|
+
...(request.credential_offer?.grants?.['urn:ietf:params:oauth:grant-type:pre-authorized_code']?.tx_code &&
|
|
54
|
+
{
|
|
55
|
+
// txCode: request.credential_offer?.grants?.['urn:ietf:params:oauth:grant-type:pre-authorized_code']?.tx_code,
|
|
56
|
+
}),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
public static toURI(
|
|
61
|
+
requestWithBaseUrl: CredentialOfferRequestWithBaseUrl,
|
|
62
|
+
opts?: {
|
|
63
|
+
version?: OpenId4VCIVersion;
|
|
64
|
+
},
|
|
65
|
+
): string {
|
|
66
|
+
debug(`Credential Offer Request with base URL: ${JSON.stringify(requestWithBaseUrl)}`);
|
|
67
|
+
const version = opts?.version ?? requestWithBaseUrl.version;
|
|
68
|
+
let baseUrl = requestWithBaseUrl.baseUrl.includes(requestWithBaseUrl.scheme)
|
|
69
|
+
? requestWithBaseUrl.baseUrl
|
|
70
|
+
: `${requestWithBaseUrl.scheme.replace('://', '')}://${requestWithBaseUrl.baseUrl}`;
|
|
71
|
+
let param: string | undefined;
|
|
72
|
+
|
|
73
|
+
const isUri = requestWithBaseUrl.credential_offer_uri !== undefined;
|
|
74
|
+
|
|
75
|
+
if (version.valueOf() >= OpenId4VCIVersion.VER_1_0_11.valueOf()) {
|
|
76
|
+
// v11 changed from encoding every param to a encoded json object with a credential_offer param key
|
|
77
|
+
if (!baseUrl.includes('?')) {
|
|
78
|
+
param = isUri ? 'credential_offer_uri' : 'credential_offer';
|
|
79
|
+
} else {
|
|
80
|
+
const split = baseUrl.split('?');
|
|
81
|
+
if (split.length > 1 && split[1] !== '') {
|
|
82
|
+
if (baseUrl.endsWith('&')) {
|
|
83
|
+
param = isUri ? 'credential_offer_uri' : 'credential_offer';
|
|
84
|
+
} else if (!baseUrl.endsWith('=')) {
|
|
85
|
+
baseUrl += `&`;
|
|
86
|
+
param = isUri ? 'credential_offer_uri' : 'credential_offer';
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return convertJsonToURI(requestWithBaseUrl.credential_offer_uri ?? requestWithBaseUrl.original_credential_offer, {
|
|
92
|
+
baseUrl,
|
|
93
|
+
arrayTypeProperties: isUri ? [] : ['credential_type'],
|
|
94
|
+
uriTypeProperties: isUri
|
|
95
|
+
? ['credential_offer_uri']
|
|
96
|
+
: version >= OpenId4VCIVersion.VER_1_0_13
|
|
97
|
+
? ['credential_issuer', 'credential_type']
|
|
98
|
+
: ['issuer', 'credential_type'],
|
|
99
|
+
param,
|
|
100
|
+
version,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -108,7 +108,7 @@ export class CredentialRequestClient {
|
|
|
108
108
|
uniformRequest: UniformCredentialRequest,
|
|
109
109
|
): Promise<OpenIDResponse<CredentialResponse> & { access_token: string }> {
|
|
110
110
|
if (this.version() < OpenId4VCIVersion.VER_1_0_13) {
|
|
111
|
-
throw new Error('Versions below v1.0.13 (draft 13) are not supported.');
|
|
111
|
+
throw new Error('Versions below v1.0.13 (draft 13) are not supported by the V13 credential request client.');
|
|
112
112
|
}
|
|
113
113
|
const request: CredentialRequestV1_0_13 = getCredentialRequestForVersion(uniformRequest, this.version()) as CredentialRequestV1_0_13;
|
|
114
114
|
const credentialEndpoint: string = this.credentialRequestOpts.credentialEndpoint;
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
CredentialOfferRequestWithBaseUrl,
|
|
7
7
|
determineSpecVersionFromOffer,
|
|
8
8
|
EndpointMetadata,
|
|
9
|
+
ExperimentalSubjectIssuance,
|
|
9
10
|
getIssuerFromCredentialOfferPayload,
|
|
10
11
|
getTypesFromOfferV1_0_11,
|
|
11
12
|
OID4VCICredentialFormat,
|
|
@@ -26,6 +27,7 @@ export class CredentialRequestClientBuilderV1_0_11 {
|
|
|
26
27
|
format?: CredentialFormat | OID4VCICredentialFormat;
|
|
27
28
|
token?: string;
|
|
28
29
|
version?: OpenId4VCIVersion;
|
|
30
|
+
subjectIssuance?: ExperimentalSubjectIssuance;
|
|
29
31
|
|
|
30
32
|
public static fromCredentialIssuer({
|
|
31
33
|
credentialIssuer,
|
|
@@ -132,6 +134,11 @@ export class CredentialRequestClientBuilderV1_0_11 {
|
|
|
132
134
|
return this;
|
|
133
135
|
}
|
|
134
136
|
|
|
137
|
+
public withSubjectIssuance(subjectIssuance: ExperimentalSubjectIssuance): this {
|
|
138
|
+
this.subjectIssuance = subjectIssuance;
|
|
139
|
+
return this;
|
|
140
|
+
}
|
|
141
|
+
|
|
135
142
|
public withToken(accessToken: string): this {
|
|
136
143
|
this.token = accessToken;
|
|
137
144
|
return this;
|
|
@@ -64,14 +64,16 @@ export class CredentialRequestClientV1_0_11 {
|
|
|
64
64
|
credentialTypes?: string | string[];
|
|
65
65
|
context?: string[];
|
|
66
66
|
format?: CredentialFormat | OID4VCICredentialFormat;
|
|
67
|
-
}): Promise<OpenIDResponse<CredentialResponse
|
|
67
|
+
}): Promise<OpenIDResponse<CredentialResponse> & { access_token: string }> {
|
|
68
68
|
const { credentialTypes, proofInput, format, context } = opts;
|
|
69
69
|
|
|
70
70
|
const request = await this.createCredentialRequest({ proofInput, credentialTypes, context, format, version: this.version() });
|
|
71
71
|
return await this.acquireCredentialsUsingRequest(request);
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
public async acquireCredentialsUsingRequest(
|
|
74
|
+
public async acquireCredentialsUsingRequest(
|
|
75
|
+
uniformRequest: UniformCredentialRequest,
|
|
76
|
+
): Promise<OpenIDResponse<CredentialResponse> & { access_token: string }> {
|
|
75
77
|
const request = getCredentialRequestForVersion(uniformRequest, this.version());
|
|
76
78
|
const credentialEndpoint: string = this.credentialRequestOpts.credentialEndpoint;
|
|
77
79
|
if (!isValidURL(credentialEndpoint)) {
|
|
@@ -81,11 +83,14 @@ export class CredentialRequestClientV1_0_11 {
|
|
|
81
83
|
debug(`Acquiring credential(s) from: ${credentialEndpoint}`);
|
|
82
84
|
debug(`request\n: ${JSON.stringify(request, null, 2)}`);
|
|
83
85
|
const requestToken: string = this.credentialRequestOpts.token;
|
|
84
|
-
let response
|
|
86
|
+
let response = (await post(credentialEndpoint, JSON.stringify(request), { bearerToken: requestToken })) as OpenIDResponse<CredentialResponse> & {
|
|
87
|
+
access_token: string;
|
|
88
|
+
};
|
|
85
89
|
this._isDeferred = isDeferredCredentialResponse(response);
|
|
86
90
|
if (this.isDeferred() && this.credentialRequestOpts.deferredCredentialAwait && response.successBody) {
|
|
87
91
|
response = await this.acquireDeferredCredential(response.successBody, { bearerToken: this.credentialRequestOpts.token });
|
|
88
92
|
}
|
|
93
|
+
response.access_token = requestToken;
|
|
89
94
|
|
|
90
95
|
debug(`Credential endpoint ${credentialEndpoint} response:\r\n${JSON.stringify(response, null, 2)}`);
|
|
91
96
|
return response;
|
|
@@ -96,7 +101,7 @@ export class CredentialRequestClientV1_0_11 {
|
|
|
96
101
|
opts?: {
|
|
97
102
|
bearerToken?: string;
|
|
98
103
|
},
|
|
99
|
-
): Promise<OpenIDResponse<CredentialResponse
|
|
104
|
+
): Promise<OpenIDResponse<CredentialResponse> & { access_token: string }> {
|
|
100
105
|
const transactionId = response.transaction_id;
|
|
101
106
|
const bearerToken = response.acceptance_token ?? opts?.bearerToken;
|
|
102
107
|
const deferredCredentialEndpoint = this.getDeferredCredentialEndpoint();
|
package/lib/MetadataClient.ts
CHANGED
|
@@ -1,17 +1,24 @@
|
|
|
1
1
|
import {
|
|
2
2
|
AuthorizationServerMetadata,
|
|
3
3
|
AuthorizationServerType,
|
|
4
|
+
CredentialIssuerMetadataV1_0_11,
|
|
4
5
|
CredentialIssuerMetadataV1_0_13,
|
|
6
|
+
CredentialOfferPayload,
|
|
5
7
|
CredentialOfferPayloadV1_0_13,
|
|
6
8
|
CredentialOfferRequestWithBaseUrl,
|
|
9
|
+
determineSpecVersionFromOffer,
|
|
10
|
+
EndpointMetadataResultV1_0_11,
|
|
7
11
|
EndpointMetadataResultV1_0_13,
|
|
8
12
|
getIssuerFromCredentialOfferPayload,
|
|
9
|
-
|
|
13
|
+
IssuerMetadataV1_0_08,
|
|
14
|
+
OpenId4VCIVersion,
|
|
10
15
|
OpenIDResponse,
|
|
11
16
|
WellKnownEndpoints,
|
|
12
17
|
} from '@sphereon/oid4vci-common';
|
|
13
18
|
import Debug from 'debug';
|
|
14
19
|
|
|
20
|
+
import { MetadataClientV1_0_11 } from './MetadataClientV1_0_11';
|
|
21
|
+
import { MetadataClientV1_0_13 } from './MetadataClientV1_0_13';
|
|
15
22
|
import { retrieveWellknown } from './functions/OpenIDUtils';
|
|
16
23
|
|
|
17
24
|
const debug = Debug('sphereon:oid4vci:metadata');
|
|
@@ -24,18 +31,28 @@ export class MetadataClient {
|
|
|
24
31
|
*/
|
|
25
32
|
public static async retrieveAllMetadataFromCredentialOffer(
|
|
26
33
|
credentialOffer: CredentialOfferRequestWithBaseUrl,
|
|
27
|
-
): Promise<EndpointMetadataResultV1_0_13> {
|
|
28
|
-
|
|
34
|
+
): Promise<EndpointMetadataResultV1_0_13 | EndpointMetadataResultV1_0_11> {
|
|
35
|
+
if (determineSpecVersionFromOffer(credentialOffer.credential_offer) >= OpenId4VCIVersion.VER_1_0_13) {
|
|
36
|
+
return await MetadataClientV1_0_13.retrieveAllMetadataFromCredentialOffer(credentialOffer);
|
|
37
|
+
} else {
|
|
38
|
+
return await MetadataClientV1_0_11.retrieveAllMetadataFromCredentialOffer(credentialOffer);
|
|
39
|
+
}
|
|
29
40
|
}
|
|
30
41
|
|
|
31
42
|
/**
|
|
32
43
|
* Retrieve the metada using the initiation request obtained from a previous step
|
|
33
44
|
* @param request
|
|
34
45
|
*/
|
|
35
|
-
public static async retrieveAllMetadataFromCredentialOfferRequest(
|
|
46
|
+
public static async retrieveAllMetadataFromCredentialOfferRequest(
|
|
47
|
+
request: CredentialOfferPayload,
|
|
48
|
+
): Promise<EndpointMetadataResultV1_0_13 | EndpointMetadataResultV1_0_11> {
|
|
36
49
|
const issuer = getIssuerFromCredentialOfferPayload(request);
|
|
37
50
|
if (issuer) {
|
|
38
|
-
|
|
51
|
+
if (determineSpecVersionFromOffer(request) >= OpenId4VCIVersion.VER_1_0_13) {
|
|
52
|
+
return MetadataClientV1_0_13.retrieveAllMetadataFromCredentialOfferRequest(request as CredentialOfferPayloadV1_0_13);
|
|
53
|
+
} else {
|
|
54
|
+
return MetadataClientV1_0_11.retrieveAllMetadataFromCredentialOfferRequest(request);
|
|
55
|
+
}
|
|
39
56
|
}
|
|
40
57
|
throw new Error("can't retrieve metadata from CredentialOfferRequest. No issuer field is present");
|
|
41
58
|
}
|
|
@@ -45,24 +62,33 @@ export class MetadataClient {
|
|
|
45
62
|
* @param issuer The issuer URL
|
|
46
63
|
* @param opts
|
|
47
64
|
*/
|
|
48
|
-
public static async retrieveAllMetadata(
|
|
65
|
+
public static async retrieveAllMetadata(
|
|
66
|
+
issuer: string,
|
|
67
|
+
opts?: { errorOnNotFound: boolean },
|
|
68
|
+
): Promise<EndpointMetadataResultV1_0_13 | EndpointMetadataResultV1_0_11> {
|
|
49
69
|
let token_endpoint: string | undefined;
|
|
50
70
|
let credential_endpoint: string | undefined;
|
|
51
71
|
let deferred_credential_endpoint: string | undefined;
|
|
52
72
|
let authorization_endpoint: string | undefined;
|
|
53
73
|
let authorizationServerType: AuthorizationServerType = 'OID4VCI';
|
|
54
|
-
let authorization_servers: string[] = [issuer];
|
|
74
|
+
let authorization_servers: string[] | undefined = [issuer];
|
|
75
|
+
let authorization_server: string | undefined = undefined;
|
|
55
76
|
const oid4vciResponse = await MetadataClient.retrieveOpenID4VCIServerMetadata(issuer, { errorOnNotFound: false }); // We will handle errors later, given we will also try other metadata locations
|
|
56
77
|
let credentialIssuerMetadata = oid4vciResponse?.successBody;
|
|
57
78
|
if (credentialIssuerMetadata) {
|
|
58
79
|
debug(`Issuer ${issuer} OID4VCI well-known server metadata\r\n${JSON.stringify(credentialIssuerMetadata)}`);
|
|
59
80
|
credential_endpoint = credentialIssuerMetadata.credential_endpoint;
|
|
60
|
-
deferred_credential_endpoint = credentialIssuerMetadata.deferred_credential_endpoint
|
|
81
|
+
deferred_credential_endpoint = credentialIssuerMetadata.deferred_credential_endpoint
|
|
82
|
+
? (credentialIssuerMetadata.deferred_credential_endpoint as string)
|
|
83
|
+
: undefined;
|
|
61
84
|
if (credentialIssuerMetadata.token_endpoint) {
|
|
62
85
|
token_endpoint = credentialIssuerMetadata.token_endpoint;
|
|
63
86
|
}
|
|
64
87
|
if (credentialIssuerMetadata.authorization_servers) {
|
|
65
|
-
authorization_servers = credentialIssuerMetadata.authorization_servers;
|
|
88
|
+
authorization_servers = credentialIssuerMetadata.authorization_servers as string[];
|
|
89
|
+
} else if (credentialIssuerMetadata.authorization_server) {
|
|
90
|
+
authorization_server = credentialIssuerMetadata.authorization_server as string;
|
|
91
|
+
authorization_servers = [authorization_server];
|
|
66
92
|
}
|
|
67
93
|
}
|
|
68
94
|
// No specific OID4VCI endpoint. Either can be an OAuth2 AS or an OIDC IDP. Let's start with OIDC first
|
|
@@ -154,7 +180,9 @@ export class MetadataClient {
|
|
|
154
180
|
|
|
155
181
|
if (!credentialIssuerMetadata && authMetadata) {
|
|
156
182
|
// Apparently everything worked out and the issuer is exposing everything in oAuth2/OIDC well-knowns. Spec is vague about this situation, but we can support it
|
|
157
|
-
credentialIssuerMetadata =
|
|
183
|
+
credentialIssuerMetadata = authorization_server
|
|
184
|
+
? (authMetadata as CredentialIssuerMetadataV1_0_11)
|
|
185
|
+
: (authMetadata as CredentialIssuerMetadataV1_0_13);
|
|
158
186
|
}
|
|
159
187
|
debug(`Issuer ${issuer} token endpoint ${token_endpoint}, credential endpoint ${credential_endpoint}`);
|
|
160
188
|
return {
|
|
@@ -162,12 +190,14 @@ export class MetadataClient {
|
|
|
162
190
|
token_endpoint,
|
|
163
191
|
credential_endpoint,
|
|
164
192
|
deferred_credential_endpoint,
|
|
165
|
-
authorization_server: authorization_servers
|
|
193
|
+
...(authorization_server ? { authorization_server } : { authorization_servers: authorization_servers }),
|
|
166
194
|
authorization_endpoint,
|
|
167
195
|
authorizationServerType,
|
|
168
|
-
credentialIssuerMetadata:
|
|
196
|
+
credentialIssuerMetadata: authorization_server
|
|
197
|
+
? (credentialIssuerMetadata as IssuerMetadataV1_0_08 & Partial<AuthorizationServerMetadata>)
|
|
198
|
+
: (credentialIssuerMetadata as CredentialIssuerMetadataV1_0_13),
|
|
169
199
|
authorizationServerMetadata: authMetadata,
|
|
170
|
-
};
|
|
200
|
+
} as EndpointMetadataResultV1_0_13 | EndpointMetadataResultV1_0_11;
|
|
171
201
|
}
|
|
172
202
|
|
|
173
203
|
/**
|
|
@@ -180,7 +210,12 @@ export class MetadataClient {
|
|
|
180
210
|
opts?: {
|
|
181
211
|
errorOnNotFound?: boolean;
|
|
182
212
|
},
|
|
183
|
-
): Promise<
|
|
213
|
+
): Promise<
|
|
214
|
+
| OpenIDResponse<
|
|
215
|
+
CredentialIssuerMetadataV1_0_11 | CredentialIssuerMetadataV1_0_13 | (IssuerMetadataV1_0_08 & Partial<AuthorizationServerMetadata>)
|
|
216
|
+
>
|
|
217
|
+
| undefined
|
|
218
|
+
> {
|
|
184
219
|
return retrieveWellknown(issuerHost, WellKnownEndpoints.OPENID4VCI_ISSUER, {
|
|
185
220
|
errorOnNotFound: opts?.errorOnNotFound === undefined ? true : opts.errorOnNotFound,
|
|
186
221
|
});
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AuthorizationServerMetadata,
|
|
3
|
+
AuthorizationServerType,
|
|
4
|
+
CredentialIssuerMetadataV1_0_13,
|
|
5
|
+
CredentialOfferPayloadV1_0_13,
|
|
6
|
+
CredentialOfferRequestWithBaseUrl,
|
|
7
|
+
EndpointMetadataResultV1_0_13,
|
|
8
|
+
getIssuerFromCredentialOfferPayload,
|
|
9
|
+
IssuerMetadataV1_0_13,
|
|
10
|
+
OpenIDResponse,
|
|
11
|
+
WellKnownEndpoints,
|
|
12
|
+
} from '@sphereon/oid4vci-common';
|
|
13
|
+
import Debug from 'debug';
|
|
14
|
+
|
|
15
|
+
import { retrieveWellknown } from './functions/OpenIDUtils';
|
|
16
|
+
|
|
17
|
+
const debug = Debug('sphereon:oid4vci:metadata');
|
|
18
|
+
|
|
19
|
+
export class MetadataClientV1_0_13 {
|
|
20
|
+
/**
|
|
21
|
+
* Retrieve metadata using the Initiation obtained from a previous step
|
|
22
|
+
*
|
|
23
|
+
* @param credentialOffer
|
|
24
|
+
*/
|
|
25
|
+
public static async retrieveAllMetadataFromCredentialOffer(
|
|
26
|
+
credentialOffer: CredentialOfferRequestWithBaseUrl,
|
|
27
|
+
): Promise<EndpointMetadataResultV1_0_13> {
|
|
28
|
+
return MetadataClientV1_0_13.retrieveAllMetadataFromCredentialOfferRequest(credentialOffer.credential_offer as CredentialOfferPayloadV1_0_13);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Retrieve the metada using the initiation request obtained from a previous step
|
|
33
|
+
* @param request
|
|
34
|
+
*/
|
|
35
|
+
public static async retrieveAllMetadataFromCredentialOfferRequest(request: CredentialOfferPayloadV1_0_13): Promise<EndpointMetadataResultV1_0_13> {
|
|
36
|
+
const issuer = getIssuerFromCredentialOfferPayload(request);
|
|
37
|
+
if (issuer) {
|
|
38
|
+
return MetadataClientV1_0_13.retrieveAllMetadata(issuer);
|
|
39
|
+
}
|
|
40
|
+
throw new Error("can't retrieve metadata from CredentialOfferRequest. No issuer field is present");
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Retrieve all metadata from an issuer
|
|
45
|
+
* @param issuer The issuer URL
|
|
46
|
+
* @param opts
|
|
47
|
+
*/
|
|
48
|
+
public static async retrieveAllMetadata(issuer: string, opts?: { errorOnNotFound: boolean }): Promise<EndpointMetadataResultV1_0_13> {
|
|
49
|
+
let token_endpoint: string | undefined;
|
|
50
|
+
let credential_endpoint: string | undefined;
|
|
51
|
+
let deferred_credential_endpoint: string | undefined;
|
|
52
|
+
let authorization_endpoint: string | undefined;
|
|
53
|
+
let authorizationServerType: AuthorizationServerType = 'OID4VCI';
|
|
54
|
+
let authorization_servers: string[] = [issuer];
|
|
55
|
+
const oid4vciResponse = await MetadataClientV1_0_13.retrieveOpenID4VCIServerMetadata(issuer, { errorOnNotFound: false }); // We will handle errors later, given we will also try other metadata locations
|
|
56
|
+
let credentialIssuerMetadata = oid4vciResponse?.successBody;
|
|
57
|
+
if (credentialIssuerMetadata) {
|
|
58
|
+
debug(`Issuer ${issuer} OID4VCI well-known server metadata\r\n${JSON.stringify(credentialIssuerMetadata)}`);
|
|
59
|
+
credential_endpoint = credentialIssuerMetadata.credential_endpoint;
|
|
60
|
+
deferred_credential_endpoint = credentialIssuerMetadata.deferred_credential_endpoint;
|
|
61
|
+
if (credentialIssuerMetadata.token_endpoint) {
|
|
62
|
+
token_endpoint = credentialIssuerMetadata.token_endpoint;
|
|
63
|
+
}
|
|
64
|
+
if (credentialIssuerMetadata.authorization_servers) {
|
|
65
|
+
authorization_servers = credentialIssuerMetadata.authorization_servers;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
// No specific OID4VCI endpoint. Either can be an OAuth2 AS or an OIDC IDP. Let's start with OIDC first
|
|
69
|
+
// TODO: for now we're taking just the first one
|
|
70
|
+
let response: OpenIDResponse<AuthorizationServerMetadata> = await retrieveWellknown(
|
|
71
|
+
authorization_servers[0],
|
|
72
|
+
WellKnownEndpoints.OPENID_CONFIGURATION,
|
|
73
|
+
{
|
|
74
|
+
errorOnNotFound: false,
|
|
75
|
+
},
|
|
76
|
+
);
|
|
77
|
+
let authMetadata = response.successBody;
|
|
78
|
+
if (authMetadata) {
|
|
79
|
+
debug(`Issuer ${issuer} has OpenID Connect Server metadata in well-known location`);
|
|
80
|
+
authorizationServerType = 'OIDC';
|
|
81
|
+
} else {
|
|
82
|
+
// Now let's do OAuth2
|
|
83
|
+
// TODO: for now we're taking just the first one
|
|
84
|
+
response = await retrieveWellknown(authorization_servers[0], WellKnownEndpoints.OAUTH_AS, { errorOnNotFound: false });
|
|
85
|
+
authMetadata = response.successBody;
|
|
86
|
+
}
|
|
87
|
+
if (!authMetadata) {
|
|
88
|
+
// We will always throw an error, no matter whether the user provided the option not to, because this is bad.
|
|
89
|
+
if (!authorization_servers.includes(issuer)) {
|
|
90
|
+
throw Error(`Issuer ${issuer} provided a separate authorization server ${authorization_servers}, but that server did not provide metadata`);
|
|
91
|
+
}
|
|
92
|
+
} else {
|
|
93
|
+
if (!authorizationServerType) {
|
|
94
|
+
authorizationServerType = 'OAuth 2.0';
|
|
95
|
+
}
|
|
96
|
+
debug(`Issuer ${issuer} has ${authorizationServerType} Server metadata in well-known location`);
|
|
97
|
+
if (!authMetadata.authorization_endpoint) {
|
|
98
|
+
console.warn(
|
|
99
|
+
`Issuer ${issuer} of type ${authorizationServerType} has no authorization_endpoint! Will use ${authorization_endpoint}. This only works for pre-authorized flows`,
|
|
100
|
+
);
|
|
101
|
+
} else if (authorization_endpoint && authMetadata.authorization_endpoint !== authorization_endpoint) {
|
|
102
|
+
throw Error(
|
|
103
|
+
`Credential issuer has a different authorization_endpoint (${authorization_endpoint}) from the Authorization Server (${authMetadata.authorization_endpoint})`,
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
authorization_endpoint = authMetadata.authorization_endpoint;
|
|
107
|
+
if (!authMetadata.token_endpoint) {
|
|
108
|
+
throw Error(`Authorization Sever ${authorization_servers} did not provide a token_endpoint`);
|
|
109
|
+
} else if (token_endpoint && authMetadata.token_endpoint !== token_endpoint) {
|
|
110
|
+
throw Error(
|
|
111
|
+
`Credential issuer has a different token_endpoint (${token_endpoint}) from the Authorization Server (${authMetadata.token_endpoint})`,
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
token_endpoint = authMetadata.token_endpoint;
|
|
115
|
+
if (authMetadata.credential_endpoint) {
|
|
116
|
+
if (credential_endpoint && authMetadata.credential_endpoint !== credential_endpoint) {
|
|
117
|
+
debug(
|
|
118
|
+
`Credential issuer has a different credential_endpoint (${credential_endpoint}) from the Authorization Server (${authMetadata.credential_endpoint}). Will use the issuer value`,
|
|
119
|
+
);
|
|
120
|
+
} else {
|
|
121
|
+
credential_endpoint = authMetadata.credential_endpoint;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
if (authMetadata.deferred_credential_endpoint) {
|
|
125
|
+
if (deferred_credential_endpoint && authMetadata.deferred_credential_endpoint !== deferred_credential_endpoint) {
|
|
126
|
+
debug(
|
|
127
|
+
`Credential issuer has a different deferred_credential_endpoint (${deferred_credential_endpoint}) from the Authorization Server (${authMetadata.deferred_credential_endpoint}). Will use the issuer value`,
|
|
128
|
+
);
|
|
129
|
+
} else {
|
|
130
|
+
deferred_credential_endpoint = authMetadata.deferred_credential_endpoint;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (!authorization_endpoint) {
|
|
136
|
+
debug(`Issuer ${issuer} does not expose authorization_endpoint, so only pre-auth will be supported`);
|
|
137
|
+
}
|
|
138
|
+
if (!token_endpoint) {
|
|
139
|
+
debug(`Issuer ${issuer} does not have a token_endpoint listed in well-known locations!`);
|
|
140
|
+
if (opts?.errorOnNotFound) {
|
|
141
|
+
throw Error(`Could not deduce the token_endpoint for ${issuer}`);
|
|
142
|
+
} else {
|
|
143
|
+
token_endpoint = `${issuer}${issuer.endsWith('/') ? 'token' : '/token'}`;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
if (!credential_endpoint) {
|
|
147
|
+
debug(`Issuer ${issuer} does not have a credential_endpoint listed in well-known locations!`);
|
|
148
|
+
if (opts?.errorOnNotFound) {
|
|
149
|
+
throw Error(`Could not deduce the credential endpoint for ${issuer}`);
|
|
150
|
+
} else {
|
|
151
|
+
credential_endpoint = `${issuer}${issuer.endsWith('/') ? 'credential' : '/credential'}`;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (!credentialIssuerMetadata && authMetadata) {
|
|
156
|
+
// Apparently everything worked out and the issuer is exposing everything in oAuth2/OIDC well-knowns. Spec is vague about this situation, but we can support it
|
|
157
|
+
credentialIssuerMetadata = authMetadata as CredentialIssuerMetadataV1_0_13;
|
|
158
|
+
}
|
|
159
|
+
debug(`Issuer ${issuer} token endpoint ${token_endpoint}, credential endpoint ${credential_endpoint}`);
|
|
160
|
+
return {
|
|
161
|
+
issuer,
|
|
162
|
+
token_endpoint,
|
|
163
|
+
credential_endpoint,
|
|
164
|
+
deferred_credential_endpoint,
|
|
165
|
+
authorization_server: authorization_servers[0],
|
|
166
|
+
authorization_endpoint,
|
|
167
|
+
authorizationServerType,
|
|
168
|
+
credentialIssuerMetadata: credentialIssuerMetadata,
|
|
169
|
+
authorizationServerMetadata: authMetadata,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Retrieve only the OID4VCI metadata for the issuer. So no OIDC/OAuth2 metadata
|
|
175
|
+
*
|
|
176
|
+
* @param issuerHost The issuer hostname
|
|
177
|
+
*/
|
|
178
|
+
public static async retrieveOpenID4VCIServerMetadata(
|
|
179
|
+
issuerHost: string,
|
|
180
|
+
opts?: {
|
|
181
|
+
errorOnNotFound?: boolean;
|
|
182
|
+
},
|
|
183
|
+
): Promise<OpenIDResponse<IssuerMetadataV1_0_13> | undefined> {
|
|
184
|
+
return retrieveWellknown(issuerHost, WellKnownEndpoints.OPENID4VCI_ISSUER, {
|
|
185
|
+
errorOnNotFound: opts?.errorOnNotFound === undefined ? true : opts.errorOnNotFound,
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
}
|