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