@connectid-tools/rp-nodejs-sdk 5.0.0 → 5.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +131 -1
- package/cache/http-response-cache.d.ts +83 -0
- package/cache/http-response-cache.js +157 -0
- package/{src/endpoints → endpoints}/participants-endpoint.d.ts +5 -2
- package/{src/endpoints → endpoints}/participants-endpoint.js +20 -3
- package/{src/endpoints → endpoints}/pushed-authorisation-request-endpoint.d.ts +3 -1
- package/{src/endpoints → endpoints}/pushed-authorisation-request-endpoint.js +3 -2
- package/{src/endpoints → endpoints}/retrieve-token-endpoint.d.ts +3 -1
- package/{src/endpoints → endpoints}/retrieve-token-endpoint.js +4 -3
- package/{src/endpoints → endpoints}/userinfo-endpoint.d.ts +3 -1
- package/{src/endpoints → endpoints}/userinfo-endpoint.js +3 -2
- package/{src/model → model}/discovery-service.d.ts +7 -2
- package/{src/model → model}/discovery-service.js +32 -3
- package/package.json +1 -1
- package/{src/relying-party-client-sdk.js → relying-party-client-sdk.js} +14 -5
- package/{src/types.d.ts → types.d.ts} +7 -0
- package/{src/utils → utils}/user-agent.d.ts +1 -1
- package/{src/utils → utils}/user-agent.js +1 -1
- package/src/conformance/config.json +0 -60
- package/src/conformance/conformance.test.d.ts +0 -1
- package/src/conformance/conformance.test.js +0 -101
- package/src/conformance/variant.json +0 -1
- package/src/integration/integration.test.d.ts +0 -1
- package/src/integration/integration.test.js +0 -30
- package/src/tests/cert-utils.test.d.ts +0 -1
- package/src/tests/cert-utils.test.js +0 -13
- package/src/tests/functional-utils.test.d.ts +0 -1
- package/src/tests/functional-utils.test.js +0 -13
- package/src/tests/participant-filters.test.d.ts +0 -1
- package/src/tests/participant-filters.test.js +0 -151
- package/src/tests/pushed-authorisation-request-endpoint.test.d.ts +0 -1
- package/src/tests/pushed-authorisation-request-endpoint.test.js +0 -159
- package/src/tests/relying-party-client-sdk.test.d.ts +0 -1
- package/src/tests/relying-party-client-sdk.test.js +0 -313
- package/src/tests/request-utils.test.d.ts +0 -1
- package/src/tests/request-utils.test.js +0 -16
- package/src/tests/system-information.test.d.ts +0 -1
- package/src/tests/system-information.test.js +0 -16
- package/src/tests/user-agent.test.d.ts +0 -1
- package/src/tests/user-agent.test.js +0 -23
- package/src/tests/validator.test.d.ts +0 -1
- package/src/tests/validator.test.js +0 -38
- /package/{src/config.d.ts → config.d.ts} +0 -0
- /package/{src/config.js → config.js} +0 -0
- /package/{src/conformance → conformance}/api/conformance-api.d.ts +0 -0
- /package/{src/conformance → conformance}/api/conformance-api.js +0 -0
- /package/{src/conformance → conformance}/conformance-config.d.ts +0 -0
- /package/{src/conformance → conformance}/conformance-config.js +0 -0
- /package/{src/crypto → crypto}/crypto-loader.d.ts +0 -0
- /package/{src/crypto → crypto}/crypto-loader.js +0 -0
- /package/{src/crypto → crypto}/jwt-helper.d.ts +0 -0
- /package/{src/crypto → crypto}/jwt-helper.js +0 -0
- /package/{src/crypto → crypto}/pkce-helper.d.ts +0 -0
- /package/{src/crypto → crypto}/pkce-helper.js +0 -0
- /package/{src/fapi → fapi}/fapi-utils.d.ts +0 -0
- /package/{src/fapi → fapi}/fapi-utils.js +0 -0
- /package/{src/filter → filter}/participant-filters.d.ts +0 -0
- /package/{src/filter → filter}/participant-filters.js +0 -0
- /package/{src/http → http}/http-client-extensions.d.ts +0 -0
- /package/{src/http → http}/http-client-extensions.js +0 -0
- /package/{src/http → http}/http-client-factory.d.ts +0 -0
- /package/{src/http → http}/http-client-factory.js +0 -0
- /package/{src/logger.d.ts → logger.d.ts} +0 -0
- /package/{src/logger.js → logger.js} +0 -0
- /package/{src/model → model}/callback-params.d.ts +0 -0
- /package/{src/model → model}/callback-params.js +0 -0
- /package/{src/model → model}/claims.d.ts +0 -0
- /package/{src/model → model}/claims.js +0 -0
- /package/{src/model → model}/consolidated-token-set.d.ts +0 -0
- /package/{src/model → model}/consolidated-token-set.js +0 -0
- /package/{src/model → model}/issuer-metadata.d.ts +0 -0
- /package/{src/model → model}/issuer-metadata.js +0 -0
- /package/{src/model → model}/jwks.d.ts +0 -0
- /package/{src/model → model}/jwks.js +0 -0
- /package/{src/model → model}/token-response.d.ts +0 -0
- /package/{src/model → model}/token-response.js +0 -0
- /package/{src/model → model}/token-set.d.ts +0 -0
- /package/{src/model → model}/token-set.js +0 -0
- /package/{src/relying-party-client-sdk.d.ts → relying-party-client-sdk.d.ts} +0 -0
- /package/{src/test-data → test-data}/large-participants-test-data.d.ts +0 -0
- /package/{src/test-data → test-data}/large-participants-test-data.js +0 -0
- /package/{src/test-data → test-data}/participants-test-data.d.ts +0 -0
- /package/{src/test-data → test-data}/participants-test-data.js +0 -0
- /package/{src/test-data → test-data}/sandbox-participants-test-data.d.ts +0 -0
- /package/{src/test-data → test-data}/sandbox-participants-test-data.js +0 -0
- /package/{src/types.js → types.js} +0 -0
- /package/{src/utils → utils}/cert-utils.d.ts +0 -0
- /package/{src/utils → utils}/cert-utils.js +0 -0
- /package/{src/utils → utils}/functional-utils.d.ts +0 -0
- /package/{src/utils → utils}/functional-utils.js +0 -0
- /package/{src/utils → utils}/request-utils.d.ts +0 -0
- /package/{src/utils → utils}/request-utils.js +0 -0
- /package/{src/utils → utils}/system-information.d.ts +0 -0
- /package/{src/utils → utils}/system-information.js +0 -0
- /package/{src/validator.d.ts → validator.d.ts} +0 -0
- /package/{src/validator.js → validator.js} +0 -0
package/README.md
CHANGED
|
@@ -120,6 +120,85 @@ const rpClient = new RelyingPartyClientSdk(config)
|
|
|
120
120
|
| `include_uncertified_participants` | By default the SDK will filter out all authorisation servers that are not fully certified. If you wish to test one of the uncertified auth servers you will need to set this to `true`. If not provided, defaults to 'false' | `false` |
|
|
121
121
|
| `required_claims` | The list of claims that the RP will be using and requires IDPs to support. If supplied, this will be used to filter the list of IDPs returned from `getParticipants` so that only IDPs supporting the claims are returned. If this value is not supplied, no filtering by claim support will be performed. | `['name', 'address']` |
|
|
122
122
|
| `required_participant_certifications` | The list of required certifications a server must support for the IDP use case (eg: TDIF Certification). If supplied, this will be used to filter the list of IDPs returned from `getParticipants` so that only IDPs with the certification are returned. If this value is not supplied, no filtering for specific certifications will be performed. | `[{ profileType: 'TDIF Accreditation', profileVariant: 'Identity Provider'}]` |
|
|
123
|
+
| `http_cache` | Optional configuration for HTTP response caching. When enabled, the SDK caches responses from participant registry, discovery documents, and JWKS endpoints to improve performance. All properties are optional with sensible defaults. | `{ enabled: true, ttl_minutes: 10, max_entries: 100, max_element_size_bytes: 5242880 }` |
|
|
124
|
+
| `http_cache.enabled` | Enable or disable HTTP response caching. Default: `true` | `true` |
|
|
125
|
+
| `http_cache.ttl_minutes` | Time-to-live for cached entries in minutes. Entries expire after this duration. Default: `10` | `15` |
|
|
126
|
+
| `http_cache.max_entries` | Maximum number of entries to store in the cache. When exceeded, least recently used entries are evicted. Default: `100` | `50` |
|
|
127
|
+
| `http_cache.max_element_size_bytes` | Maximum size in bytes for a single cached response. Responses larger than this will not be cached. Default: `5242880` (5MB) | `1048576` |
|
|
128
|
+
|
|
129
|
+
# HTTP Response Cache
|
|
130
|
+
|
|
131
|
+
The SDK includes an in-memory HTTP response cache that improves performance by reducing redundant network calls to stable endpoints. The cache is enabled by default with sensible configuration values.
|
|
132
|
+
|
|
133
|
+
## Cached Endpoints
|
|
134
|
+
|
|
135
|
+
The following endpoint types are automatically cached:
|
|
136
|
+
|
|
137
|
+
1. **Participant Registry** (`/participants`) - List of identity providers
|
|
138
|
+
2. **OIDC Discovery Documents** (`/.well-known/openid-configuration`) - OpenID Connect configuration
|
|
139
|
+
3. **JWKS Endpoints** - JSON Web Key Sets for token validation
|
|
140
|
+
|
|
141
|
+
## Cache Behavior
|
|
142
|
+
|
|
143
|
+
- **Cache-Aside Pattern**: The SDK checks the cache before making HTTP requests. On cache miss or stale entry, it fetches from the network and caches the response.
|
|
144
|
+
- **Only Successful Responses**: Only HTTP 2xx responses are cached. Error responses (4xx, 5xx) and network failures are never cached.
|
|
145
|
+
- **LRU Eviction**: When the cache reaches `max_entries`, the least recently used entries are automatically evicted.
|
|
146
|
+
- **TTL Expiration**: Cached entries expire after `ttl_minutes` and are refreshed on the next access.
|
|
147
|
+
|
|
148
|
+
## Configuration
|
|
149
|
+
|
|
150
|
+
The cache can be configured or disabled using the `http_cache` configuration option:
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
const sdk = new RelyingPartyClientSdk({
|
|
154
|
+
data: {
|
|
155
|
+
// ... other config ...
|
|
156
|
+
|
|
157
|
+
// Cache enabled with custom settings
|
|
158
|
+
http_cache: {
|
|
159
|
+
enabled: true,
|
|
160
|
+
ttl_minutes: 15, // Cache entries for 15 minutes
|
|
161
|
+
max_entries: 50, // Store up to 50 entries
|
|
162
|
+
max_element_size_bytes: 1048576 // Max 1MB per entry
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
})
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
To disable caching entirely:
|
|
169
|
+
|
|
170
|
+
```typescript
|
|
171
|
+
const sdk = new RelyingPartyClientSdk({
|
|
172
|
+
data: {
|
|
173
|
+
// ... other config ...
|
|
174
|
+
http_cache: { enabled: false }
|
|
175
|
+
}
|
|
176
|
+
})
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Default Configuration
|
|
180
|
+
|
|
181
|
+
If `http_cache` is not specified, the SDK uses the following defaults:
|
|
182
|
+
|
|
183
|
+
- `enabled`: `true`
|
|
184
|
+
- `ttl_minutes`: `10`
|
|
185
|
+
- `max_entries`: `100`
|
|
186
|
+
- `max_element_size_bytes`: `5242880` (5MB)
|
|
187
|
+
|
|
188
|
+
## Performance Benefits
|
|
189
|
+
|
|
190
|
+
- **Reduced Latency**: Cached responses are returned instantly without network round-trips
|
|
191
|
+
- **Lower Network Load**: Fewer HTTP requests to registry and authorization servers
|
|
192
|
+
- **Improved Reliability**: Cached data remains available even if network requests fail
|
|
193
|
+
- **Better User Experience**: Faster response times for repeated operations
|
|
194
|
+
|
|
195
|
+
## Memory Usage
|
|
196
|
+
|
|
197
|
+
With default settings:
|
|
198
|
+
- Maximum entries: 100
|
|
199
|
+
- Maximum size per entry: 5MB
|
|
200
|
+
- Worst-case memory usage: ~500MB (if all entries are at maximum size)
|
|
201
|
+
- Typical usage: Much lower (~5-10MB) as registry responses average 50KB, discovery documents ~5KB, and JWKS ~2KB
|
|
123
202
|
|
|
124
203
|
# Process Overview Sequence Diagram
|
|
125
204
|
|
|
@@ -389,7 +468,58 @@ The required function parameters are:
|
|
|
389
468
|
|
|
390
469
|
# Release Notes
|
|
391
470
|
|
|
392
|
-
### 5.
|
|
471
|
+
### 5.1.0 (Feb 12, 2026)
|
|
472
|
+
|
|
473
|
+
**HTTP Response Cache Implementation**
|
|
474
|
+
|
|
475
|
+
This release adds HTTP response caching to improve SDK performance by reducing redundant network calls to stable endpoints.
|
|
476
|
+
|
|
477
|
+
**New Features:**
|
|
478
|
+
- **In-Memory HTTP Response Cache**: Implements LRU (Least Recently Used) cache with TTL (Time-To-Live) expiration for HTTP responses
|
|
479
|
+
- **Cached Endpoints**:
|
|
480
|
+
- Participant registry (`/participants`)
|
|
481
|
+
- OIDC discovery documents (`/.well-known/openid-configuration`)
|
|
482
|
+
- JWKS endpoints (JSON Web Key Sets)
|
|
483
|
+
- **Configurable Cache Settings**: Optional `http_cache` configuration with the following parameters:
|
|
484
|
+
- `enabled` (default: `true`) - Enable/disable caching globally
|
|
485
|
+
- `ttl_minutes` (default: `10`) - Time-to-live for cache entries in minutes
|
|
486
|
+
- `max_entries` (default: `100`) - Maximum number of cached entries before eviction
|
|
487
|
+
- `max_element_size_bytes` (default: `5242880` / 5MB) - Maximum size per cached entry
|
|
488
|
+
|
|
489
|
+
**Performance Benefits:**
|
|
490
|
+
- Reduces network latency for repeated operations
|
|
491
|
+
- Improves response times for common SDK operations
|
|
492
|
+
- Reduces load on registry and authorization servers
|
|
493
|
+
|
|
494
|
+
**Cache Behavior:**
|
|
495
|
+
- Uses cache-aside pattern: checks cache before making HTTP requests
|
|
496
|
+
- Only caches successful responses (HTTP 2xx status codes)
|
|
497
|
+
- Never caches error responses (4xx, 5xx, network failures)
|
|
498
|
+
- LRU eviction automatically removes oldest entries when cache is full
|
|
499
|
+
- Entries automatically expire based on TTL configuration
|
|
500
|
+
|
|
501
|
+
**Configuration Example:**
|
|
502
|
+
```typescript
|
|
503
|
+
const sdk = new RelyingPartyClientSdk({
|
|
504
|
+
data: {
|
|
505
|
+
// ... other config ...
|
|
506
|
+
http_cache: {
|
|
507
|
+
enabled: true,
|
|
508
|
+
ttl_minutes: 15,
|
|
509
|
+
max_entries: 50,
|
|
510
|
+
max_element_size_bytes: 1048576 // 1MB
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
})
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
**Note:** The cache is enabled by default with sensible defaults. To disable caching, set `http_cache: { enabled: false }` in your configuration.
|
|
517
|
+
|
|
518
|
+
### 5.0.1 (Jan 16, 2026)
|
|
519
|
+
|
|
520
|
+
* Fixed packaging structure which caused conflicts when including the library.
|
|
521
|
+
|
|
522
|
+
### 5.0.0 (Jan 15, 2026)
|
|
393
523
|
|
|
394
524
|
**Major architectural refactoring - Breaking Changes**
|
|
395
525
|
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { Logger } from 'winston';
|
|
2
|
+
/**
|
|
3
|
+
* Configuration for HTTP Response Cache
|
|
4
|
+
*/
|
|
5
|
+
export interface HttpCacheConfig {
|
|
6
|
+
enabled: boolean;
|
|
7
|
+
ttlMinutes: number;
|
|
8
|
+
maxEntries: number;
|
|
9
|
+
maxElementSizeBytes: number;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* HTTP Response Cache
|
|
13
|
+
*
|
|
14
|
+
* In-memory LRU cache with TTL expiration for HTTP responses.
|
|
15
|
+
* Thread-safe for Node.js single-threaded event loop.
|
|
16
|
+
*
|
|
17
|
+
* Features:
|
|
18
|
+
* - LRU (Least Recently Used) eviction when maxEntries exceeded
|
|
19
|
+
* - TTL (Time-To-Live) expiration with lazy staleness checking
|
|
20
|
+
* - Size limits with oversized entry rejection
|
|
21
|
+
* - Configurable enable/disable
|
|
22
|
+
*/
|
|
23
|
+
export declare class HttpResponseCache {
|
|
24
|
+
private readonly cache;
|
|
25
|
+
private readonly config;
|
|
26
|
+
private readonly logger;
|
|
27
|
+
constructor(config: HttpCacheConfig, logger: Logger);
|
|
28
|
+
/**
|
|
29
|
+
* Retrieves cached response for a given URL.
|
|
30
|
+
*
|
|
31
|
+
* @param url - The URL key to lookup
|
|
32
|
+
* @returns Cached content if entry exists and is fresh, undefined otherwise
|
|
33
|
+
*/
|
|
34
|
+
get(url: string): string | undefined;
|
|
35
|
+
/**
|
|
36
|
+
* Stores HTTP response in cache.
|
|
37
|
+
*
|
|
38
|
+
* @param url - The URL key for caching
|
|
39
|
+
* @param content - The HTTP response body
|
|
40
|
+
*/
|
|
41
|
+
put(url: string, content: string): void;
|
|
42
|
+
/**
|
|
43
|
+
* Checks if a cached entry exists and is stale.
|
|
44
|
+
*
|
|
45
|
+
* @param url - The URL key to check
|
|
46
|
+
* @returns true if entry exists and is stale, false otherwise
|
|
47
|
+
*/
|
|
48
|
+
isStale(url: string): boolean;
|
|
49
|
+
/**
|
|
50
|
+
* Removes specific entry from cache.
|
|
51
|
+
*
|
|
52
|
+
* @param url - The URL key to remove
|
|
53
|
+
*/
|
|
54
|
+
evict(url: string): void;
|
|
55
|
+
/**
|
|
56
|
+
* Removes all entries from cache.
|
|
57
|
+
*/
|
|
58
|
+
clear(): void;
|
|
59
|
+
/**
|
|
60
|
+
* Gets current number of entries in cache.
|
|
61
|
+
*
|
|
62
|
+
* @returns Number of entries currently in cache
|
|
63
|
+
*/
|
|
64
|
+
size(): number;
|
|
65
|
+
/**
|
|
66
|
+
* Checks if caching is enabled.
|
|
67
|
+
*
|
|
68
|
+
* @returns true if caching is enabled, false otherwise
|
|
69
|
+
*/
|
|
70
|
+
isEnabled(): boolean;
|
|
71
|
+
/**
|
|
72
|
+
* Checks if a cache entry is stale based on TTL.
|
|
73
|
+
*
|
|
74
|
+
* @param entry - Cache entry to check
|
|
75
|
+
* @returns true if entry is stale, false if fresh
|
|
76
|
+
*/
|
|
77
|
+
private isEntryStale;
|
|
78
|
+
/**
|
|
79
|
+
* Evicts the least recently used entry from cache.
|
|
80
|
+
* Map maintains insertion order, so the first entry is the LRU.
|
|
81
|
+
*/
|
|
82
|
+
private evictLRU;
|
|
83
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP Response Cache
|
|
3
|
+
*
|
|
4
|
+
* In-memory LRU cache with TTL expiration for HTTP responses.
|
|
5
|
+
* Thread-safe for Node.js single-threaded event loop.
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - LRU (Least Recently Used) eviction when maxEntries exceeded
|
|
9
|
+
* - TTL (Time-To-Live) expiration with lazy staleness checking
|
|
10
|
+
* - Size limits with oversized entry rejection
|
|
11
|
+
* - Configurable enable/disable
|
|
12
|
+
*/
|
|
13
|
+
export class HttpResponseCache {
|
|
14
|
+
constructor(config, logger) {
|
|
15
|
+
this.config = config;
|
|
16
|
+
this.logger = logger;
|
|
17
|
+
this.cache = new Map();
|
|
18
|
+
this.logger.debug(`HTTP cache initialized: enabled=${config.enabled}, ttlMinutes=${config.ttlMinutes}, maxEntries=${config.maxEntries}, maxElementSize=${config.maxElementSizeBytes}`);
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Retrieves cached response for a given URL.
|
|
22
|
+
*
|
|
23
|
+
* @param url - The URL key to lookup
|
|
24
|
+
* @returns Cached content if entry exists and is fresh, undefined otherwise
|
|
25
|
+
*/
|
|
26
|
+
get(url) {
|
|
27
|
+
if (!this.config.enabled) {
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
const entry = this.cache.get(url);
|
|
31
|
+
if (!entry) {
|
|
32
|
+
this.logger.debug(`Cache miss for URL: ${url}`);
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
// Check if entry is stale
|
|
36
|
+
if (this.isEntryStale(entry)) {
|
|
37
|
+
this.logger.debug(`Cache entry stale for URL: ${url}`);
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
// Cache hit - promote entry in LRU order by deleting and re-inserting
|
|
41
|
+
this.cache.delete(url);
|
|
42
|
+
this.cache.set(url, entry);
|
|
43
|
+
this.logger.debug(`Cache hit for URL: ${url}`);
|
|
44
|
+
return entry.content;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Stores HTTP response in cache.
|
|
48
|
+
*
|
|
49
|
+
* @param url - The URL key for caching
|
|
50
|
+
* @param content - The HTTP response body
|
|
51
|
+
*/
|
|
52
|
+
put(url, content) {
|
|
53
|
+
if (!this.config.enabled) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
// Check content size
|
|
57
|
+
const sizeBytes = Buffer.byteLength(content, 'utf8');
|
|
58
|
+
if (sizeBytes > this.config.maxElementSizeBytes) {
|
|
59
|
+
this.logger.warn(`Response too large to cache: ${sizeBytes} bytes (max: ${this.config.maxElementSizeBytes}), URL: ${url}`);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
// Create cache entry
|
|
63
|
+
const entry = {
|
|
64
|
+
content,
|
|
65
|
+
fetchedAt: Date.now(),
|
|
66
|
+
sizeBytes,
|
|
67
|
+
};
|
|
68
|
+
// Remove existing entry if present (to update position in LRU order)
|
|
69
|
+
this.cache.delete(url);
|
|
70
|
+
// Store entry (added to end of Map, which is most recently used)
|
|
71
|
+
this.cache.set(url, entry);
|
|
72
|
+
this.logger.debug(`Cached response for URL: ${url}, size: ${sizeBytes} bytes`);
|
|
73
|
+
// Evict LRU entry if cache size exceeds maxEntries
|
|
74
|
+
if (this.cache.size > this.config.maxEntries) {
|
|
75
|
+
this.evictLRU();
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Checks if a cached entry exists and is stale.
|
|
80
|
+
*
|
|
81
|
+
* @param url - The URL key to check
|
|
82
|
+
* @returns true if entry exists and is stale, false otherwise
|
|
83
|
+
*/
|
|
84
|
+
isStale(url) {
|
|
85
|
+
if (!this.config.enabled) {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
const entry = this.cache.get(url);
|
|
89
|
+
if (!entry) {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
return this.isEntryStale(entry);
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Removes specific entry from cache.
|
|
96
|
+
*
|
|
97
|
+
* @param url - The URL key to remove
|
|
98
|
+
*/
|
|
99
|
+
evict(url) {
|
|
100
|
+
if (!this.config.enabled) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
const deleted = this.cache.delete(url);
|
|
104
|
+
if (deleted) {
|
|
105
|
+
this.logger.debug(`Evicted cache entry for URL: ${url}`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Removes all entries from cache.
|
|
110
|
+
*/
|
|
111
|
+
clear() {
|
|
112
|
+
if (!this.config.enabled) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
this.cache.clear();
|
|
116
|
+
this.logger.debug('Cache cleared');
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Gets current number of entries in cache.
|
|
120
|
+
*
|
|
121
|
+
* @returns Number of entries currently in cache
|
|
122
|
+
*/
|
|
123
|
+
size() {
|
|
124
|
+
return this.cache.size;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Checks if caching is enabled.
|
|
128
|
+
*
|
|
129
|
+
* @returns true if caching is enabled, false otherwise
|
|
130
|
+
*/
|
|
131
|
+
isEnabled() {
|
|
132
|
+
return this.config.enabled;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Checks if a cache entry is stale based on TTL.
|
|
136
|
+
*
|
|
137
|
+
* @param entry - Cache entry to check
|
|
138
|
+
* @returns true if entry is stale, false if fresh
|
|
139
|
+
*/
|
|
140
|
+
isEntryStale(entry) {
|
|
141
|
+
const now = Date.now();
|
|
142
|
+
const expiryTime = entry.fetchedAt + this.config.ttlMinutes * 60 * 1000;
|
|
143
|
+
return now >= expiryTime;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Evicts the least recently used entry from cache.
|
|
147
|
+
* Map maintains insertion order, so the first entry is the LRU.
|
|
148
|
+
*/
|
|
149
|
+
evictLRU() {
|
|
150
|
+
// Get first entry (oldest in insertion order)
|
|
151
|
+
const firstKey = this.cache.keys().next().value;
|
|
152
|
+
if (firstKey) {
|
|
153
|
+
this.cache.delete(firstKey);
|
|
154
|
+
this.logger.debug(`Evicted cache entry for URL: ${firstKey}`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
@@ -2,11 +2,12 @@ import { Agent } from 'undici';
|
|
|
2
2
|
import { Logger } from 'winston';
|
|
3
3
|
import { AuthorisationServer, Participant, RelyingPartyClientSdkConfig } from '../types.js';
|
|
4
4
|
import ParticipantFilters from '../filter/participant-filters.js';
|
|
5
|
+
import { HttpResponseCache } from '../cache/http-response-cache.js';
|
|
5
6
|
/**
|
|
6
7
|
* Participants Endpoint
|
|
7
8
|
*
|
|
8
9
|
* Handles fetching and filtering of participant lists from the registry.
|
|
9
|
-
*
|
|
10
|
+
* Uses HTTP cache for performance optimization.
|
|
10
11
|
*/
|
|
11
12
|
export declare class ParticipantsEndpoint {
|
|
12
13
|
private readonly sdkConfig;
|
|
@@ -14,7 +15,8 @@ export declare class ParticipantsEndpoint {
|
|
|
14
15
|
private readonly httpClient;
|
|
15
16
|
private readonly logger;
|
|
16
17
|
private readonly getCurrentDate;
|
|
17
|
-
|
|
18
|
+
private readonly cache;
|
|
19
|
+
constructor(sdkConfig: RelyingPartyClientSdkConfig, participantFilters: ParticipantFilters, httpClient: Agent, logger: Logger, getCurrentDate: () => Date, cache: HttpResponseCache);
|
|
18
20
|
/**
|
|
19
21
|
* Retrieves the list of active participants.
|
|
20
22
|
*
|
|
@@ -40,6 +42,7 @@ export declare class ParticipantsEndpoint {
|
|
|
40
42
|
getFallbackProviderParticipants(): Promise<Participant[]>;
|
|
41
43
|
/**
|
|
42
44
|
* Fetches participants from the registry.
|
|
45
|
+
* Uses cache-aside pattern for performance optimization.
|
|
43
46
|
*
|
|
44
47
|
* @param uri - Registry participants URI
|
|
45
48
|
* @returns Raw list of participants
|
|
@@ -4,15 +4,16 @@ import { randomUUID } from 'node:crypto';
|
|
|
4
4
|
* Participants Endpoint
|
|
5
5
|
*
|
|
6
6
|
* Handles fetching and filtering of participant lists from the registry.
|
|
7
|
-
*
|
|
7
|
+
* Uses HTTP cache for performance optimization.
|
|
8
8
|
*/
|
|
9
9
|
export class ParticipantsEndpoint {
|
|
10
|
-
constructor(sdkConfig, participantFilters, httpClient, logger, getCurrentDate) {
|
|
10
|
+
constructor(sdkConfig, participantFilters, httpClient, logger, getCurrentDate, cache) {
|
|
11
11
|
this.sdkConfig = sdkConfig;
|
|
12
12
|
this.participantFilters = participantFilters;
|
|
13
13
|
this.httpClient = httpClient;
|
|
14
14
|
this.logger = logger;
|
|
15
15
|
this.getCurrentDate = getCurrentDate;
|
|
16
|
+
this.cache = cache;
|
|
16
17
|
}
|
|
17
18
|
/**
|
|
18
19
|
* Retrieves the list of active participants.
|
|
@@ -90,18 +91,34 @@ export class ParticipantsEndpoint {
|
|
|
90
91
|
}
|
|
91
92
|
/**
|
|
92
93
|
* Fetches participants from the registry.
|
|
94
|
+
* Uses cache-aside pattern for performance optimization.
|
|
93
95
|
*
|
|
94
96
|
* @param uri - Registry participants URI
|
|
95
97
|
* @returns Raw list of participants
|
|
96
98
|
*/
|
|
97
99
|
async fetchParticipants(uri) {
|
|
98
100
|
try {
|
|
101
|
+
// Check cache first
|
|
102
|
+
const cachedContent = this.cache.get(uri);
|
|
103
|
+
if (cachedContent) {
|
|
104
|
+
this.logger.debug(`Cache hit for URL: ${uri}`);
|
|
105
|
+
return JSON.parse(cachedContent);
|
|
106
|
+
}
|
|
107
|
+
this.logger.debug(`Cache miss for URL: ${uri}`);
|
|
108
|
+
// Fetch from network
|
|
99
109
|
const response = await HttpClientExtensions.get(uri, {
|
|
100
110
|
agent: this.httpClient,
|
|
101
111
|
clientId: this.sdkConfig.data.client_id,
|
|
102
112
|
xFapiInteractionId: randomUUID(),
|
|
103
113
|
});
|
|
104
|
-
|
|
114
|
+
const participants = await HttpClientExtensions.parseJsonResponse(response);
|
|
115
|
+
// Cache successful response (only 2xx)
|
|
116
|
+
if (response.status >= 200 && response.status < 300) {
|
|
117
|
+
const content = JSON.stringify(participants);
|
|
118
|
+
this.cache.put(uri, content);
|
|
119
|
+
this.logger.debug(`Cached response for URL: ${uri}, size: ${Buffer.byteLength(content, 'utf8')} bytes`);
|
|
120
|
+
}
|
|
121
|
+
return participants;
|
|
105
122
|
}
|
|
106
123
|
catch (error) {
|
|
107
124
|
throw new Error(`Failed to fetch participants from ${uri}: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -2,6 +2,7 @@ import { Agent } from 'undici';
|
|
|
2
2
|
import { Logger } from 'winston';
|
|
3
3
|
import { RelyingPartyClientSdkConfig } from '../types.js';
|
|
4
4
|
import { JwtHelper } from '../crypto/jwt-helper.js';
|
|
5
|
+
import { HttpResponseCache } from '../cache/http-response-cache.js';
|
|
5
6
|
import { ParticipantsEndpoint } from './participants-endpoint';
|
|
6
7
|
/**
|
|
7
8
|
* Response from the Pushed Authorization Request endpoint.
|
|
@@ -40,8 +41,9 @@ export declare class PushedAuthorisationRequestEndpoint {
|
|
|
40
41
|
private readonly jwtHelper;
|
|
41
42
|
private readonly logger;
|
|
42
43
|
private readonly participantsEndpoint;
|
|
44
|
+
private readonly cache;
|
|
43
45
|
private static readonly EXTENDED_CLAIMS;
|
|
44
|
-
constructor(sdkConfig: RelyingPartyClientSdkConfig, httpClient: Agent, jwtHelper: JwtHelper, logger: Logger, participantsEndpoint: ParticipantsEndpoint);
|
|
46
|
+
constructor(sdkConfig: RelyingPartyClientSdkConfig, httpClient: Agent, jwtHelper: JwtHelper, logger: Logger, participantsEndpoint: ParticipantsEndpoint, cache: HttpResponseCache);
|
|
45
47
|
/**
|
|
46
48
|
* Sends a Pushed Authorization Request to the authorization server.
|
|
47
49
|
*
|
|
@@ -10,12 +10,13 @@ import { generateXFapiInteractionId } from '../fapi/fapi-utils.js';
|
|
|
10
10
|
* Creates signed request objects and submits them to the PAR endpoint.
|
|
11
11
|
*/
|
|
12
12
|
export class PushedAuthorisationRequestEndpoint {
|
|
13
|
-
constructor(sdkConfig, httpClient, jwtHelper, logger, participantsEndpoint) {
|
|
13
|
+
constructor(sdkConfig, httpClient, jwtHelper, logger, participantsEndpoint, cache) {
|
|
14
14
|
this.sdkConfig = sdkConfig;
|
|
15
15
|
this.httpClient = httpClient;
|
|
16
16
|
this.jwtHelper = jwtHelper;
|
|
17
17
|
this.logger = logger;
|
|
18
18
|
this.participantsEndpoint = participantsEndpoint;
|
|
19
|
+
this.cache = cache;
|
|
19
20
|
}
|
|
20
21
|
/**
|
|
21
22
|
* Sends a Pushed Authorization Request to the authorization server.
|
|
@@ -59,7 +60,7 @@ export class PushedAuthorisationRequestEndpoint {
|
|
|
59
60
|
}
|
|
60
61
|
async generateRequest(authServer, claimsRequest, purpose, xFapiInteractionId) {
|
|
61
62
|
// Fetch discovery document
|
|
62
|
-
const discoveryMetadata = await DiscoveryService.fetchDiscoveryDocument(authServer.OpenIDDiscoveryDocument, this.httpClient);
|
|
63
|
+
const discoveryMetadata = await DiscoveryService.fetchDiscoveryDocument(authServer.OpenIDDiscoveryDocument, this.httpClient, this.cache);
|
|
63
64
|
if (!discoveryMetadata.pushed_authorization_request_endpoint) {
|
|
64
65
|
throw new Error(`Authorization server ${authServer.AuthorisationServerId} does not support PAR`);
|
|
65
66
|
}
|
|
@@ -2,6 +2,7 @@ import { Agent } from 'undici';
|
|
|
2
2
|
import { Logger } from 'winston';
|
|
3
3
|
import { CallbackParams, RelyingPartyClientSdkConfig } from '../types.js';
|
|
4
4
|
import { JwtHelper } from '../crypto/jwt-helper.js';
|
|
5
|
+
import { HttpResponseCache } from '../cache/http-response-cache.js';
|
|
5
6
|
import { ConsolidatedTokenSet } from '../model/consolidated-token-set.js';
|
|
6
7
|
import { ParticipantsEndpoint } from './participants-endpoint';
|
|
7
8
|
/**
|
|
@@ -16,7 +17,8 @@ export declare class RetrieveTokenEndpoint {
|
|
|
16
17
|
private readonly jwtHelper;
|
|
17
18
|
private readonly logger;
|
|
18
19
|
private readonly participantsEndpoint;
|
|
19
|
-
|
|
20
|
+
private readonly cache;
|
|
21
|
+
constructor(sdkConfig: RelyingPartyClientSdkConfig, httpClient: Agent, jwtHelper: JwtHelper, logger: Logger, participantsEndpoint: ParticipantsEndpoint, cache: HttpResponseCache);
|
|
20
22
|
/**
|
|
21
23
|
* Retrieves tokens from the authorization server.
|
|
22
24
|
*
|
|
@@ -10,12 +10,13 @@ import { generateXFapiInteractionId } from '../fapi/fapi-utils.js';
|
|
|
10
10
|
* Validates the ID token and optionally calls UserInfo for compliance.
|
|
11
11
|
*/
|
|
12
12
|
export class RetrieveTokenEndpoint {
|
|
13
|
-
constructor(sdkConfig, httpClient, jwtHelper, logger, participantsEndpoint) {
|
|
13
|
+
constructor(sdkConfig, httpClient, jwtHelper, logger, participantsEndpoint, cache) {
|
|
14
14
|
this.sdkConfig = sdkConfig;
|
|
15
15
|
this.httpClient = httpClient;
|
|
16
16
|
this.jwtHelper = jwtHelper;
|
|
17
17
|
this.logger = logger;
|
|
18
18
|
this.participantsEndpoint = participantsEndpoint;
|
|
19
|
+
this.cache = cache;
|
|
19
20
|
}
|
|
20
21
|
/**
|
|
21
22
|
* Retrieves tokens from the authorization server.
|
|
@@ -41,7 +42,7 @@ export class RetrieveTokenEndpoint {
|
|
|
41
42
|
this.validateCallbackParams(callbackParams, state);
|
|
42
43
|
const authServer = await this.participantsEndpoint.getAuthServerDetails(authorisationServerId);
|
|
43
44
|
// Fetch discovery document
|
|
44
|
-
const discoveryMetadata = await DiscoveryService.fetchDiscoveryDocument(authServer.OpenIDDiscoveryDocument, this.httpClient);
|
|
45
|
+
const discoveryMetadata = await DiscoveryService.fetchDiscoveryDocument(authServer.OpenIDDiscoveryDocument, this.httpClient, this.cache);
|
|
45
46
|
// Validate issuer parameter (REQUIRED per FAPI 2.0)
|
|
46
47
|
if (!callbackParams.iss) {
|
|
47
48
|
throw new Error('Authorization response missing required iss parameter');
|
|
@@ -54,7 +55,7 @@ export class RetrieveTokenEndpoint {
|
|
|
54
55
|
// Exchange authorization code for tokens
|
|
55
56
|
const tokenResponse = await this.requestToken(discoveryMetadata.token_endpoint, callbackParams.code, codeVerifier, clientAssertion, xFapiInteractionId);
|
|
56
57
|
const tokenSet = new TokenSet(tokenResponse);
|
|
57
|
-
const jwks = await DiscoveryService.fetchJwks(discoveryMetadata.jwks_uri, this.httpClient);
|
|
58
|
+
const jwks = await DiscoveryService.fetchJwks(discoveryMetadata.jwks_uri, this.httpClient, this.cache);
|
|
58
59
|
await tokenSet.validate(jwks, discoveryMetadata.issuer, this.sdkConfig.data.client_id, nonce, discoveryMetadata.id_token_signing_alg_values_supported);
|
|
59
60
|
// Must call validate first before accessing claims
|
|
60
61
|
this.logger.info(`Retrieved tokenSet from auth server: ${authorisationServerId} - ${authServer.CustomerFriendlyName}, x-fapi-interaction-id: ${xFapiInteractionId}, txn: ${tokenSet.claims().txn}`);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Agent } from 'undici';
|
|
2
2
|
import { Logger } from 'winston';
|
|
3
|
+
import { HttpResponseCache } from '../cache/http-response-cache.js';
|
|
3
4
|
import { ParticipantsEndpoint } from './participants-endpoint';
|
|
4
5
|
/**
|
|
5
6
|
* UserInfo Endpoint
|
|
@@ -12,7 +13,8 @@ export declare class UserInfoEndpoint {
|
|
|
12
13
|
private readonly logger;
|
|
13
14
|
private readonly clientId;
|
|
14
15
|
private readonly participantsEndpoint;
|
|
15
|
-
|
|
16
|
+
private readonly cache;
|
|
17
|
+
constructor(httpClient: Agent, logger: Logger, clientId: string, participantsEndpoint: ParticipantsEndpoint, cache: HttpResponseCache);
|
|
16
18
|
/**
|
|
17
19
|
* Retrieves user information from the UserInfo endpoint.
|
|
18
20
|
*
|
|
@@ -8,11 +8,12 @@ import { generateXFapiInteractionId } from '../fapi/fapi-utils.js';
|
|
|
8
8
|
* Returns user claims using an access token.
|
|
9
9
|
*/
|
|
10
10
|
export class UserInfoEndpoint {
|
|
11
|
-
constructor(httpClient, logger, clientId, participantsEndpoint) {
|
|
11
|
+
constructor(httpClient, logger, clientId, participantsEndpoint, cache) {
|
|
12
12
|
this.httpClient = httpClient;
|
|
13
13
|
this.logger = logger;
|
|
14
14
|
this.clientId = clientId;
|
|
15
15
|
this.participantsEndpoint = participantsEndpoint;
|
|
16
|
+
this.cache = cache;
|
|
16
17
|
}
|
|
17
18
|
/**
|
|
18
19
|
* Retrieves user information from the UserInfo endpoint.
|
|
@@ -27,7 +28,7 @@ export class UserInfoEndpoint {
|
|
|
27
28
|
try {
|
|
28
29
|
const authServer = await this.participantsEndpoint.getAuthServerDetails(authorisationServerId);
|
|
29
30
|
// Fetch discovery document
|
|
30
|
-
const discoveryMetadata = await DiscoveryService.fetchDiscoveryDocument(authServer.OpenIDDiscoveryDocument, this.httpClient);
|
|
31
|
+
const discoveryMetadata = await DiscoveryService.fetchDiscoveryDocument(authServer.OpenIDDiscoveryDocument, this.httpClient, this.cache);
|
|
31
32
|
if (!discoveryMetadata.userinfo_endpoint) {
|
|
32
33
|
throw new Error(`Authorization server ${authServer.AuthorisationServerId} does not have a UserInfo endpoint`);
|
|
33
34
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Agent } from 'undici';
|
|
2
2
|
import { IssuerMetadata } from './issuer-metadata.js';
|
|
3
3
|
import { JWKSet } from './jwks.js';
|
|
4
|
+
import { HttpResponseCache } from '../cache/http-response-cache.js';
|
|
4
5
|
/**
|
|
5
6
|
* Service for fetching OIDC discovery documents and JWKS.
|
|
6
7
|
*
|
|
@@ -10,22 +11,26 @@ import { JWKSet } from './jwks.js';
|
|
|
10
11
|
export declare class DiscoveryService {
|
|
11
12
|
/**
|
|
12
13
|
* Fetches and parses an OIDC discovery document.
|
|
14
|
+
* Uses cache-aside pattern for performance optimization.
|
|
13
15
|
*
|
|
14
16
|
* @param discoveryUrl - URL to the .well-known/openid-configuration endpoint
|
|
15
17
|
* @param httpAgent - Optional undici Agent for mTLS
|
|
18
|
+
* @param cache - Optional HTTP response cache
|
|
16
19
|
* @returns Parsed issuer metadata
|
|
17
20
|
* @throws Error if the discovery document cannot be fetched or parsed
|
|
18
21
|
*/
|
|
19
|
-
static fetchDiscoveryDocument(discoveryUrl: string, httpAgent?: Agent): Promise<IssuerMetadata>;
|
|
22
|
+
static fetchDiscoveryDocument(discoveryUrl: string, httpAgent?: Agent, cache?: HttpResponseCache): Promise<IssuerMetadata>;
|
|
20
23
|
/**
|
|
21
24
|
* Fetches and parses a JWKS document.
|
|
25
|
+
* Uses cache-aside pattern for performance optimization.
|
|
22
26
|
*
|
|
23
27
|
* @param jwksUri - URL to the JWKS endpoint
|
|
24
28
|
* @param httpAgent - Optional HTTPS agent for mTLS
|
|
29
|
+
* @param cache - Optional HTTP response cache
|
|
25
30
|
* @returns Parsed JWKS
|
|
26
31
|
* @throws Error if the JWKS cannot be fetched or parsed
|
|
27
32
|
*/
|
|
28
|
-
static fetchJwks(jwksUri: string, httpAgent?: Agent): Promise<JWKSet>;
|
|
33
|
+
static fetchJwks(jwksUri: string, httpAgent?: Agent, cache?: HttpResponseCache): Promise<JWKSet>;
|
|
29
34
|
/**
|
|
30
35
|
* Validates that required discovery document fields are present.
|
|
31
36
|
*
|