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

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,65 @@
# jwt-tenant-error
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Defines the structured error hierarchy for `@suaveplan/jwt-tenant`. All thrown errors extend `SuaveplanError` with a stable code registry under the `JWT.*` namespace. Using `defineCodes` ensures code strings are deduplicated at module load time and are inspectable programmatically without string matching.
## Features
- `JWT_TENANT_CODES` registry with 9 distinct error codes
- `JwtTenantError` class extending `SuaveplanError<JwtTenantErrorCode>`
- Cause-chain forwarding: non-Error causes are swallowed, Error causes preserved
- Machine-inspectable error codes for upstream routing and logging
## Usage: Basic Example
```ts
import { JwtTenantError, JWT_TENANT_CODES } from "@suaveplan/jwt-tenant";
throw new JwtTenantError(
JWT_TENANT_CODES.EXPIRED,
"Token has expired"
);
```
## Usage: Advanced Example
```ts
import { JwtTenantError, JWT_TENANT_CODES } from "@suaveplan/jwt-tenant";
try {
await verifyToken(token, key);
} catch (raw) {
if (raw instanceof Error) {
throw new JwtTenantError(
JWT_TENANT_CODES.INVALID_SIGNATURE,
"Signature verification failed",
{ cause: raw }
);
}
}
```
## API Reference
### `JWT_TENANT_CODES`
Object mapping each error name to its fully qualified code string (e.g., `JWT.EXPIRED`). Produced by `defineCodes("JWT", [...])`.
### `JwtTenantError`
- **constructor(code, message, options?)** — Creates a structured error.
- `code` — `JwtTenantErrorCode`
- `message` — human-readable description
- `options.cause` — optional underlying `Error` for cause-chain
### `JwtTenantErrorCode`
Union type of all code keys: `"JWT_INVALID_SIGNATURE" | "JWT_EXPIRED" | ... | "JWT_SERIALIZATION_FAILED"`.
## Implementation Notes
`JwtTenantError` wraps the `SuaveplanError` constructor pattern established across Suaveplan. Non-Error causes are dropped rather than coerced because `SuaveplanError` only accepts `Error` in its cause chain — this prevents accidental serialisation of arbitrary objects.

View File

@@ -0,0 +1,74 @@
# in-memory-key-store
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Provides an in-process, non-persistent implementation of `TenantKeyStore`. Used as the default key store in development, testing, and single-node deployments where distributed key sharing is not required. The injectable `Clock` makes expiry checks deterministic in unit tests without real time manipulation.
## Features
- `InMemoryKeyStore` implementing the full `TenantKeyStore` interface
- `Map<keyId, KeyPair>` backing store with O(1) lookup
- Expiry-aware `getActiveKeys()` — filters out keys where `clock() >= expiresAt`
- `pruneExpired()` housekeeping method for long-running processes
- `createInMemoryKeyStore(clock?)` factory function
- Optional `tenantId` parameter in `getActiveKeys` (pass-through; use one store per tenant)
## Usage: Basic Example
```ts
import { InMemoryKeyStore } from "@suaveplan/jwt-tenant/jwks/in-memory-key-store";
import { generateKeyPair } from "@suaveplan/jwt-tenant/keys/key-generator";
const store = new InMemoryKeyStore();
const kp = await generateKeyPair("EdDSA");
await store.addKey(kp);
const active = await store.getActiveKeys();
console.log(active.length); // 1
```
## Usage: Advanced Example
```ts
import { InMemoryKeyStore } from "@suaveplan/jwt-tenant/jwks/in-memory-key-store";
import { generateKeyPair } from "@suaveplan/jwt-tenant/keys/key-generator";
let time = 0;
const clock = () => time;
const store = new InMemoryKeyStore(clock);
// Add a key that expires at t=1000
const kp = await generateKeyPair("EdDSA", { ttlMs: 1000, clock });
await store.addKey(kp);
time = 500;
console.log((await store.getActiveKeys()).length); // 1 — not yet expired
time = 2000;
console.log((await store.getActiveKeys()).length); // 0 — expired
store.pruneExpired(); // remove expired entries from the Map
```
## API Reference
### `InMemoryKeyStore`
Constructor: `new InMemoryKeyStore(clock?)`.
- **`getActiveKeys(tenantId?): Promise<KeyPair[]>`** — Returns all non-expired key pairs. Expired keys (where `clock() >= expiresAt`) are excluded. The `tenantId` parameter is accepted for interface compatibility but is a no-op in this implementation.
- **`getKeyById(keyId): Promise<KeyPair | null>`** — Looks up a key by its ID. Returns `null` when not found.
- **`addKey(keyPair): Promise<void>`** — Inserts or replaces a key pair in the store.
- **`removeKey(keyId): Promise<void>`** — Removes a key pair. No-op if the key doesn't exist.
- **`pruneExpired(): void`** — Synchronously removes all expired entries. Call periodically to prevent unbounded memory growth in long-running processes.
### `createInMemoryKeyStore(clock?): InMemoryKeyStore`
Factory function; equivalent to `new InMemoryKeyStore(clock)`.
## Implementation Notes
The `keys` map is `protected` (not `private`) to allow test subclasses to inspect internal state without public API exposure. The `tenantId` parameter in `getActiveKeys` is explicitly voided (`void tenantId`) to satisfy the TypeScript "unused variable" checker while documenting the intentional no-op. In production multi-tenant scenarios, use a separate `InMemoryKeyStore` instance per tenant or implement a `DatabaseKeyStore` with tenant-scoped queries.

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.

