@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.
Files changed (56) hide show
  1. package/README.md +64 -71
  2. package/config.js +2 -31
  3. package/conformance/api/conformance-api.d.ts +38 -0
  4. package/conformance/api/conformance-api.js +53 -0
  5. package/conformance/conformance-config.d.ts +2 -0
  6. package/conformance/conformance-config.js +34 -0
  7. package/crypto/crypto-loader.d.ts +32 -0
  8. package/crypto/crypto-loader.js +49 -0
  9. package/crypto/jwt-helper.d.ts +61 -0
  10. package/crypto/jwt-helper.js +92 -0
  11. package/crypto/pkce-helper.d.ts +43 -0
  12. package/crypto/pkce-helper.js +75 -0
  13. package/endpoints/participants-endpoint.d.ts +55 -0
  14. package/endpoints/participants-endpoint.js +137 -0
  15. package/endpoints/pushed-authorisation-request-endpoint.d.ts +87 -0
  16. package/endpoints/pushed-authorisation-request-endpoint.js +192 -0
  17. package/endpoints/retrieve-token-endpoint.d.ts +66 -0
  18. package/endpoints/retrieve-token-endpoint.js +159 -0
  19. package/endpoints/userinfo-endpoint.d.ts +24 -0
  20. package/endpoints/userinfo-endpoint.js +50 -0
  21. package/fapi/fapi-utils.d.ts +6 -0
  22. package/fapi/fapi-utils.js +9 -0
  23. package/http/http-client-extensions.d.ts +60 -0
  24. package/http/http-client-extensions.js +106 -0
  25. package/http/http-client-factory.d.ts +27 -0
  26. package/http/http-client-factory.js +45 -0
  27. package/model/callback-params.d.ts +31 -0
  28. package/model/callback-params.js +1 -0
  29. package/model/claims.d.ts +100 -0
  30. package/model/claims.js +1 -0
  31. package/model/consolidated-token-set.d.ts +74 -0
  32. package/model/consolidated-token-set.js +100 -0
  33. package/model/discovery-service.d.ts +46 -0
  34. package/model/discovery-service.js +112 -0
  35. package/model/issuer-metadata.d.ts +165 -0
  36. package/model/issuer-metadata.js +1 -0
  37. package/model/jwks.d.ts +12 -0
  38. package/model/jwks.js +1 -0
  39. package/model/token-response.d.ts +31 -0
  40. package/model/token-response.js +1 -0
  41. package/model/token-set.d.ts +73 -0
  42. package/model/token-set.js +179 -0
  43. package/package.json +4 -5
  44. package/relying-party-client-sdk.d.ts +55 -24
  45. package/relying-party-client-sdk.js +90 -304
  46. package/test-data/large-participants-test-data.d.ts +865 -0
  47. package/test-data/large-participants-test-data.js +18907 -0
  48. package/test-data/participants-test-data.d.ts +149 -0
  49. package/test-data/participants-test-data.js +458 -0
  50. package/test-data/sandbox-participants-test-data.d.ts +865 -0
  51. package/test-data/sandbox-participants-test-data.js +3794 -0
  52. package/types.d.ts +61 -32
  53. package/utils/request-utils.d.ts +1 -1
  54. package/utils/request-utils.js +5 -5
  55. package/utils/user-agent.d.ts +1 -1
  56. 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,6 @@
1
+ /**
2
+ * Generates a unique x-fapi-interaction-id.
3
+ *
4
+ * @returns UUID string
5
+ */
6
+ export declare const generateXFapiInteractionId: () => `${string}-${string}-${string}-${string}-${string}`;
@@ -0,0 +1,9 @@
1
+ import { randomUUID } from 'crypto';
2
+ /**
3
+ * Generates a unique x-fapi-interaction-id.
4
+ *
5
+ * @returns UUID string
6
+ */
7
+ export const generateXFapiInteractionId = () => {
8
+ return randomUUID();
9
+ };
@@ -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 {};