@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.
Files changed (142) hide show
  1. package/LICENSE.md +32 -0
  2. package/README.md +205 -47
  3. package/dist/adapters/astro.d.ts +55 -0
  4. package/dist/adapters/astro.d.ts.map +1 -0
  5. package/dist/adapters/astro.js +91 -0
  6. package/dist/adapters/astro.js.map +1 -0
  7. package/dist/adapters/express.d.ts +15 -0
  8. package/dist/adapters/express.d.ts.map +1 -1
  9. package/dist/adapters/express.js +27 -0
  10. package/dist/adapters/express.js.map +1 -1
  11. package/dist/adapters/fastify.d.ts +17 -0
  12. package/dist/adapters/fastify.d.ts.map +1 -1
  13. package/dist/adapters/fastify.js +27 -0
  14. package/dist/adapters/fastify.js.map +1 -1
  15. package/dist/adapters/hono.d.ts +8 -4
  16. package/dist/adapters/hono.d.ts.map +1 -1
  17. package/dist/adapters/hono.js +7 -4
  18. package/dist/adapters/hono.js.map +1 -1
  19. package/dist/adapters/index.d.ts +5 -3
  20. package/dist/adapters/index.d.ts.map +1 -1
  21. package/dist/adapters/index.js +5 -3
  22. package/dist/adapters/index.js.map +1 -1
  23. package/dist/adapters/nextjs.d.ts +16 -0
  24. package/dist/adapters/nextjs.d.ts.map +1 -1
  25. package/dist/adapters/nextjs.js +26 -0
  26. package/dist/adapters/nextjs.js.map +1 -1
  27. package/dist/adapters/sveltekit.d.ts +57 -0
  28. package/dist/adapters/sveltekit.d.ts.map +1 -0
  29. package/dist/adapters/sveltekit.js +88 -0
  30. package/dist/adapters/sveltekit.js.map +1 -0
  31. package/dist/client/action-signer.d.ts +53 -0
  32. package/dist/client/action-signer.d.ts.map +1 -0
  33. package/dist/client/action-signer.js +48 -0
  34. package/dist/client/action-signer.js.map +1 -0
  35. package/dist/client/binding.d.ts +24 -7
  36. package/dist/client/binding.d.ts.map +1 -1
  37. package/dist/client/binding.js +17 -6
  38. package/dist/client/binding.js.map +1 -1
  39. package/dist/client/index.d.ts +13 -0
  40. package/dist/client/index.d.ts.map +1 -1
  41. package/dist/client/index.js +27 -1
  42. package/dist/client/index.js.map +1 -1
  43. package/dist/client/signer.d.ts +17 -0
  44. package/dist/client/signer.d.ts.map +1 -1
  45. package/dist/client/signer.js +23 -0
  46. package/dist/client/signer.js.map +1 -1
  47. package/dist/client/webauthn.d.ts +45 -0
  48. package/dist/client/webauthn.d.ts.map +1 -0
  49. package/dist/client/webauthn.js +118 -0
  50. package/dist/client/webauthn.js.map +1 -0
  51. package/dist/server/action-verify.d.ts +56 -0
  52. package/dist/server/action-verify.d.ts.map +1 -0
  53. package/dist/server/action-verify.js +47 -0
  54. package/dist/server/action-verify.js.map +1 -0
  55. package/dist/server/aitm-heuristics.d.ts +57 -0
  56. package/dist/server/aitm-heuristics.d.ts.map +1 -0
  57. package/dist/server/aitm-heuristics.js +120 -0
  58. package/dist/server/aitm-heuristics.js.map +1 -0
  59. package/dist/server/bound-cookie.d.ts +39 -0
  60. package/dist/server/bound-cookie.d.ts.map +1 -0
  61. package/dist/server/bound-cookie.js +59 -0
  62. package/dist/server/bound-cookie.js.map +1 -0
  63. package/dist/server/cbor.d.ts +20 -0
  64. package/dist/server/cbor.d.ts.map +1 -0
  65. package/dist/server/cbor.js +110 -0
  66. package/dist/server/cbor.js.map +1 -0
  67. package/dist/server/dbsc-challenge.d.ts +51 -0
  68. package/dist/server/dbsc-challenge.d.ts.map +1 -0
  69. package/dist/server/dbsc-challenge.js +78 -0
  70. package/dist/server/dbsc-challenge.js.map +1 -0
  71. package/dist/server/index.d.ts +2 -4
  72. package/dist/server/index.d.ts.map +1 -1
  73. package/dist/server/index.js +6 -4
  74. package/dist/server/index.js.map +1 -1
  75. package/dist/server/jws-verify.d.ts +46 -0
  76. package/dist/server/jws-verify.d.ts.map +1 -0
  77. package/dist/server/jws-verify.js +64 -0
  78. package/dist/server/jws-verify.js.map +1 -0
  79. package/dist/server/middleware.d.ts +15 -0
  80. package/dist/server/middleware.d.ts.map +1 -1
  81. package/dist/server/middleware.js +27 -0
  82. package/dist/server/middleware.js.map +1 -1
  83. package/dist/server/oidc-verify.d.ts +56 -0
  84. package/dist/server/oidc-verify.d.ts.map +1 -0
  85. package/dist/server/oidc-verify.js +107 -0
  86. package/dist/server/oidc-verify.js.map +1 -0
  87. package/dist/server/policy.d.ts +47 -0
  88. package/dist/server/policy.d.ts.map +1 -0
  89. package/dist/server/policy.js +84 -0
  90. package/dist/server/policy.js.map +1 -0
  91. package/dist/server/step-up-policy.d.ts +36 -0
  92. package/dist/server/step-up-policy.d.ts.map +1 -0
  93. package/dist/server/step-up-policy.js +101 -0
  94. package/dist/server/step-up-policy.js.map +1 -0
  95. package/dist/server/trust-score-fixtures.d.ts +9 -0
  96. package/dist/server/trust-score-fixtures.d.ts.map +1 -0
  97. package/dist/server/trust-score-fixtures.js +23 -0
  98. package/dist/server/trust-score-fixtures.js.map +1 -0
  99. package/dist/server/webauthn-verify.d.ts +41 -0
  100. package/dist/server/webauthn-verify.d.ts.map +1 -0
  101. package/dist/server/webauthn-verify.js +175 -0
  102. package/dist/server/webauthn-verify.js.map +1 -0
  103. package/dist/server/webauthn-verify.test-helpers.d.ts +8 -0
  104. package/dist/server/webauthn-verify.test-helpers.d.ts.map +1 -0
  105. package/dist/server/webauthn-verify.test-helpers.js +39 -0
  106. package/dist/server/webauthn-verify.test-helpers.js.map +1 -0
  107. package/dist/server/webhooks.d.ts +42 -0
  108. package/dist/server/webhooks.d.ts.map +1 -0
  109. package/dist/server/webhooks.js +77 -0
  110. package/dist/server/webhooks.js.map +1 -0
  111. package/dist/shared/action-hash.d.ts +23 -0
  112. package/dist/shared/action-hash.d.ts.map +1 -0
  113. package/dist/shared/action-hash.js +35 -0
  114. package/dist/shared/action-hash.js.map +1 -0
  115. package/dist/shared/types.d.ts +2 -0
  116. package/dist/shared/types.d.ts.map +1 -1
  117. package/dist/shared/types.js.map +1 -1
  118. package/package.json +44 -16
  119. package/dist/server/binding.d.ts +0 -19
  120. package/dist/server/binding.d.ts.map +0 -1
  121. package/dist/server/binding.js +0 -87
  122. package/dist/server/binding.js.map +0 -1
  123. package/dist/server/step-up.d.ts +0 -19
  124. package/dist/server/step-up.d.ts.map +0 -1
  125. package/dist/server/step-up.js +0 -139
  126. package/dist/server/step-up.js.map +0 -1
  127. package/dist/server/storage/index.d.ts +0 -3
  128. package/dist/server/storage/index.d.ts.map +0 -1
  129. package/dist/server/storage/index.js +0 -2
  130. package/dist/server/storage/index.js.map +0 -1
  131. package/dist/server/storage/interface.d.ts +0 -61
  132. package/dist/server/storage/interface.d.ts.map +0 -1
  133. package/dist/server/storage/interface.js +0 -2
  134. package/dist/server/storage/interface.js.map +0 -1
  135. package/dist/server/storage/memory.d.ts +0 -40
  136. package/dist/server/storage/memory.d.ts.map +0 -1
  137. package/dist/server/storage/memory.js +0 -107
  138. package/dist/server/storage/memory.js.map +0 -1
  139. package/dist/server/totp.d.ts +0 -53
  140. package/dist/server/totp.d.ts.map +0 -1
  141. package/dist/server/totp.js +0 -114
  142. 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
