@farthershore/backend 0.16.0 → 0.18.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 +31 -0
- package/README.md +1 -1
- package/dist/adapters/express.js +21 -7
- package/dist/generated/runtime-contract.js +2 -1
- package/dist/index.js +44 -16
- package/dist/testing/index.js +28 -12
- package/dist/types/adapters/express.d.ts +21 -0
- package/dist/types/core/errors.d.ts +4 -1
- package/dist/types/core/runtime.d.ts +0 -1
- package/dist/types/core/subject.d.ts +20 -0
- package/dist/types/generated/runtime-contract.d.ts +1 -0
- package/dist/types/index.d.ts +2 -2
- package/dist/types/runtime-types.d.ts +3 -5
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,37 @@ All notable changes to the runtime backend SDK are documented here. This SDK
|
|
|
4
4
|
versions independently from the frontend and business SDKs. Pre-1.0: minor
|
|
5
5
|
versions may include breaking changes.
|
|
6
6
|
|
|
7
|
+
## [0.18.0]
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- `createExpressMiddleware` accepts `onVerificationError`, called when
|
|
12
|
+
verification REJECTS a request with the diagnostic detail the response body
|
|
13
|
+
deliberately withholds (`{ code, message, status, method, path }`).
|
|
14
|
+
|
|
15
|
+
The wire body stays `{ error: <code> }` — several distinct causes share one
|
|
16
|
+
code (`route_mismatch` covers a business mismatch, a backend mismatch, AND an
|
|
17
|
+
unserved route id) and the specifics must not leak to an unauthenticated
|
|
18
|
+
caller. Previously they were discarded entirely, leaving the operator no way
|
|
19
|
+
to tell which cause fired even in their own logs. Defaults to a one-line
|
|
20
|
+
`console.warn`; pass a function to route it into a structured logger, or
|
|
21
|
+
`() => {}` to silence it. A throwing reporter cannot turn a 401 into a 500.
|
|
22
|
+
|
|
23
|
+
## [0.17.0]
|
|
24
|
+
|
|
25
|
+
### Changed — BREAKING
|
|
26
|
+
|
|
27
|
+
- `RUNTIME_TOKEN_CAPABILITIES` is renamed `RUNTIME_TOKEN_OPERATIONS`, and the
|
|
28
|
+
type `RuntimeTokenCapability` is renamed `RuntimeTokenOperation`. The values
|
|
29
|
+
are operations, not capabilities, and the old names contradicted the runtime
|
|
30
|
+
contract they mirror. Update imports; no behaviour change.
|
|
31
|
+
|
|
32
|
+
### Added
|
|
33
|
+
|
|
34
|
+
- `credentialKind()` and `isPortalSession()` — route-surface credential-kind
|
|
35
|
+
derivation from the signed principal. No new claim is required; both are
|
|
36
|
+
derived from what the gateway already signs.
|
|
37
|
+
|
|
7
38
|
## [0.16.0]
|
|
8
39
|
|
|
9
40
|
Consumer-principal runtime. Every verified request now carries a typed
|
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@ graceful lifecycle (health + shutdown). Everything else — your business, backe
|
|
|
12
12
|
and environment ids, the verification keys, and the metering endpoint — is
|
|
13
13
|
fetched automatically from the token at startup.
|
|
14
14
|
|
|
15
|
-
> **Status: `0.
|
|
15
|
+
> **Status: `0.18.0`.** Pre-1.0: minor releases may include breaking changes, so
|
|
16
16
|
> pin this package to an exact version (or a patch-only range) and upgrade
|
|
17
17
|
> deliberately.
|
|
18
18
|
|
package/dist/adapters/express.js
CHANGED
|
@@ -18,7 +18,9 @@ var FartherShoreError = class extends Error {
|
|
|
18
18
|
}
|
|
19
19
|
};
|
|
20
20
|
function statusForCode(code) {
|
|
21
|
-
|
|
21
|
+
if (code === "body_too_large") return 413;
|
|
22
|
+
if (code === "surface_not_allowed") return 403;
|
|
23
|
+
return 401;
|
|
22
24
|
}
|
|
23
25
|
|
|
24
26
|
// src/core/permissions.ts
|
|
@@ -83,15 +85,27 @@ async function runMiddleware(fs, options, req, res, next) {
|
|
|
83
85
|
stripFartherShoreHeaders(req);
|
|
84
86
|
next();
|
|
85
87
|
} catch (error) {
|
|
86
|
-
fail(res, error);
|
|
88
|
+
fail(res, error, options, req);
|
|
87
89
|
}
|
|
88
90
|
}
|
|
89
|
-
function fail(res, error) {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
91
|
+
function fail(res, error, options, req) {
|
|
92
|
+
const code = error instanceof FartherShoreError ? error.code : "bad_signature";
|
|
93
|
+
const status = error instanceof FartherShoreError ? error.status : 401;
|
|
94
|
+
const message = error instanceof Error && error.message ? error.message : code;
|
|
95
|
+
const report = options.onVerificationError ?? ((d) => {
|
|
96
|
+
console.warn(`[farthershore] request rejected (${d.code}): ${d.message}`);
|
|
97
|
+
});
|
|
98
|
+
try {
|
|
99
|
+
report({
|
|
100
|
+
code,
|
|
101
|
+
message,
|
|
102
|
+
status,
|
|
103
|
+
method: req.method ?? "",
|
|
104
|
+
path: req.url ?? ""
|
|
105
|
+
});
|
|
106
|
+
} catch {
|
|
93
107
|
}
|
|
94
|
-
res.status(
|
|
108
|
+
res.status(status).json({ error: code });
|
|
95
109
|
}
|
|
96
110
|
function stripFartherShoreHeaders(req) {
|
|
97
111
|
const headers = req.headers;
|
|
@@ -166,7 +166,8 @@ var RUNTIME_ERROR_CODES = {
|
|
|
166
166
|
invalidToken: "invalid_token",
|
|
167
167
|
contextUnverified: "context_unverified",
|
|
168
168
|
memberSubjectRequired: "member_subject_required",
|
|
169
|
-
serviceSubjectRequired: "service_subject_required"
|
|
169
|
+
serviceSubjectRequired: "service_subject_required",
|
|
170
|
+
surfaceNotAllowed: "surface_not_allowed"
|
|
170
171
|
};
|
|
171
172
|
var RUNTIME_METERING_CONTRACT = {
|
|
172
173
|
endpoint: "/v1/metering/events",
|
package/dist/index.js
CHANGED
|
@@ -217,7 +217,8 @@ var RUNTIME_ERROR_CODES = {
|
|
|
217
217
|
invalidToken: "invalid_token",
|
|
218
218
|
contextUnverified: "context_unverified",
|
|
219
219
|
memberSubjectRequired: "member_subject_required",
|
|
220
|
-
serviceSubjectRequired: "service_subject_required"
|
|
220
|
+
serviceSubjectRequired: "service_subject_required",
|
|
221
|
+
surfaceNotAllowed: "surface_not_allowed"
|
|
221
222
|
};
|
|
222
223
|
var RUNTIME_RESPONSE_METERING_CONTRACT = {
|
|
223
224
|
headers: {
|
|
@@ -284,7 +285,9 @@ var RUNTIME_ERROR_CODE_TO_ERROR_CODE = {
|
|
|
284
285
|
[RUNTIME_ERROR_CODES.bodyTooLarge]: "VALIDATION_ERROR",
|
|
285
286
|
// Consumer-principal wave — route subject-requirement faults → FORBIDDEN (403).
|
|
286
287
|
[RUNTIME_ERROR_CODES.memberSubjectRequired]: "FORBIDDEN",
|
|
287
|
-
[RUNTIME_ERROR_CODES.serviceSubjectRequired]: "FORBIDDEN"
|
|
288
|
+
[RUNTIME_ERROR_CODES.serviceSubjectRequired]: "FORBIDDEN",
|
|
289
|
+
// A visible route whose surface set excludes the caller → 403.
|
|
290
|
+
[RUNTIME_ERROR_CODES.surfaceNotAllowed]: "FORBIDDEN"
|
|
288
291
|
};
|
|
289
292
|
function runtimeErrorToErrorCode(code) {
|
|
290
293
|
return RUNTIME_ERROR_CODE_TO_ERROR_CODE[code] ?? "INTERNAL_ERROR";
|
|
@@ -294,16 +297,16 @@ var RUNTIME_TOKEN_PREFIXES = {
|
|
|
294
297
|
live: "fsrt_live_",
|
|
295
298
|
test: "fsrt_test_"
|
|
296
299
|
};
|
|
297
|
-
var
|
|
300
|
+
var RUNTIME_TOKEN_OPERATIONS = [
|
|
298
301
|
"gateway_verification",
|
|
299
302
|
"metering",
|
|
300
303
|
"health",
|
|
301
304
|
"tunnel",
|
|
302
|
-
// Hand-maintained mirror of @farthershore/contracts
|
|
305
|
+
// Hand-maintained mirror of @farthershore/contracts RUNTIME_TOKEN_OPERATIONS
|
|
303
306
|
// (`runtime.ts`). Bound to that source by the SET-EQUALITY + ORDER assertions
|
|
304
307
|
// in `deny-taxonomy-drift.test.ts` (test-only contracts devDep) — NOT by the
|
|
305
308
|
// generated runtime-contract.ts, which mirrors only RUNTIME_ERROR_CODES.
|
|
306
|
-
// `drift_report` is the opt-in
|
|
309
|
+
// `drift_report` is the opt-in operation for reporting route drift.
|
|
307
310
|
"drift_report"
|
|
308
311
|
];
|
|
309
312
|
var RUNTIME_HEADER_NAMES = {
|
|
@@ -457,7 +460,9 @@ var FartherShoreError = class extends Error {
|
|
|
457
460
|
}
|
|
458
461
|
};
|
|
459
462
|
function statusForCode(code) {
|
|
460
|
-
|
|
463
|
+
if (code === "body_too_large") return 413;
|
|
464
|
+
if (code === "surface_not_allowed") return 403;
|
|
465
|
+
return 401;
|
|
461
466
|
}
|
|
462
467
|
|
|
463
468
|
// src/core/bootstrap.ts
|
|
@@ -1913,8 +1918,7 @@ function headerGetter(headers) {
|
|
|
1913
1918
|
|
|
1914
1919
|
// src/core/runtime.ts
|
|
1915
1920
|
var DEFAULT_CORE_URL = "https://core.farthershore.com";
|
|
1916
|
-
var SDK_VERSION = "0.
|
|
1917
|
-
var CONTRACTS_FP = "c3961d4ea07ff178".length > 0 ? "c3961d4ea07ff178" : "0000000000000000";
|
|
1921
|
+
var SDK_VERSION = "0.18.0".length > 0 ? "0.18.0" : "0.0.0-dev";
|
|
1918
1922
|
var FartherShore = class {
|
|
1919
1923
|
bootstrapClient;
|
|
1920
1924
|
fetchImpl;
|
|
@@ -2272,15 +2276,27 @@ async function runMiddleware(fs, options, req, res, next) {
|
|
|
2272
2276
|
stripFartherShoreHeaders(req);
|
|
2273
2277
|
next();
|
|
2274
2278
|
} catch (error) {
|
|
2275
|
-
fail(res, error);
|
|
2279
|
+
fail(res, error, options, req);
|
|
2276
2280
|
}
|
|
2277
2281
|
}
|
|
2278
|
-
function fail(res, error) {
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
+
function fail(res, error, options, req) {
|
|
2283
|
+
const code = error instanceof FartherShoreError ? error.code : "bad_signature";
|
|
2284
|
+
const status = error instanceof FartherShoreError ? error.status : 401;
|
|
2285
|
+
const message = error instanceof Error && error.message ? error.message : code;
|
|
2286
|
+
const report = options.onVerificationError ?? ((d) => {
|
|
2287
|
+
console.warn(`[farthershore] request rejected (${d.code}): ${d.message}`);
|
|
2288
|
+
});
|
|
2289
|
+
try {
|
|
2290
|
+
report({
|
|
2291
|
+
code,
|
|
2292
|
+
message,
|
|
2293
|
+
status,
|
|
2294
|
+
method: req.method ?? "",
|
|
2295
|
+
path: req.url ?? ""
|
|
2296
|
+
});
|
|
2297
|
+
} catch {
|
|
2282
2298
|
}
|
|
2283
|
-
res.status(
|
|
2299
|
+
res.status(status).json({ error: code });
|
|
2284
2300
|
}
|
|
2285
2301
|
function stripFartherShoreHeaders(req) {
|
|
2286
2302
|
const headers = req.headers;
|
|
@@ -2378,6 +2394,16 @@ function requireService(ctx) {
|
|
|
2378
2394
|
}
|
|
2379
2395
|
return subject;
|
|
2380
2396
|
}
|
|
2397
|
+
function credentialKind(ctx) {
|
|
2398
|
+
const subject = ctx.principal?.subject;
|
|
2399
|
+
if (!subject) return void 0;
|
|
2400
|
+
if (subject.kind === "service") return "api_key";
|
|
2401
|
+
return subject.via === "session" ? "portal_session" : "api_key";
|
|
2402
|
+
}
|
|
2403
|
+
function isPortalSession(ctx) {
|
|
2404
|
+
const kind = credentialKind(ctx);
|
|
2405
|
+
return kind === void 0 ? void 0 : kind === "portal_session";
|
|
2406
|
+
}
|
|
2381
2407
|
|
|
2382
2408
|
// src/testing/signers.ts
|
|
2383
2409
|
import { generateKeyPairSync, randomBytes } from "node:crypto";
|
|
@@ -2652,7 +2678,7 @@ function createDevGateway(options) {
|
|
|
2652
2678
|
name: "Dev Backend"
|
|
2653
2679
|
},
|
|
2654
2680
|
environment: { id: null, kind: "test" },
|
|
2655
|
-
|
|
2681
|
+
operations: ["gateway_verification", "metering", "health"],
|
|
2656
2682
|
verification: {
|
|
2657
2683
|
required: options.mode === "simulated",
|
|
2658
2684
|
jwksUrl: DEV_JWKS_URL,
|
|
@@ -3185,7 +3211,7 @@ export {
|
|
|
3185
3211
|
RUNTIME_ERROR_CODE_TO_ERROR_CODE,
|
|
3186
3212
|
RUNTIME_HEADER_NAMES,
|
|
3187
3213
|
RUNTIME_REPLAY_WINDOW_SECONDS,
|
|
3188
|
-
|
|
3214
|
+
RUNTIME_TOKEN_OPERATIONS,
|
|
3189
3215
|
RUNTIME_TOKEN_PREFIXES,
|
|
3190
3216
|
STREAMING_EXEMPT_BODY_HASH,
|
|
3191
3217
|
ShutdownManager,
|
|
@@ -3196,11 +3222,13 @@ export {
|
|
|
3196
3222
|
createExpressHandler,
|
|
3197
3223
|
createExpressMiddleware,
|
|
3198
3224
|
createUsage,
|
|
3225
|
+
credentialKind,
|
|
3199
3226
|
decodeContextClaims,
|
|
3200
3227
|
fartherShore,
|
|
3201
3228
|
hasPermission,
|
|
3202
3229
|
hashBody2 as hashBody,
|
|
3203
3230
|
initFromEnv2 as initFromEnv,
|
|
3231
|
+
isPortalSession,
|
|
3204
3232
|
nodeSpawn,
|
|
3205
3233
|
permissionGrants,
|
|
3206
3234
|
permissionSatisfies,
|
package/dist/testing/index.js
CHANGED
|
@@ -220,7 +220,8 @@ var RUNTIME_ERROR_CODES = {
|
|
|
220
220
|
invalidToken: "invalid_token",
|
|
221
221
|
contextUnverified: "context_unverified",
|
|
222
222
|
memberSubjectRequired: "member_subject_required",
|
|
223
|
-
serviceSubjectRequired: "service_subject_required"
|
|
223
|
+
serviceSubjectRequired: "service_subject_required",
|
|
224
|
+
surfaceNotAllowed: "surface_not_allowed"
|
|
224
225
|
};
|
|
225
226
|
var RUNTIME_RESPONSE_METERING_CONTRACT = {
|
|
226
227
|
headers: {
|
|
@@ -287,7 +288,9 @@ var RUNTIME_ERROR_CODE_TO_ERROR_CODE = {
|
|
|
287
288
|
[RUNTIME_ERROR_CODES.bodyTooLarge]: "VALIDATION_ERROR",
|
|
288
289
|
// Consumer-principal wave — route subject-requirement faults → FORBIDDEN (403).
|
|
289
290
|
[RUNTIME_ERROR_CODES.memberSubjectRequired]: "FORBIDDEN",
|
|
290
|
-
[RUNTIME_ERROR_CODES.serviceSubjectRequired]: "FORBIDDEN"
|
|
291
|
+
[RUNTIME_ERROR_CODES.serviceSubjectRequired]: "FORBIDDEN",
|
|
292
|
+
// A visible route whose surface set excludes the caller → 403.
|
|
293
|
+
[RUNTIME_ERROR_CODES.surfaceNotAllowed]: "FORBIDDEN"
|
|
291
294
|
};
|
|
292
295
|
var FS_RUNTIME_TOKEN_ENV = "FS_RUNTIME_TOKEN";
|
|
293
296
|
var RUNTIME_TOKEN_PREFIXES = {
|
|
@@ -444,7 +447,9 @@ var FartherShoreError = class extends Error {
|
|
|
444
447
|
}
|
|
445
448
|
};
|
|
446
449
|
function statusForCode(code) {
|
|
447
|
-
|
|
450
|
+
if (code === "body_too_large") return 413;
|
|
451
|
+
if (code === "surface_not_allowed") return 403;
|
|
452
|
+
return 401;
|
|
448
453
|
}
|
|
449
454
|
|
|
450
455
|
// src/core/jwks.ts
|
|
@@ -868,7 +873,7 @@ function createDevGateway(options) {
|
|
|
868
873
|
name: "Dev Backend"
|
|
869
874
|
},
|
|
870
875
|
environment: { id: null, kind: "test" },
|
|
871
|
-
|
|
876
|
+
operations: ["gateway_verification", "metering", "health"],
|
|
872
877
|
verification: {
|
|
873
878
|
required: options.mode === "simulated",
|
|
874
879
|
jwksUrl: DEV_JWKS_URL,
|
|
@@ -2144,8 +2149,7 @@ function headerGetter(headers) {
|
|
|
2144
2149
|
|
|
2145
2150
|
// src/core/runtime.ts
|
|
2146
2151
|
var DEFAULT_CORE_URL = "https://core.farthershore.com";
|
|
2147
|
-
var SDK_VERSION = "0.
|
|
2148
|
-
var CONTRACTS_FP = "c3961d4ea07ff178".length > 0 ? "c3961d4ea07ff178" : "0000000000000000";
|
|
2152
|
+
var SDK_VERSION = "0.18.0".length > 0 ? "0.18.0" : "0.0.0-dev";
|
|
2149
2153
|
var FartherShore = class {
|
|
2150
2154
|
bootstrapClient;
|
|
2151
2155
|
fetchImpl;
|
|
@@ -2498,15 +2502,27 @@ async function runMiddleware(fs, options, req, res, next) {
|
|
|
2498
2502
|
stripFartherShoreHeaders(req);
|
|
2499
2503
|
next();
|
|
2500
2504
|
} catch (error) {
|
|
2501
|
-
fail(res, error);
|
|
2505
|
+
fail(res, error, options, req);
|
|
2502
2506
|
}
|
|
2503
2507
|
}
|
|
2504
|
-
function fail(res, error) {
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
+
function fail(res, error, options, req) {
|
|
2509
|
+
const code = error instanceof FartherShoreError ? error.code : "bad_signature";
|
|
2510
|
+
const status = error instanceof FartherShoreError ? error.status : 401;
|
|
2511
|
+
const message = error instanceof Error && error.message ? error.message : code;
|
|
2512
|
+
const report = options.onVerificationError ?? ((d) => {
|
|
2513
|
+
console.warn(`[farthershore] request rejected (${d.code}): ${d.message}`);
|
|
2514
|
+
});
|
|
2515
|
+
try {
|
|
2516
|
+
report({
|
|
2517
|
+
code,
|
|
2518
|
+
message,
|
|
2519
|
+
status,
|
|
2520
|
+
method: req.method ?? "",
|
|
2521
|
+
path: req.url ?? ""
|
|
2522
|
+
});
|
|
2523
|
+
} catch {
|
|
2508
2524
|
}
|
|
2509
|
-
res.status(
|
|
2525
|
+
res.status(status).json({ error: code });
|
|
2510
2526
|
}
|
|
2511
2527
|
function stripFartherShoreHeaders(req) {
|
|
2512
2528
|
const headers = req.headers;
|
|
@@ -36,6 +36,27 @@ export type MiddlewareOptions = {
|
|
|
36
36
|
* consumes no identity; the secure default is strict.
|
|
37
37
|
*/
|
|
38
38
|
always?: boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Called when verification REJECTS a request, with the diagnostic detail that
|
|
41
|
+
* is deliberately withheld from the response body.
|
|
42
|
+
*
|
|
43
|
+
* The wire response is only `{ error: <code> }` — several distinct causes
|
|
44
|
+
* share one code (`route_mismatch` covers a business mismatch, a backend
|
|
45
|
+
* mismatch, AND an unserved route id), and the specifics must not leak to an
|
|
46
|
+
* unauthenticated caller. But discarding them entirely leaves the operator
|
|
47
|
+
* with no way to tell which cause fired, in their OWN logs, for their OWN
|
|
48
|
+
* server. That is what this hook restores.
|
|
49
|
+
*
|
|
50
|
+
* Defaults to a one-line `console.warn`. Pass a function to route it into a
|
|
51
|
+
* structured logger, or `() => {}` to silence it.
|
|
52
|
+
*/
|
|
53
|
+
onVerificationError?: (detail: {
|
|
54
|
+
code: string;
|
|
55
|
+
message: string;
|
|
56
|
+
status: number;
|
|
57
|
+
method: string;
|
|
58
|
+
path: string;
|
|
59
|
+
}) => void;
|
|
39
60
|
};
|
|
40
61
|
/**
|
|
41
62
|
* A verified request context whose {@link ConsumerPrincipal} is GUARANTEED
|
|
@@ -21,6 +21,9 @@ export declare class FartherShoreError extends Error {
|
|
|
21
21
|
}
|
|
22
22
|
/**
|
|
23
23
|
* Map a runtime error code to its fail-closed HTTP status. Oversized bodies are
|
|
24
|
-
*
|
|
24
|
+
* 413; a wrong-credential-surface denial is 403 (the caller IS authenticated,
|
|
25
|
+
* just not on a surface this route admits — mirrors the canonical
|
|
26
|
+
* `surface_not_allowed → FORBIDDEN` mapping in contracts/error-codes.ts);
|
|
27
|
+
* all other verification failures are 401.
|
|
25
28
|
*/
|
|
26
29
|
export declare function statusForCode(code: RuntimeErrorCode): number;
|
|
@@ -69,7 +69,6 @@ export type FartherShoreInitOptions = {
|
|
|
69
69
|
nonceStore?: NonceStore;
|
|
70
70
|
};
|
|
71
71
|
export declare const SDK_VERSION: string;
|
|
72
|
-
export declare const CONTRACTS_FP: string;
|
|
73
72
|
/**
|
|
74
73
|
* The runtime instance. Lazily bootstraps; holds the JWKS client, nonce cache,
|
|
75
74
|
* metering buffer, and shutdown hooks.
|
|
@@ -23,3 +23,23 @@ export declare function requireMember(ctx: PrincipalCarrier): MemberSubject;
|
|
|
23
23
|
* Throws `service_subject_required` when the subject is a member (or absent).
|
|
24
24
|
*/
|
|
25
25
|
export declare function requireService(ctx: PrincipalCarrier): ServiceSubject;
|
|
26
|
+
/**
|
|
27
|
+
* The credential SURFACE behind a verified request, DERIVED from the signed
|
|
28
|
+
* principal (route-surfaces wave) — no new claim, no spoofable header. A member
|
|
29
|
+
* subject carries `via`; a service subject is always key-borne. Returns
|
|
30
|
+
* `undefined` when the request carried no verified principal (identity-less), so
|
|
31
|
+
* a caller can distinguish "not a portal session" from "unknown".
|
|
32
|
+
*
|
|
33
|
+
* - `"portal_session"` ⟺ a member via a browser session (`fsc_`).
|
|
34
|
+
* - `"api_key"` ⟺ a member's personal key OR any service key (`fsk_`).
|
|
35
|
+
*/
|
|
36
|
+
export declare function credentialKind(ctx: PrincipalCarrier): "portal_session" | "api_key" | undefined;
|
|
37
|
+
/**
|
|
38
|
+
* True when the verified request came from the managed portal UI (a member
|
|
39
|
+
* browser session), false when it came from an API key, and `undefined` when
|
|
40
|
+
* there is no verified principal. Convenience over {@link credentialKind} for
|
|
41
|
+
* the common portal-vs-API branch (e.g. richer UI payloads for portal callers).
|
|
42
|
+
* The gateway's `enforce-surface` middleware is the SECURITY boundary; this is
|
|
43
|
+
* for in-handler ergonomics.
|
|
44
|
+
*/
|
|
45
|
+
export declare function isPortalSession(ctx: PrincipalCarrier): boolean | undefined;
|
|
@@ -140,6 +140,7 @@ export declare const RUNTIME_ERROR_CODES: {
|
|
|
140
140
|
readonly contextUnverified: "context_unverified";
|
|
141
141
|
readonly memberSubjectRequired: "member_subject_required";
|
|
142
142
|
readonly serviceSubjectRequired: "service_subject_required";
|
|
143
|
+
readonly surfaceNotAllowed: "surface_not_allowed";
|
|
143
144
|
};
|
|
144
145
|
export type RuntimeErrorCode = (typeof RUNTIME_ERROR_CODES)[keyof typeof RUNTIME_ERROR_CODES];
|
|
145
146
|
export declare const RUNTIME_METERING_CONTRACT: {
|
package/dist/types/index.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ export { FartherShoreError, statusForCode } from "./core/errors.js";
|
|
|
6
6
|
export { verifyRequest, type VerifyRequestInput, type VerifyRequestDeps, type FartherShoreRequestContext, type HeadersLike, } from "./core/verifyRequest.js";
|
|
7
7
|
export { verifyContext, decodeContextClaims, principalFromContextClaims, } from "./core/verifyContext.js";
|
|
8
8
|
export type { FartherShoreSignedContext, ConsumerPrincipal, } from "./core/verifyContext.js";
|
|
9
|
-
export { requireMember, requireService, type MemberSubject, type ServiceSubject, type PrincipalCarrier, } from "./core/subject.js";
|
|
9
|
+
export { requireMember, requireService, credentialKind, isPortalSession, type MemberSubject, type ServiceSubject, type PrincipalCarrier, } from "./core/subject.js";
|
|
10
10
|
export { hasPermission, requirePermission, permissionGrants, permissionSatisfies, FartherShorePermissionError, type PermissionCarrier, } from "./core/permissions.js";
|
|
11
11
|
export { JwksClient, type Jwk, type JwksClientOptions } from "./core/jwks.js";
|
|
12
12
|
export { NonceCache, type NonceCacheOptions, type NonceStore, } from "./core/nonceCache.js";
|
|
@@ -18,7 +18,7 @@ export { ShutdownManager, type ShutdownHook } from "./core/shutdown.js";
|
|
|
18
18
|
export { CloudflaredSupervisor, nodeSpawn, REDACTED_TOKEN, type SpawnFn, type SpawnedTunnelProcess, type CloudflaredSupervisorOptions, type TunnelState, type TunnelStatus, } from "./core/tunnel.js";
|
|
19
19
|
export type { FartherShoreTunnelOptions } from "./core/runtime.js";
|
|
20
20
|
export { createExpressMiddleware, createExpressHandler, type ExpressMiddleware, type ExpressRequestLike, type ExpressResponseLike, type ExpressNext, type MiddlewareOptions, type VerifiedExpressHandler, type VerifiedPrincipalContext, } from "./adapters/express.js";
|
|
21
|
-
export { FS_RUNTIME_TOKEN_ENV, RUNTIME_TOKEN_PREFIXES,
|
|
21
|
+
export { FS_RUNTIME_TOKEN_ENV, RUNTIME_TOKEN_PREFIXES, RUNTIME_TOKEN_OPERATIONS, RUNTIME_HEADER_NAMES, RUNTIME_CLOCK_SKEW_SECONDS, RUNTIME_REPLAY_WINDOW_SECONDS, EMPTY_BODY_SHA256, STREAMING_EXEMPT_BODY_HASH, MAX_BODY_BYTES, type RuntimeErrorCode, type RuntimeTokenOperation, 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";
|
|
22
22
|
export { RUNTIME_ERROR_CODES } from "./generated/runtime-contract.js";
|
|
23
23
|
export { hashBody, buildCanonicalSigningString, canonicalizeQuery, signCanonicalString, verifyCanonicalSignature, runtimeTokenKind, } from "./runtime-signing.js";
|
|
24
24
|
export { createUsage, withUsage, computeMeteringHeaders, MeteringError, METERING_PAYLOAD_HEADER, METERING_SIGNATURE_HEADER, METERING_TOKEN_HEADER, DEFAULT_TOKEN_ENV, type UsageMap, type UsageReporter, type MeteringOptions, type MeteringHeaders, type ComputeMeteringOptions, type ResponseMeteringUsagePayload, } from "./response-metering.js";
|
|
@@ -119,8 +119,8 @@ export declare const RUNTIME_TOKEN_PREFIXES: {
|
|
|
119
119
|
readonly test: "fsrt_test_";
|
|
120
120
|
};
|
|
121
121
|
export type RuntimeTokenKind = keyof typeof RUNTIME_TOKEN_PREFIXES;
|
|
122
|
-
export declare const
|
|
123
|
-
export type
|
|
122
|
+
export declare const RUNTIME_TOKEN_OPERATIONS: readonly ["gateway_verification", "metering", "health", "tunnel", "drift_report"];
|
|
123
|
+
export type RuntimeTokenOperation = (typeof RUNTIME_TOKEN_OPERATIONS)[number];
|
|
124
124
|
export declare const RUNTIME_HEADER_NAMES: {
|
|
125
125
|
readonly signature: "x-fs-signature";
|
|
126
126
|
readonly keyId: "x-fs-key-id";
|
|
@@ -237,7 +237,7 @@ export type RuntimeBootstrapResponse = {
|
|
|
237
237
|
id: string | null;
|
|
238
238
|
kind: RuntimeEnvironmentKind;
|
|
239
239
|
};
|
|
240
|
-
|
|
240
|
+
operations: RuntimeTokenOperation[];
|
|
241
241
|
verification: RuntimeVerificationConfig;
|
|
242
242
|
metering: RuntimeMeteringConfig;
|
|
243
243
|
transport: RuntimeTransportConfig;
|
|
@@ -273,8 +273,6 @@ export type RuntimePostStreamUsageEvent = {
|
|
|
273
273
|
measureContext?: Record<string, unknown>;
|
|
274
274
|
signature: string;
|
|
275
275
|
};
|
|
276
|
-
export declare const RUNTIME_READINESS_STATES: readonly ["UNKNOWN", "WAITING", "READY", "DEGRADED", "OFFLINE"];
|
|
277
|
-
export type RuntimeReadinessState = (typeof RUNTIME_READINESS_STATES)[number];
|
|
278
276
|
export type RuntimeHealthReport = {
|
|
279
277
|
runtimeToken: boolean;
|
|
280
278
|
bootstrap: boolean;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@farthershore/backend",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.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",
|
|
@@ -39,8 +39,8 @@
|
|
|
39
39
|
"optionalDependencies": {
|
|
40
40
|
"@farthershore/cloudflared-linux-x64": "0.0.0",
|
|
41
41
|
"@farthershore/cloudflared-linux-arm64": "0.0.0",
|
|
42
|
-
"@farthershore/cloudflared-darwin-
|
|
43
|
-
"@farthershore/cloudflared-darwin-
|
|
42
|
+
"@farthershore/cloudflared-darwin-arm64": "0.0.0",
|
|
43
|
+
"@farthershore/cloudflared-darwin-x64": "0.0.0"
|
|
44
44
|
},
|
|
45
45
|
"peerDependencies": {
|
|
46
46
|
"express": "^4.0.0 || ^5.0.0"
|