@opensyber/tokenforge 0.1.1 → 1.0.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/LICENSE.md +32 -0
- package/README.md +205 -47
- package/dist/adapters/astro.d.ts +55 -0
- package/dist/adapters/astro.d.ts.map +1 -0
- package/dist/adapters/astro.js +91 -0
- package/dist/adapters/astro.js.map +1 -0
- package/dist/adapters/express.d.ts +15 -0
- package/dist/adapters/express.d.ts.map +1 -1
- package/dist/adapters/express.js +27 -0
- package/dist/adapters/express.js.map +1 -1
- package/dist/adapters/fastify.d.ts +17 -0
- package/dist/adapters/fastify.d.ts.map +1 -1
- package/dist/adapters/fastify.js +27 -0
- package/dist/adapters/fastify.js.map +1 -1
- package/dist/adapters/hono.d.ts +8 -4
- package/dist/adapters/hono.d.ts.map +1 -1
- package/dist/adapters/hono.js +7 -4
- package/dist/adapters/hono.js.map +1 -1
- package/dist/adapters/index.d.ts +5 -3
- package/dist/adapters/index.d.ts.map +1 -1
- package/dist/adapters/index.js +5 -3
- package/dist/adapters/index.js.map +1 -1
- package/dist/adapters/nextjs.d.ts +16 -0
- package/dist/adapters/nextjs.d.ts.map +1 -1
- package/dist/adapters/nextjs.js +26 -0
- package/dist/adapters/nextjs.js.map +1 -1
- package/dist/adapters/sveltekit.d.ts +57 -0
- package/dist/adapters/sveltekit.d.ts.map +1 -0
- package/dist/adapters/sveltekit.js +88 -0
- package/dist/adapters/sveltekit.js.map +1 -0
- package/dist/client/action-signer.d.ts +53 -0
- package/dist/client/action-signer.d.ts.map +1 -0
- package/dist/client/action-signer.js +48 -0
- package/dist/client/action-signer.js.map +1 -0
- package/dist/client/binding.d.ts +24 -7
- package/dist/client/binding.d.ts.map +1 -1
- package/dist/client/binding.js +17 -6
- package/dist/client/binding.js.map +1 -1
- package/dist/client/index.d.ts +13 -0
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +27 -1
- package/dist/client/index.js.map +1 -1
- package/dist/client/signer.d.ts +17 -0
- package/dist/client/signer.d.ts.map +1 -1
- package/dist/client/signer.js +23 -0
- package/dist/client/signer.js.map +1 -1
- package/dist/client/webauthn.d.ts +45 -0
- package/dist/client/webauthn.d.ts.map +1 -0
- package/dist/client/webauthn.js +118 -0
- package/dist/client/webauthn.js.map +1 -0
- package/dist/server/action-verify.d.ts +56 -0
- package/dist/server/action-verify.d.ts.map +1 -0
- package/dist/server/action-verify.js +47 -0
- package/dist/server/action-verify.js.map +1 -0
- package/dist/server/aitm-heuristics.d.ts +57 -0
- package/dist/server/aitm-heuristics.d.ts.map +1 -0
- package/dist/server/aitm-heuristics.js +120 -0
- package/dist/server/aitm-heuristics.js.map +1 -0
- package/dist/server/bound-cookie.d.ts +39 -0
- package/dist/server/bound-cookie.d.ts.map +1 -0
- package/dist/server/bound-cookie.js +59 -0
- package/dist/server/bound-cookie.js.map +1 -0
- package/dist/server/cbor.d.ts +20 -0
- package/dist/server/cbor.d.ts.map +1 -0
- package/dist/server/cbor.js +110 -0
- package/dist/server/cbor.js.map +1 -0
- package/dist/server/dbsc-challenge.d.ts +51 -0
- package/dist/server/dbsc-challenge.d.ts.map +1 -0
- package/dist/server/dbsc-challenge.js +78 -0
- package/dist/server/dbsc-challenge.js.map +1 -0
- package/dist/server/index.d.ts +2 -4
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +6 -4
- package/dist/server/index.js.map +1 -1
- package/dist/server/jws-verify.d.ts +46 -0
- package/dist/server/jws-verify.d.ts.map +1 -0
- package/dist/server/jws-verify.js +64 -0
- package/dist/server/jws-verify.js.map +1 -0
- package/dist/server/middleware.d.ts +15 -0
- package/dist/server/middleware.d.ts.map +1 -1
- package/dist/server/middleware.js +27 -0
- package/dist/server/middleware.js.map +1 -1
- package/dist/server/oidc-verify.d.ts +56 -0
- package/dist/server/oidc-verify.d.ts.map +1 -0
- package/dist/server/oidc-verify.js +107 -0
- package/dist/server/oidc-verify.js.map +1 -0
- package/dist/server/policy.d.ts +47 -0
- package/dist/server/policy.d.ts.map +1 -0
- package/dist/server/policy.js +84 -0
- package/dist/server/policy.js.map +1 -0
- package/dist/server/step-up-policy.d.ts +36 -0
- package/dist/server/step-up-policy.d.ts.map +1 -0
- package/dist/server/step-up-policy.js +101 -0
- package/dist/server/step-up-policy.js.map +1 -0
- package/dist/server/trust-score-fixtures.d.ts +9 -0
- package/dist/server/trust-score-fixtures.d.ts.map +1 -0
- package/dist/server/trust-score-fixtures.js +23 -0
- package/dist/server/trust-score-fixtures.js.map +1 -0
- package/dist/server/webauthn-verify.d.ts +41 -0
- package/dist/server/webauthn-verify.d.ts.map +1 -0
- package/dist/server/webauthn-verify.js +175 -0
- package/dist/server/webauthn-verify.js.map +1 -0
- package/dist/server/webauthn-verify.test-helpers.d.ts +8 -0
- package/dist/server/webauthn-verify.test-helpers.d.ts.map +1 -0
- package/dist/server/webauthn-verify.test-helpers.js +39 -0
- package/dist/server/webauthn-verify.test-helpers.js.map +1 -0
- package/dist/server/webhooks.d.ts +42 -0
- package/dist/server/webhooks.d.ts.map +1 -0
- package/dist/server/webhooks.js +77 -0
- package/dist/server/webhooks.js.map +1 -0
- package/dist/shared/action-hash.d.ts +23 -0
- package/dist/shared/action-hash.d.ts.map +1 -0
- package/dist/shared/action-hash.js +35 -0
- package/dist/shared/action-hash.js.map +1 -0
- package/dist/shared/types.d.ts +2 -0
- package/dist/shared/types.d.ts.map +1 -1
- package/dist/shared/types.js.map +1 -1
- package/package.json +44 -16
- package/dist/server/binding.d.ts +0 -19
- package/dist/server/binding.d.ts.map +0 -1
- package/dist/server/binding.js +0 -87
- package/dist/server/binding.js.map +0 -1
- package/dist/server/step-up.d.ts +0 -19
- package/dist/server/step-up.d.ts.map +0 -1
- package/dist/server/step-up.js +0 -139
- package/dist/server/step-up.js.map +0 -1
- package/dist/server/storage/index.d.ts +0 -3
- package/dist/server/storage/index.d.ts.map +0 -1
- package/dist/server/storage/index.js +0 -2
- package/dist/server/storage/index.js.map +0 -1
- package/dist/server/storage/interface.d.ts +0 -61
- package/dist/server/storage/interface.d.ts.map +0 -1
- package/dist/server/storage/interface.js +0 -2
- package/dist/server/storage/interface.js.map +0 -1
- package/dist/server/storage/memory.d.ts +0 -40
- package/dist/server/storage/memory.d.ts.map +0 -1
- package/dist/server/storage/memory.js +0 -107
- package/dist/server/storage/memory.js.map +0 -1
- package/dist/server/totp.d.ts +0 -53
- package/dist/server/totp.d.ts.map +0 -1
- package/dist/server/totp.js +0 -114
- package/dist/server/totp.js.map +0 -1
package/LICENSE.md
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 OpenSyber
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Note on the hosted verification service
|
|
26
|
+
|
|
27
|
+
The TokenForge SDK code in this repository is MIT-licensed. The TokenForge
|
|
28
|
+
verification service hosted at `tokenforge.opensyber.cloud` requires a
|
|
29
|
+
TokenForge API key and is governed by the OpenSyber Terms of Service at
|
|
30
|
+
https://tokenforge.opensyber.cloud/terms. You are free to self-host the
|
|
31
|
+
server adapters using your own database / KV storage backend (see
|
|
32
|
+
`@opensyber/tokenforge/server/storage`) under MIT terms.
|
package/README.md
CHANGED
|
@@ -1,10 +1,20 @@
|
|
|
1
1
|
# @opensyber/tokenforge
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/@opensyber/tokenforge)
|
|
4
|
+
[](./LICENSE.md)
|
|
5
|
+
|
|
6
|
+
**Device-bound session security for the post-AiTM era.** W3C [DBSC](https://w3c.github.io/webappsec-dbsc/)–aligned, ECDSA P-256 + WebAuthn, drop-in for Auth0 / Okta / Clerk / Microsoft Entra ID. Every request after login is cryptographically signed with a device key that never leaves the browser. A stolen cookie without the device key is useless.
|
|
7
|
+
|
|
8
|
+
## Why this matters in 2026
|
|
9
|
+
|
|
10
|
+
- **Session hijacking attacks grew 127% YoY** ([Microsoft, May 2026](https://www.microsoft.com/en-us/security/blog/2026/05/04/breaking-the-code-multi-stage-code-of-conduct-phishing-campaign-leads-to-aitm-token-compromise/))
|
|
11
|
+
- Adversary-in-the-Middle (AiTM) toolkits — EvilProxy, Tycoon — bypass MFA in real time
|
|
12
|
+
- Chrome 146 (April 2026) shipped browser-native DBSC for Windows; macOS/Linux pending
|
|
13
|
+
- Auth0 and Okta's "session protection" features rely on IP/ASN/UA fingerprinting — defeated by VPN-equipped attackers. **TokenForge uses cryptographic device binding — defeats them.**
|
|
4
14
|
|
|
5
15
|
## Quick Start
|
|
6
16
|
|
|
7
|
-
### 1. Add the script tag (
|
|
17
|
+
### 1. Add the script tag (zero-code path)
|
|
8
18
|
|
|
9
19
|
```html
|
|
10
20
|
<script
|
|
@@ -13,96 +23,199 @@ Device-bound session security. Add one script tag — every request is cryptogra
|
|
|
13
23
|
></script>
|
|
14
24
|
```
|
|
15
25
|
|
|
16
|
-
|
|
26
|
+
The script auto-generates ECDSA P-256 device keys (non-extractable, IndexedDB-stored), binds the session, and signs every `fetch()` request with `X-TF-Signature`, `X-TF-Nonce`, `X-TF-Timestamp` headers. On Chrome 146+ Windows it transparently uses the browser's native DBSC; on other browsers it polyfills via Web Crypto.
|
|
17
27
|
|
|
18
|
-
### 2. Add server middleware
|
|
28
|
+
### 2. Add server middleware
|
|
19
29
|
|
|
20
30
|
```bash
|
|
21
31
|
npm install @opensyber/tokenforge
|
|
22
32
|
```
|
|
23
33
|
|
|
24
|
-
**Express**
|
|
25
34
|
```ts
|
|
35
|
+
// Express
|
|
26
36
|
import { tokenForgeMiddleware } from '@opensyber/tokenforge/express';
|
|
27
37
|
app.use(tokenForgeMiddleware({ apiKey: process.env.TOKENFORGE_API_KEY! }));
|
|
28
38
|
// req.tf.bound, req.tf.trustScore, req.tf.deviceId
|
|
29
|
-
```
|
|
30
39
|
|
|
31
|
-
|
|
32
|
-
```ts
|
|
40
|
+
// Next.js (App Router)
|
|
33
41
|
import { withTokenForge } from '@opensyber/tokenforge/nextjs';
|
|
34
42
|
export const GET = withTokenForge(handler, { apiKey: process.env.TOKENFORGE_API_KEY! });
|
|
35
|
-
```
|
|
36
43
|
|
|
37
|
-
|
|
38
|
-
```ts
|
|
44
|
+
// Fastify
|
|
39
45
|
import { tokenForgePlugin } from '@opensyber/tokenforge/fastify';
|
|
40
46
|
fastify.register(tokenForgePlugin, { apiKey: process.env.TOKENFORGE_API_KEY! });
|
|
41
|
-
```
|
|
42
47
|
|
|
43
|
-
|
|
44
|
-
```ts
|
|
48
|
+
// Hono
|
|
45
49
|
import { tokenForgeMiddleware } from '@opensyber/tokenforge/hono';
|
|
46
50
|
app.use('/api/*', tokenForgeMiddleware({ apiKey: env.TOKENFORGE_API_KEY }));
|
|
47
51
|
```
|
|
48
52
|
|
|
49
53
|
### 3. Get your API key
|
|
50
54
|
|
|
51
|
-
Sign up
|
|
55
|
+
Sign up at [tokenforge.opensyber.cloud](https://tokenforge.opensyber.cloud) — free tier: 1,000 verifications/month, no credit card.
|
|
56
|
+
|
|
57
|
+
## What's new in v1.0.0 (May 2026)
|
|
58
|
+
|
|
59
|
+
This release graduates the protocol surface from beta. Subsequent 1.x is backward-compatible additions only.
|
|
60
|
+
|
|
61
|
+
### W3C DBSC protocol (Sprint 37)
|
|
62
|
+
|
|
63
|
+
Aligned with the W3C draft + Chrome 146 native rollout:
|
|
64
|
+
|
|
65
|
+
```
|
|
66
|
+
POST /v1/dbsc/challenge — issue one-shot challenge (register/refresh/step_up)
|
|
67
|
+
POST /v1/dbsc/register — bind device with JWS-signed challenge response
|
|
68
|
+
POST /v1/dbsc/refresh — rotate bound cookie, run risk policy
|
|
69
|
+
POST /v1/dbsc/sessions/:id/revoke — admin soft-revoke
|
|
70
|
+
GET /.well-known/tokenforge/jwks — public verifier keys (5-min edge cache)
|
|
71
|
+
GET /.well-known/tokenforge/dbsc — service descriptor for SDK auto-discovery
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Workforce SSO replaces Cisco Duo Premier (Sprint 36)
|
|
75
|
+
|
|
76
|
+
Five OIDC IdPs + SAML 2.0:
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
import { exchangeSso } from '@opensyber/tokenforge/server/internal';
|
|
80
|
+
|
|
81
|
+
// After Okta / Entra / Google Workspace / Auth0 / generic OIDC login
|
|
82
|
+
const result = await exchangeSso(db, store, {
|
|
83
|
+
tenantId, workforceAppId,
|
|
84
|
+
idToken: req.body.idToken, // from your IdP
|
|
85
|
+
jwks, // cached via getJwks()
|
|
86
|
+
});
|
|
87
|
+
// result: { ok: true, subjectId, externalSubject, email, challenge, challengeExpiresAt }
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
JWKS cache: 24-hour freshness with stale-fallback when IdP is unreachable. xmlsoap claim namespace for Microsoft AD FS / Azure AD compatibility built in.
|
|
91
|
+
|
|
92
|
+
### AitM detection + per-route step-up (Sprint 39)
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
import { requireFreshSig } from '@opensyber/tokenforge/server';
|
|
96
|
+
|
|
97
|
+
app.use('/admin/*', requireFreshSig({ minTrustScore: 90 }));
|
|
98
|
+
app.use('/billing/*', requireFreshSig({
|
|
99
|
+
minTrustScore: 95,
|
|
100
|
+
requireWebAuthn: true,
|
|
101
|
+
}));
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Per-tenant policy via `tf_tenants.step_up_actions` JSON:
|
|
105
|
+
|
|
106
|
+
```json
|
|
107
|
+
[
|
|
108
|
+
{ "path": "/admin/billing", "requireFreshSig": true, "freshSigMaxAgeSec": 30 },
|
|
109
|
+
{ "path": "/admin/*", "requireFreshSig": true, "requireWebAuthn": true }
|
|
110
|
+
]
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Exact match wins over glob. Glob /admin/* matches /admin/users but not /admin (no segment past prefix).
|
|
114
|
+
|
|
115
|
+
### Action signing for sensitive operations
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
// Client
|
|
119
|
+
const sig = await tokenforge.signAction({
|
|
120
|
+
action: 'transfer',
|
|
121
|
+
body: { fromAccount, toAccount, amount },
|
|
122
|
+
});
|
|
123
|
+
fetch('/api/transfer', {
|
|
124
|
+
method: 'POST',
|
|
125
|
+
headers: { 'X-TF-Action-Signature': sig },
|
|
126
|
+
body: JSON.stringify({ fromAccount, toAccount, amount }),
|
|
127
|
+
});
|
|
128
|
+
```
|
|
52
129
|
|
|
53
|
-
|
|
130
|
+
5-second freshness window. JWS claims include `actionHash` (SHA-256 over canonicalized body) so an attacker can't replay the signature with a different transfer amount.
|
|
54
131
|
|
|
55
|
-
|
|
132
|
+
### Webhook event stream
|
|
56
133
|
|
|
57
|
-
-
|
|
58
|
-
- **AiTM attacks** — relay proxies can't sign requests on behalf of the device
|
|
59
|
-
- **Token theft** — XSS-exfiltrated tokens fail server-side signature verification
|
|
60
|
-
- **Trust scoring** — 7 signals detect anomalies on every request in real time
|
|
134
|
+
12 events, HMAC-SHA256 signed, retried with [1s, 4s, 15s] backoff, stable `X-TF-Delivery-Id` across retries:
|
|
61
135
|
|
|
62
|
-
|
|
136
|
+
```
|
|
137
|
+
session.bound session.verified session.revoked
|
|
138
|
+
trust_score.degraded trust_score.critical session.hijack_attempt
|
|
139
|
+
usage.cap_exceeded dbsc.risk_signal dbsc.policy_block
|
|
140
|
+
dbsc.session_step_up dbsc.session_revoked webhook.test
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
```ts
|
|
144
|
+
import { verifyWebhookSignature } from '@opensyber/tokenforge/webhooks';
|
|
145
|
+
|
|
146
|
+
app.post('/webhooks/tokenforge', async (c) => {
|
|
147
|
+
const rawBody = await c.req.text();
|
|
148
|
+
const ok = await verifyWebhookSignature({
|
|
149
|
+
body: rawBody,
|
|
150
|
+
signatureHeader: c.req.header('X-TF-Signature') ?? '',
|
|
151
|
+
timestampHeader: c.req.header('X-TF-Timestamp') ?? '',
|
|
152
|
+
secret: c.env.TOKENFORGE_WEBHOOK_SECRET,
|
|
153
|
+
});
|
|
154
|
+
if (!ok) return c.json({ error: 'bad_signature' }, 401);
|
|
155
|
+
// handle event ...
|
|
156
|
+
});
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Secret rotation grace window: 24 hours. Send the new secret while the old one stays valid; receivers verify against either.
|
|
160
|
+
|
|
161
|
+
## How it works
|
|
63
162
|
|
|
64
163
|
```
|
|
65
164
|
Browser TokenForge API Your Server
|
|
66
165
|
│ │ │
|
|
67
|
-
│ 1.
|
|
68
|
-
│
|
|
166
|
+
│ 1. Generate ECDSA P-256 │ │
|
|
167
|
+
│ keypair (non-extractable) │ │
|
|
69
168
|
│ │ │
|
|
70
|
-
│ 2. POST /v1/
|
|
71
|
-
│
|
|
169
|
+
│ 2. POST /v1/dbsc/challenge ───>│ │
|
|
170
|
+
│ <─────── { challenge } ────────│ │
|
|
72
171
|
│ │ │
|
|
73
|
-
│ 3.
|
|
74
|
-
│ +
|
|
172
|
+
│ 3. POST /v1/dbsc/register ────>│ Store public key │
|
|
173
|
+
│ + JWS over challenge │ │
|
|
174
|
+
│ <─── { sessionId, deviceId } ──│ │
|
|
175
|
+
│ │ │
|
|
176
|
+
│ 4. fetch('/api/data') │ │
|
|
177
|
+
│ + X-TF-Signature ──────────────────────────────────────> │
|
|
75
178
|
│ │ │
|
|
76
179
|
│ │ <── POST /v1/edge/verify ── │
|
|
77
180
|
│ │ Verify signature │
|
|
78
|
-
│ │
|
|
181
|
+
│ │ Run AitM heuristics │
|
|
79
182
|
│ │ Compute trust score │
|
|
80
|
-
│ │
|
|
81
|
-
│
|
|
82
|
-
│ <──────────────────────────────────── 200 OK ─────────────── │
|
|
183
|
+
│ │ ──> { allow, score: 92 } │
|
|
184
|
+
│ <──────────────────────────────────── 200 OK ─────────────── │
|
|
83
185
|
```
|
|
84
186
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
## Trust Score
|
|
187
|
+
The device key never leaves the client. The TokenForge service holds the public key + session metadata. Your server passes request context, gets back an allow/step_up/block decision.
|
|
88
188
|
|
|
89
|
-
|
|
189
|
+
## Trust score signals
|
|
90
190
|
|
|
91
191
|
| Signal | Weight | Detects |
|
|
92
192
|
|--------|--------|---------|
|
|
93
|
-
| Signature | 30 | Tampering, missing device key |
|
|
193
|
+
| Signature | 30 | Tampering, missing/wrong device key |
|
|
94
194
|
| IP Address | 15 | IP change since binding |
|
|
95
195
|
| Geo Location | 15 | Country mismatch |
|
|
96
|
-
| Fingerprint | 15 | Browser fingerprint drift |
|
|
196
|
+
| Fingerprint | 15 | Browser/device fingerprint drift |
|
|
97
197
|
| Velocity | 10 | Multiple IPs in short window |
|
|
98
|
-
| Timing | 10 | Clock skew beyond
|
|
198
|
+
| Timing | 10 | Clock skew beyond ±60s |
|
|
99
199
|
| Nonce | 5 | Replay attacks |
|
|
100
200
|
|
|
101
|
-
Score >= 80: allow
|
|
201
|
+
Score >= 80: `allow`. Score 40-79: `step_up`. Score < 40: `block`. Thresholds configurable per tenant.
|
|
102
202
|
|
|
103
|
-
##
|
|
203
|
+
## Drop-in for your existing IdP
|
|
104
204
|
|
|
105
|
-
|
|
205
|
+
TokenForge runs *after* authentication — bring your own IdP:
|
|
206
|
+
|
|
207
|
+
| IdP | Integration | Built-in support |
|
|
208
|
+
|---|---|---|
|
|
209
|
+
| **Microsoft Entra ID** | Custom Authentication Extension webhook | Roadmap M11 (Q3 2026) |
|
|
210
|
+
| **Auth0** | Auth0 Action snippet | Roadmap M14 — Marketplace listing pending review |
|
|
211
|
+
| **Okta** | Inline Hook + Custom Authenticator | Roadmap M15 — OIN listing pending review |
|
|
212
|
+
| **Clerk** | `@tokenforge/clerk-middleware` shim | Roadmap M16 |
|
|
213
|
+
| **Auth.js / NextAuth** | Works today via `@opensyber/tokenforge/nextjs` | ✅ |
|
|
214
|
+
| **Firebase Auth** | Works today via `@opensyber/tokenforge/express` | ✅ |
|
|
215
|
+
| **Supabase Auth** | Works today via `@opensyber/tokenforge/express` | ✅ |
|
|
216
|
+
| **Custom JWT** | Works today | ✅ |
|
|
217
|
+
|
|
218
|
+
## React integration
|
|
106
219
|
|
|
107
220
|
```tsx
|
|
108
221
|
import { TokenForgeProvider, useTokenForge } from '@opensyber/tokenforge/react';
|
|
@@ -117,21 +230,66 @@ function App() {
|
|
|
117
230
|
</TokenForgeProvider>
|
|
118
231
|
);
|
|
119
232
|
}
|
|
233
|
+
|
|
234
|
+
function ProtectedPage() {
|
|
235
|
+
const { bound, trustScore, deviceId } = useTokenForge();
|
|
236
|
+
if (!bound) return <BindButton />;
|
|
237
|
+
return <Dashboard trustScore={trustScore} deviceId={deviceId} />;
|
|
238
|
+
}
|
|
120
239
|
```
|
|
121
240
|
|
|
122
|
-
##
|
|
241
|
+
## Self-hosted server
|
|
242
|
+
|
|
243
|
+
The server adapters work against your own database / KV — no hosted service required:
|
|
244
|
+
|
|
245
|
+
```ts
|
|
246
|
+
import { createTokenForgeRoutes } from '@opensyber/tokenforge/server';
|
|
247
|
+
import { D1Storage } from '@opensyber/tokenforge/server/storage';
|
|
248
|
+
// or PostgresStorage, RedisStorage, or implement the StorageInterface
|
|
249
|
+
|
|
250
|
+
const tf = createTokenForgeRoutes({
|
|
251
|
+
storage: new D1Storage(env.DB),
|
|
252
|
+
sessionMaxAge: 86400,
|
|
253
|
+
});
|
|
254
|
+
app.route('/api/tf', tf);
|
|
255
|
+
```
|
|
123
256
|
|
|
124
|
-
|
|
257
|
+
Storage backends shipped: D1 (Cloudflare Workers), PostgreSQL, Redis. Bring your own via `StorageInterface`.
|
|
125
258
|
|
|
126
|
-
##
|
|
259
|
+
## Compatibility
|
|
260
|
+
|
|
261
|
+
| Platform | Status |
|
|
262
|
+
|---|---|
|
|
263
|
+
| Chrome 146+ Windows | ✅ Native DBSC + polyfill fallback |
|
|
264
|
+
| Chrome 146+ macOS / Linux | ⏳ DBSC pending Google rollout; polyfill works today |
|
|
265
|
+
| Safari (macOS / iOS) | ✅ Polyfill via Web Crypto + IndexedDB |
|
|
266
|
+
| Firefox | ✅ Polyfill |
|
|
267
|
+
| Edge 146+ | ✅ Inherits Chromium DBSC |
|
|
268
|
+
| Node.js 18+ (server) | ✅ |
|
|
269
|
+
| Bun, Deno | ✅ |
|
|
270
|
+
| Cloudflare Workers | ✅ |
|
|
271
|
+
|
|
272
|
+
## Pricing (hosted service)
|
|
127
273
|
|
|
128
274
|
| Plan | Price | Verifications/mo |
|
|
129
275
|
|------|-------|-----------------|
|
|
130
276
|
| Free | $0 | 1,000 |
|
|
131
277
|
| Pro | $49/mo | 50,000 |
|
|
132
278
|
| Team | $199/mo | 250,000 |
|
|
133
|
-
| Enterprise | Custom | Unlimited |
|
|
279
|
+
| Enterprise | Custom | Unlimited + SLA |
|
|
280
|
+
|
|
281
|
+
The SDK code in this repository is MIT-licensed and runs against any storage backend — you can self-host without a TokenForge subscription.
|
|
282
|
+
|
|
283
|
+
## Examples & docs
|
|
284
|
+
|
|
285
|
+
- Full API reference: [tokenforge.opensyber.cloud/docs](https://tokenforge.opensyber.cloud/docs)
|
|
286
|
+
- DBSC protocol explainer: [tokenforge.opensyber.cloud/dbsc](https://tokenforge.opensyber.cloud/dbsc)
|
|
287
|
+
- Reference apps (Next.js, Express, Hono, Fastify): [github.com/opensyber/tokenforge-examples](https://github.com/opensyber/tokenforge-examples)
|
|
288
|
+
|
|
289
|
+
## Security disclosure
|
|
290
|
+
|
|
291
|
+
Security issues: `security@opensyber.cloud` (PGP key available). Coordinated disclosure within 90 days; CVE assignment for confirmed vulnerabilities.
|
|
134
292
|
|
|
135
293
|
## License
|
|
136
294
|
|
|
137
|
-
|
|
295
|
+
SDK: MIT (see [LICENSE.md](./LICENSE.md)). Hosted service requires API key — governed by [TokenForge Terms](https://tokenforge.opensyber.cloud/terms).
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/** TokenForge context attached to context.locals.tf */
|
|
2
|
+
export interface TfContext {
|
|
3
|
+
bound: boolean;
|
|
4
|
+
trustScore: number;
|
|
5
|
+
deviceId: string | null;
|
|
6
|
+
}
|
|
7
|
+
/** Options for the Astro middleware. */
|
|
8
|
+
export interface TokenForgeAstroOptions {
|
|
9
|
+
/** Your TokenForge API key (starts with `tf_`). */
|
|
10
|
+
apiKey: string;
|
|
11
|
+
/** API base URL (defaults to TokenForge cloud). */
|
|
12
|
+
apiBase?: string;
|
|
13
|
+
/** Paths to skip verification. */
|
|
14
|
+
skipPaths?: string[];
|
|
15
|
+
}
|
|
16
|
+
/** Minimal Astro middleware context — avoids astro:middleware dep. */
|
|
17
|
+
interface APIContext {
|
|
18
|
+
request: Request;
|
|
19
|
+
url: URL;
|
|
20
|
+
locals: {
|
|
21
|
+
tf?: TfContext;
|
|
22
|
+
};
|
|
23
|
+
clientAddress?: string;
|
|
24
|
+
}
|
|
25
|
+
type MiddlewareNext = () => Promise<Response>;
|
|
26
|
+
type MiddlewareHandler = (context: APIContext, next: MiddlewareNext) => Promise<Response>;
|
|
27
|
+
/**
|
|
28
|
+
* Astro middleware for TokenForge verification via the cloud API.
|
|
29
|
+
*
|
|
30
|
+
* Usage:
|
|
31
|
+
* ```ts
|
|
32
|
+
* // src/middleware.ts
|
|
33
|
+
* import { tokenForgeMiddleware } from '@opensyber/tokenforge/astro';
|
|
34
|
+
* export const onRequest = tokenForgeMiddleware({
|
|
35
|
+
* apiKey: import.meta.env.TOKENFORGE_API_KEY,
|
|
36
|
+
* });
|
|
37
|
+
* ```
|
|
38
|
+
*
|
|
39
|
+
* Reads `Astro.locals.tf` for downstream pages and endpoints.
|
|
40
|
+
*/
|
|
41
|
+
export declare function tokenForgeMiddleware(options: TokenForgeAstroOptions): MiddlewareHandler;
|
|
42
|
+
/**
|
|
43
|
+
* Per-route step-up gate. Use inside page or endpoint code after the
|
|
44
|
+
* middleware has populated `Astro.locals.tf`.
|
|
45
|
+
*
|
|
46
|
+
* Returns a 403 `Response` when below threshold; the caller should
|
|
47
|
+
* `return` it. Returns `null` on pass.
|
|
48
|
+
*/
|
|
49
|
+
export declare function requireFreshSig(locals: {
|
|
50
|
+
tf?: TfContext;
|
|
51
|
+
}, opts?: {
|
|
52
|
+
minTrustScore?: number;
|
|
53
|
+
}): Response | null;
|
|
54
|
+
export {};
|
|
55
|
+
//# sourceMappingURL=astro.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"astro.d.ts","sourceRoot":"","sources":["../../src/adapters/astro.ts"],"names":[],"mappings":"AAEA,uDAAuD;AACvD,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,OAAO,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAED,wCAAwC;AACxC,MAAM,WAAW,sBAAsB;IACrC,mDAAmD;IACnD,MAAM,EAAE,MAAM,CAAC;IACf,mDAAmD;IACnD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,kCAAkC;IAClC,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;CACtB;AAED,sEAAsE;AACtE,UAAU,UAAU;IAClB,OAAO,EAAE,OAAO,CAAC;IACjB,GAAG,EAAE,GAAG,CAAC;IACT,MAAM,EAAE;QAAE,EAAE,CAAC,EAAE,SAAS,CAAA;KAAE,CAAC;IAC3B,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,KAAK,cAAc,GAAG,MAAM,OAAO,CAAC,QAAQ,CAAC,CAAC;AAC9C,KAAK,iBAAiB,GAAG,CACvB,OAAO,EAAE,UAAU,EACnB,IAAI,EAAE,cAAc,KACjB,OAAO,CAAC,QAAQ,CAAC,CAAC;AAEvB;;;;;;;;;;;;;GAaG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,sBAAsB,GAC9B,iBAAiB,CAoEnB;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAC7B,MAAM,EAAE;IAAE,EAAE,CAAC,EAAE,SAAS,CAAA;CAAE,EAC1B,IAAI,GAAE;IAAE,aAAa,CAAC,EAAE,MAAM,CAAA;CAAO,GACpC,QAAQ,GAAG,IAAI,CAcjB"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
const DEFAULT_API_BASE = 'https://tokenforge-api.opensyber.cloud';
|
|
2
|
+
/**
|
|
3
|
+
* Astro middleware for TokenForge verification via the cloud API.
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* ```ts
|
|
7
|
+
* // src/middleware.ts
|
|
8
|
+
* import { tokenForgeMiddleware } from '@opensyber/tokenforge/astro';
|
|
9
|
+
* export const onRequest = tokenForgeMiddleware({
|
|
10
|
+
* apiKey: import.meta.env.TOKENFORGE_API_KEY,
|
|
11
|
+
* });
|
|
12
|
+
* ```
|
|
13
|
+
*
|
|
14
|
+
* Reads `Astro.locals.tf` for downstream pages and endpoints.
|
|
15
|
+
*/
|
|
16
|
+
export function tokenForgeMiddleware(options) {
|
|
17
|
+
const apiBase = options.apiBase ?? DEFAULT_API_BASE;
|
|
18
|
+
return async (context, next) => {
|
|
19
|
+
const path = context.url.pathname;
|
|
20
|
+
if (shouldSkip(path, options.skipPaths)) {
|
|
21
|
+
context.locals.tf = { bound: false, trustScore: 0, deviceId: null };
|
|
22
|
+
return next();
|
|
23
|
+
}
|
|
24
|
+
const header = (name) => context.request.headers.get(name);
|
|
25
|
+
try {
|
|
26
|
+
const apiRes = await fetch(`${apiBase}/v1/edge/verify`, {
|
|
27
|
+
method: 'POST',
|
|
28
|
+
headers: {
|
|
29
|
+
Authorization: `Bearer ${options.apiKey}`,
|
|
30
|
+
'Content-Type': 'application/json',
|
|
31
|
+
},
|
|
32
|
+
body: JSON.stringify({
|
|
33
|
+
path,
|
|
34
|
+
method: context.request.method,
|
|
35
|
+
headers: {
|
|
36
|
+
signature: header('x-tf-signature'),
|
|
37
|
+
nonce: header('x-tf-nonce'),
|
|
38
|
+
timestamp: header('x-tf-timestamp'),
|
|
39
|
+
deviceId: header('x-tf-device-id'),
|
|
40
|
+
},
|
|
41
|
+
ipAddress: context.clientAddress ?? header('x-forwarded-for') ?? '',
|
|
42
|
+
countryCode: header('cf-ipcountry') ?? '',
|
|
43
|
+
userAgent: header('user-agent') ?? '',
|
|
44
|
+
}),
|
|
45
|
+
});
|
|
46
|
+
if (!apiRes.ok) {
|
|
47
|
+
context.locals.tf = { bound: false, trustScore: 0, deviceId: null };
|
|
48
|
+
return next();
|
|
49
|
+
}
|
|
50
|
+
const { data } = (await apiRes.json());
|
|
51
|
+
if (data.status === 'block') {
|
|
52
|
+
return new Response(JSON.stringify({ error: 'session_blocked', reason: data.reason }), { status: 401, headers: { 'Content-Type': 'application/json' } });
|
|
53
|
+
}
|
|
54
|
+
context.locals.tf = {
|
|
55
|
+
bound: data.bound,
|
|
56
|
+
trustScore: data.trustScore,
|
|
57
|
+
deviceId: data.deviceId,
|
|
58
|
+
};
|
|
59
|
+
return next();
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
context.locals.tf = { bound: false, trustScore: 0, deviceId: null };
|
|
63
|
+
return next();
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Per-route step-up gate. Use inside page or endpoint code after the
|
|
69
|
+
* middleware has populated `Astro.locals.tf`.
|
|
70
|
+
*
|
|
71
|
+
* Returns a 403 `Response` when below threshold; the caller should
|
|
72
|
+
* `return` it. Returns `null` on pass.
|
|
73
|
+
*/
|
|
74
|
+
export function requireFreshSig(locals, opts = {}) {
|
|
75
|
+
const min = opts.minTrustScore ?? 90;
|
|
76
|
+
const score = locals.tf?.trustScore ?? 0;
|
|
77
|
+
if (score < min) {
|
|
78
|
+
return new Response(JSON.stringify({
|
|
79
|
+
error: 'elevated_trust_required',
|
|
80
|
+
action: 'step_up_required',
|
|
81
|
+
trustScore: score,
|
|
82
|
+
}), { status: 403, headers: { 'Content-Type': 'application/json' } });
|
|
83
|
+
}
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
function shouldSkip(path, skipPaths) {
|
|
87
|
+
if (!skipPaths)
|
|
88
|
+
return false;
|
|
89
|
+
return skipPaths.some((p) => p.endsWith('*') ? path.startsWith(p.slice(0, -1)) : path === p);
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=astro.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"astro.js","sourceRoot":"","sources":["../../src/adapters/astro.ts"],"names":[],"mappings":"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"}
|
|
@@ -44,5 +44,20 @@ export interface TokenForgeExpressOptions {
|
|
|
44
44
|
* - `deviceId: string | null` — device identifier
|
|
45
45
|
*/
|
|
46
46
|
export declare function tokenForgeMiddleware(options: TokenForgeExpressOptions): (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => Promise<void>;
|
|
47
|
+
/**
|
|
48
|
+
* Per-route step-up gate. Use after `tokenForgeMiddleware` ran globally,
|
|
49
|
+
* mounted on the routes that need elevated trust.
|
|
50
|
+
*
|
|
51
|
+
* ```ts
|
|
52
|
+
* app.use(tokenForgeMiddleware({ apiKey }));
|
|
53
|
+
* app.use('/admin', requireFreshSig({ minTrustScore: 90 }));
|
|
54
|
+
* ```
|
|
55
|
+
*
|
|
56
|
+
* Responds 403 `elevated_trust_required` when `req.tf` is missing or
|
|
57
|
+
* `trustScore` is below the threshold.
|
|
58
|
+
*/
|
|
59
|
+
export declare function requireFreshSig(opts?: {
|
|
60
|
+
minTrustScore?: number;
|
|
61
|
+
}): (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => void;
|
|
47
62
|
export {};
|
|
48
63
|
//# sourceMappingURL=express.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"express.d.ts","sourceRoot":"","sources":["../../src/adapters/express.ts"],"names":[],"mappings":"AAEA,2CAA2C;AAC3C,UAAU,cAAc;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,CAAC;IACvD,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,EAAE,CAAC,EAAE,SAAS,CAAC;IACf,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,UAAU,eAAe;IACvB,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe,CAAC;IACtC,IAAI,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,CAAC;CAC3B;AAED,4CAA4C;AAC5C,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,OAAO,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAED,KAAK,YAAY,GAAG,CAAC,GAAG,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;AAE5C,0CAA0C;AAC1C,MAAM,WAAW,wBAAwB;IACvC,mDAAmD;IACnD,MAAM,EAAE,MAAM,CAAC;IACf,mDAAmD;IACnD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,kCAAkC;IAClC,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,4CAA4C;IAC5C,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;CACzB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,wBAAwB,IAGtD,KAAK,cAAc,EAAE,KAAK,eAAe,EAAE,MAAM,YAAY,KAAG,OAAO,CAAC,IAAI,CAAC,CAwD5F"}
|
|
1
|
+
{"version":3,"file":"express.d.ts","sourceRoot":"","sources":["../../src/adapters/express.ts"],"names":[],"mappings":"AAEA,2CAA2C;AAC3C,UAAU,cAAc;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,CAAC;IACvD,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,EAAE,CAAC,EAAE,SAAS,CAAC;IACf,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,UAAU,eAAe;IACvB,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe,CAAC;IACtC,IAAI,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,CAAC;CAC3B;AAED,4CAA4C;AAC5C,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,OAAO,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAED,KAAK,YAAY,GAAG,CAAC,GAAG,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;AAE5C,0CAA0C;AAC1C,MAAM,WAAW,wBAAwB;IACvC,mDAAmD;IACnD,MAAM,EAAE,MAAM,CAAC;IACf,mDAAmD;IACnD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,kCAAkC;IAClC,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,4CAA4C;IAC5C,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;CACzB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,wBAAwB,IAGtD,KAAK,cAAc,EAAE,KAAK,eAAe,EAAE,MAAM,YAAY,KAAG,OAAO,CAAC,IAAI,CAAC,CAwD5F;AAOD;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAAC,IAAI,GAAE;IAAE,aAAa,CAAC,EAAE,MAAM,CAAA;CAAO,IAE3D,KAAK,cAAc,EAAE,KAAK,eAAe,EAAE,MAAM,YAAY,KAAG,IAAI,CAY7E"}
|
package/dist/adapters/express.js
CHANGED
|
@@ -70,4 +70,31 @@ function shouldSkip(path, skipPaths) {
|
|
|
70
70
|
return false;
|
|
71
71
|
return skipPaths.some((p) => p.endsWith('*') ? path.startsWith(p.slice(0, -1)) : path === p);
|
|
72
72
|
}
|
|
73
|
+
/**
|
|
74
|
+
* Per-route step-up gate. Use after `tokenForgeMiddleware` ran globally,
|
|
75
|
+
* mounted on the routes that need elevated trust.
|
|
76
|
+
*
|
|
77
|
+
* ```ts
|
|
78
|
+
* app.use(tokenForgeMiddleware({ apiKey }));
|
|
79
|
+
* app.use('/admin', requireFreshSig({ minTrustScore: 90 }));
|
|
80
|
+
* ```
|
|
81
|
+
*
|
|
82
|
+
* Responds 403 `elevated_trust_required` when `req.tf` is missing or
|
|
83
|
+
* `trustScore` is below the threshold.
|
|
84
|
+
*/
|
|
85
|
+
export function requireFreshSig(opts = {}) {
|
|
86
|
+
const min = opts.minTrustScore ?? 90;
|
|
87
|
+
return (req, res, next) => {
|
|
88
|
+
const score = req.tf?.trustScore ?? 0;
|
|
89
|
+
if (score < min) {
|
|
90
|
+
res.status(403).json({
|
|
91
|
+
error: 'elevated_trust_required',
|
|
92
|
+
action: 'step_up_required',
|
|
93
|
+
trustScore: score,
|
|
94
|
+
});
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
next();
|
|
98
|
+
};
|
|
99
|
+
}
|
|
73
100
|
//# sourceMappingURL=express.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"express.js","sourceRoot":"","sources":["../../src/adapters/express.ts"],"names":[],"mappings":"AAAA,MAAM,gBAAgB,GAAG,wCAAwC,CAAC;AAsClE;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,oBAAoB,CAAC,OAAiC;IACpE,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,gBAAgB,CAAC;IAEpD,OAAO,KAAK,EAAE,GAAmB,EAAE,GAAoB,EAAE,IAAkB,EAAiB,EAAE;QAC5F,IAAI,UAAU,CAAC,GAAG,CAAC,IAAI,EAAE,OAAO,CAAC,SAAS,CAAC,EAAE,CAAC;YAC5C,GAAG,CAAC,EAAE,GAAG,EAAE,KAAK,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;YACzD,IAAI,EAAE,CAAC;YACP,OAAO;QACT,CAAC;QAED,MAAM,MAAM,GAAG,CAAC,IAAY,EAAiB,EAAE;YAC7C,MAAM,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC;YAC5C,OAAO,OAAO,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC;QAC9C,CAAC,CAAC;QAEF,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,GAAG,OAAO,iBAAiB,EAAE;gBACtD,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE;oBACP,aAAa,EAAE,UAAU,OAAO,CAAC,MAAM,EAAE;oBACzC,cAAc,EAAE,kBAAkB;iBACnC;gBACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;oBACnB,IAAI,EAAE,GAAG,CAAC,IAAI;oBACd,MAAM,EAAE,GAAG,CAAC,MAAM;oBAClB,OAAO,EAAE;wBACP,SAAS,EAAE,MAAM,CAAC,gBAAgB,CAAC;wBACnC,KAAK,EAAE,MAAM,CAAC,YAAY,CAAC;wBAC3B,SAAS,EAAE,MAAM,CAAC,gBAAgB,CAAC;wBACnC,QAAQ,EAAE,MAAM,CAAC,gBAAgB,CAAC;qBACnC;oBACD,SAAS,EAAE,GAAG,CAAC,EAAE,IAAI,MAAM,CAAC,iBAAiB,CAAC,IAAI,EAAE;oBACpD,WAAW,EAAE,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE;oBACzC,SAAS,EAAE,MAAM,CAAC,YAAY,CAAC,IAAI,EAAE;iBACtC,CAAC;aACH,CAAC,CAAC;YAEH,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;gBACf,GAAG,CAAC,EAAE,GAAG,EAAE,KAAK,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;gBACzD,IAAI,EAAE,CAAC;gBACP,OAAO;YACT,CAAC;YAED,MAAM,EAAE,IAAI,EAAE,GAAG,CAAC,MAAM,MAAM,CAAC,IAAI,EAAE,CAEpC,CAAC;YAEF,IAAI,IAAI,CAAC,MAAM,KAAK,OAAO,EAAE,CAAC;gBAC5B,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,iBAAiB,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;gBACxE,OAAO;YACT,CAAC;YAED,GAAG,CAAC,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,UAAU,EAAE,IAAI,CAAC,UAAU,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,CAAC;YACrF,IAAI,EAAE,CAAC;QACT,CAAC;QAAC,MAAM,CAAC;YACP,GAAG,CAAC,EAAE,GAAG,EAAE,KAAK,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;YACzD,IAAI,EAAE,CAAC;QACT,CAAC;IACH,CAAC,CAAC;AACJ,CAAC;AAED,SAAS,UAAU,CAAC,IAAY,EAAE,SAAoB;IACpD,IAAI,CAAC,SAAS;QAAE,OAAO,KAAK,CAAC;IAC7B,OAAO,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC;AAC/F,CAAC"}
|
|
1
|
+
{"version":3,"file":"express.js","sourceRoot":"","sources":["../../src/adapters/express.ts"],"names":[],"mappings":"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"}
|
|
@@ -40,5 +40,22 @@ interface FastifyInstance {
|
|
|
40
40
|
* ```
|
|
41
41
|
*/
|
|
42
42
|
export declare function tokenForgePlugin(fastify: FastifyInstance, options: TokenForgeFastifyOptions, done: (err?: Error) => void): void;
|
|
43
|
+
/**
|
|
44
|
+
* Per-route step-up gate. Register after `tokenForgePlugin` ran globally,
|
|
45
|
+
* scoped to a route that needs elevated trust.
|
|
46
|
+
*
|
|
47
|
+
* ```ts
|
|
48
|
+
* fastify.register(tokenForgePlugin, { apiKey });
|
|
49
|
+
* fastify.addHook('preHandler', async (req, reply) => {
|
|
50
|
+
* if (req.url.startsWith('/admin')) await requireFreshSig()(req, reply);
|
|
51
|
+
* });
|
|
52
|
+
* ```
|
|
53
|
+
*
|
|
54
|
+
* Responds 403 `elevated_trust_required` when `request.tf` is missing or
|
|
55
|
+
* `trustScore` is below the threshold.
|
|
56
|
+
*/
|
|
57
|
+
export declare function requireFreshSig(opts?: {
|
|
58
|
+
minTrustScore?: number;
|
|
59
|
+
}): (req: FastifyRequest, reply: FastifyReply) => Promise<void>;
|
|
43
60
|
export {};
|
|
44
61
|
//# sourceMappingURL=fastify.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fastify.d.ts","sourceRoot":"","sources":["../../src/adapters/fastify.ts"],"names":[],"mappings":"AAEA,gDAAgD;AAChD,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,OAAO,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAED,sCAAsC;AACtC,MAAM,WAAW,wBAAwB;IACvC,mDAAmD;IACnD,MAAM,EAAE,MAAM,CAAC;IACf,mDAAmD;IACnD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,kCAAkC;IAClC,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;CACtB;AAED,UAAU,cAAc;IACtB,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,CAAC;IACvD,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,EAAE,CAAC,EAAE,SAAS,CAAC;CAChB;AAED,UAAU,YAAY;IACpB,IAAI,CAAC,UAAU,EAAE,MAAM,GAAG,YAAY,CAAC;IACvC,IAAI,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAAC;CAC9B;AAED,UAAU,eAAe;IACvB,OAAO,CACL,IAAI,EAAE,YAAY,EAClB,OAAO,EAAE,CAAC,GAAG,EAAE,cAAc,EAAE,KAAK,EAAE,YAAY,KAAK,OAAO,CAAC,IAAI,CAAC,GACnE,IAAI,CAAC;IACR,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI,CAAC;CACrD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,eAAe,EACxB,OAAO,EAAE,wBAAwB,EACjC,IAAI,EAAE,CAAC,GAAG,CAAC,EAAE,KAAK,KAAK,IAAI,GAC1B,IAAI,CA6DN"}
|
|
1
|
+
{"version":3,"file":"fastify.d.ts","sourceRoot":"","sources":["../../src/adapters/fastify.ts"],"names":[],"mappings":"AAEA,gDAAgD;AAChD,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,OAAO,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAED,sCAAsC;AACtC,MAAM,WAAW,wBAAwB;IACvC,mDAAmD;IACnD,MAAM,EAAE,MAAM,CAAC;IACf,mDAAmD;IACnD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,kCAAkC;IAClC,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;CACtB;AAED,UAAU,cAAc;IACtB,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,CAAC;IACvD,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,EAAE,CAAC,EAAE,SAAS,CAAC;CAChB;AAED,UAAU,YAAY;IACpB,IAAI,CAAC,UAAU,EAAE,MAAM,GAAG,YAAY,CAAC;IACvC,IAAI,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAAC;CAC9B;AAED,UAAU,eAAe;IACvB,OAAO,CACL,IAAI,EAAE,YAAY,EAClB,OAAO,EAAE,CAAC,GAAG,EAAE,cAAc,EAAE,KAAK,EAAE,YAAY,KAAK,OAAO,CAAC,IAAI,CAAC,GACnE,IAAI,CAAC;IACR,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI,CAAC;CACrD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,eAAe,EACxB,OAAO,EAAE,wBAAwB,EACjC,IAAI,EAAE,CAAC,GAAG,CAAC,EAAE,KAAK,KAAK,IAAI,GAC1B,IAAI,CA6DN;AAOD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,eAAe,CAAC,IAAI,GAAE;IAAE,aAAa,CAAC,EAAE,MAAM,CAAA;CAAO,IAErD,KAAK,cAAc,EAAE,OAAO,YAAY,KAAG,OAAO,CAAC,IAAI,CAAC,CAUvE"}
|