first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,81 @@
# jwks-endpoint
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Provides a framework-agnostic JWKS endpoint backed by a `TenantKeyStore`. The endpoint serialises active public keys from the store into a `{ keys: JWK[] }` JSON Web Key Set and exposes an HTTP response builder that any framework can consume. An internal cache reduces key-store reads on high-traffic endpoints, with the TTL controlled by `cacheMaxAge`.
## Features
- `JWKSEndpoint` class with configurable cache TTL and injectable clock
- `getJWKS(config?)` — fetches active public keys and returns a `JWKS` object
- `handleRequest(request)` — returns a framework-agnostic `{ status, body, headers }` response
- `clearCache()` — invalidates the internal JWKS cache on demand
- `formatJWKSResponse(keys)` — standalone pure function to wrap JWK arrays
- `createJWKSEndpoint(options)` — factory function
- `includeExpired` option to bypass cache and include all keys
## Usage: Basic Example
```ts
import { InMemoryKeyStore } from "@suaveplan/jwt-tenant/jwks/in-memory-key-store";
import { JWKSEndpoint } from "@suaveplan/jwt-tenant/jwks/jwks-endpoint";
import { generateKeyPair } from "@suaveplan/jwt-tenant/keys/key-generator";
const store = new InMemoryKeyStore();
const kp = await generateKeyPair("EdDSA");
await store.addKey(kp);
const endpoint = new JWKSEndpoint({ keyStore: store });
const response = await endpoint.handleRequest(null);
// response.status === 200
// JSON.parse(response.body).keys.length === 1
```
## Usage: Advanced Example
```ts
import { JWKSEndpoint } from "@suaveplan/jwt-tenant/jwks/jwks-endpoint";
// Use in a Hono or Express route
app.get("/.well-known/jwks.json", async (req, res) => {
const response = await endpoint.handleRequest(req);
res.status(response.status)
.set(response.headers)
.send(response.body);
});
// Force cache invalidation after key rotation
await store.addKey(newKeyPair);
endpoint.clearCache();
```
## API Reference
### `JWKSEndpoint`
Constructor: `new JWKSEndpoint(options: JWKSEndpointOptions)`.
- `options.keyStore` — `TenantKeyStore` implementation
- `options.cacheMaxAge` — cache TTL in milliseconds (default 5 min)
- `options.clock` — injectable clock for cache expiry
#### Methods
- **`getJWKS(config?): Promise<JWKS>`** — Returns active public keys as a JWKS. Caches result unless `config.includeExpired` is `true`.
- **`handleRequest(request): Promise<JWKSResponse>`** — Returns `{ status: 200, body: string, headers }`. The `request` parameter is accepted for interface compatibility.
- **`clearCache(): void`** — Resets the internal cache immediately.
### `formatJWKSResponse(keys: JWK[]): JWKS`
Pure function that wraps a `JWK[]` in a `{ keys }` envelope.
### `createJWKSEndpoint(options): JWKSEndpoint`
Factory function; equivalent to `new JWKSEndpoint(options)`.
## Implementation Notes
The cache uses a simple timestamp check against `clock()`. Cache invalidation on key rotation must be explicit (`clearCache()`). The `handleRequest` `_request` parameter is intentionally ignored — the endpoint serves the same JWKS for all requests. In production, mount the endpoint behind a CDN with short TTLs for automatic key rotation propagation.