View File

@@ -0,0 +1,110 @@
# key-generator
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Provides cryptographic key pair generation and serialization utilities for `@suaveplan/jwt-tenant`. Key generation delegates to the `jose` library for actual key material, while serialization converts between opaque `KeyPair` objects (with CryptoKey references) and JSON-safe `SerializedKeyPair` objects suitable for database storage or transmission. An injectable `Clock` makes timestamp generation deterministic in tests.
## Features
- `generateKeyPair(algorithm, options)` — generates ES256/ES384/ES512/EdDSA/RS256/RS384/RS512 key pairs
- `serializeKeyPair(keyPair)` — exports keys to JWK format with metadata headers
- `deserializeKeyPair(serialized)` — imports JWKs back to opaque key references
- `keyPairToJWK(keyPair)` — extracts the public JWK for JWKS endpoint serving
- `importPublicKeyFromJWK(jwk)` — imports a raw JWK for use in verification
- `calculateKeyExpiration(createdAt, ttlMs)` — computes expiry Date from creation time and TTL
- `isKeyPairExpired(keyPair, clock?)` — checks whether a key pair has passed its `expiresAt`
- `getAlgorithmFamily(algorithm)` — returns `"EC" | "OKP" | "RSA"`
- Injectable `Clock` for deterministic `createdAt`/`expiresAt` in tests
## Usage: Basic Example
```ts
import { generateKeyPair, keyPairToJWK } from "@suaveplan/jwt-tenant/keys/key-generator";
const keyPair = await generateKeyPair("EdDSA");
console.log(keyPair.keyId); // e.g. "550e8400-e29b-41d4-a716-446655440000"
const jwk = await keyPairToJWK(keyPair);
console.log(jwk.kty); // "OKP"
```
## Usage: Advanced Example
```ts
import {
generateKeyPair,
serializeKeyPair,
deserializeKeyPair,
isKeyPairExpired,
calculateKeyExpiration,
} from "@suaveplan/jwt-tenant/keys/key-generator";
// Generate with 30-day TTL using a fixed clock for tests
const clock = () => new Date("2025-01-01").getTime();
const keyPair = await generateKeyPair("ES256", {
keyId: "my-key-v1",
ttlMs: 30 * 24 * 60 * 60 * 1000,
clock,
});
// Persist to storage
const serialized = await serializeKeyPair(keyPair);
localStorage.setItem("keyPair", JSON.stringify(serialized));
// Restore from storage
const restored = await deserializeKeyPair(JSON.parse(localStorage.getItem("keyPair")!));
console.log(restored.keyId === keyPair.keyId); // true
// Check expiry
const expired = isKeyPairExpired(restored, () => Date.now());
```
## API Reference
### `generateKeyPair(algorithm?, options?): Promise<KeyPair>`
Generates a key pair for the given algorithm. Default algorithm is `"EdDSA"`.
- `options.keyId` — override the auto-generated UUID key ID
- `options.ttlMs` — milliseconds until expiry; sets `expiresAt`
- `options.clock` — injectable clock for `createdAt`/`expiresAt`
- Throws `JwtTenantError(JWT.ALGORITHM_UNSUPPORTED)` on unknown algorithms
### `serializeKeyPair(keyPair): Promise<SerializedKeyPair>`
Exports a `KeyPair` to a JSON-serializable form with JWK-encoded keys.
- Throws `JwtTenantError(JWT.SERIALIZATION_FAILED)` on export failure
### `deserializeKeyPair(serialized): Promise<KeyPair>`
Imports a `SerializedKeyPair` back to an in-memory `KeyPair` with opaque key references.
- Throws `JwtTenantError(JWT.SERIALIZATION_FAILED)` on import failure
### `keyPairToJWK(keyPair): Promise<JWK>`
Returns the public JWK with `kid`, `alg`, and `use: "sig"` embedded.
### `importPublicKeyFromJWK(jwk): Promise<unknown>`
Imports a raw JWK as an opaque key reference for token verification.
### `calculateKeyExpiration(createdAt, ttlMs): Date`
Pure function — returns `createdAt + ttlMs` as a `Date`.
### `isKeyPairExpired(keyPair, clock?): boolean`
Returns `true` when `clock() >= keyPair.expiresAt.getTime()`. Returns `false` when `expiresAt` is absent.
### `getAlgorithmFamily(algorithm): "EC" | "OKP" | "RSA"`
Maps algorithm names to their key family for key generation routing.
## Implementation Notes
All `privateKey`/`publicKey` fields in `KeyPair` are typed as `unknown` to avoid leaking `jose` internal types into the public API. The `as Parameters<typeof exportJWK>[0]` casts are intentional adapter boundaries — they are not part of the public API contract. RSA key generation uses 2048-bit moduli for RS256, 3072 for RS384, and 4096 for RS512 to meet NIST recommendations.

