@farthershore/backend 0.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 +71 -0
- package/dist/adapters/express.js +101 -0
- package/dist/generated/runtime-contract.js +239 -0
- package/dist/index.js +1496 -0
- package/dist/types/adapters/express.d.ts +37 -0
- package/dist/types/core/bootstrap.d.ts +40 -0
- package/dist/types/core/errors.d.ts +15 -0
- package/dist/types/core/health.d.ts +23 -0
- package/dist/types/core/jwks.d.ts +48 -0
- package/dist/types/core/metering.d.ts +49 -0
- package/dist/types/core/nonceCache.d.ts +29 -0
- package/dist/types/core/runtime.d.ts +96 -0
- package/dist/types/core/shutdown.d.ts +10 -0
- package/dist/types/core/tunnel.d.ts +159 -0
- package/dist/types/core/verifyRequest.d.ts +54 -0
- package/dist/types/generated/metering-contract.d.ts +36 -0
- package/dist/types/generated/runtime-contract.d.ts +191 -0
- package/dist/types/index.d.ts +34 -0
- package/dist/types/legacy/metering.d.ts +20 -0
- package/dist/types/runtime-signing.d.ts +25 -0
- package/dist/types/runtime-types.d.ts +137 -0
- package/package.json +59 -0
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
export declare const RUNTIME_CONTRACT_VERSION: 1;
|
|
2
|
+
export declare const RUNTIME_TOKEN_ENV: "FS_RUNTIME_TOKEN";
|
|
3
|
+
export declare const RUNTIME_TOKEN_CONTRACT: {
|
|
4
|
+
readonly environmentVariable: "FS_RUNTIME_TOKEN";
|
|
5
|
+
readonly prefixes: {
|
|
6
|
+
readonly live: "fsrt_live_";
|
|
7
|
+
readonly test: "fsrt_test_";
|
|
8
|
+
};
|
|
9
|
+
readonly opaque: true;
|
|
10
|
+
readonly storage: "sha256-hash-only";
|
|
11
|
+
readonly lastFour: true;
|
|
12
|
+
readonly capabilities: readonly ["gateway_verification", "metering", "health", "tunnel"];
|
|
13
|
+
};
|
|
14
|
+
export declare const RUNTIME_BOOTSTRAP_CONTRACT: {
|
|
15
|
+
readonly method: "POST";
|
|
16
|
+
readonly path: "/v1/runtime/bootstrap";
|
|
17
|
+
readonly authorization: "Bearer fsrt_...";
|
|
18
|
+
readonly request: {
|
|
19
|
+
readonly instanceId: "string?";
|
|
20
|
+
readonly sdkVersion: "string?";
|
|
21
|
+
readonly sdkLanguage: "string?";
|
|
22
|
+
};
|
|
23
|
+
readonly response: {
|
|
24
|
+
readonly product: {
|
|
25
|
+
readonly id: "string";
|
|
26
|
+
readonly slug: "string";
|
|
27
|
+
};
|
|
28
|
+
readonly backend: {
|
|
29
|
+
readonly id: "string";
|
|
30
|
+
readonly slug: "string";
|
|
31
|
+
readonly name: "string";
|
|
32
|
+
};
|
|
33
|
+
readonly environment: {
|
|
34
|
+
readonly id: "string?";
|
|
35
|
+
readonly kind: "live | test";
|
|
36
|
+
};
|
|
37
|
+
readonly capabilities: "string[]";
|
|
38
|
+
readonly verification: {
|
|
39
|
+
readonly required: "boolean";
|
|
40
|
+
readonly jwksUrl: "string";
|
|
41
|
+
readonly clockSkewSeconds: "number";
|
|
42
|
+
readonly replayWindowSeconds: "number";
|
|
43
|
+
readonly headerNames: "object";
|
|
44
|
+
};
|
|
45
|
+
readonly metering: {
|
|
46
|
+
readonly enabled: "boolean";
|
|
47
|
+
readonly endpoint: "string";
|
|
48
|
+
readonly credential: "string";
|
|
49
|
+
readonly allowedMeters: "string[]";
|
|
50
|
+
readonly allowedRoutes: "string[]";
|
|
51
|
+
readonly perEventMax: "number";
|
|
52
|
+
};
|
|
53
|
+
readonly transport: {
|
|
54
|
+
readonly mode: "public_origin | mtls | cloudflare_tunnel";
|
|
55
|
+
readonly runner: "managed_cloudflared | sidecar | null";
|
|
56
|
+
readonly originUrl: "string?";
|
|
57
|
+
readonly originHostname: "string?";
|
|
58
|
+
readonly localTarget: "string?";
|
|
59
|
+
readonly cloudflared: "object?";
|
|
60
|
+
};
|
|
61
|
+
readonly routes: "object[]";
|
|
62
|
+
readonly policyVersion: "string";
|
|
63
|
+
readonly refreshAfterSeconds: "number";
|
|
64
|
+
};
|
|
65
|
+
};
|
|
66
|
+
export declare const RUNTIME_SIGNING_CONTRACT: {
|
|
67
|
+
readonly algorithm: "Ed25519";
|
|
68
|
+
readonly encoding: "base64url";
|
|
69
|
+
readonly keyMaterial: "service-jwt-jwks";
|
|
70
|
+
readonly canonicalString: {
|
|
71
|
+
readonly description: "Byte-exact, language-neutral serialization of the signed claim set. Fields are emitted in the FIXED order below, one per line, each as `name:value`, joined by a single newline (\\n, U+000A). NO trailing newline. The serialization depends on neither JSON key ordering nor any Node.js Buffer/serialization detail — only UTF-8 byte encoding of the field values. Empty/absent values are emitted as the empty string after the colon. Go/Python/Java/Rust reproduce this identically.";
|
|
72
|
+
readonly fieldSeparator: "\n";
|
|
73
|
+
readonly keyValueSeparator: ":";
|
|
74
|
+
readonly trailingNewline: false;
|
|
75
|
+
readonly fieldEncoding: "utf-8";
|
|
76
|
+
readonly fields: readonly ["method", "path", "query", "body-hash", "request-id", "timestamp", "product-id", "backend-id", "route-id", "policy-version"];
|
|
77
|
+
readonly fieldRules: {
|
|
78
|
+
readonly method: "Uppercased HTTP method (e.g. GET, POST).";
|
|
79
|
+
readonly path: "Request path, percent-encoded as received, no host, no query string. Always begins with '/'.";
|
|
80
|
+
readonly query: "Canonical query string: parse pairs, sort by (name, then value) using byte (code-unit) order, re-join name=value pairs with '&'. Names and values are NOT re-encoded (passed through as received). Empty string when there is no query.";
|
|
81
|
+
readonly "body-hash": "Lowercase hex SHA-256 of the RAW request body bytes. For an empty body, the SHA-256 of zero bytes (e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855). Streaming-exempt requests use the literal token 'STREAM'.";
|
|
82
|
+
readonly "request-id": "Opaque unique request id minted by the gateway (also the replay-cache nonce).";
|
|
83
|
+
readonly timestamp: "Integer Unix epoch seconds (UTC) at signing time, as a base-10 string with no padding.";
|
|
84
|
+
readonly "product-id": "Product id the request is routed to.";
|
|
85
|
+
readonly "backend-id": "Backend id the route binds to.";
|
|
86
|
+
readonly "route-id": "Resolved route id; empty string if the route is unresolved.";
|
|
87
|
+
readonly "policy-version": "Tenant artifact / policy version the gateway signed under.";
|
|
88
|
+
};
|
|
89
|
+
};
|
|
90
|
+
};
|
|
91
|
+
/**
|
|
92
|
+
* Ordered list of fields in the canonical signing string. The order here is
|
|
93
|
+
* load-bearing and identical across all language SDKs.
|
|
94
|
+
*/
|
|
95
|
+
export declare const RUNTIME_CANONICAL_FIELDS: readonly ["method", "path", "query", "body-hash", "request-id", "timestamp", "product-id", "backend-id", "route-id", "policy-version"];
|
|
96
|
+
export declare const RUNTIME_BODY_HASH_CONTRACT: {
|
|
97
|
+
readonly algorithm: "SHA-256";
|
|
98
|
+
readonly encoding: "hex-lower";
|
|
99
|
+
readonly source: "raw-request-bytes";
|
|
100
|
+
readonly emptyBodyHash: "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
|
|
101
|
+
readonly maxBodyBytes: 10485760;
|
|
102
|
+
readonly streamingExemptToken: "STREAM";
|
|
103
|
+
readonly streamingExemptContentTypes: readonly ["text/event-stream", "application/octet-stream", "multipart/form-data"];
|
|
104
|
+
readonly overMaxStatus: 413;
|
|
105
|
+
};
|
|
106
|
+
export declare const RUNTIME_HEADERS: {
|
|
107
|
+
readonly signature: "x-fs-signature";
|
|
108
|
+
readonly keyId: "x-fs-key-id";
|
|
109
|
+
readonly requestId: "x-fs-request-id";
|
|
110
|
+
readonly timestamp: "x-fs-timestamp";
|
|
111
|
+
readonly productId: "x-fs-product-id";
|
|
112
|
+
readonly backendId: "x-fs-backend-id";
|
|
113
|
+
readonly routeId: "x-fs-route-id";
|
|
114
|
+
readonly policyVersion: "x-fs-policy-version";
|
|
115
|
+
readonly bodyHash: "x-fs-body-hash";
|
|
116
|
+
};
|
|
117
|
+
export declare const RUNTIME_REPLAY_CONTRACT: {
|
|
118
|
+
readonly windowSeconds: 300;
|
|
119
|
+
readonly clockSkewSeconds: 5;
|
|
120
|
+
readonly nonce: "x-fs-request-id";
|
|
121
|
+
readonly nonceCache: "bounded-lru";
|
|
122
|
+
readonly policy: "fail-closed";
|
|
123
|
+
};
|
|
124
|
+
export declare const RUNTIME_ERROR_CODES: {
|
|
125
|
+
readonly missingSignature: "missing_signature";
|
|
126
|
+
readonly malformedSignature: "malformed_signature";
|
|
127
|
+
readonly unknownKeyId: "unknown_key_id";
|
|
128
|
+
readonly jwksUnavailable: "jwks_unavailable";
|
|
129
|
+
readonly badSignature: "bad_signature";
|
|
130
|
+
readonly bodyHashMismatch: "body_hash_mismatch";
|
|
131
|
+
readonly routeMismatch: "route_mismatch";
|
|
132
|
+
readonly clockSkew: "clock_skew";
|
|
133
|
+
readonly expiredSignature: "expired_signature";
|
|
134
|
+
readonly replayedNonce: "replayed_nonce";
|
|
135
|
+
readonly bodyTooLarge: "body_too_large";
|
|
136
|
+
readonly environmentMismatch: "environment_mismatch";
|
|
137
|
+
readonly missingToken: "missing_token";
|
|
138
|
+
readonly invalidToken: "invalid_token";
|
|
139
|
+
};
|
|
140
|
+
export type RuntimeErrorCode = (typeof RUNTIME_ERROR_CODES)[keyof typeof RUNTIME_ERROR_CODES];
|
|
141
|
+
export declare const RUNTIME_METERING_CONTRACT: {
|
|
142
|
+
readonly endpoint: "/v1/metering/events";
|
|
143
|
+
readonly method: "POST";
|
|
144
|
+
readonly credential: "reusable-bearer";
|
|
145
|
+
readonly event: {
|
|
146
|
+
readonly event_id: "string";
|
|
147
|
+
readonly product_id: "string";
|
|
148
|
+
readonly backend_id: "string";
|
|
149
|
+
readonly route_id: "string?";
|
|
150
|
+
readonly request_id: "string?";
|
|
151
|
+
readonly meter: "string";
|
|
152
|
+
readonly qty: "number";
|
|
153
|
+
readonly timestamp: "string";
|
|
154
|
+
};
|
|
155
|
+
readonly idempotencyKey: "event_id";
|
|
156
|
+
readonly delivery: "at-least-once";
|
|
157
|
+
readonly billingOnly: true;
|
|
158
|
+
readonly realtimeEnforced: false;
|
|
159
|
+
readonly trustModel: "backend-reported values are NOT cryptographically attested; a buggy or compromised backend can self-report arbitrary values for its OWN product only. Core enforces allowedMeters/allowedRoutes from the authoritative token record at ingest, applies a per-event sanity max (perEventMax), and raises an implausible-volume alert.";
|
|
160
|
+
};
|
|
161
|
+
export declare const RUNTIME_HEALTH_CONTRACT: {
|
|
162
|
+
readonly endpoint: "/v1/runtime/health";
|
|
163
|
+
readonly method: "POST";
|
|
164
|
+
readonly request: {
|
|
165
|
+
readonly instanceId: "string?";
|
|
166
|
+
readonly status: "starting | ready | degraded | stopping";
|
|
167
|
+
};
|
|
168
|
+
readonly readinessStates: readonly ["UNKNOWN", "WAITING", "READY", "DEGRADED", "OFFLINE"];
|
|
169
|
+
readonly checks: readonly ["runtime_token_valid", "bootstrapped", "tunnel_running", "signed_request_2xx", "unsigned_request_401", "stale_signature_401", "wrong_route_401", "metering_observed"];
|
|
170
|
+
readonly report: {
|
|
171
|
+
readonly runtimeToken: "boolean";
|
|
172
|
+
readonly bootstrap: "boolean";
|
|
173
|
+
readonly tunnel: "string?";
|
|
174
|
+
readonly verification: "boolean";
|
|
175
|
+
readonly metering: "boolean";
|
|
176
|
+
};
|
|
177
|
+
};
|
|
178
|
+
export declare const RUNTIME_TRANSPORT_CONTRACT: {
|
|
179
|
+
readonly modes: {
|
|
180
|
+
readonly public_origin: "Gateway fetches the builder's public origin URL; the SDK middleware fail-closed-verifies every request. Provisions zero Cloudflare objects. Standard tier; also the dev path.";
|
|
181
|
+
readonly mtls: "Gateway presents a Farther-Shore-issued platform-wide client cert; the builder's ingress requires + verifies it. Channel-level mutual auth; provisions zero Cloudflare objects. Standard-plus / self-hosted tier.";
|
|
182
|
+
readonly cloudflare_tunnel: "Farther Shore provisions a private outbound Cloudflare Tunnel; no inbound port. The only Production-secure tier. Consumes Cloudflare tunnel/route slots.";
|
|
183
|
+
};
|
|
184
|
+
readonly runners: {
|
|
185
|
+
readonly managed_cloudflared: "fs.start() supervises cloudflared as a child process (default DX).";
|
|
186
|
+
readonly sidecar: "Vanilla cloudflare/cloudflared container beside the app (production / non-Node).";
|
|
187
|
+
};
|
|
188
|
+
readonly channelTrust: readonly ["mtls", "cloudflare_tunnel"];
|
|
189
|
+
readonly requestTrust: "x-fs-signature";
|
|
190
|
+
readonly invariant: "Channel trust (mtls/tunnel) and request trust (the X-FS-* signature) are distinct layers; both always apply. CF-Access-* headers are transport-layer only and are IGNORED by the SDK.";
|
|
191
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { FartherShore, type FartherShoreInitOptions } from "./core/runtime.js";
|
|
2
|
+
import { type ExpressMiddleware, type MiddlewareOptions } from "./adapters/express.js";
|
|
3
|
+
export { FartherShore } from "./core/runtime.js";
|
|
4
|
+
export type { FartherShoreInitOptions } from "./core/runtime.js";
|
|
5
|
+
export { FartherShoreError, statusForCode } from "./core/errors.js";
|
|
6
|
+
export { verifyRequest, type VerifyRequestInput, type VerifyRequestDeps, type FartherShoreRequestContext, type HeadersLike, } from "./core/verifyRequest.js";
|
|
7
|
+
export { JwksClient, type Jwk, type JwksClientOptions } from "./core/jwks.js";
|
|
8
|
+
export { NonceCache, type NonceCacheOptions } from "./core/nonceCache.js";
|
|
9
|
+
export { BootstrapClient, type BootstrapClientOptions, } from "./core/bootstrap.js";
|
|
10
|
+
export { MeteringClient, type MeteringClientOptions, type MeterOptions, } from "./core/metering.js";
|
|
11
|
+
export { buildHealthReport, reportHealth, type HealthSnapshot, type HealthStatus, type HeartbeatOptions, } from "./core/health.js";
|
|
12
|
+
export { ShutdownManager, type ShutdownHook } from "./core/shutdown.js";
|
|
13
|
+
export { CloudflaredSupervisor, nodeSpawn, REDACTED_TOKEN, type SpawnFn, type SpawnedTunnelProcess, type CloudflaredSupervisorOptions, type TunnelState, type TunnelStatus, } from "./core/tunnel.js";
|
|
14
|
+
export type { FartherShoreTunnelOptions } from "./core/runtime.js";
|
|
15
|
+
export { createExpressMiddleware, type ExpressMiddleware, type ExpressRequestLike, type ExpressResponseLike, type ExpressNext, type MiddlewareOptions, } from "./adapters/express.js";
|
|
16
|
+
export { FS_RUNTIME_TOKEN_ENV, RUNTIME_TOKEN_PREFIXES, RUNTIME_TOKEN_CAPABILITIES, RUNTIME_HEADER_NAMES, RUNTIME_CLOCK_SKEW_SECONDS, RUNTIME_REPLAY_WINDOW_SECONDS, EMPTY_BODY_SHA256, STREAMING_EXEMPT_BODY_HASH, MAX_BODY_BYTES, type RuntimeErrorCode, type RuntimeTokenCapability, type CanonicalSigningInput, type RuntimeBootstrapResponse, type RuntimeMeteringEvent, type RuntimeHealthReport, type TransportMode, } from "./runtime-types.js";
|
|
17
|
+
export { RUNTIME_ERROR_CODES } from "./generated/runtime-contract.js";
|
|
18
|
+
export { hashBody, buildCanonicalSigningString, canonicalizeQuery, signCanonicalString, verifyCanonicalSignature, runtimeTokenKind, } from "./runtime-signing.js";
|
|
19
|
+
export { createUsage, withUsage, MeteringError, METERING_PAYLOAD_HEADER, METERING_SIGNATURE_HEADER, METERING_TOKEN_HEADER, DEFAULT_TOKEN_ENV, type UsageMap, type UsageReporter, type MeteringOptions, } from "./legacy/metering.js";
|
|
20
|
+
/**
|
|
21
|
+
* The conceptual public entrypoint. `fartherShore.initFromEnv()` mirrors the
|
|
22
|
+
* language-neutral spec. The returned instance is augmented with `middleware()`
|
|
23
|
+
* (the Express adapter) bound to itself.
|
|
24
|
+
*/
|
|
25
|
+
export type FartherShoreInstance = FartherShore & {
|
|
26
|
+
/** Express middleware: fail-closed verify → req.fartherShore. */
|
|
27
|
+
middleware(options?: MiddlewareOptions): ExpressMiddleware;
|
|
28
|
+
};
|
|
29
|
+
export declare const fartherShore: {
|
|
30
|
+
/** Derive everything from FS_RUNTIME_TOKEN via bootstrap. */
|
|
31
|
+
initFromEnv(options?: FartherShoreInitOptions): FartherShoreInstance;
|
|
32
|
+
};
|
|
33
|
+
/** Convenience: top-level initFromEnv mirroring fartherShore.initFromEnv(). */
|
|
34
|
+
export declare function initFromEnv(options?: FartherShoreInitOptions): FartherShoreInstance;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type MeteringErrorCode } from "../generated/metering-contract.js";
|
|
2
|
+
export declare const METERING_PAYLOAD_HEADER: "x-fs-metering";
|
|
3
|
+
export declare const METERING_SIGNATURE_HEADER: "x-fs-metering-sig";
|
|
4
|
+
export declare const METERING_TOKEN_HEADER: "x-fs-metering-token";
|
|
5
|
+
export declare const DEFAULT_TOKEN_ENV: "FARTHERSHORE_METERING_TOKEN";
|
|
6
|
+
export type UsageMap = Record<string, number>;
|
|
7
|
+
export type MeteringOptions = {
|
|
8
|
+
token?: string;
|
|
9
|
+
env?: Record<string, string | undefined>;
|
|
10
|
+
};
|
|
11
|
+
export type UsageReporter = {
|
|
12
|
+
report(meter: string, value: number): UsageReporter;
|
|
13
|
+
wrap(response: Response): Promise<Response>;
|
|
14
|
+
};
|
|
15
|
+
export declare class MeteringError extends Error {
|
|
16
|
+
readonly code: MeteringErrorCode;
|
|
17
|
+
constructor(code: MeteringErrorCode, message: string);
|
|
18
|
+
}
|
|
19
|
+
export declare function createUsage(request: Request, options?: MeteringOptions): UsageReporter;
|
|
20
|
+
export declare function withUsage(request: Request, response: Response, usage: UsageMap, options?: MeteringOptions): Promise<Response>;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { CanonicalSigningInput, RuntimeTokenKind } from "./runtime-types.js";
|
|
2
|
+
/** Lowercase-hex SHA-256 of the RAW request bytes. Never re-serialized JSON. */
|
|
3
|
+
export declare const hashBody: (body: Uint8Array) => Promise<string>;
|
|
4
|
+
/**
|
|
5
|
+
* Normalize a query string into the canonical form: split into name=value
|
|
6
|
+
* pairs on '&', sort by (name, then value) using byte (code-unit) order, and
|
|
7
|
+
* re-join with '&'. Pass-through (no re-encoding) so all SDKs agree on bytes.
|
|
8
|
+
* An empty input yields an empty string.
|
|
9
|
+
*/
|
|
10
|
+
export declare const canonicalizeQuery: (query: string) => string;
|
|
11
|
+
/**
|
|
12
|
+
* Build the byte-exact canonical signing string. This is the cross-language
|
|
13
|
+
* gate: every SDK MUST produce identical output for identical inputs, with NO
|
|
14
|
+
* dependence on JSON key order or Node Buffer serialization.
|
|
15
|
+
*/
|
|
16
|
+
export declare const buildCanonicalSigningString: (input: CanonicalSigningInput) => string;
|
|
17
|
+
/** Sign the canonical string with an Ed25519 private JWK → base64url signature. */
|
|
18
|
+
export declare const signCanonicalString: (canonical: string, privateJwk: JsonWebKey) => Promise<string>;
|
|
19
|
+
/** Verify a base64url Ed25519 signature over the canonical string. */
|
|
20
|
+
export declare const verifyCanonicalSignature: (canonical: string, signatureB64Url: string, publicJwk: JsonWebKey) => Promise<boolean>;
|
|
21
|
+
/**
|
|
22
|
+
* Classify a presented runtime token by prefix without trusting its body.
|
|
23
|
+
* Returns the environment kind, or `null` when the prefix is not recognized.
|
|
24
|
+
*/
|
|
25
|
+
export declare const runtimeTokenKind: (token: string) => RuntimeTokenKind | null;
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
export type { RuntimeErrorCode } from "./generated/runtime-contract.js";
|
|
2
|
+
export declare const FS_RUNTIME_TOKEN_ENV: "FS_RUNTIME_TOKEN";
|
|
3
|
+
export declare const RUNTIME_TOKEN_PREFIXES: {
|
|
4
|
+
readonly live: "fsrt_live_";
|
|
5
|
+
readonly test: "fsrt_test_";
|
|
6
|
+
};
|
|
7
|
+
export type RuntimeTokenKind = keyof typeof RUNTIME_TOKEN_PREFIXES;
|
|
8
|
+
export declare const RUNTIME_TOKEN_CAPABILITIES: readonly ["gateway_verification", "metering", "health", "tunnel"];
|
|
9
|
+
export type RuntimeTokenCapability = (typeof RUNTIME_TOKEN_CAPABILITIES)[number];
|
|
10
|
+
export declare const RUNTIME_HEADER_NAMES: {
|
|
11
|
+
readonly signature: "x-fs-signature";
|
|
12
|
+
readonly keyId: "x-fs-key-id";
|
|
13
|
+
readonly requestId: "x-fs-request-id";
|
|
14
|
+
readonly timestamp: "x-fs-timestamp";
|
|
15
|
+
readonly productId: "x-fs-product-id";
|
|
16
|
+
readonly backendId: "x-fs-backend-id";
|
|
17
|
+
readonly routeId: "x-fs-route-id";
|
|
18
|
+
readonly policyVersion: "x-fs-policy-version";
|
|
19
|
+
readonly bodyHash: "x-fs-body-hash";
|
|
20
|
+
};
|
|
21
|
+
export type RuntimeHeaderName = (typeof RUNTIME_HEADER_NAMES)[keyof typeof RUNTIME_HEADER_NAMES];
|
|
22
|
+
/** Mirrors SERVICE_JWT_CLOCK_SKEW_SECONDS — the per-request signer reuses the
|
|
23
|
+
* same Ed25519/JWKS infra so the skew allowance is kept identical. */
|
|
24
|
+
export declare const RUNTIME_CLOCK_SKEW_SECONDS = 5;
|
|
25
|
+
export declare const RUNTIME_REPLAY_WINDOW_SECONDS = 300;
|
|
26
|
+
/** SHA-256 of zero bytes — the canonical empty-body hash. */
|
|
27
|
+
export declare const EMPTY_BODY_SHA256 = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
|
|
28
|
+
/** Sentinel emitted into the canonical string when a request is body-hash
|
|
29
|
+
* exempt (streaming). Raw-byte hashing is otherwise mandatory. */
|
|
30
|
+
export declare const STREAMING_EXEMPT_BODY_HASH = "STREAM";
|
|
31
|
+
/** Max raw-body bytes the gateway/SDK will hash before returning 413. */
|
|
32
|
+
export declare const MAX_BODY_BYTES: number;
|
|
33
|
+
/**
|
|
34
|
+
* The signed claim set. Field VALUES are language-neutral strings/integers;
|
|
35
|
+
* the canonical serialization (in runtime-signing.ts) pins the wire bytes.
|
|
36
|
+
*/
|
|
37
|
+
export type CanonicalSigningInput = {
|
|
38
|
+
/** HTTP method; canonicalized to upper-case. */
|
|
39
|
+
method: string;
|
|
40
|
+
/** Request path (no host, no query). */
|
|
41
|
+
path: string;
|
|
42
|
+
/** Raw query string (without leading '?'), or "" when absent. */
|
|
43
|
+
query: string;
|
|
44
|
+
/** Lowercase-hex SHA-256 of the raw body, EMPTY_BODY_SHA256, or "STREAM". */
|
|
45
|
+
bodyHash: string;
|
|
46
|
+
/** Gateway-minted request id (also the replay nonce). */
|
|
47
|
+
requestId: string;
|
|
48
|
+
/** Unix epoch seconds at signing time. */
|
|
49
|
+
timestamp: number;
|
|
50
|
+
productId: string;
|
|
51
|
+
backendId: string;
|
|
52
|
+
/** Resolved route id; "" when unresolved. */
|
|
53
|
+
routeId: string;
|
|
54
|
+
policyVersion: string;
|
|
55
|
+
};
|
|
56
|
+
export type RuntimeEnvironmentKind = RuntimeTokenKind;
|
|
57
|
+
export type TransportMode = "public_origin" | "mtls" | "cloudflare_tunnel";
|
|
58
|
+
export type TransportRunner = "managed_cloudflared" | "sidecar";
|
|
59
|
+
export type RuntimeBootstrapRequest = {
|
|
60
|
+
instanceId?: string;
|
|
61
|
+
sdkVersion?: string;
|
|
62
|
+
sdkLanguage?: string;
|
|
63
|
+
};
|
|
64
|
+
export type RuntimeVerificationConfig = {
|
|
65
|
+
required: boolean;
|
|
66
|
+
jwksUrl: string;
|
|
67
|
+
clockSkewSeconds: number;
|
|
68
|
+
replayWindowSeconds: number;
|
|
69
|
+
headerNames: typeof RUNTIME_HEADER_NAMES;
|
|
70
|
+
};
|
|
71
|
+
export type RuntimeMeteringConfig = {
|
|
72
|
+
enabled: boolean;
|
|
73
|
+
endpoint: string;
|
|
74
|
+
credential: string;
|
|
75
|
+
allowedMeters: string[];
|
|
76
|
+
allowedRoutes: string[];
|
|
77
|
+
perEventMax: number;
|
|
78
|
+
};
|
|
79
|
+
export type RuntimeTransportConfig = {
|
|
80
|
+
mode: TransportMode;
|
|
81
|
+
runner: TransportRunner | null;
|
|
82
|
+
originUrl?: string;
|
|
83
|
+
originHostname?: string;
|
|
84
|
+
localTarget?: string;
|
|
85
|
+
cloudflared?: {
|
|
86
|
+
tunnelToken: string;
|
|
87
|
+
version: string;
|
|
88
|
+
};
|
|
89
|
+
mtlsClientCertRef?: string;
|
|
90
|
+
};
|
|
91
|
+
export type RuntimeRouteDescriptor = {
|
|
92
|
+
id: string;
|
|
93
|
+
method: string;
|
|
94
|
+
path: string;
|
|
95
|
+
backendId: string;
|
|
96
|
+
};
|
|
97
|
+
export type RuntimeBootstrapResponse = {
|
|
98
|
+
product: {
|
|
99
|
+
id: string;
|
|
100
|
+
slug: string;
|
|
101
|
+
};
|
|
102
|
+
backend: {
|
|
103
|
+
id: string;
|
|
104
|
+
slug: string;
|
|
105
|
+
name: string;
|
|
106
|
+
};
|
|
107
|
+
environment: {
|
|
108
|
+
id: string | null;
|
|
109
|
+
kind: RuntimeEnvironmentKind;
|
|
110
|
+
};
|
|
111
|
+
capabilities: RuntimeTokenCapability[];
|
|
112
|
+
verification: RuntimeVerificationConfig;
|
|
113
|
+
metering: RuntimeMeteringConfig;
|
|
114
|
+
transport: RuntimeTransportConfig;
|
|
115
|
+
routes: RuntimeRouteDescriptor[];
|
|
116
|
+
policyVersion: string;
|
|
117
|
+
refreshAfterSeconds: number;
|
|
118
|
+
};
|
|
119
|
+
export type RuntimeMeteringEvent = {
|
|
120
|
+
event_id: string;
|
|
121
|
+
product_id: string;
|
|
122
|
+
backend_id: string;
|
|
123
|
+
route_id?: string;
|
|
124
|
+
request_id?: string;
|
|
125
|
+
meter: string;
|
|
126
|
+
qty: number;
|
|
127
|
+
timestamp: string;
|
|
128
|
+
};
|
|
129
|
+
export declare const RUNTIME_READINESS_STATES: readonly ["UNKNOWN", "WAITING", "READY", "DEGRADED", "OFFLINE"];
|
|
130
|
+
export type RuntimeReadinessState = (typeof RUNTIME_READINESS_STATES)[number];
|
|
131
|
+
export type RuntimeHealthReport = {
|
|
132
|
+
runtimeToken: boolean;
|
|
133
|
+
bootstrap: boolean;
|
|
134
|
+
tunnel: string | null;
|
|
135
|
+
verification: boolean;
|
|
136
|
+
metering: boolean;
|
|
137
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@farthershore/backend",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Farther Shore backend SDK: secure BYO-backend runtime — fail-closed gateway request verification, metering, health, and lifecycle, all derived from FS_RUNTIME_TOKEN",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/types/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/types/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"./express": {
|
|
15
|
+
"types": "./dist/types/adapters/express.d.ts",
|
|
16
|
+
"import": "./dist/adapters/express.js"
|
|
17
|
+
},
|
|
18
|
+
"./runtime": {
|
|
19
|
+
"types": "./dist/types/generated/runtime-contract.d.ts",
|
|
20
|
+
"import": "./dist/generated/runtime-contract.js"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"dist",
|
|
25
|
+
"README.md"
|
|
26
|
+
],
|
|
27
|
+
"publishConfig": {
|
|
28
|
+
"access": "public"
|
|
29
|
+
},
|
|
30
|
+
"peerDependencies": {
|
|
31
|
+
"express": "^4.0.0 || ^5.0.0"
|
|
32
|
+
},
|
|
33
|
+
"peerDependenciesMeta": {
|
|
34
|
+
"express": {
|
|
35
|
+
"optional": true
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@types/node": "^22.19.17",
|
|
40
|
+
"esbuild": "^0.27.7",
|
|
41
|
+
"eslint": "^9.39.4",
|
|
42
|
+
"eslint-plugin-sonarjs": "^4.0.3",
|
|
43
|
+
"typescript": "^6.0.2",
|
|
44
|
+
"typescript-eslint": "^8.59.0",
|
|
45
|
+
"vitest": "^4.1.6",
|
|
46
|
+
"@farthershore/contracts": "0.61.1"
|
|
47
|
+
},
|
|
48
|
+
"engines": {
|
|
49
|
+
"node": ">=22"
|
|
50
|
+
},
|
|
51
|
+
"license": "MIT",
|
|
52
|
+
"scripts": {
|
|
53
|
+
"build": "tsc --noEmit && node scripts/build.mjs",
|
|
54
|
+
"lint": "eslint .",
|
|
55
|
+
"lint:fix": "eslint . --fix",
|
|
56
|
+
"test": "vitest run",
|
|
57
|
+
"pack:check": "node scripts/check-pack.mjs"
|
|
58
|
+
}
|
|
59
|
+
}
|