- Device-bound session security. Add one script tag — every request is cryptographically signed with a device key that never leaves the browser. A stolen cookie without the device key is useless.
3
+ [![npm](https://img.shields.io/npm/v/@opensyber/tokenforge.svg)](https://www.npmjs.com/package/@opensyber/tokenforge)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](./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 (client — zero code)
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
- That's it. The script auto-generates ECDSA P-256 device keys, binds the session, and signs every `fetch()` request with `X-TF-*` headers.
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 (one line)
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
- **Next.js**
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
- **Fastify**
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
- **Hono**
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 free at [tokenforge.opensyber.cloud](https://tokenforge.opensyber.cloud). Free tier: 1,000 verifications/month.
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
- ## Why
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
- Authentication protects login. TokenForge protects everything **after** login:
132
+ ### Webhook event stream
56
133
 
57
- - **Session hijacking** stolen cookies are useless without the device's private key
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
- ## How It Works
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. Script auto-generates │ │
68
- ECDSA P-256 key pair │ │
166
+ │ 1. Generate ECDSA P-256 │ │
167
+ keypair (non-extractable) │ │
69
168
  │ │ │
70
- │ 2. POST /v1/bind ─────────────>│ Store public key
71
- <──────────────────────────────│ Return deviceId
169
+ │ 2. POST /v1/dbsc/challenge ───>│
170
+ <─────── { challenge } ────────│
72
171
  │ │ │
73
- │ 3. fetch('/api/data')
74
- │ + X-TF-Signature header ────────────────────────────────>
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
- │ │ Check nonce replay
181
+ │ │ Run AitM heuristics
79
182
  │ │ Compute trust score │
80
- │ │ ── { allow, score: 92 } ──>
81
-
82
- │ <──────────────────────────────────── 200 OK ─────────────── │
183
+ │ │ ──> { allow, score: 92 }
184
+ <──────────────────────────────────── 200 OK ───────────────
83
185
  ```
84
186
 
85
- All verification runs on TokenForge's infrastructure. Your server sends request context, gets back allow/deny. No crypto libraries, no database, no storage to manage.
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
- 7 signals scored 0-100 on every request:
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 tolerance |
198
+ | Timing | 10 | Clock skew beyond ±60s |
99
199
  | Nonce | 5 | Replay attacks |
100
200
 
101
- Score >= 80: allow. Score 40-79: step-up auth. Score < 40: block.
201
+ Score >= 80: `allow`. Score 40-79: `step_up`. Score < 40: `block`. Thresholds configurable per tenant.
102
202
 
103
- ## Advanced: npm Client SDK
203
+ ## Drop-in for your existing IdP
104
204
 
105
- For React apps or when you need more control than the script tag:
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
- ## Works With Any Auth Provider
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
- Microsoft Entra ID, Clerk, Auth.js, NextAuth, Firebase, Supabase, Okta, or custom JWT. TokenForge runs after authentication — no changes to your auth flow.
257
+ Storage backends shipped: D1 (Cloudflare Workers), PostgreSQL, Redis. Bring your own via `StorageInterface`.
125
258
 
126
- ## Pricing
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
- Client SDK: MIT | Server verification requires a TokenForge API key from [tokenforge.opensyber.cloud](https://tokenforge.opensyber.cloud).
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"}
@@ -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":"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"}
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"}