@connectid-tools/rp-nodejs-sdk 4.2.1 → 5.0.1
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 +64 -71
- package/config.js +2 -31
- package/conformance/api/conformance-api.d.ts +38 -0
- package/conformance/api/conformance-api.js +53 -0
- package/conformance/conformance-config.d.ts +2 -0
- package/conformance/conformance-config.js +34 -0
- package/crypto/crypto-loader.d.ts +32 -0
- package/crypto/crypto-loader.js +49 -0
- package/crypto/jwt-helper.d.ts +61 -0
- package/crypto/jwt-helper.js +92 -0
- package/crypto/pkce-helper.d.ts +43 -0
- package/crypto/pkce-helper.js +75 -0
- package/endpoints/participants-endpoint.d.ts +55 -0
- package/endpoints/participants-endpoint.js +137 -0
- package/endpoints/pushed-authorisation-request-endpoint.d.ts +87 -0
- package/endpoints/pushed-authorisation-request-endpoint.js +192 -0
- package/endpoints/retrieve-token-endpoint.d.ts +66 -0
- package/endpoints/retrieve-token-endpoint.js +159 -0
- package/endpoints/userinfo-endpoint.d.ts +24 -0
- package/endpoints/userinfo-endpoint.js +50 -0
- package/fapi/fapi-utils.d.ts +6 -0
- package/fapi/fapi-utils.js +9 -0
- package/http/http-client-extensions.d.ts +60 -0
- package/http/http-client-extensions.js +106 -0
- package/http/http-client-factory.d.ts +27 -0
- package/http/http-client-factory.js +45 -0
- package/model/callback-params.d.ts +31 -0
- package/model/callback-params.js +1 -0
- package/model/claims.d.ts +100 -0
- package/model/claims.js +1 -0
- package/model/consolidated-token-set.d.ts +74 -0
- package/model/consolidated-token-set.js +100 -0
- package/model/discovery-service.d.ts +46 -0
- package/model/discovery-service.js +112 -0
- package/model/issuer-metadata.d.ts +165 -0
- package/model/issuer-metadata.js +1 -0
- package/model/jwks.d.ts +12 -0
- package/model/jwks.js +1 -0
- package/model/token-response.d.ts +31 -0
- package/model/token-response.js +1 -0
- package/model/token-set.d.ts +73 -0
- package/model/token-set.js +179 -0
- package/package.json +4 -5
- package/relying-party-client-sdk.d.ts +55 -24
- package/relying-party-client-sdk.js +90 -304
- package/test-data/large-participants-test-data.d.ts +865 -0
- package/test-data/large-participants-test-data.js +18907 -0
- package/test-data/participants-test-data.d.ts +149 -0
- package/test-data/participants-test-data.js +458 -0
- package/test-data/sandbox-participants-test-data.d.ts +865 -0
- package/test-data/sandbox-participants-test-data.js +3794 -0
- package/types.d.ts +61 -32
- package/utils/request-utils.d.ts +1 -1
- package/utils/request-utils.js +5 -5
- package/utils/user-agent.d.ts +1 -1
- package/utils/user-agent.js +1 -1
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { Agent } from 'undici';
|
|
2
|
+
import { Logger } from 'winston';
|
|
3
|
+
import { CallbackParams, RelyingPartyClientSdkConfig } from '../types.js';
|
|
4
|
+
import { JwtHelper } from '../crypto/jwt-helper.js';
|
|
5
|
+
import { ConsolidatedTokenSet } from '../model/consolidated-token-set.js';
|
|
6
|
+
import { ParticipantsEndpoint } from './participants-endpoint';
|
|
7
|
+
/**
|
|
8
|
+
* Retrieve Token Endpoint
|
|
9
|
+
*
|
|
10
|
+
* Handles the token exchange flow (authorization code for tokens).
|
|
11
|
+
* Validates the ID token and optionally calls UserInfo for compliance.
|
|
12
|
+
*/
|
|
13
|
+
export declare class RetrieveTokenEndpoint {
|
|
14
|
+
private readonly sdkConfig;
|
|
15
|
+
private readonly httpClient;
|
|
16
|
+
private readonly jwtHelper;
|
|
17
|
+
private readonly logger;
|
|
18
|
+
private readonly participantsEndpoint;
|
|
19
|
+
constructor(sdkConfig: RelyingPartyClientSdkConfig, httpClient: Agent, jwtHelper: JwtHelper, logger: Logger, participantsEndpoint: ParticipantsEndpoint);
|
|
20
|
+
/**
|
|
21
|
+
* Retrieves tokens from the authorization server.
|
|
22
|
+
*
|
|
23
|
+
* Performs the following steps:
|
|
24
|
+
* 1. Validates callback parameters
|
|
25
|
+
* 2. Exchanges authorization code for tokens
|
|
26
|
+
* 3. Validates ID token
|
|
27
|
+
* 4. Optionally calls UserInfo for compliance
|
|
28
|
+
*
|
|
29
|
+
* @param authorisationServerId - Authorization server id
|
|
30
|
+
* @param callbackParams - OAuth callback parameters from redirect
|
|
31
|
+
* @param codeVerifier - PKCE code verifier from PAR
|
|
32
|
+
* @param state - State parameter from PAR
|
|
33
|
+
* @param nonce - Nonce parameter from PAR
|
|
34
|
+
* @returns Consolidated token set with validated claims
|
|
35
|
+
*/
|
|
36
|
+
retrieveTokens(authorisationServerId: string, callbackParams: CallbackParams, codeVerifier: string, state: string, nonce: string): Promise<ConsolidatedTokenSet>;
|
|
37
|
+
/**
|
|
38
|
+
* Validates the OAuth callback parameters.
|
|
39
|
+
*
|
|
40
|
+
* @param callbackParams - Callback parameters from redirect
|
|
41
|
+
* @param expectedState - Expected state value
|
|
42
|
+
* @throws Error if validation fails
|
|
43
|
+
*/
|
|
44
|
+
private validateCallbackParams;
|
|
45
|
+
/**
|
|
46
|
+
* Requests tokens from the token endpoint.
|
|
47
|
+
*
|
|
48
|
+
* @param tokenEndpoint - Token endpoint URL
|
|
49
|
+
* @param code - Authorization code from callback
|
|
50
|
+
* @param codeVerifier - PKCE code verifier
|
|
51
|
+
* @param clientAssertion - Client assertion JWT for authentication
|
|
52
|
+
* @param xFapiInteractionId - FAPI interaction ID
|
|
53
|
+
* @returns Token response
|
|
54
|
+
*/
|
|
55
|
+
private requestToken;
|
|
56
|
+
/**
|
|
57
|
+
* Calls the UserInfo endpoint for compliance verification.
|
|
58
|
+
*
|
|
59
|
+
* This is required by some conformance test suites.
|
|
60
|
+
*
|
|
61
|
+
* @param userinfoEndpoint - UserInfo endpoint URL
|
|
62
|
+
* @param accessToken - Access token
|
|
63
|
+
* @param xFapiInteractionId - FAPI interaction ID
|
|
64
|
+
*/
|
|
65
|
+
private callUserInfoForCompliance;
|
|
66
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { DiscoveryService } from '../model/discovery-service.js';
|
|
2
|
+
import { HttpClientExtensions } from '../http/http-client-extensions.js';
|
|
3
|
+
import { TokenSet } from '../model/token-set.js';
|
|
4
|
+
import { ConsolidatedTokenSet } from '../model/consolidated-token-set.js';
|
|
5
|
+
import { generateXFapiInteractionId } from '../fapi/fapi-utils.js';
|
|
6
|
+
/**
|
|
7
|
+
* Retrieve Token Endpoint
|
|
8
|
+
*
|
|
9
|
+
* Handles the token exchange flow (authorization code for tokens).
|
|
10
|
+
* Validates the ID token and optionally calls UserInfo for compliance.
|
|
11
|
+
*/
|
|
12
|
+
export class RetrieveTokenEndpoint {
|
|
13
|
+
constructor(sdkConfig, httpClient, jwtHelper, logger, participantsEndpoint) {
|
|
14
|
+
this.sdkConfig = sdkConfig;
|
|
15
|
+
this.httpClient = httpClient;
|
|
16
|
+
this.jwtHelper = jwtHelper;
|
|
17
|
+
this.logger = logger;
|
|
18
|
+
this.participantsEndpoint = participantsEndpoint;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Retrieves tokens from the authorization server.
|
|
22
|
+
*
|
|
23
|
+
* Performs the following steps:
|
|
24
|
+
* 1. Validates callback parameters
|
|
25
|
+
* 2. Exchanges authorization code for tokens
|
|
26
|
+
* 3. Validates ID token
|
|
27
|
+
* 4. Optionally calls UserInfo for compliance
|
|
28
|
+
*
|
|
29
|
+
* @param authorisationServerId - Authorization server id
|
|
30
|
+
* @param callbackParams - OAuth callback parameters from redirect
|
|
31
|
+
* @param codeVerifier - PKCE code verifier from PAR
|
|
32
|
+
* @param state - State parameter from PAR
|
|
33
|
+
* @param nonce - Nonce parameter from PAR
|
|
34
|
+
* @returns Consolidated token set with validated claims
|
|
35
|
+
*/
|
|
36
|
+
async retrieveTokens(authorisationServerId, callbackParams, codeVerifier, state, nonce) {
|
|
37
|
+
const xFapiInteractionId = generateXFapiInteractionId();
|
|
38
|
+
try {
|
|
39
|
+
this.logger.info(`Retrieving tokens for auth server: ${authorisationServerId}`);
|
|
40
|
+
// Validate callback parameters
|
|
41
|
+
this.validateCallbackParams(callbackParams, state);
|
|
42
|
+
const authServer = await this.participantsEndpoint.getAuthServerDetails(authorisationServerId);
|
|
43
|
+
// Fetch discovery document
|
|
44
|
+
const discoveryMetadata = await DiscoveryService.fetchDiscoveryDocument(authServer.OpenIDDiscoveryDocument, this.httpClient);
|
|
45
|
+
// Validate issuer parameter (REQUIRED per FAPI 2.0)
|
|
46
|
+
if (!callbackParams.iss) {
|
|
47
|
+
throw new Error('Authorization response missing required iss parameter');
|
|
48
|
+
}
|
|
49
|
+
if (callbackParams.iss !== discoveryMetadata.issuer) {
|
|
50
|
+
throw new Error(`Issuer mismatch: expected ${discoveryMetadata.issuer}, got ${callbackParams.iss}`);
|
|
51
|
+
}
|
|
52
|
+
// Create client assertion for authentication
|
|
53
|
+
const clientAssertion = await this.jwtHelper.generateClientAssertionJwt(discoveryMetadata.issuer);
|
|
54
|
+
// Exchange authorization code for tokens
|
|
55
|
+
const tokenResponse = await this.requestToken(discoveryMetadata.token_endpoint, callbackParams.code, codeVerifier, clientAssertion, xFapiInteractionId);
|
|
56
|
+
const tokenSet = new TokenSet(tokenResponse);
|
|
57
|
+
const jwks = await DiscoveryService.fetchJwks(discoveryMetadata.jwks_uri, this.httpClient);
|
|
58
|
+
await tokenSet.validate(jwks, discoveryMetadata.issuer, this.sdkConfig.data.client_id, nonce, discoveryMetadata.id_token_signing_alg_values_supported);
|
|
59
|
+
// Must call validate first before accessing claims
|
|
60
|
+
this.logger.info(`Retrieved tokenSet from auth server: ${authorisationServerId} - ${authServer.CustomerFriendlyName}, x-fapi-interaction-id: ${xFapiInteractionId}, txn: ${tokenSet.claims().txn}`);
|
|
61
|
+
this.logger.debug(`Tokens returned from: ${authorisationServerId} - ${authServer.CustomerFriendlyName}: ${JSON.stringify(tokenSet)} claims: ${JSON.stringify(tokenSet.claims())}`);
|
|
62
|
+
const consolidatedTokenSet = new ConsolidatedTokenSet(tokenSet, xFapiInteractionId);
|
|
63
|
+
// If using the OIDF test suite, need to make a call to userInfo when claims were successfully decoded
|
|
64
|
+
if (this.sdkConfig.data.enable_auto_compliance_verification) {
|
|
65
|
+
this.logger.warn('Auto compliance verification mode is enabled - calling UserInfo endpoint. This mode should only be enabled while performing OIDF conformance suite testing and will cause issues if used in production.');
|
|
66
|
+
if (!tokenSet.access_token) {
|
|
67
|
+
throw new Error('Cannot call UserInfo: no access token present');
|
|
68
|
+
}
|
|
69
|
+
if (!discoveryMetadata.userinfo_endpoint) {
|
|
70
|
+
throw new Error('Authorization server does not have a UserInfo endpoint');
|
|
71
|
+
}
|
|
72
|
+
// Call UserInfo endpoint for compliance
|
|
73
|
+
await this.callUserInfoForCompliance(discoveryMetadata.userinfo_endpoint, tokenSet.access_token, xFapiInteractionId);
|
|
74
|
+
}
|
|
75
|
+
return consolidatedTokenSet;
|
|
76
|
+
}
|
|
77
|
+
catch (err) {
|
|
78
|
+
this.logger.error(`Error retrieving tokens with authorisation server ${authorisationServerId}, x-fapi-interaction-id: ${xFapiInteractionId}`, err);
|
|
79
|
+
throw new Error(`Unable to retrieve tokens with authorisation server ${authorisationServerId}, x-fapi-interaction-id: ${xFapiInteractionId}`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Validates the OAuth callback parameters.
|
|
84
|
+
*
|
|
85
|
+
* @param callbackParams - Callback parameters from redirect
|
|
86
|
+
* @param expectedState - Expected state value
|
|
87
|
+
* @throws Error if validation fails
|
|
88
|
+
*/
|
|
89
|
+
validateCallbackParams(callbackParams, expectedState) {
|
|
90
|
+
// Check for error response
|
|
91
|
+
if (callbackParams.error) {
|
|
92
|
+
throw new Error(`Authorization failed: ${callbackParams.error}${callbackParams.error_description ? ` - ${callbackParams.error_description}` : ''}`);
|
|
93
|
+
}
|
|
94
|
+
// Validate code is present
|
|
95
|
+
if (!callbackParams.code) {
|
|
96
|
+
throw new Error('Authorization code missing from callback parameters');
|
|
97
|
+
}
|
|
98
|
+
// Validate state
|
|
99
|
+
if (!callbackParams.state) {
|
|
100
|
+
throw new Error('State parameter missing from callback parameters');
|
|
101
|
+
}
|
|
102
|
+
if (callbackParams.state !== expectedState) {
|
|
103
|
+
throw new Error(`State mismatch: expected ${expectedState}, got ${callbackParams.state}`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Requests tokens from the token endpoint.
|
|
108
|
+
*
|
|
109
|
+
* @param tokenEndpoint - Token endpoint URL
|
|
110
|
+
* @param code - Authorization code from callback
|
|
111
|
+
* @param codeVerifier - PKCE code verifier
|
|
112
|
+
* @param clientAssertion - Client assertion JWT for authentication
|
|
113
|
+
* @param xFapiInteractionId - FAPI interaction ID
|
|
114
|
+
* @returns Token response
|
|
115
|
+
*/
|
|
116
|
+
async requestToken(tokenEndpoint, code, codeVerifier, clientAssertion, xFapiInteractionId) {
|
|
117
|
+
const body = new URLSearchParams({
|
|
118
|
+
grant_type: 'authorization_code',
|
|
119
|
+
code,
|
|
120
|
+
redirect_uri: this.sdkConfig.data.application_redirect_uri,
|
|
121
|
+
code_verifier: codeVerifier,
|
|
122
|
+
client_assertion: clientAssertion,
|
|
123
|
+
client_assertion_type: 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
|
|
124
|
+
});
|
|
125
|
+
this.logger.debug(`Requesting tokens from: ${tokenEndpoint}`);
|
|
126
|
+
this.logger.debug(` with body: ${body}`);
|
|
127
|
+
const response = await HttpClientExtensions.postForm(tokenEndpoint, body, {
|
|
128
|
+
agent: this.httpClient,
|
|
129
|
+
clientId: this.sdkConfig.data.client_id,
|
|
130
|
+
xFapiInteractionId,
|
|
131
|
+
});
|
|
132
|
+
return HttpClientExtensions.parseJsonResponse(response);
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Calls the UserInfo endpoint for compliance verification.
|
|
136
|
+
*
|
|
137
|
+
* This is required by some conformance test suites.
|
|
138
|
+
*
|
|
139
|
+
* @param userinfoEndpoint - UserInfo endpoint URL
|
|
140
|
+
* @param accessToken - Access token
|
|
141
|
+
* @param xFapiInteractionId - FAPI interaction ID
|
|
142
|
+
*/
|
|
143
|
+
async callUserInfoForCompliance(userinfoEndpoint, accessToken, xFapiInteractionId) {
|
|
144
|
+
try {
|
|
145
|
+
const response = await HttpClientExtensions.getWithToken(userinfoEndpoint, accessToken, {
|
|
146
|
+
agent: this.httpClient,
|
|
147
|
+
clientId: this.sdkConfig.data.client_id,
|
|
148
|
+
xFapiInteractionId,
|
|
149
|
+
});
|
|
150
|
+
const userInfo = await HttpClientExtensions.parseJsonResponse(response);
|
|
151
|
+
this.logger.debug(`UserInfo response: ${JSON.stringify(userInfo, null, 2)}`);
|
|
152
|
+
this.logger.info('UserInfo endpoint called successfully for compliance: ' + userinfoEndpoint);
|
|
153
|
+
}
|
|
154
|
+
catch (error) {
|
|
155
|
+
this.logger.warn(`UserInfo call failed (compliance verification): ${error instanceof Error ? error.message : String(error)}`);
|
|
156
|
+
// Don't throw - this is just for compliance, not critical
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Agent } from 'undici';
|
|
2
|
+
import { Logger } from 'winston';
|
|
3
|
+
import { ParticipantsEndpoint } from './participants-endpoint';
|
|
4
|
+
/**
|
|
5
|
+
* UserInfo Endpoint
|
|
6
|
+
*
|
|
7
|
+
* Handles calls to the OIDC UserInfo endpoint.
|
|
8
|
+
* Returns user claims using an access token.
|
|
9
|
+
*/
|
|
10
|
+
export declare class UserInfoEndpoint {
|
|
11
|
+
private readonly httpClient;
|
|
12
|
+
private readonly logger;
|
|
13
|
+
private readonly clientId;
|
|
14
|
+
private readonly participantsEndpoint;
|
|
15
|
+
constructor(httpClient: Agent, logger: Logger, clientId: string, participantsEndpoint: ParticipantsEndpoint);
|
|
16
|
+
/**
|
|
17
|
+
* Retrieves user information from the UserInfo endpoint.
|
|
18
|
+
*
|
|
19
|
+
* @param authorisationServerId - Authorization server id
|
|
20
|
+
* @param accessToken - Access token to authenticate the request
|
|
21
|
+
* @returns UserInfo claims
|
|
22
|
+
*/
|
|
23
|
+
getUserInfo(authorisationServerId: string, accessToken: string): Promise<Record<string, unknown>>;
|
|
24
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { DiscoveryService } from '../model/discovery-service.js';
|
|
2
|
+
import { HttpClientExtensions } from '../http/http-client-extensions.js';
|
|
3
|
+
import { generateXFapiInteractionId } from '../fapi/fapi-utils.js';
|
|
4
|
+
/**
|
|
5
|
+
* UserInfo Endpoint
|
|
6
|
+
*
|
|
7
|
+
* Handles calls to the OIDC UserInfo endpoint.
|
|
8
|
+
* Returns user claims using an access token.
|
|
9
|
+
*/
|
|
10
|
+
export class UserInfoEndpoint {
|
|
11
|
+
constructor(httpClient, logger, clientId, participantsEndpoint) {
|
|
12
|
+
this.httpClient = httpClient;
|
|
13
|
+
this.logger = logger;
|
|
14
|
+
this.clientId = clientId;
|
|
15
|
+
this.participantsEndpoint = participantsEndpoint;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Retrieves user information from the UserInfo endpoint.
|
|
19
|
+
*
|
|
20
|
+
* @param authorisationServerId - Authorization server id
|
|
21
|
+
* @param accessToken - Access token to authenticate the request
|
|
22
|
+
* @returns UserInfo claims
|
|
23
|
+
*/
|
|
24
|
+
async getUserInfo(authorisationServerId, accessToken) {
|
|
25
|
+
this.logger.info(`Fetching UserInfo for auth server: ${authorisationServerId}`);
|
|
26
|
+
const xFapiInteractionId = generateXFapiInteractionId();
|
|
27
|
+
try {
|
|
28
|
+
const authServer = await this.participantsEndpoint.getAuthServerDetails(authorisationServerId);
|
|
29
|
+
// Fetch discovery document
|
|
30
|
+
const discoveryMetadata = await DiscoveryService.fetchDiscoveryDocument(authServer.OpenIDDiscoveryDocument, this.httpClient);
|
|
31
|
+
if (!discoveryMetadata.userinfo_endpoint) {
|
|
32
|
+
throw new Error(`Authorization server ${authServer.AuthorisationServerId} does not have a UserInfo endpoint`);
|
|
33
|
+
}
|
|
34
|
+
// Call UserInfo endpoint
|
|
35
|
+
const response = await HttpClientExtensions.getWithToken(discoveryMetadata.userinfo_endpoint, accessToken, {
|
|
36
|
+
agent: this.httpClient,
|
|
37
|
+
clientId: this.clientId,
|
|
38
|
+
xFapiInteractionId,
|
|
39
|
+
});
|
|
40
|
+
const userInfo = await HttpClientExtensions.parseJsonResponse(response);
|
|
41
|
+
this.logger.info(`UserInfo retrieved successfully, x-fapi-interaction-id: ${xFapiInteractionId}`);
|
|
42
|
+
this.logger.debug(`UserInfo claims: ${JSON.stringify(userInfo, null, 2)}`);
|
|
43
|
+
return userInfo;
|
|
44
|
+
}
|
|
45
|
+
catch (err) {
|
|
46
|
+
this.logger.error(`Error calling user info authorisation server ${authorisationServerId}, x-fapi-interaction-id: ${xFapiInteractionId}`, err);
|
|
47
|
+
throw new Error(`Unable to calling user info with authorisation server ${authorisationServerId}, x-fapi-interaction-id: ${xFapiInteractionId}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { Agent, Dispatcher } from 'undici';
|
|
2
|
+
export interface UndiciRequestInit extends RequestInit {
|
|
3
|
+
dispatcher?: Dispatcher;
|
|
4
|
+
}
|
|
5
|
+
export interface FetchOptions extends UndiciRequestInit {
|
|
6
|
+
agent?: Agent;
|
|
7
|
+
}
|
|
8
|
+
export interface FapiRequestOptions {
|
|
9
|
+
agent: Agent;
|
|
10
|
+
clientId: string;
|
|
11
|
+
xFapiInteractionId: string;
|
|
12
|
+
contentType?: string;
|
|
13
|
+
additionalHeaders?: Record<string, string>;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Utility functions for making HTTP requests with the configured mTLS client.
|
|
17
|
+
*/
|
|
18
|
+
export declare class HttpClientExtensions {
|
|
19
|
+
/**
|
|
20
|
+
* Creates standard headers for FAPI requests.
|
|
21
|
+
*
|
|
22
|
+
* @param options - Request configuration including client ID and interaction ID
|
|
23
|
+
* @returns Headers object with required FAPI headers
|
|
24
|
+
*/
|
|
25
|
+
static createFapiHeaders(options: FapiRequestOptions): HeadersInit;
|
|
26
|
+
/**
|
|
27
|
+
* Makes a POST request with form-encoded body.
|
|
28
|
+
*
|
|
29
|
+
* @param url - Target URL
|
|
30
|
+
* @param body - Form data as URLSearchParams
|
|
31
|
+
* @param options - Request options including agent and headers
|
|
32
|
+
* @returns Response promise
|
|
33
|
+
*/
|
|
34
|
+
static postForm(url: string, body: URLSearchParams, options: FapiRequestOptions): Promise<Response>;
|
|
35
|
+
/**
|
|
36
|
+
* Makes a GET request with FAPI headers.
|
|
37
|
+
*
|
|
38
|
+
* @param url - Target URL
|
|
39
|
+
* @param options - Request options including agent and headers
|
|
40
|
+
* @returns Response promise
|
|
41
|
+
*/
|
|
42
|
+
static get(url: string, options: FapiRequestOptions): Promise<Response>;
|
|
43
|
+
/**
|
|
44
|
+
* Makes a GET request with Bearer token authentication.
|
|
45
|
+
*
|
|
46
|
+
* @param url - Target URL
|
|
47
|
+
* @param accessToken - Bearer access token
|
|
48
|
+
* @param options - Request options including agent and headers
|
|
49
|
+
* @returns Response promise
|
|
50
|
+
*/
|
|
51
|
+
static getWithToken(url: string, accessToken: string, options: FapiRequestOptions): Promise<Response>;
|
|
52
|
+
/**
|
|
53
|
+
* Parses a JSON response body.
|
|
54
|
+
*
|
|
55
|
+
* @param response - Fetch response
|
|
56
|
+
* @returns Parsed JSON object
|
|
57
|
+
* @throws Error if response is not OK or JSON parsing fails
|
|
58
|
+
*/
|
|
59
|
+
static parseJsonResponse<T = unknown>(response: Response): Promise<T>;
|
|
60
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { buildUserAgent } from '../utils/user-agent.js';
|
|
2
|
+
/**
|
|
3
|
+
* Utility functions for making HTTP requests with the configured mTLS client.
|
|
4
|
+
*/
|
|
5
|
+
export class HttpClientExtensions {
|
|
6
|
+
/**
|
|
7
|
+
* Creates standard headers for FAPI requests.
|
|
8
|
+
*
|
|
9
|
+
* @param options - Request configuration including client ID and interaction ID
|
|
10
|
+
* @returns Headers object with required FAPI headers
|
|
11
|
+
*/
|
|
12
|
+
static createFapiHeaders(options) {
|
|
13
|
+
const headers = {
|
|
14
|
+
'User-Agent': buildUserAgent(options.clientId),
|
|
15
|
+
'x-fapi-interaction-id': options.xFapiInteractionId,
|
|
16
|
+
};
|
|
17
|
+
if (options.contentType) {
|
|
18
|
+
headers['Content-Type'] = options.contentType;
|
|
19
|
+
}
|
|
20
|
+
if (options.additionalHeaders) {
|
|
21
|
+
Object.assign(headers, options.additionalHeaders);
|
|
22
|
+
}
|
|
23
|
+
return headers;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Makes a POST request with form-encoded body.
|
|
27
|
+
*
|
|
28
|
+
* @param url - Target URL
|
|
29
|
+
* @param body - Form data as URLSearchParams
|
|
30
|
+
* @param options - Request options including agent and headers
|
|
31
|
+
* @returns Response promise
|
|
32
|
+
*/
|
|
33
|
+
static async postForm(url, body, options) {
|
|
34
|
+
const headers = this.createFapiHeaders({
|
|
35
|
+
...options,
|
|
36
|
+
contentType: 'application/x-www-form-urlencoded',
|
|
37
|
+
});
|
|
38
|
+
return fetch(url, {
|
|
39
|
+
method: 'POST',
|
|
40
|
+
headers,
|
|
41
|
+
body: body.toString(),
|
|
42
|
+
dispatcher: options.agent, // undici uses 'dispatcher' instead of 'agent'
|
|
43
|
+
redirect: 'manual', // Don't follow redirects automatically
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Makes a GET request with FAPI headers.
|
|
48
|
+
*
|
|
49
|
+
* @param url - Target URL
|
|
50
|
+
* @param options - Request options including agent and headers
|
|
51
|
+
* @returns Response promise
|
|
52
|
+
*/
|
|
53
|
+
static async get(url, options) {
|
|
54
|
+
const headers = this.createFapiHeaders(options);
|
|
55
|
+
return fetch(url, {
|
|
56
|
+
method: 'GET',
|
|
57
|
+
headers,
|
|
58
|
+
dispatcher: options.agent, // undici uses 'dispatcher' instead of 'agent'
|
|
59
|
+
redirect: 'manual',
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Makes a GET request with Bearer token authentication.
|
|
64
|
+
*
|
|
65
|
+
* @param url - Target URL
|
|
66
|
+
* @param accessToken - Bearer access token
|
|
67
|
+
* @param options - Request options including agent and headers
|
|
68
|
+
* @returns Response promise
|
|
69
|
+
*/
|
|
70
|
+
static async getWithToken(url, accessToken, options) {
|
|
71
|
+
const headers = this.createFapiHeaders({
|
|
72
|
+
...options,
|
|
73
|
+
additionalHeaders: {
|
|
74
|
+
...options.additionalHeaders,
|
|
75
|
+
Authorization: `Bearer ${accessToken}`,
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
return fetch(url, {
|
|
79
|
+
method: 'GET',
|
|
80
|
+
headers,
|
|
81
|
+
dispatcher: options.agent, // undici uses 'dispatcher' instead of 'agent'
|
|
82
|
+
redirect: 'manual',
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Parses a JSON response body.
|
|
87
|
+
*
|
|
88
|
+
* @param response - Fetch response
|
|
89
|
+
* @returns Parsed JSON object
|
|
90
|
+
* @throws Error if response is not OK or JSON parsing fails
|
|
91
|
+
*/
|
|
92
|
+
static async parseJsonResponse(response) {
|
|
93
|
+
if (!response.ok) {
|
|
94
|
+
let errorDetails = '';
|
|
95
|
+
try {
|
|
96
|
+
const errorBody = await response.text();
|
|
97
|
+
errorDetails = errorBody ? `: ${errorBody}` : '';
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
// Ignore errors when reading error body
|
|
101
|
+
}
|
|
102
|
+
throw new Error(`HTTP request failed: ${response.status} ${response.statusText}${errorDetails}`);
|
|
103
|
+
}
|
|
104
|
+
return await response.json();
|
|
105
|
+
}
|
|
106
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Agent } from 'undici';
|
|
2
|
+
export interface HttpClientConfig {
|
|
3
|
+
transportKey: string | Buffer;
|
|
4
|
+
transportPem: string | Buffer;
|
|
5
|
+
caPem: string | Buffer;
|
|
6
|
+
clientId: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Factory for creating HTTP clients with mTLS support.
|
|
10
|
+
*
|
|
11
|
+
* This factory creates undici.Agent instances configured with:
|
|
12
|
+
* - Mutual TLS (mTLS) using transport certificates
|
|
13
|
+
* - CA certificate chain validation
|
|
14
|
+
* - Appropriate timeouts for OIDC/FAPI operations
|
|
15
|
+
*/
|
|
16
|
+
export declare class HttpClientFactory {
|
|
17
|
+
/**
|
|
18
|
+
* Creates an undici Agent configured for mTLS operations.
|
|
19
|
+
*
|
|
20
|
+
* undici.Agent is used instead of https.Agent because Node.js's
|
|
21
|
+
* native fetch API properly supports mTLS when using undici's Agent.
|
|
22
|
+
*
|
|
23
|
+
* @param config - Configuration containing certificates and client ID
|
|
24
|
+
* @returns Configured undici.Agent for use with fetch
|
|
25
|
+
*/
|
|
26
|
+
static createClient(config: HttpClientConfig): Agent;
|
|
27
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { Agent } from 'undici';
|
|
2
|
+
import { rootCertificates } from 'node:tls';
|
|
3
|
+
/**
|
|
4
|
+
* Factory for creating HTTP clients with mTLS support.
|
|
5
|
+
*
|
|
6
|
+
* This factory creates undici.Agent instances configured with:
|
|
7
|
+
* - Mutual TLS (mTLS) using transport certificates
|
|
8
|
+
* - CA certificate chain validation
|
|
9
|
+
* - Appropriate timeouts for OIDC/FAPI operations
|
|
10
|
+
*/
|
|
11
|
+
export class HttpClientFactory {
|
|
12
|
+
/**
|
|
13
|
+
* Creates an undici Agent configured for mTLS operations.
|
|
14
|
+
*
|
|
15
|
+
* undici.Agent is used instead of https.Agent because Node.js's
|
|
16
|
+
* native fetch API properly supports mTLS when using undici's Agent.
|
|
17
|
+
*
|
|
18
|
+
* @param config - Configuration containing certificates and client ID
|
|
19
|
+
* @returns Configured undici.Agent for use with fetch
|
|
20
|
+
*/
|
|
21
|
+
static createClient(config) {
|
|
22
|
+
// Combine custom CA with system root certificates
|
|
23
|
+
const caCerts = [config.caPem, ...rootCertificates].join('\n');
|
|
24
|
+
return new Agent({
|
|
25
|
+
// mTLS client certificate and key
|
|
26
|
+
connect: {
|
|
27
|
+
cert: config.transportPem,
|
|
28
|
+
key: config.transportKey,
|
|
29
|
+
ca: caCerts,
|
|
30
|
+
rejectUnauthorized: true,
|
|
31
|
+
// Request certificate from server
|
|
32
|
+
requestCert: true,
|
|
33
|
+
},
|
|
34
|
+
// Timeout configuration (60 seconds)
|
|
35
|
+
bodyTimeout: 60000,
|
|
36
|
+
headersTimeout: 60000,
|
|
37
|
+
connectTimeout: 60000,
|
|
38
|
+
// Keep connections alive for performance
|
|
39
|
+
keepAliveTimeout: 30000,
|
|
40
|
+
keepAliveMaxTimeout: 60000,
|
|
41
|
+
// Connection pooling
|
|
42
|
+
connections: 50,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OAuth 2.0 Authorization Response Parameters
|
|
3
|
+
*
|
|
4
|
+
* Parameters returned to the redirect URI after authorization.
|
|
5
|
+
*/
|
|
6
|
+
export interface CallbackParams {
|
|
7
|
+
/**
|
|
8
|
+
* The authorization code generated by the authorization server.
|
|
9
|
+
*/
|
|
10
|
+
code?: string;
|
|
11
|
+
/**
|
|
12
|
+
* The state parameter from the authorization request.
|
|
13
|
+
*/
|
|
14
|
+
state?: string;
|
|
15
|
+
/**
|
|
16
|
+
* The issuer identifier (OIDC extension).
|
|
17
|
+
*/
|
|
18
|
+
iss?: string;
|
|
19
|
+
/**
|
|
20
|
+
* Error code if the request failed.
|
|
21
|
+
*/
|
|
22
|
+
error?: string;
|
|
23
|
+
/**
|
|
24
|
+
* Human-readable error description.
|
|
25
|
+
*/
|
|
26
|
+
error_description?: string;
|
|
27
|
+
/**
|
|
28
|
+
* URI identifying a human-readable web page with error information.
|
|
29
|
+
*/
|
|
30
|
+
error_uri?: string;
|
|
31
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|