dbsc-toolkit 2.9.7 → 2.9.8
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 +74 -79
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -5,24 +5,24 @@
|
|
|
5
5
|
[](./LICENSE)
|
|
6
6
|
[](https://nodejs.org)
|
|
7
7
|
|
|
8
|
-
>
|
|
8
|
+
> Stop stolen session cookies from being replayed on another device.
|
|
9
9
|
>
|
|
10
|
-
> `dbsc-toolkit`
|
|
10
|
+
> `dbsc-toolkit` is a Node.js implementation of W3C [Device Bound Session Credentials](https://www.w3.org/TR/dbsc/) with a Web Crypto polyfill for browsers that don't speak the protocol natively yet. Native DBSC on Chromium 146+ (TPM on Windows, Secure Enclave on macOS); the polyfill covers Firefox, Safari, mobile, and older Chromium.
|
|
11
11
|
>
|
|
12
|
-
>
|
|
12
|
+
> Adapters: Express, Fastify, Hono, Next.js. Storage: in-memory, Redis, Postgres.
|
|
13
13
|
|
|
14
14
|
```ts
|
|
15
|
-
//
|
|
15
|
+
// Bind the session inside your existing login route:
|
|
16
16
|
app.post("/login", async (req, res) => {
|
|
17
17
|
await dbsc.bind(res, sessionId, { userId });
|
|
18
18
|
res.json({ ok: true });
|
|
19
19
|
});
|
|
20
20
|
|
|
21
|
-
//
|
|
21
|
+
// Guard sensitive routes:
|
|
22
22
|
app.post("/payment", express.raw({ type: "*/*" }), requireProof(), paymentHandler);
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
Key generation, the protocol routes, cookie management, and the polyfill all live inside the library.
|
|
26
26
|
|
|
27
27
|
## Contents
|
|
28
28
|
|
|
@@ -32,7 +32,7 @@ That's it. The rest — key generation, protocol routes, cookie management, poly
|
|
|
32
32
|
- [Why dbsc-toolkit](#why-dbsc-toolkit)
|
|
33
33
|
- [Comparison](#comparison)
|
|
34
34
|
- [Why not JWT?](#why-not-just-use-jwt)
|
|
35
|
-
- [How it works](#how-it-works
|
|
35
|
+
- [How it works](#how-it-works)
|
|
36
36
|
- [Who is this for?](#who-is-this-for)
|
|
37
37
|
- [Adding to an existing app](#adding-to-an-existing-app)
|
|
38
38
|
- [Protect your routes](#protect-your-routes)
|
|
@@ -43,18 +43,20 @@ That's it. The rest — key generation, protocol routes, cookie management, poly
|
|
|
43
43
|
|
|
44
44
|
## Why dbsc-toolkit
|
|
45
45
|
|
|
46
|
-
Native DBSC ships on Chrome 146+ for Windows users with a
|
|
46
|
+
Native DBSC ships on Chrome 146+ for Windows and macOS users with a hardware key store. That's roughly a third of the desktop traffic most apps see. Most other DBSC implementations focus on native browser support only, which leaves the rest of your users on plain bearer cookies.
|
|
47
47
|
|
|
48
|
-
This library
|
|
48
|
+
This library ships a Web Crypto polyfill alongside native DBSC, so non-Chromium browsers get the same wire-level protection against cookie theft and the same `requireProof()` guard on the server. The polyfill key is stored non-extractably in IndexedDB rather than a TPM — there is no biometric prompt or any user interaction.
|
|
49
49
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
-
|
|
53
|
-
-
|
|
54
|
-
-
|
|
55
|
-
-
|
|
56
|
-
-
|
|
57
|
-
-
|
|
50
|
+
What you actually get:
|
|
51
|
+
|
|
52
|
+
- Native W3C DBSC on Chromium 146+ (TPM on Windows, Secure Enclave on macOS).
|
|
53
|
+
- A Web Crypto polyfill that brings the same protection to Firefox, Safari, mobile and older Chromium. Non-extractable IndexedDB key.
|
|
54
|
+
- Adapters for Express, Fastify, Hono and Next.js — all four in one package.
|
|
55
|
+
- Storage adapters for memory, Redis and Postgres. Swap by changing one import.
|
|
56
|
+
- `requireProof()` — a per-request signature guard that works on every browser and defends against MITM body substitution via the signed body hash.
|
|
57
|
+
- Replay cache (since v2.8) that rejects a captured-proof replay inside the timestamp window. Opt-in; you wire `RedisReplayCache` when you want it.
|
|
58
|
+
- Multi-subdomain binding (since v2.9) via `cookieScope: "site"` for apps split across `app.example.com` + `api.example.com`.
|
|
59
|
+
- Apache 2.0.
|
|
58
60
|
|
|
59
61
|
## Install
|
|
60
62
|
|
|
@@ -62,16 +64,16 @@ This library is among the first DBSC implementations to provide a Web Crypto fal
|
|
|
62
64
|
npm install dbsc-toolkit
|
|
63
65
|
```
|
|
64
66
|
|
|
65
|
-
Optional peer
|
|
67
|
+
Optional peer dependencies — install only what you actually use:
|
|
66
68
|
|
|
67
69
|
```sh
|
|
68
|
-
npm install express ioredis
|
|
70
|
+
npm install express ioredis # Express + Redis
|
|
69
71
|
npm install fastify @fastify/cookie pg # Fastify + Postgres
|
|
70
72
|
```
|
|
71
73
|
|
|
72
74
|
## Quick start
|
|
73
75
|
|
|
74
|
-
|
|
76
|
+
`createDbsc()` takes your config once. `install()` mounts the protocol routes, the bound-route JSON parser, the `/dbsc-client` SDK, and sets `trust proxy`.
|
|
75
77
|
|
|
76
78
|
```ts
|
|
77
79
|
import express from "express";
|
|
@@ -83,7 +85,7 @@ const app = express();
|
|
|
83
85
|
app.use(express.json()); // for your own routes' JSON bodies
|
|
84
86
|
|
|
85
87
|
const dbsc = createDbsc({ storage: new MemoryStorage() }); // swap for Redis/Postgres in prod
|
|
86
|
-
dbsc.install(app);
|
|
88
|
+
dbsc.install(app);
|
|
87
89
|
|
|
88
90
|
app.post("/login", async (req, res) => {
|
|
89
91
|
await dbsc.bind(res, randomUUID(), { userId: req.body.username });
|
|
@@ -94,7 +96,7 @@ app.get("/me", (_req, res) => res.json(res.locals.dbsc));
|
|
|
94
96
|
app.listen(3000);
|
|
95
97
|
```
|
|
96
98
|
|
|
97
|
-
|
|
99
|
+
Load the polyfill in your HTML so Firefox / Safari / older Chromium can reach `tier: "bound"`:
|
|
98
100
|
|
|
99
101
|
```html
|
|
100
102
|
<script type="module">
|
|
@@ -103,16 +105,16 @@ In your HTML, load the polyfill once so Firefox / Safari / older Chromium reach
|
|
|
103
105
|
</script>
|
|
104
106
|
```
|
|
105
107
|
|
|
106
|
-
Without the script
|
|
108
|
+
Without the script those browsers stay on `tier: "none"`. Chromium 146+ doesn't need it — it negotiates the protocol on its own from the headers `dbsc()` sets.
|
|
107
109
|
|
|
108
110
|
### Common failure modes
|
|
109
111
|
|
|
110
|
-
- **`tier` always reads `"none"` on Chromium 146
|
|
111
|
-
- **Chrome loops registration
|
|
112
|
-
- **Tier flips back to `"none"` right after login
|
|
113
|
-
- **Firefox / Safari still on `"none"
|
|
112
|
+
- **`tier` always reads `"none"` on Chromium 146+** — running on plain HTTP (DBSC needs HTTPS), or `dbsc.install(app)` was never called. `install()` sets `trust proxy` and parses cookies, so the old "middleware order" class of bug is gone.
|
|
113
|
+
- **Chrome loops registration** — storage got wiped. Move off `MemoryStorage` to Redis or Postgres before deploying anywhere that restarts. I learned this the hard way on Render free tier.
|
|
114
|
+
- **Tier flips back to `"none"` right after login** — the race between `/login` returning and the browser running `POST /dbsc/registration`. Poll `/me` for ~1 s after login, or await the bound-SDK outcome promise. The demo wires both — see [examples/express/src/server.js](./examples/express/src/server.js).
|
|
115
|
+
- **Firefox / Safari still on `"none"`** — the `<script type="module">` tag is missing. `install()` serves the SDK at `/dbsc-client`; you still load it on the page.
|
|
114
116
|
|
|
115
|
-
|
|
117
|
+
Walk-through: [docs/getting-started.md](./docs/getting-started.md).
|
|
116
118
|
|
|
117
119
|
## Live demo
|
|
118
120
|
|
|
@@ -134,43 +136,43 @@ Sign up, log in, click **Check session**. Chromium 146+ lands on `tier: "dbsc"`
|
|
|
134
136
|
| Multi-subdomain binding | ✅ (loose) | ✅ (loose) | ❌ (`__Host-` only) | ✅ (v2.9 `cookieScope: "site"`) |
|
|
135
137
|
| Server runtime | any | any | n/a (browser-side) | Node.js ≥ 20 |
|
|
136
138
|
|
|
137
|
-
The native-DBSC column
|
|
139
|
+
The native-DBSC column above describes a server that uses the spec without any polyfill. The polyfill key in this library lives in IndexedDB (`extractable: false`) rather than a TPM — a notch weaker against malware reading the browser profile, but it stops every remote-theft scenario the same way native DBSC does.
|
|
138
140
|
|
|
139
141
|
## "Why not just use JWT?"
|
|
140
142
|
|
|
141
|
-
JWTs are
|
|
143
|
+
JWTs are bearer tokens. If stolen, they can be replayed from another device. DBSC adds proof-of-possession: the browser has to prove on every refresh (and on every `requireProof()`-guarded request) that it still has the private key bound at login. An attacker who got the token alone has nothing to sign with.
|
|
142
144
|
|
|
143
|
-
|
|
145
|
+
It complements your existing auth — passwords, MFA, session cookies, JWTs — rather than replacing any of them. The gap it closes is *replay after issue*.
|
|
144
146
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
## How it works (the 30-second version)
|
|
147
|
+
## How it works
|
|
148
148
|
|
|
149
149
|
```
|
|
150
|
-
Login → Key Registration → Cookie Issued → Challenge → Signature Verification →
|
|
150
|
+
Login → Key Registration → Cookie Issued → Challenge → Signature Verification → Refresh
|
|
151
151
|
```
|
|
152
152
|
|
|
153
|
-
On login the server responds with a `Secure-Session-Registration` header. The browser generates an ECDSA keypair in the TPM (
|
|
153
|
+
On login the server responds with a `Secure-Session-Registration` header. The browser generates an ECDSA keypair in the TPM (native DBSC) or IndexedDB (polyfill), POSTs the public key, and gets a short-lived session cookie. Every ~10 minutes the browser re-signs a fresh server challenge to renew the cookie. A copy of the cookie pasted into another device has no key — the next refresh fails, and any route guarded by `requireProof()` returns 403 immediately.
|
|
154
154
|
|
|
155
155
|
Full wire-format walk-through: [HOW-IT-WORKS.md](./HOW-IT-WORKS.md).
|
|
156
156
|
|
|
157
157
|
## Who is this for?
|
|
158
158
|
|
|
159
|
-
|
|
159
|
+
Use `dbsc-toolkit` if:
|
|
160
|
+
|
|
160
161
|
- You run a Node.js backend.
|
|
161
162
|
- Your users authenticate with session cookies (or JWT bearer tokens).
|
|
162
163
|
- Cookie / token theft is in your threat model — XSS, infostealer malware, leaked logs, compromised proxies.
|
|
163
|
-
- You want
|
|
164
|
+
- You want coverage on Chrome and Firefox / Safari now, not eventually.
|
|
165
|
+
|
|
166
|
+
Not the right fit if:
|
|
164
167
|
|
|
165
|
-
**Not the right fit if:**
|
|
166
168
|
- You don't issue any persistent client credential (every request re-authenticates from scratch).
|
|
167
169
|
- You can't deploy HTTPS — DBSC cookies require `Secure`, and the spec rejects insecure origins.
|
|
168
|
-
- Your backend is Python / Go / Rust / Java / .NET — no port
|
|
169
|
-
- You need DBSC on iOS / mobile Chrome / ChromeOS *with native key storage* — those platforms don't ship native DBSC.
|
|
170
|
+
- Your backend is Python / Go / Rust / Java / .NET — no port exists yet; you'd implement against the [W3C spec](https://www.w3.org/TR/dbsc/) directly.
|
|
171
|
+
- You need DBSC on iOS / mobile Chrome / ChromeOS *with native key storage* — those platforms don't ship native DBSC. The polyfill still works there; the key just lives in IndexedDB instead of secure hardware.
|
|
170
172
|
|
|
171
173
|
## Adding to an existing app
|
|
172
174
|
|
|
173
|
-
You don't rewrite login
|
|
175
|
+
You don't rewrite login and you don't migrate the session store. DBSC sits alongside your existing session cookie and binds to the same session id.
|
|
174
176
|
|
|
175
177
|
```ts
|
|
176
178
|
import { createDbsc, requireProof } from "dbsc-toolkit/express";
|
|
@@ -202,40 +204,38 @@ app.post("/logout", async (req, res) => {
|
|
|
202
204
|
});
|
|
203
205
|
```
|
|
204
206
|
|
|
205
|
-
`install()` handles `trust proxy`, cookie parsing, the bound-route JSON parser, and the `/dbsc-client` static mount
|
|
207
|
+
`install()` handles `trust proxy`, cookie parsing, the bound-route JSON parser, and the `/dbsc-client` static mount. You still need `express.json()` for your *own* routes' bodies. `sessionId` is whatever id your session store already issues — DBSC binds to it, no second id-space.
|
|
206
208
|
|
|
207
|
-
**No server-side session id
|
|
209
|
+
**No server-side session id?** (NextAuth JWT mode, iron-session, Lucia stateless) Call `dbsc.bind(res, { userId })` with no id — the kit derives a stable one and manages a per-device cookie so each browser of the same user binds independently. Per-system recipes: [docs/integration-recipes.md](./docs/integration-recipes.md).
|
|
208
210
|
|
|
209
|
-
**App split across subdomains
|
|
211
|
+
**App split across subdomains?** (`app.example.com` + `api.example.com`) Default `__Host-` cookies are origin-locked — proxy `/dbsc/*` and `/dbsc-bound/*` through one origin if you can; that's the strongest setting. If a same-origin layout isn't workable, v2.9.0+ exposes `cookieScope: "site"` + `cookieDomain: "example.com"` on `createDbsc({...})`, switching the binding cookies to `__Secure-` with a `Domain` attribute. The validator throws at construction time if either is wrong — misconfiguration is loud, not silent. Trade-off and concrete recipe: [docs/integration-recipes.md#multi-subdomain-apps-cookiescope-site](./docs/integration-recipes.md#multi-subdomain-apps-cookiescope-site).
|
|
210
212
|
|
|
211
|
-
|
|
213
|
+
Full walk-through with `autoBind`, per-route policy, and the migration timeline: [docs/integrating-existing-auth.md](./docs/integrating-existing-auth.md).
|
|
212
214
|
|
|
213
215
|
## Protect your routes
|
|
214
216
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
After `createDbsc().install()`, every request through the middleware has a `tier` field on the request context. The library does not auto-protect anything — you add **one guard, `requireProof()`**, to each route that matters.
|
|
217
|
+
After `createDbsc().install()`, every request through the middleware has a `tier` field on the request context. The library does not auto-protect anything — you add `requireProof()` to each route that matters.
|
|
218
218
|
|
|
219
219
|
| Your route does… | Use this guard | What it stops |
|
|
220
220
|
|---|---|---|
|
|
221
221
|
| Public / read-only (feed, search, public profile) | Nothing | n/a — no auth gate at all |
|
|
222
|
-
| Anything authenticated (post, comment, upvote, settings, payment, admin) | `requireProof()` (server) + `wrapFetch()` (client) | A stolen cookie
|
|
222
|
+
| Anything authenticated (post, comment, upvote, settings, payment, admin) | `requireProof()` (server) + `wrapFetch()` (client) | A stolen cookie can't be replayed from another device, can't ride along during the freshness window, and an MITM can't substitute a POST body |
|
|
223
223
|
|
|
224
|
-
|
|
224
|
+
`requireProof()` requires a bound device plus a per-request proof, and works on every browser. As of v2.7, Chromium sessions co-register a polyfill key alongside the TPM key so the per-request signature is enforced uniformly across tiers. `wrapFetch` signs the request body by default since v2.8; POST routes mount `express.raw()` in front. Apps with many guarded routes can swap to `installFetchInterceptor({ pathPrefixes: ["/api/secure/"] })` and wrap once at boot.
|
|
225
225
|
|
|
226
|
-
For apps
|
|
226
|
+
For apps facing active MITM or log-spillage exposure, v2.8 also adds an optional **replay cache** that rejects a second arrival of the same proof bytes — pass `replayCache: new RedisReplayCache(redis)` to `createDbsc`. See [docs/per-request-signing.md](./docs/per-request-signing.md).
|
|
227
227
|
|
|
228
|
-
|
|
228
|
+
Full threat boundary, per-framework wiring (Fastify / Hono / Next.js), and the migration timeline: [docs/integrating-existing-auth.md](./docs/integrating-existing-auth.md) and [docs/per-request-signing.md](./docs/per-request-signing.md).
|
|
229
229
|
|
|
230
230
|
## Production checklist
|
|
231
231
|
|
|
232
|
-
- [ ] HTTPS enabled — DBSC requires `Secure` cookies; plain HTTP locks every user to `tier: "none"
|
|
233
|
-
- [ ] Redis or PostgreSQL storage — `MemoryStorage` is lost on restart; bound browsers loop registration
|
|
234
|
-
- [ ] `requireProof()` on every authenticated route — read-only public routes need no guard
|
|
235
|
-
- [ ] `wrapFetch({ signBody: true })` on the client side for those routes (
|
|
236
|
-
- [ ] Replay cache enabled — `replayCache: new RedisReplayCache(redis)` in `createDbsc({...})
|
|
237
|
-
- [ ] `trust proxy`
|
|
238
|
-
- [ ] Polyfill script loaded — the `<script type="module">` tag; without it Firefox/Safari stay on `tier: "none"
|
|
232
|
+
- [ ] HTTPS enabled — DBSC requires `Secure` cookies; plain HTTP locks every user to `tier: "none"`.
|
|
233
|
+
- [ ] Redis or PostgreSQL storage — `MemoryStorage` is lost on restart; bound browsers loop registration.
|
|
234
|
+
- [ ] `requireProof()` on every authenticated route — read-only public routes need no guard.
|
|
235
|
+
- [ ] `wrapFetch({ signBody: true })` on the client side for those routes (default since v2.8).
|
|
236
|
+
- [ ] Replay cache enabled when relevant — `replayCache: new RedisReplayCache(redis)` in `createDbsc({...})`.
|
|
237
|
+
- [ ] `trust proxy` set — `install()` sets it, but verify nothing in your own app config overrides it.
|
|
238
|
+
- [ ] Polyfill script loaded — the `<script type="module">` tag; without it Firefox/Safari stay on `tier: "none"`.
|
|
239
239
|
|
|
240
240
|
## Subpath imports
|
|
241
241
|
|
|
@@ -249,11 +249,11 @@ The full threat boundary, the per-framework wiring (Fastify / Hono / Next.js), a
|
|
|
249
249
|
| `dbsc-toolkit/client` | Browser SDK with `initBoundDbsc()` for the polyfill |
|
|
250
250
|
| `dbsc-toolkit/storage/{memory,redis,postgres}` | Storage adapters |
|
|
251
251
|
|
|
252
|
-
Tree-shaking eliminates anything you don't import. Using Koa, Hapi, raw `http`, Bun, or Deno? Call core directly
|
|
252
|
+
Tree-shaking eliminates anything you don't import. Using Koa, Hapi, raw `http`, Bun, or Deno? Call core directly — see [docs/adapters.md](./docs/adapters.md).
|
|
253
253
|
|
|
254
254
|
## Protection tiers
|
|
255
255
|
|
|
256
|
-
`tier` is the
|
|
256
|
+
`tier` is the state of a session — what kind of binding the browser achieved. Don't gate on it directly; gate with `requireProof()` instead and read `tier` only for display.
|
|
257
257
|
|
|
258
258
|
| Tier | Mechanism | Protects against |
|
|
259
259
|
|------|-----------|------------------|
|
|
@@ -261,37 +261,32 @@ Tree-shaking eliminates anything you don't import. Using Koa, Hapi, raw `http`,
|
|
|
261
261
|
| `bound` | Web Crypto polyfill, non-extractable ECDSA P-256 key in IndexedDB | Cookie theft. Does not defeat infostealer malware on the user's machine. |
|
|
262
262
|
| `none` | No active / fresh binding | Nothing the cookie itself doesn't already do |
|
|
263
263
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
- **Public / read-only route?** → no guard.
|
|
267
|
-
- **Anything authenticated?** → `requireProof()`. That's it.
|
|
268
|
-
|
|
269
|
-
There is no third option, and **never gate a route on `tier === "dbsc"`** — that locks out every Firefox and Safari user (they can only reach `tier: "bound"`), and `requireProof()` already gives those browsers the same per-request guarantee via a signed proof. `requireProof()` is the one guard; it works on every browser. Read `res.locals.dbsc.tier` only if you want to *display* binding state in the UI — not to build a gate. Full guidance in [docs/security/best-practices.md](./docs/security/best-practices.md).
|
|
264
|
+
Why not gate routes on `tier === "dbsc"`? Because every Firefox and Safari user can only reach `tier: "bound"`, and `requireProof()` already gives those browsers the same per-request guarantee via a signed proof. The one exception is routes whose threat model specifically includes on-device infostealer malware — there, hardware-backed key isolation actually matters, and you accept the trade-off of locking non-Chromium browsers out. More in [docs/security/best-practices.md](./docs/security/best-practices.md).
|
|
270
265
|
|
|
271
266
|
## Security model
|
|
272
267
|
|
|
273
|
-
|
|
268
|
+
What this protects against:
|
|
274
269
|
|
|
275
|
-
- Stolen session cookies replayed from another device
|
|
276
|
-
- Stolen bearer tokens (same category — bearer == portable)
|
|
277
|
-
- Cookie exfiltration via XSS, network capture, log leakage, or proxy leakage
|
|
270
|
+
- Stolen session cookies replayed from another device.
|
|
271
|
+
- Stolen bearer tokens (same category — bearer == portable).
|
|
272
|
+
- Cookie exfiltration via XSS, network capture, log leakage, or proxy leakage.
|
|
278
273
|
|
|
279
|
-
|
|
274
|
+
What the Web Crypto polyfill (`tier: "bound"`) does not protect against:
|
|
280
275
|
|
|
281
|
-
- Malware
|
|
282
|
-
- Browser or OS compromise
|
|
276
|
+
- Malware running on the user's machine with access to the browser profile.
|
|
277
|
+
- Browser or OS compromise.
|
|
283
278
|
|
|
284
|
-
Native DBSC (`tier: "dbsc"`)
|
|
279
|
+
Native DBSC (`tier: "dbsc"`) is stronger against local malware because the private key lives inside the TPM or Secure Enclave — software can't extract it even with admin access. The polyfill key is non-extractable from JavaScript, but the encrypted blob lives on disk and a privileged on-device attacker can reach it.
|
|
285
280
|
|
|
286
|
-
|
|
281
|
+
None of this replaces HTTPS, input validation, strong passwords, or MFA.
|
|
287
282
|
|
|
288
283
|
## Going deeper
|
|
289
284
|
|
|
290
285
|
- **Concepts and protocol:** [HOW-IT-WORKS.md](./HOW-IT-WORKS.md)
|
|
291
286
|
- **Bound polyfill wire protocol:** [docs/bound-polyfill.md](./docs/bound-polyfill.md)
|
|
292
|
-
- **Per-request signing (
|
|
287
|
+
- **Per-request signing (v2.7 dual-key + v2.8 replay cache):** [docs/per-request-signing.md](./docs/per-request-signing.md)
|
|
293
288
|
- **API reference:** [docs/api-reference.md](./docs/api-reference.md)
|
|
294
|
-
- **Adapters (Express / Fastify / Hono / Next.js +
|
|
289
|
+
- **Adapters (Express / Fastify / Hono / Next.js + writing your own):** [docs/adapters.md](./docs/adapters.md)
|
|
295
290
|
- **Storage (memory / Redis / Postgres):** [docs/storage.md](./docs/storage.md)
|
|
296
291
|
- **Telemetry hooks:** [docs/telemetry.md](./docs/telemetry.md)
|
|
297
292
|
- **Deployment (Render / Fly / Vercel / Cloudflare / nginx):** [docs/deployment.md](./docs/deployment.md)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dbsc-toolkit",
|
|
3
|
-
"version": "2.9.
|
|
3
|
+
"version": "2.9.8",
|
|
4
4
|
"description": "Server-side Device Bound Session Credentials (DBSC) for Node.js plus a Web Crypto polyfill for Firefox, Safari, and older Chromium. Cookie-theft protection on every modern browser.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|