@connectid-tools/rp-nodejs-sdk 4.2.1 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/README.md +60 -71
  2. package/package.json +4 -5
  3. package/{config.js → src/config.js} +2 -31
  4. package/src/conformance/api/conformance-api.d.ts +38 -0
  5. package/src/conformance/api/conformance-api.js +53 -0
  6. package/src/conformance/config.json +60 -0
  7. package/src/conformance/conformance-config.d.ts +2 -0
  8. package/src/conformance/conformance-config.js +34 -0
  9. package/src/conformance/conformance.test.js +101 -0
  10. package/src/conformance/variant.json +1 -0
  11. package/src/crypto/crypto-loader.d.ts +32 -0
  12. package/src/crypto/crypto-loader.js +49 -0
  13. package/src/crypto/jwt-helper.d.ts +61 -0
  14. package/src/crypto/jwt-helper.js +92 -0
  15. package/src/crypto/pkce-helper.d.ts +43 -0
  16. package/src/crypto/pkce-helper.js +75 -0
  17. package/src/endpoints/participants-endpoint.d.ts +55 -0
  18. package/src/endpoints/participants-endpoint.js +137 -0
  19. package/src/endpoints/pushed-authorisation-request-endpoint.d.ts +87 -0
  20. package/src/endpoints/pushed-authorisation-request-endpoint.js +192 -0
  21. package/src/endpoints/retrieve-token-endpoint.d.ts +66 -0
  22. package/src/endpoints/retrieve-token-endpoint.js +159 -0
  23. package/src/endpoints/userinfo-endpoint.d.ts +24 -0
  24. package/src/endpoints/userinfo-endpoint.js +50 -0
  25. package/src/fapi/fapi-utils.d.ts +6 -0
  26. package/src/fapi/fapi-utils.js +9 -0
  27. package/src/http/http-client-extensions.d.ts +60 -0
  28. package/src/http/http-client-extensions.js +106 -0
  29. package/src/http/http-client-factory.d.ts +27 -0
  30. package/src/http/http-client-factory.js +45 -0
  31. package/src/integration/integration.test.d.ts +1 -0
  32. package/src/integration/integration.test.js +30 -0
  33. package/src/model/callback-params.d.ts +31 -0
  34. package/src/model/callback-params.js +1 -0
  35. package/src/model/claims.d.ts +100 -0
  36. package/src/model/claims.js +1 -0
  37. package/src/model/consolidated-token-set.d.ts +74 -0
  38. package/src/model/consolidated-token-set.js +100 -0
  39. package/src/model/discovery-service.d.ts +46 -0
  40. package/src/model/discovery-service.js +112 -0
  41. package/src/model/issuer-metadata.d.ts +165 -0
  42. package/src/model/issuer-metadata.js +1 -0
  43. package/src/model/jwks.d.ts +12 -0
  44. package/src/model/jwks.js +1 -0
  45. package/src/model/token-response.d.ts +31 -0
  46. package/src/model/token-response.js +1 -0
  47. package/src/model/token-set.d.ts +73 -0
  48. package/src/model/token-set.js +179 -0
  49. package/src/relying-party-client-sdk.d.ts +68 -0
  50. package/src/relying-party-client-sdk.js +150 -0
  51. package/src/test-data/large-participants-test-data.d.ts +865 -0
  52. package/src/test-data/large-participants-test-data.js +18907 -0
  53. package/src/test-data/participants-test-data.d.ts +149 -0
  54. package/src/test-data/participants-test-data.js +458 -0
  55. package/src/test-data/sandbox-participants-test-data.d.ts +865 -0
  56. package/src/test-data/sandbox-participants-test-data.js +3794 -0
  57. package/src/tests/cert-utils.test.d.ts +1 -0
  58. package/src/tests/cert-utils.test.js +13 -0
  59. package/src/tests/functional-utils.test.d.ts +1 -0
  60. package/src/tests/functional-utils.test.js +13 -0
  61. package/src/tests/participant-filters.test.d.ts +1 -0
  62. package/src/tests/participant-filters.test.js +151 -0
  63. package/src/tests/pushed-authorisation-request-endpoint.test.d.ts +1 -0
  64. package/src/tests/pushed-authorisation-request-endpoint.test.js +159 -0
  65. package/src/tests/relying-party-client-sdk.test.d.ts +1 -0
  66. package/src/tests/relying-party-client-sdk.test.js +313 -0
  67. package/src/tests/request-utils.test.d.ts +1 -0
  68. package/src/tests/request-utils.test.js +16 -0
  69. package/src/tests/system-information.test.d.ts +1 -0
  70. package/src/tests/system-information.test.js +16 -0
  71. package/src/tests/user-agent.test.d.ts +1 -0
  72. package/src/tests/user-agent.test.js +23 -0
  73. package/src/tests/validator.test.d.ts +1 -0
  74. package/src/tests/validator.test.js +38 -0
  75. package/{types.d.ts → src/types.d.ts} +61 -32
  76. package/src/types.js +1 -0
  77. package/{utils → src/utils}/request-utils.d.ts +1 -1
  78. package/src/utils/request-utils.js +8 -0
  79. package/{utils → src/utils}/user-agent.d.ts +1 -1
  80. package/{utils → src/utils}/user-agent.js +1 -1
  81. package/relying-party-client-sdk.d.ts +0 -37
  82. package/relying-party-client-sdk.js +0 -364
  83. package/utils/request-utils.js +0 -8
  84. /package/{config.d.ts → src/config.d.ts} +0 -0
  85. /package/{types.js → src/conformance/conformance.test.d.ts} +0 -0
  86. /package/{filter → src/filter}/participant-filters.d.ts +0 -0
  87. /package/{filter → src/filter}/participant-filters.js +0 -0
  88. /package/{logger.d.ts → src/logger.d.ts} +0 -0
  89. /package/{logger.js → src/logger.js} +0 -0
  90. /package/{utils → src/utils}/cert-utils.d.ts +0 -0
  91. /package/{utils → src/utils}/cert-utils.js +0 -0
  92. /package/{utils → src/utils}/functional-utils.d.ts +0 -0
  93. /package/{utils → src/utils}/functional-utils.js +0 -0
  94. /package/{utils → src/utils}/system-information.d.ts +0 -0
  95. /package/{utils → src/utils}/system-information.js +0 -0
  96. /package/{validator.d.ts → src/validator.d.ts} +0 -0
  97. /package/{validator.js → src/validator.js} +0 -0
