@farthershore/backend 0.18.0 → 0.20.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 +56 -0
- package/README.md +29 -1
- package/dist/index.js +351 -72
- package/dist/testing/index.js +351 -72
- package/dist/types/core/bootstrap.d.ts +8 -0
- package/dist/types/core/deadline.d.ts +80 -0
- package/dist/types/core/jwks.d.ts +42 -7
- package/dist/types/core/post-stream-usage.d.ts +4 -0
- package/dist/types/core/replay-protection.d.ts +28 -0
- package/dist/types/core/runtime.d.ts +19 -8
- package/dist/types/core/verifyRequest.d.ts +14 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/runtime-types.d.ts +15 -0
- package/package.json +11 -11
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,62 @@ 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.20.0] - 2026-08-07
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
- **An injected shared nonce store now fails closed on outage.** If you supply
|
|
12
|
+
`nonceStore`, a throw from it rejects the request instead of falling through
|
|
13
|
+
to "not a replay" — otherwise knocking that store over would switch
|
|
14
|
+
one-time-use enforcement off entirely. No effect on the default in-memory
|
|
15
|
+
cache, which cannot fail this way.
|
|
16
|
+
|
|
17
|
+
`fs.replayProtection()` reports whether enforcement is `"shared"`
|
|
18
|
+
(cross-replica) or `"single-instance"` (this process), for boot logging.
|
|
19
|
+
|
|
20
|
+
Replay protection remains **zero-config**: the signature's ~305s time window
|
|
21
|
+
is the always-on defense and needs nothing from you. A shared store is purely
|
|
22
|
+
an opt-in upgrade for builders who want one-time-use enforced across replicas
|
|
23
|
+
rather than within each one. (FAR-760)
|
|
24
|
+
|
|
25
|
+
### Security
|
|
26
|
+
|
|
27
|
+
- **A stale JWKS key set is no longer trusted forever.** The client held the
|
|
28
|
+
last successful fetch indefinitely, so during a prolonged Core/JWKS outage a
|
|
29
|
+
revoked signing key kept verifying. Stale-while-revalidate now has a ceiling:
|
|
30
|
+
fresh (<5m) → soft-stale (<15m, still served if refresh fails) → hard-stale
|
|
31
|
+
(fail closed). Adds an `onObservation` hook reporting
|
|
32
|
+
`fresh` / `soft_stale` / `hard_stale` / `cold`. (FAR-759)
|
|
33
|
+
|
|
34
|
+
### Fixed
|
|
35
|
+
|
|
36
|
+
- **Every outbound SDK call now has a deadline.** Bootstrap, JWKS refresh,
|
|
37
|
+
metering flush, post-stream usage, the health heartbeat and the drift report
|
|
38
|
+
all awaited without an `AbortSignal`; a half-open connection could wedge
|
|
39
|
+
request verification, boot, readiness, or usage reporting indefinitely.
|
|
40
|
+
Caller cancellation composes with the SDK deadline, timeouts are classifiable,
|
|
41
|
+
and parsed response bodies are byte-bounded. (FAR-785)
|
|
42
|
+
|
|
43
|
+
## [0.19.0]
|
|
44
|
+
|
|
45
|
+
### Added
|
|
46
|
+
|
|
47
|
+
- One runtime token can now serve EVERY environment of a business. Bootstrap
|
|
48
|
+
reports `backendIds` — every backend the token may serve — and the SDK checks
|
|
49
|
+
the gateway's signed backend id for MEMBERSHIP of that set instead of equality
|
|
50
|
+
with a single id.
|
|
51
|
+
|
|
52
|
+
A process holds exactly one `FS_RUNTIME_TOKEN`, so an environment-scoped token
|
|
53
|
+
forced a SEPARATE deployment per environment: pointing a backend at a preview
|
|
54
|
+
environment meant repointing, and breaking, production. A business-scoped
|
|
55
|
+
token (`farthershore backend tokens create <biz>`, no `--env`) now serves them
|
|
56
|
+
all from one deployment; `--env <name>` still pins a token to one environment
|
|
57
|
+
when you want that guarantee.
|
|
58
|
+
|
|
59
|
+
Still fail-closed and still bounded by the business — this widens across
|
|
60
|
+
ENVIRONMENTS, never across businesses. Additive: an older core omits
|
|
61
|
+
`backendIds` and the single-id equality check still applies.
|
|
62
|
+
|
|
7
63
|
## [0.18.0]
|
|
8
64
|
|
|
9
65
|
### Added
|
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.20.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
|
|
|
@@ -99,6 +99,34 @@ wrong-route / body-hash-mismatch / replayed-nonce / unknown-key /
|
|
|
99
99
|
keys-unavailable) throws a typed `FartherShoreError` that maps to **HTTP 401**
|
|
100
100
|
(413 for oversized bodies). There is no fail-open path.
|
|
101
101
|
|
|
102
|
+
### Replay protection (nothing to configure)
|
|
103
|
+
|
|
104
|
+
A signed request is one-time-use, and two things enforce that:
|
|
105
|
+
|
|
106
|
+
- **A time window, always on.** The gateway signs a timestamp into the request;
|
|
107
|
+
anything older than ~305s (replay window + clock skew) is rejected outright.
|
|
108
|
+
This holds for every deployment shape and needs nothing from you.
|
|
109
|
+
- **A seen-id list.** Inside that window, the SDK remembers each
|
|
110
|
+
`X-Fs-Request-Id` it has accepted and rejects a second sighting. The default
|
|
111
|
+
list is in-memory and per-process.
|
|
112
|
+
|
|
113
|
+
That default is deliberate: running more than one replica means each has its own
|
|
114
|
+
list, so a captured request could be replayed once per replica **within the
|
|
115
|
+
~305s window** — and closing that would mean asking you to provision and operate
|
|
116
|
+
a distributed cache. We would rather keep your setup to one environment variable
|
|
117
|
+
and let the time window bound the exposure.
|
|
118
|
+
|
|
119
|
+
If you do want one-time-use enforced across replicas, inject a shared store:
|
|
120
|
+
|
|
121
|
+
```ts
|
|
122
|
+
initFromEnv({ nonceStore: myStore }); // checkAndRemember(id) => boolean | Promise<boolean>
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Make it TTL-bound to the signature validity window. If that store goes down,
|
|
126
|
+
requests fail **closed** — it never degrades to "not a replay".
|
|
127
|
+
`fs.replayProtection()` reports which mode is active (`"shared"` |
|
|
128
|
+
`"single-instance"`) if you want it in your boot logs.
|
|
129
|
+
|
|
102
130
|
## Response-bound usage reporting
|
|
103
131
|
|
|
104
132
|
Use `withUsage()` (or the builder-style `createUsage()`) when you know the usage
|