View File

@@ -0,0 +1,77 @@
# jwt-tenant.schemas
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Provides Zod schemas for all public configuration and option types in `@suaveplan/jwt-tenant`. These schemas enable runtime validation of user-supplied configuration, making it possible to surface configuration errors at construction time rather than at operation time. They are also useful for serializing and deserializing key pairs in storage-layer integrations.
## Features
- `KeyGenOptionsSchema` — validates key generation options
- `TokenIssuerConfigSchema` — validates issuer configuration
- `CreateTokenOptionsSchema` — validates per-token creation options (required `subject` and `tenantId`)
- `VerifyTokenOptionsSchema` — validates per-verification constraint options
- `TokenVerifierConfigSchema` — validates verifier configuration
- `JWKSEndpointOptionsSchema` — validates JWKS endpoint options
- `SerializedKeyPairSchema` — validates serialized key pair JSON format
## Usage: Basic Example
```ts
import { CreateTokenOptionsSchema } from "@suaveplan/jwt-tenant/schemas";
const result = CreateTokenOptionsSchema.safeParse({
subject: "user-123",
tenantId: "acme",
role: "admin",
});
if (!result.success) {
console.error(result.error.format());
}
```
## Usage: Advanced Example
```ts
import { SerializedKeyPairSchema } from "@suaveplan/jwt-tenant/schemas";
const rawJson = JSON.parse(fs.readFileSync("keypair.json", "utf8"));
const parsed = SerializedKeyPairSchema.parse(rawJson);
// parsed is type-narrowed as SerializedKeyPair
```
## API Reference
### `KeyGenOptionsSchema`
Strict schema: `keyId?` (string), `ttlMs?` (positive number).
### `TokenIssuerConfigSchema`
Strict schema: `issuer?` (URL string), `defaultExpiresIn?` (string).
### `CreateTokenOptionsSchema`
Strict schema with required `subject` (min 1) and `tenantId` (min 1). Optional: `expiresIn`, `domains`, `role`, `permissions`, `sessionId`, `issuer`, `additionalClaims`.
### `VerifyTokenOptionsSchema`
Strict schema: all fields optional — `requiredClaims`, `requiredRole`, `requiredPermissions`, `requiredTenantId`, `requiredDomains`.
### `TokenVerifierConfigSchema`
Strict schema: `issuer?` (URL), `audience?` (string[]), `clockTolerance?` (nonnegative), `jwksUri?` (URL), `cacheMaxAge?` (positive), `maxRetries?` (positive int).
### `JWKSEndpointOptionsSchema`
Strict schema: `cacheMaxAge?` (positive). The `keyStore` and `clock` fields are runtime objects and are excluded.
### `SerializedKeyPairSchema`
Validates `keyId`, `algorithm` (enum), `privateKeyJwk`/`publicKeyJwk` (records), `createdAt`/`expiresAt?` (ISO-8601 datetime strings).
## Implementation Notes
All schemas use `.strict()` to reject unknown keys, making configuration typos immediately surfaced. The `clock` and `signal` fields in config interfaces are runtime objects that cannot be validated by Zod and are intentionally excluded from the schemas.