@@ -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 @@
1
+ export {};
@@ -0,0 +1,30 @@
1
+ import jsonPackage from '../../package.json';
2
+ import { describe, it } from 'node:test';
3
+ import assert from 'node:assert';
4
+ import { config } from '../config.js';
5
+ import RelyingPartyClientSdk from '../relying-party-client-sdk.js';
6
+ describe('RelyingPartyClientSdk', () => {
7
+ it('should retrieve user agent and match details', async () => {
8
+ const version = jsonPackage.version;
9
+ const newConfig = {
10
+ ...config,
11
+ data: {
12
+ ...config.data,
13
+ registry_participants_uri: 'https://api.sandbox.connectid.com.au/useragent-responder/participants',
14
+ },
15
+ };
16
+ const participants = await new RelyingPartyClientSdk(newConfig).getParticipants();
17
+ const regex = /(.*?)\/(.*?)\s(\(.*?\))\s(\+.*)/;
18
+ const organisationName = participants[0].OrganisationName;
19
+ const match = organisationName.match(regex);
20
+ assert.ok(match, `Organisation name '${organisationName}' failed to match pattern '${regex.source}'`);
21
+ const namePart = match[1];
22
+ const versionPart = match[2];
23
+ const platformPart = match[3].trim();
24
+ const clientIdPart = match[4];
25
+ assert.strictEqual(namePart, 'cid-rp-nodejs-sdk');
26
+ assert.strictEqual(versionPart, version);
27
+ assert.match(platformPart, /\(.+; .+; .+\)/);
28
+ assert.strictEqual(clientIdPart, '+https://rp.directory.sandbox.connectid.com.au/openid_relying_party/280518db-9807-4824-b080-324d94b45f6a');
29
+ });
30
+ });
@@ -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 {};
@@ -0,0 +1,100 @@
1
+ import { JWTPayload } from 'jose';
2
+ /**
3
+ * Address Claim as defined in OIDC Core spec.
4
+ */
5
+ export interface AddressClaim {
6
+ formatted?: string;
7
+ street_address?: string;
8
+ locality?: string;
9
+ region?: string;
10
+ postal_code?: string;
11
+ country?: string;
12
+ }
13
+ /**
14
+ * Verified Claims structure for extended claims (ConnectID).
15
+ */
16
+ export interface VerifiedClaims {
17
+ verification?: {
18
+ trust_framework?: {
19
+ value?: string;
20
+ };
21
+ time?: string;
22
+ verification_process?: string;
23
+ evidence?: unknown[];
24
+ };
25
+ claims?: {
26
+ [key: string]: unknown;
27
+ over16?: boolean;
28
+ over18?: boolean;
29
+ over21?: boolean;
30
+ over25?: boolean;
31
+ over65?: boolean;
32
+ beneficiary_account_au?: unknown;
33
+ beneficiary_account_au_payid?: unknown;
34
+ beneficiary_account_international?: unknown;
35
+ cba_loyalty?: unknown;
36
+ };
37
+ }
38
+ /**
39
+ * ID Token Claims
40
+ *
41
+ * Extends the standard JWT payload with OIDC-specific claims.
42
+ * Includes both standard OIDC claims and ConnectID extensions.
43
+ */
44
+ export interface IdTokenClaims extends JWTPayload {
45
+ /**
46
+ * Subject identifier (unique user ID).
47
+ */
48
+ sub: string;
49
+ name?: string;
50
+ given_name?: string;
51
+ middle_name?: string;
52
+ family_name?: string;
53
+ nickname?: string;
54
+ preferred_username?: string;
55
+ profile?: string;
56
+ picture?: string;
57
+ website?: string;
58
+ email?: string;
59
+ email_verified?: boolean;
60
+ gender?: string;
61
+ birthdate?: string;
62
+ zoneinfo?: string;
63
+ locale?: string;
64
+ phone_number?: string;
65
+ phone_number_verified?: boolean;
66
+ address?: AddressClaim;
67
+ updated_at?: number;
68
+ /**
69
+ * Authentication time (Unix timestamp).
70
+ */
71
+ auth_time?: number;
72
+ /**
73
+ * Nonce value for replay protection.
74
+ */
75
+ nonce?: string;
76
+ /**
77
+ * Transaction identifier.
78
+ */
79
+ txn?: string;
80
+ /**
81
+ * Authentication Context Class Reference.
82
+ */
83
+ acr?: string;
84
+ /**
85
+ * Authentication Methods References.
86
+ */
87
+ amr?: string[];
88
+ /**
89
+ * Authorized party (client ID of the party to which the ID token was issued).
90
+ */
91
+ azp?: string;
92
+ /**
93
+ * Verified claims for extended attributes.
94
+ */
95
+ verified_claims?: VerifiedClaims;
96
+ /**
97
+ * Allows for additional custom claims.
98
+ */
99
+ [key: string]: unknown;
100
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,74 @@
1
+ import { IdTokenClaims } from './claims.js';
2
+ import { TokenSet } from './token-set.js';
3
+ import { ConsolidatedTokenSet as IConsolidatedTokenSet } from '../types.js';
4
+ /**
5
+ * Consolidated Token Set
6
+ *
7
+ * Wraps a TokenSet and provides additional convenience methods
8
+ * for accessing token data and claims.
9
+ *
10
+ * Implements the ConsolidatedTokenSet interface from types.ts.
11
+ */
12
+ export declare class ConsolidatedTokenSet implements IConsolidatedTokenSet {
13
+ private tokenSet;
14
+ readonly xFapiInteractionId: string;
15
+ /**
16
+ * Creates a new ConsolidatedTokenSet.
17
+ *
18
+ * @param tokenSet - Validated token set
19
+ * @param xFapiInteractionId - FAPI interaction ID from the response
20
+ */
21
+ constructor(tokenSet: TokenSet, xFapiInteractionId: string);
22
+ get access_token(): string | undefined;
23
+ get token_type(): string | undefined;
24
+ get expires_in(): number | undefined;
25
+ get refresh_token(): string | undefined;
26
+ get scope(): string | undefined;
27
+ get id_token(): string | undefined;
28
+ /**
29
+ * Checks if the access token has expired.
30
+ *
31
+ * @returns true if the token is expired, false otherwise
32
+ */
33
+ expired(): boolean;
34
+ /**
35
+ * Returns the parsed ID token claims.
36
+ *
37
+ * @returns Parsed and validated ID token claims
38
+ */
39
+ claims(): IdTokenClaims;
40
+ /**
41
+ * Returns consolidated claims with verified_claims merged into top level.
42
+ *
43
+ * This method extracts extended claims from the verified_claims structure
44
+ * and merges them into the top-level claims object for easier access.
45
+ *
46
+ * For example, if the ID token contains:
47
+ * ```json
48
+ * {
49
+ * "sub": "12345",
50
+ * "name": "John Doe",
51
+ * "verified_claims": {
52
+ * "claims": {
53
+ * "over18": true,
54
+ * "over21": false
55
+ * }
56
+ * }
57
+ * }
58
+ * ```
59
+ *
60
+ * This method will return:
61
+ * ```json
62
+ * {
63
+ * "sub": "12345",
64
+ * "name": "John Doe",
65
+ * "over18": true,
66
+ * "over21": false,
67
+ * "verified_claims": { ... }
68
+ * }
69
+ * ```
70
+ *
71
+ * @returns Consolidated claims object
72
+ */
73
+ consolidatedClaims(): IdTokenClaims;
74
+ }
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Consolidated Token Set
3
+ *
4
+ * Wraps a TokenSet and provides additional convenience methods
5
+ * for accessing token data and claims.
6
+ *
7
+ * Implements the ConsolidatedTokenSet interface from types.ts.
8
+ */
9
+ export class ConsolidatedTokenSet {
10
+ /**
11
+ * Creates a new ConsolidatedTokenSet.
12
+ *
13
+ * @param tokenSet - Validated token set
14
+ * @param xFapiInteractionId - FAPI interaction ID from the response
15
+ */
16
+ constructor(tokenSet, xFapiInteractionId) {
17
+ this.tokenSet = tokenSet;
18
+ this.xFapiInteractionId = xFapiInteractionId;
19
+ }
20
+ // Delegate token properties to underlying TokenSet
21
+ get access_token() {
22
+ return this.tokenSet.access_token;
23
+ }
24
+ get token_type() {
25
+ return this.tokenSet.token_type;
26
+ }
27
+ get expires_in() {
28
+ return this.tokenSet.expires_in;
29
+ }
30
+ get refresh_token() {
31
+ return this.tokenSet.refresh_token;
32
+ }
33
+ get scope() {
34
+ return this.tokenSet.scope;
35
+ }
36
+ get id_token() {
37
+ return this.tokenSet.id_token;
38
+ }
39
+ /**
40
+ * Checks if the access token has expired.
41
+ *
42
+ * @returns true if the token is expired, false otherwise
43
+ */
44
+ expired() {
45
+ return this.tokenSet.expired();
46
+ }
47
+ /**
48
+ * Returns the parsed ID token claims.
49
+ *
50
+ * @returns Parsed and validated ID token claims
51
+ */
52
+ claims() {
53
+ return this.tokenSet.claims();
54
+ }
55
+ /**
56
+ * Returns consolidated claims with verified_claims merged into top level.
57
+ *
58
+ * This method extracts extended claims from the verified_claims structure
59
+ * and merges them into the top-level claims object for easier access.
60
+ *
61
+ * For example, if the ID token contains:
62
+ * ```json
63
+ * {
64
+ * "sub": "12345",
65
+ * "name": "John Doe",
66
+ * "verified_claims": {
67
+ * "claims": {
68
+ * "over18": true,
69
+ * "over21": false
70
+ * }
71
+ * }
72
+ * }
73
+ * ```
74
+ *
75
+ * This method will return:
76
+ * ```json
77
+ * {
78
+ * "sub": "12345",
79
+ * "name": "John Doe",
80
+ * "over18": true,
81
+ * "over21": false,
82
+ * "verified_claims": { ... }
83
+ * }
84
+ * ```
85
+ *
86
+ * @returns Consolidated claims object
87
+ */
88
+ consolidatedClaims() {
89
+ const claims = this.claims();
90
+ // If there are no verified_claims, return claims as-is
91
+ if (!claims.verified_claims?.claims) {
92
+ return claims;
93
+ }
94
+ // Merge verified_claims.claims into top level
95
+ return {
96
+ ...claims,
97
+ ...claims.verified_claims.claims,
98
+ };
99
+ }
100
+ }
@@ -0,0 +1,46 @@
1
+ import { Agent } from 'undici';
2
+ import { IssuerMetadata } from './issuer-metadata.js';
3
+ import { JWKSet } from './jwks.js';
4
+ /**
5
+ * Service for fetching OIDC discovery documents and JWKS.
6
+ *
7
+ * Handles fetching and parsing of OpenID Connect discovery documents
8
+ * and JSON Web Key Sets from authorization servers.
9
+ */
10
+ export declare class DiscoveryService {
11
+ /**
12
+ * Fetches and parses an OIDC discovery document.
13
+ *
14
+ * @param discoveryUrl - URL to the .well-known/openid-configuration endpoint
15
+ * @param httpAgent - Optional undici Agent for mTLS
16
+ * @returns Parsed issuer metadata
17
+ * @throws Error if the discovery document cannot be fetched or parsed
18
+ */
19
+ static fetchDiscoveryDocument(discoveryUrl: string, httpAgent?: Agent): Promise<IssuerMetadata>;
20
+ /**
21
+ * Fetches and parses a JWKS document.
22
+ *
23
+ * @param jwksUri - URL to the JWKS endpoint
24
+ * @param httpAgent - Optional HTTPS agent for mTLS
25
+ * @returns Parsed JWKS
26
+ * @throws Error if the JWKS cannot be fetched or parsed
27
+ */
28
+ static fetchJwks(jwksUri: string, httpAgent?: Agent): Promise<JWKSet>;
29
+ /**
30
+ * Validates that required discovery document fields are present.
31
+ *
32
+ * @param metadata - Discovery document to validate
33
+ * @throws Error if required fields are missing
34
+ */
35
+ private static validateDiscoveryDocument;
36
+ /**
37
+ * Applies mtls_endpoint_aliases to override standard endpoints.
38
+ *
39
+ * If mtls_endpoint_aliases are present, they should be used instead of
40
+ * the standard endpoints for certificate-bound operations.
41
+ *
42
+ * @param metadata - Original discovery metadata
43
+ * @returns Metadata with mTLS aliases applied
44
+ */
45
+ private static applyMtlsAliases;
46
+ }
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Service for fetching OIDC discovery documents and JWKS.
3
+ *
4
+ * Handles fetching and parsing of OpenID Connect discovery documents
5
+ * and JSON Web Key Sets from authorization servers.
6
+ */
7
+ export class DiscoveryService {
8
+ /**
9
+ * Fetches and parses an OIDC discovery document.
10
+ *
11
+ * @param discoveryUrl - URL to the .well-known/openid-configuration endpoint
12
+ * @param httpAgent - Optional undici Agent for mTLS
13
+ * @returns Parsed issuer metadata
14
+ * @throws Error if the discovery document cannot be fetched or parsed
15
+ */
16
+ static async fetchDiscoveryDocument(discoveryUrl, httpAgent) {
17
+ try {
18
+ const response = await fetch(discoveryUrl, {
19
+ method: 'GET',
20
+ headers: {
21
+ Accept: 'application/json',
22
+ },
23
+ dispatcher: httpAgent, // undici uses 'dispatcher' instead of 'agent'
24
+ });
25
+ if (!response.ok) {
26
+ throw new Error(`Failed to fetch discovery document: ${response.status} ${response.statusText}`);
27
+ }
28
+ const metadata = (await response.json());
29
+ // Validate required fields
30
+ this.validateDiscoveryDocument(metadata);
31
+ // Apply mtls_endpoint_aliases if present
32
+ return this.applyMtlsAliases(metadata);
33
+ }
34
+ catch (error) {
35
+ throw new Error(`Failed to fetch discovery document from ${discoveryUrl}: ${error instanceof Error ? error.message : String(error)}`);
36
+ }
37
+ }
38
+ /**
39
+ * Fetches and parses a JWKS document.
40
+ *
41
+ * @param jwksUri - URL to the JWKS endpoint
42
+ * @param httpAgent - Optional HTTPS agent for mTLS
43
+ * @returns Parsed JWKS
44
+ * @throws Error if the JWKS cannot be fetched or parsed
45
+ */
46
+ static async fetchJwks(jwksUri, httpAgent) {
47
+ try {
48
+ const response = await fetch(jwksUri, {
49
+ method: 'GET',
50
+ headers: {
51
+ Accept: 'application/json',
52
+ },
53
+ dispatcher: httpAgent, // undici uses 'dispatcher' instead of 'agent'
54
+ });
55
+ if (!response.ok) {
56
+ throw new Error(`Failed to fetch JWKS: ${response.status} ${response.statusText}`);
57
+ }
58
+ const jwks = (await response.json());
59
+ // Validate JWKS structure
60
+ if (!jwks.keys || !Array.isArray(jwks.keys)) {
61
+ throw new Error('Invalid JWKS: missing or invalid keys array');
62
+ }
63
+ return jwks;
64
+ }
65
+ catch (error) {
66
+ throw new Error(`Failed to fetch JWKS from ${jwksUri}: ${error instanceof Error ? error.message : String(error)}`);
67
+ }
68
+ }
69
+ /**
70
+ * Validates that required discovery document fields are present.
71
+ *
72
+ * @param metadata - Discovery document to validate
73
+ * @throws Error if required fields are missing
74
+ */
75
+ static validateDiscoveryDocument(metadata) {
76
+ const requiredFields = [
77
+ 'issuer',
78
+ 'authorization_endpoint',
79
+ 'token_endpoint',
80
+ 'jwks_uri',
81
+ ];
82
+ for (const field of requiredFields) {
83
+ if (!metadata[field]) {
84
+ throw new Error(`Discovery document missing required field: ${field}`);
85
+ }
86
+ }
87
+ }
88
+ /**
89
+ * Applies mtls_endpoint_aliases to override standard endpoints.
90
+ *
91
+ * If mtls_endpoint_aliases are present, they should be used instead of
92
+ * the standard endpoints for certificate-bound operations.
93
+ *
94
+ * @param metadata - Original discovery metadata
95
+ * @returns Metadata with mTLS aliases applied
96
+ */
97
+ static applyMtlsAliases(metadata) {
98
+ if (!metadata.mtls_endpoint_aliases) {
99
+ return metadata;
100
+ }
101
+ const aliases = metadata.mtls_endpoint_aliases;
102
+ return {
103
+ ...metadata,
104
+ token_endpoint: aliases.token_endpoint || metadata.token_endpoint,
105
+ pushed_authorization_request_endpoint: aliases.pushed_authorization_request_endpoint ||
106
+ metadata.pushed_authorization_request_endpoint,
107
+ userinfo_endpoint: aliases.userinfo_endpoint || metadata.userinfo_endpoint,
108
+ revocation_endpoint: aliases.revocation_endpoint || metadata.revocation_endpoint,
109
+ introspection_endpoint: aliases.introspection_endpoint || metadata.introspection_endpoint,
110
+ };
111
+ }
112
+ }