@farthershore/backend 0.14.0 → 0.16.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/CHANGELOG.md +68 -0
- package/README.md +6 -6
- package/dist/adapters/express.js +63 -1
- package/dist/generated/runtime-contract.js +15 -10
- package/dist/index.js +330 -174
- package/dist/testing/index.js +294 -167
- package/dist/types/adapters/express.d.ts +55 -5
- package/dist/types/core/metering.d.ts +8 -2
- package/dist/types/core/nonceCache.d.ts +25 -4
- package/dist/types/core/permissions.d.ts +14 -25
- package/dist/types/core/runtime.d.ts +23 -30
- package/dist/types/core/subject.d.ts +25 -0
- package/dist/types/core/verifyContext.d.ts +96 -19
- package/dist/types/core/verifyRequest.d.ts +37 -27
- package/dist/types/generated/runtime-contract.d.ts +9 -6
- package/dist/types/index.d.ts +16 -7
- package/dist/types/response-metering.d.ts +7 -0
- package/dist/types/runtime-signing.d.ts +7 -0
- package/dist/types/runtime-types.d.ts +18 -10
- package/dist/types/testing/devGateway.d.ts +3 -3
- package/dist/types/testing/devRuntime.d.ts +4 -2
- package/dist/types/testing/keysFile.d.ts +1 -1
- package/dist/types/testing/personas.d.ts +2 -2
- package/dist/types/testing/signers.d.ts +14 -6
- package/package.json +5 -4
package/dist/types/index.d.ts
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import { FartherShore, type FartherShoreInitOptions } from "./core/runtime.js";
|
|
2
|
-
import { type ExpressMiddleware, type MiddlewareOptions } from "./adapters/express.js";
|
|
2
|
+
import { type ExpressMiddleware, type ExpressRequestLike, type ExpressResponseLike, type MiddlewareOptions, type VerifiedExpressHandler } from "./adapters/express.js";
|
|
3
3
|
export { FartherShore } from "./core/runtime.js";
|
|
4
4
|
export type { FartherShoreInitOptions } from "./core/runtime.js";
|
|
5
5
|
export { FartherShoreError, statusForCode } from "./core/errors.js";
|
|
6
6
|
export { verifyRequest, type VerifyRequestInput, type VerifyRequestDeps, type FartherShoreRequestContext, type HeadersLike, } from "./core/verifyRequest.js";
|
|
7
|
-
export { verifyContext } from "./core/verifyContext.js";
|
|
8
|
-
export type { FartherShoreSignedContext } from "./core/verifyContext.js";
|
|
9
|
-
export {
|
|
7
|
+
export { verifyContext, decodeContextClaims, principalFromContextClaims, } from "./core/verifyContext.js";
|
|
8
|
+
export type { FartherShoreSignedContext, ConsumerPrincipal, } from "./core/verifyContext.js";
|
|
9
|
+
export { requireMember, requireService, type MemberSubject, type ServiceSubject, type PrincipalCarrier, } from "./core/subject.js";
|
|
10
|
+
export { hasPermission, requirePermission, permissionGrants, permissionSatisfies, FartherShorePermissionError, type PermissionCarrier, } from "./core/permissions.js";
|
|
10
11
|
export { JwksClient, type Jwk, type JwksClientOptions } from "./core/jwks.js";
|
|
11
|
-
export { NonceCache, type NonceCacheOptions } from "./core/nonceCache.js";
|
|
12
|
+
export { NonceCache, type NonceCacheOptions, type NonceStore, } from "./core/nonceCache.js";
|
|
12
13
|
export { BootstrapClient, type BootstrapClientOptions, } from "./core/bootstrap.js";
|
|
13
14
|
export { MeteringClient, type MeteringClientOptions, type MeterOptions, } from "./core/metering.js";
|
|
14
15
|
export { PostStreamUsageClient, type PostStreamUsageClientOptions, type ReportUsageInput, type RequestScopedReportUsageInput, type ReportUsageResult, } from "./core/post-stream-usage.js";
|
|
@@ -16,7 +17,7 @@ export { buildHealthReport, reportHealth, type HealthSnapshot, type HealthStatus
|
|
|
16
17
|
export { ShutdownManager, type ShutdownHook } from "./core/shutdown.js";
|
|
17
18
|
export { CloudflaredSupervisor, nodeSpawn, REDACTED_TOKEN, type SpawnFn, type SpawnedTunnelProcess, type CloudflaredSupervisorOptions, type TunnelState, type TunnelStatus, } from "./core/tunnel.js";
|
|
18
19
|
export type { FartherShoreTunnelOptions } from "./core/runtime.js";
|
|
19
|
-
export { createExpressMiddleware, type ExpressMiddleware, type ExpressRequestLike, type ExpressResponseLike, type ExpressNext, type MiddlewareOptions, } from "./adapters/express.js";
|
|
20
|
+
export { createExpressMiddleware, createExpressHandler, type ExpressMiddleware, type ExpressRequestLike, type ExpressResponseLike, type ExpressNext, type MiddlewareOptions, type VerifiedExpressHandler, type VerifiedPrincipalContext, } from "./adapters/express.js";
|
|
20
21
|
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, RUNTIME_ERROR_CODE_TO_ERROR_CODE, runtimeErrorToErrorCode, type LimitDescriptor, type RuntimeMappedErrorCode, } from "./runtime-types.js";
|
|
21
22
|
export { RUNTIME_ERROR_CODES } from "./generated/runtime-contract.js";
|
|
22
23
|
export { hashBody, buildCanonicalSigningString, canonicalizeQuery, signCanonicalString, verifyCanonicalSignature, runtimeTokenKind, } from "./runtime-signing.js";
|
|
@@ -27,8 +28,16 @@ export { createUsage, withUsage, computeMeteringHeaders, MeteringError, METERING
|
|
|
27
28
|
* (the Express adapter) bound to itself.
|
|
28
29
|
*/
|
|
29
30
|
export type FartherShoreInstance = FartherShore & {
|
|
30
|
-
/** Express middleware: fail-closed verify → req.fartherShore. */
|
|
31
|
+
/** Express middleware: strict fail-closed verify → req.fartherShore (+ strip x-fs-*). */
|
|
31
32
|
middleware(options?: MiddlewareOptions): ExpressMiddleware;
|
|
33
|
+
/**
|
|
34
|
+
* Wrap a route handler so it runs only with a GUARANTEED verified PRINCIPAL —
|
|
35
|
+
* the handler's first argument is a {@link VerifiedPrincipalContext} whose
|
|
36
|
+
* `principal` is NON-OPTIONAL. Fails closed (401 `context_unverified` when the
|
|
37
|
+
* context is absent, `principal_required` when it is identity-less) before the
|
|
38
|
+
* callback runs. Pairs with the strict `middleware()`.
|
|
39
|
+
*/
|
|
40
|
+
handler<Req extends ExpressRequestLike = ExpressRequestLike, Res extends ExpressResponseLike = ExpressResponseLike>(handler: VerifiedExpressHandler<Req, Res>): ExpressMiddleware;
|
|
32
41
|
};
|
|
33
42
|
export declare const fartherShore: {
|
|
34
43
|
/** Derive everything from FS_RUNTIME_TOKEN via bootstrap. */
|
|
@@ -48,6 +48,13 @@ export type BillableUsageMap = UsageMap;
|
|
|
48
48
|
export type MeteringOptions = {
|
|
49
49
|
token?: string;
|
|
50
50
|
env?: Record<string, string | undefined>;
|
|
51
|
+
/**
|
|
52
|
+
* Gateway request id for dev-mode trace/usage association (never signed).
|
|
53
|
+
* Thread the VERIFIED `ctx.requestId` here — the strict middleware strips the
|
|
54
|
+
* inbound `x-fs-request-id` header, so the request object no longer carries it.
|
|
55
|
+
* Falls back to the request's `x-fs-request-id` header when unset.
|
|
56
|
+
*/
|
|
57
|
+
requestId?: string;
|
|
51
58
|
measureContext?: Record<string, unknown>;
|
|
52
59
|
creditUnitsConsumed?: BillableUsageMap;
|
|
53
60
|
/** Gateway-validated operation identity hint. The SDK signs and transports it
|
|
@@ -14,6 +14,13 @@ export declare const canonicalizeQuery: (query: string) => string;
|
|
|
14
14
|
* dependence on JSON key order or Node Buffer serialization.
|
|
15
15
|
*/
|
|
16
16
|
export declare const buildCanonicalSigningString: (input: CanonicalSigningInput) => string;
|
|
17
|
+
/**
|
|
18
|
+
* SHA-256 hex of the presented `X-Fs-Context` JWT string (identity-context
|
|
19
|
+
* binding). A missing/empty context hashes the EMPTY string, so signer and
|
|
20
|
+
* verifier agree byte-for-byte when there is no identity context. The value
|
|
21
|
+
* feeds {@link CanonicalSigningInput.contextHash}.
|
|
22
|
+
*/
|
|
23
|
+
export declare const hashContextToken: (token: string | null | undefined) => Promise<string>;
|
|
17
24
|
/** Sign the canonical string with an Ed25519 private JWK → base64url signature. */
|
|
18
25
|
export declare const signCanonicalString: (canonical: string, privateJwk: JsonWebKey) => Promise<string>;
|
|
19
26
|
/** Verify a base64url Ed25519 signature over the canonical string. */
|
|
@@ -99,7 +99,7 @@ export declare const DENY_ENVELOPE_FIELDS: {
|
|
|
99
99
|
* the subset its bridge produces. Each is a verbatim core `ErrorCode` string —
|
|
100
100
|
* the drift guard asserts membership in the canonical enum.
|
|
101
101
|
*/
|
|
102
|
-
export type RuntimeMappedErrorCode = "UNAUTHORIZED" | "SERVICE_UNAVAILABLE" | "VALIDATION_ERROR" | "INTERNAL_ERROR";
|
|
102
|
+
export type RuntimeMappedErrorCode = "UNAUTHORIZED" | "FORBIDDEN" | "SERVICE_UNAVAILABLE" | "VALIDATION_ERROR" | "INTERNAL_ERROR";
|
|
103
103
|
/**
|
|
104
104
|
* C-2 — map every canonical {@link RuntimeErrorCode} (wire snake_case value) to
|
|
105
105
|
* the core `ErrorCode` it belongs to. Total over `RuntimeErrorCode` (the
|
|
@@ -126,18 +126,13 @@ export declare const RUNTIME_HEADER_NAMES: {
|
|
|
126
126
|
readonly keyId: "x-fs-key-id";
|
|
127
127
|
readonly requestId: "x-fs-request-id";
|
|
128
128
|
readonly timestamp: "x-fs-timestamp";
|
|
129
|
-
readonly
|
|
129
|
+
readonly businessId: "x-fs-business-id";
|
|
130
130
|
readonly backendId: "x-fs-backend-id";
|
|
131
131
|
readonly routeId: "x-fs-route-id";
|
|
132
132
|
readonly policyVersion: "x-fs-policy-version";
|
|
133
133
|
readonly bodyHash: "x-fs-body-hash";
|
|
134
134
|
};
|
|
135
135
|
export type RuntimeHeaderName = (typeof RUNTIME_HEADER_NAMES)[keyof typeof RUNTIME_HEADER_NAMES];
|
|
136
|
-
export declare const RUNTIME_IDENTITY_HEADER_NAMES: {
|
|
137
|
-
readonly permissions: "x-fs-permissions";
|
|
138
|
-
readonly roles: "x-fs-roles";
|
|
139
|
-
};
|
|
140
|
-
export type RuntimeIdentityHeaderName = (typeof RUNTIME_IDENTITY_HEADER_NAMES)[keyof typeof RUNTIME_IDENTITY_HEADER_NAMES];
|
|
141
136
|
/** Mirrors SERVICE_JWT_CLOCK_SKEW_SECONDS — the per-request signer reuses the
|
|
142
137
|
* same Ed25519/JWKS infra so the skew allowance is kept identical. */
|
|
143
138
|
export declare const RUNTIME_CLOCK_SKEW_SECONDS = 5;
|
|
@@ -166,11 +161,19 @@ export type CanonicalSigningInput = {
|
|
|
166
161
|
requestId: string;
|
|
167
162
|
/** Unix epoch seconds at signing time. */
|
|
168
163
|
timestamp: number;
|
|
169
|
-
|
|
164
|
+
businessId: string;
|
|
170
165
|
backendId: string;
|
|
171
166
|
/** Resolved route id; "" when unresolved. */
|
|
172
167
|
routeId: string;
|
|
173
168
|
policyVersion: string;
|
|
169
|
+
/**
|
|
170
|
+
* Consumer-principal wave (D3) — SHA-256 hex of the presented `X-Fs-Context`
|
|
171
|
+
* JWT string, or the SHA-256 of the EMPTY string when context signing is off.
|
|
172
|
+
* Bound into the canonical string so one request-signature verification
|
|
173
|
+
* covers both the request and the identity context. Compute via
|
|
174
|
+
* {@link hashContextToken}.
|
|
175
|
+
*/
|
|
176
|
+
contextHash: string;
|
|
174
177
|
};
|
|
175
178
|
export type RuntimeEnvironmentKind = RuntimeTokenKind;
|
|
176
179
|
export type TransportMode = "direct" | "tunnel";
|
|
@@ -221,7 +224,7 @@ export type RuntimeLockDescriptor = {
|
|
|
221
224
|
lockVersion: number;
|
|
222
225
|
};
|
|
223
226
|
export type RuntimeBootstrapResponse = {
|
|
224
|
-
|
|
227
|
+
business: {
|
|
225
228
|
id: string;
|
|
226
229
|
slug: string;
|
|
227
230
|
};
|
|
@@ -247,10 +250,15 @@ export type RuntimeBootstrapResponse = {
|
|
|
247
250
|
};
|
|
248
251
|
export type RuntimeMeteringEvent = {
|
|
249
252
|
event_id: string;
|
|
250
|
-
|
|
253
|
+
business_id: string;
|
|
251
254
|
backend_id: string;
|
|
252
255
|
route_id?: string;
|
|
253
256
|
request_id?: string;
|
|
257
|
+
/** Subscription the usage belongs to (billing attribution). Optional and
|
|
258
|
+
* additive: legacy emitters omit it; core then falls back to resolving the
|
|
259
|
+
* served gateway request via `request_id`, and rows that resolve neither
|
|
260
|
+
* way are persisted unbilled + flagged unattributable. */
|
|
261
|
+
subscription_id?: string;
|
|
254
262
|
meter: string;
|
|
255
263
|
qty: number;
|
|
256
264
|
timestamp: string;
|
|
@@ -7,9 +7,9 @@ export declare const DEV_METERING_ENDPOINT = "https://dev-gateway.farthershore.l
|
|
|
7
7
|
export type DevGatewayOptions = {
|
|
8
8
|
mode: DevMode;
|
|
9
9
|
keys: DevSignerKeys;
|
|
10
|
-
|
|
10
|
+
businessId?: string;
|
|
11
11
|
backendId?: string;
|
|
12
|
-
|
|
12
|
+
businessSlug?: string;
|
|
13
13
|
backendSlug?: string;
|
|
14
14
|
/** Extra route ids to expose in bootstrap for route-binding tests. */
|
|
15
15
|
routeIds?: string[];
|
|
@@ -27,7 +27,7 @@ export type DevGateway = {
|
|
|
27
27
|
meterEvents: RuntimeMeteringEvent[];
|
|
28
28
|
/** Every captured attested post-stream usage report. */
|
|
29
29
|
reportUsageEvents: RuntimePostStreamUsageEvent[];
|
|
30
|
-
|
|
30
|
+
businessId: string;
|
|
31
31
|
backendId: string;
|
|
32
32
|
jwksUrl: string;
|
|
33
33
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { FartherShore } from "../core/runtime.js";
|
|
2
|
-
import { type ExpressMiddleware, type MiddlewareOptions } from "../adapters/express.js";
|
|
2
|
+
import { type ExpressMiddleware, type ExpressRequestLike, type ExpressResponseLike, type MiddlewareOptions, type VerifiedExpressHandler } from "../adapters/express.js";
|
|
3
3
|
import { type PermissionCarrier } from "../core/permissions.js";
|
|
4
4
|
import { type DevGateway } from "./devGateway.js";
|
|
5
5
|
import { DevUsageSink } from "./usageSink.js";
|
|
@@ -13,6 +13,8 @@ export type TracedCarrier = PermissionCarrier & {
|
|
|
13
13
|
/** The FartherShore instance augmented with a bound Express middleware. */
|
|
14
14
|
export type FartherShoreDevInstance = FartherShore & {
|
|
15
15
|
middleware(options?: MiddlewareOptions): ExpressMiddleware;
|
|
16
|
+
/** Strict-context handler wrapper (guaranteed non-optional ctx). */
|
|
17
|
+
handler<Req extends ExpressRequestLike = ExpressRequestLike, Res extends ExpressResponseLike = ExpressResponseLike>(handler: VerifiedExpressHandler<Req, Res>): ExpressMiddleware;
|
|
16
18
|
/** The dev harness attached to this runtime. */
|
|
17
19
|
dev: DevRuntime;
|
|
18
20
|
};
|
|
@@ -24,7 +26,7 @@ export type CreateDevRuntimeOptions = {
|
|
|
24
26
|
routes?: string[];
|
|
25
27
|
/** Meter keys (informational — the dev gateway accepts any meter). */
|
|
26
28
|
meters?: string[];
|
|
27
|
-
|
|
29
|
+
businessId?: string;
|
|
28
30
|
backendId?: string;
|
|
29
31
|
/** Optional app transport for persona `.fetch()`; defaults to global fetch. */
|
|
30
32
|
appFetch?: typeof fetch;
|
|
@@ -53,7 +53,7 @@ export type PersonaRequestSpec = {
|
|
|
53
53
|
/** The context a persona client signs against (bootstrap ids + dev keys). */
|
|
54
54
|
export type PersonaClientContext = {
|
|
55
55
|
keys: DevSignerKeys;
|
|
56
|
-
|
|
56
|
+
businessId: string;
|
|
57
57
|
backendId: string;
|
|
58
58
|
contextSecret: string;
|
|
59
59
|
contextKid: string;
|
|
@@ -96,7 +96,7 @@ export declare const SIGNED_HEADER_NAMES: {
|
|
|
96
96
|
readonly keyId: "x-fs-key-id";
|
|
97
97
|
readonly requestId: "x-fs-request-id";
|
|
98
98
|
readonly timestamp: "x-fs-timestamp";
|
|
99
|
-
readonly
|
|
99
|
+
readonly businessId: "x-fs-business-id";
|
|
100
100
|
readonly backendId: "x-fs-backend-id";
|
|
101
101
|
readonly routeId: "x-fs-route-id";
|
|
102
102
|
readonly policyVersion: "x-fs-policy-version";
|
|
@@ -13,7 +13,7 @@ export type SignedHeaderOverrides = Partial<{
|
|
|
13
13
|
kid: string;
|
|
14
14
|
requestId: string;
|
|
15
15
|
timestamp: number;
|
|
16
|
-
|
|
16
|
+
businessId: string;
|
|
17
17
|
backendId: string;
|
|
18
18
|
routeId: string;
|
|
19
19
|
policyVersion: string;
|
|
@@ -25,7 +25,7 @@ export type SignedRequestSpec = {
|
|
|
25
25
|
query?: string;
|
|
26
26
|
body?: Uint8Array | null;
|
|
27
27
|
streamingExempt?: boolean;
|
|
28
|
-
|
|
28
|
+
businessId?: string;
|
|
29
29
|
backendId?: string;
|
|
30
30
|
routeId?: string;
|
|
31
31
|
policyVersion?: string;
|
|
@@ -38,6 +38,13 @@ export type SignedRequestSpec = {
|
|
|
38
38
|
privateJwk?: JsonWebKey;
|
|
39
39
|
/** Key id stamped into `x-fs-key-id`. Defaults to {@link TEST_KID}. */
|
|
40
40
|
kid?: string;
|
|
41
|
+
/**
|
|
42
|
+
* Consumer-principal wave (D3) — the signed `X-Fs-Context` JWT to bind into
|
|
43
|
+
* the request signature (and stamp as the `x-fs-context` header). Its SHA-256
|
|
44
|
+
* is appended to the canonical string, exactly as the gateway signer does. A
|
|
45
|
+
* missing token binds the empty-string hash (identity-less request).
|
|
46
|
+
*/
|
|
47
|
+
contextToken?: string;
|
|
41
48
|
};
|
|
42
49
|
/**
|
|
43
50
|
* Produce a valid signed claim + the X-FS-* header bag. Returns both the verify
|
|
@@ -63,10 +70,11 @@ export declare function unreachableJwks(): JwksClient;
|
|
|
63
70
|
/**
|
|
64
71
|
* Mint an HS256-signed `X-Fs-Context` token — the exact INVERSE of
|
|
65
72
|
* `verifyContext.ts`. The header is `{alg:"HS256",typ:"JWT",kid}` and the
|
|
66
|
-
* payload is the
|
|
67
|
-
* /
|
|
68
|
-
*
|
|
69
|
-
* (`contextSecrets` /
|
|
73
|
+
* payload is the cv=2 `ConsumerContextClaims` shape (sub / client_id? / act? /
|
|
74
|
+
* subjectKind / org / businessId / compiledPlanId / subscriptionId /
|
|
75
|
+
* subscriberId / environmentId / subjectKey plus optional permissions / roles).
|
|
76
|
+
* Signs with the same secret the SDK would verify against (`contextSecrets` /
|
|
77
|
+
* `FS_CONTEXT_SECRETS`).
|
|
70
78
|
*/
|
|
71
79
|
export declare function signContextToken(claim: FartherShoreSignedContext, secret?: string, kid?: string): Promise<string>;
|
|
72
80
|
export type DevSignerKeys = {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@farthershore/backend",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.0",
|
|
4
4
|
"description": "Farther Shore backend SDK for builder upstreams: signed response usage, fail-closed gateway request verification, health, and lifecycle from FS_RUNTIME_TOKEN",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -30,7 +30,8 @@
|
|
|
30
30
|
},
|
|
31
31
|
"files": [
|
|
32
32
|
"dist",
|
|
33
|
-
"README.md"
|
|
33
|
+
"README.md",
|
|
34
|
+
"CHANGELOG.md"
|
|
34
35
|
],
|
|
35
36
|
"publishConfig": {
|
|
36
37
|
"access": "public"
|
|
@@ -38,8 +39,8 @@
|
|
|
38
39
|
"optionalDependencies": {
|
|
39
40
|
"@farthershore/cloudflared-linux-x64": "0.0.0",
|
|
40
41
|
"@farthershore/cloudflared-linux-arm64": "0.0.0",
|
|
41
|
-
"@farthershore/cloudflared-darwin-
|
|
42
|
-
"@farthershore/cloudflared-darwin-
|
|
42
|
+
"@farthershore/cloudflared-darwin-x64": "0.0.0",
|
|
43
|
+
"@farthershore/cloudflared-darwin-arm64": "0.0.0"
|
|
43
44
|
},
|
|
44
45
|
"peerDependencies": {
|
|
45
46
|
"express": "^4.0.0 || ^5.0.0"
|