View File

@@ -0,0 +1,75 @@
# token-issuer
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Issues signed JWTs with per-tenant claims using the `jose` library. The `TokenIssuer` class wraps `SignJWT` with tenant-aware defaults: it injects `tenant_id`, `session_id`, `domains`, `role`, and `permissions` claims alongside standard `sub`, `iat`, `exp`, and `jti`. An injectable `Clock` makes token timestamps deterministic for unit tests. Duration strings like `"1h"` are parsed to seconds for `exp` computation.
## Features
- `TokenIssuer` class with configurable issuer, default expiry, and injectable clock
- `createToken(keyPair, options)` — signs a JWT with tenant claims
- `createTokenIssuer(config)` — factory function
- `signToken(keyPair, options, config?)` — standalone convenience function
- Duration string parsing: `"1h"`, `"15m"`, `"30s"`, `"2d"`, `"500ms"`
- Standard `jti` (UUID), `iat`, `exp`, `sub`, `iss` claim population
- Optional `additionalClaims` spread for domain-specific payloads
## Usage: Basic Example
```ts
import { generateKeyPair } from "@suaveplan/jwt-tenant/keys/key-generator";
import { TokenIssuer } from "@suaveplan/jwt-tenant/token/token-issuer";
const keyPair = await generateKeyPair("EdDSA");
const issuer = new TokenIssuer({ issuer: "https://auth.example.com" });
const token = await issuer.createToken(keyPair, {
subject: "user-123",
tenantId: "acme",
});
```
## Usage: Advanced Example
```ts
import { signToken } from "@suaveplan/jwt-tenant/token/token-issuer";
const clock = () => new Date("2025-06-01T00:00:00Z").getTime();
const token = await signToken(keyPair, {
subject: "svc-account",
tenantId: "internal",
expiresIn: "15m",
role: "service",
permissions: ["read:metrics", "write:events"],
sessionId: "sess-abc123",
additionalClaims: { env: "production" },
}, { clock });
```
## API Reference
### `TokenIssuer`
Constructor: `new TokenIssuer(config?: TokenIssuerConfig)`.
- `config.issuer` — `iss` claim (optional)
- `config.defaultExpiresIn` — default duration string (default `"1h"`)
- `config.clock` — injectable clock for `iat`/`exp`
#### Methods
- **`createToken(keyPair, options): Promise<string>`** — Signs and returns a compact JWT.
### `createTokenIssuer(config?): TokenIssuer`
Factory function.
### `signToken(keyPair, options, config?): Promise<string>`
Standalone convenience that creates a `TokenIssuer` internally and issues one token.
## Implementation Notes
Duration strings are parsed with a simple regex supporting `ms`, `s`, `m`, `h`, `d` units. Fractional values are not supported. The `exp` claim is set to `floor(now/1000) + duration_in_seconds`, matching the JWT spec's integer seconds requirement. The `jti` is a `crypto.randomUUID()` call for uniqueness.

View File

@@ -0,0 +1,83 @@
# token-verifier
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Verifies signed JWTs against local keys or remote JWKS endpoints. The `TokenVerifier` class resolves verification keys from either an in-memory key cache or a remote JWKS URI (with retry and AbortSignal support), then delegates cryptographic verification to `jose`. After successful signature verification, per-verification constraint checks enforce tenant isolation, role requirements, and permission scopes.
## Features
- `TokenVerifier` class with configurable issuer, audience, clock tolerance, and injectable clock
- Remote JWKS URI support with configurable TTL caching and retry (default 3 attempts)
- `AbortSignal` cancellation for JWKS fetch operations
- `addKey(jwk)` — register a local JWK for key-only verification (no remote fetch)
- `clearCache()` — invalidate the JWKS cache for immediate rotation
- `verifyToken(token, options?)` — returns `VerifyTokenResult` (never throws)
- Per-verification constraints: `requiredTenantId`, `requiredRole`, `requiredPermissions`, `requiredClaims`, `requiredDomains`
- `verifyTokenWithKey(token, key, options?)` — standalone function for pre-imported keys
- `createTokenVerifier(config?)` — factory function
## Usage: Basic Example
```ts
import { generateKeyPair, keyPairToJWK } from "@suaveplan/jwt-tenant/keys/key-generator";
import { TokenIssuer } from "@suaveplan/jwt-tenant/token/token-issuer";
import { TokenVerifier } from "@suaveplan/jwt-tenant/token/token-verifier";
const kp = await generateKeyPair("EdDSA");
const issuer = new TokenIssuer({ issuer: "https://auth.example.com" });
const token = await issuer.createToken(kp, { subject: "u1", tenantId: "acme" });
const verifier = new TokenVerifier({ issuer: "https://auth.example.com" });
const jwk = await keyPairToJWK(kp);
verifier.addKey(jwk);
const result = await verifier.verifyToken(token);
// result.valid === true
// result.payload.tenant_id === "acme"
```
## Usage: Advanced Example
```ts
const verifier = new TokenVerifier({
jwksUri: "https://auth.example.com/.well-known/jwks.json",
issuer: "https://auth.example.com",
cacheMaxAge: 5 * 60 * 1000,
clockTolerance: 30,
maxRetries: 3,
signal: AbortSignal.timeout(5000),
});
const result = await verifier.verifyToken(token, {
requiredTenantId: "acme",
requiredRole: "admin",
requiredPermissions: ["write:data"],
requiredDomains: ["acme.example.com"],
});
```
## API Reference
### `TokenVerifier`
Constructor: `new TokenVerifier(config?: TokenVerifierConfig)`.
#### Methods
- **`verifyToken(token, options?): Promise<VerifyTokenResult>`** — Verifies the token. Returns `{ valid: true, payload }` or `{ valid: false, error, errorCode }`. Never throws.
- **`addKey(jwk): void`** — Adds a local JWK to the key cache.
- **`clearCache(): void`** — Clears the key cache.
### `createTokenVerifier(config?): TokenVerifier`
Factory function.
### `verifyTokenWithKey(token, key, options?): Promise<VerifyTokenResult>`
Standalone verification with a pre-imported key reference.
## Implementation Notes
JWKS cache uses timestamp comparison (`clock() >= cacheExpiresAt`) for expiry. Multi-key verification tries each JWK in order and returns on the first success — the last error is preserved and wrapped if all keys fail. The `checkConstraints` method is `protected` to allow subclass extension without exposing it in the public API. All jose errors are classified by message substring matching into one of the JWT error codes.

View File

@@ -0,0 +1,102 @@
# jwt-tenant.types
## Purpose
Defines the complete public type surface for `@suaveplan/jwt-tenant`. Every interface and type alias used across the package is declared here: key pair shapes, JWT payload structure, issuer/verifier configuration, JWKS endpoint types, and the `TenantKeyStore` adapter interface. Centralising types in one file avoids circular imports and makes the public contract immediately readable.
## Features
- `KeyAlgorithm` union covering ECDSA (ES256/ES384/ES512), EdDSA, and RSA (RS256/RS384/RS512)
- `KeyPair` / `SerializedKeyPair` for in-memory and JSON-exportable key representations
- `JWK` / `JWKS` RFC 7517-compatible web key types with open index signature
- `TenantJWTPayload` with required `tenant_id`, `sub`, `iat`, `exp`, `jti` plus optional tenant claims
- `TokenIssuerConfig` / `CreateTokenOptions` for token creation
- `TokenVerifierConfig` / `VerifyTokenOptions` / `VerifyTokenResult` for verification
- `KeyGenOptions` with injectable `Clock` for deterministic timestamps
- `JWKSEndpointOptions` / `JWKSEndpointConfig` / `JWKSResponse` for JWKS serving
- `TenantKeyStore` adapter interface for pluggable key storage
## Usage: Basic Example
```ts
import type {
KeyPair,
TenantJWTPayload,
TokenIssuerConfig,
CreateTokenOptions,
} from "@suaveplan/jwt-tenant";
const config: TokenIssuerConfig = {
issuer: "https://auth.example.com",
defaultExpiresIn: "1h",
};
const options: CreateTokenOptions = {
subject: "user-123",
tenantId: "acme",
role: "admin",
};
```
## Usage: Advanced Example
```ts
import type {
TenantKeyStore,
KeyPair,
JWKSEndpointOptions,
TokenVerifierConfig,
} from "@suaveplan/jwt-tenant";
// Implement a custom key store backed by a database
class DatabaseKeyStore implements TenantKeyStore {
async getActiveKeys(tenantId?: string): Promise<KeyPair[]> {
// fetch from DB filtered by tenantId + expiry
return [];
}
async getKeyById(keyId: string): Promise<KeyPair | null> {
// SELECT by keyId
return null;
}
async addKey(keyPair: KeyPair): Promise<void> {
// INSERT
}
async removeKey(keyId: string): Promise<void> {
// DELETE
}
}
const verifierConfig: TokenVerifierConfig = {
issuer: "https://auth.example.com",
clockTolerance: 30,
jwksUri: "https://auth.example.com/.well-known/jwks.json",
cacheMaxAge: 5 * 60 * 1000,
maxRetries: 3,
};
```
## API Reference
### `KeyAlgorithm`
Union type: `"ES256" | "ES384" | "ES512" | "EdDSA" | "RS256" | "RS384" | "RS512"`.
### `KeyPair`
In-memory key pair with opaque `privateKey`/`publicKey` fields (CryptoKey or jose KeyLike).
### `SerializedKeyPair`
JSON-exportable form with `privateKeyJwk`/`publicKeyJwk` as JWK objects and ISO-8601 date strings.
### `TenantJWTPayload`
Standard JWT claims plus `tenant_id`, optional `domains`, `role`, `permissions`, `session_id`. Open index signature allows additional claims.
### `TenantKeyStore`
Pluggable storage adapter: `getActiveKeys(tenantId?)`, `getKeyById(id)`, `addKey(kp)`, `removeKey(id)`.
## Implementation Notes
All `privateKey` and `publicKey` fields are typed as `unknown` to avoid exposing the `jose` library's internal types in the public interface. Consumers that need raw key material should use `serializeKeyPair` / `deserializeKeyPair` instead.