satgate 1.5.1

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 (79) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +207 -0
  3. package/dist/bin/satgate.d.ts +3 -0
  4. package/dist/bin/satgate.d.ts.map +1 -0
  5. package/dist/bin/satgate.js +7 -0
  6. package/dist/bin/satgate.js.map +1 -0
  7. package/dist/src/auth/allowlist.d.ts +24 -0
  8. package/dist/src/auth/allowlist.d.ts.map +1 -0
  9. package/dist/src/auth/allowlist.js +130 -0
  10. package/dist/src/auth/allowlist.js.map +1 -0
  11. package/dist/src/auth/middleware.d.ts +15 -0
  12. package/dist/src/auth/middleware.d.ts.map +1 -0
  13. package/dist/src/auth/middleware.js +29 -0
  14. package/dist/src/auth/middleware.js.map +1 -0
  15. package/dist/src/cli.d.ts +2 -0
  16. package/dist/src/cli.d.ts.map +1 -0
  17. package/dist/src/cli.js +275 -0
  18. package/dist/src/cli.js.map +1 -0
  19. package/dist/src/config.d.ts +133 -0
  20. package/dist/src/config.d.ts.map +1 -0
  21. package/dist/src/config.js +237 -0
  22. package/dist/src/config.js.map +1 -0
  23. package/dist/src/discovery/llms-txt.d.ts +10 -0
  24. package/dist/src/discovery/llms-txt.d.ts.map +1 -0
  25. package/dist/src/discovery/llms-txt.js +25 -0
  26. package/dist/src/discovery/llms-txt.js.map +1 -0
  27. package/dist/src/discovery/openapi.d.ts +8 -0
  28. package/dist/src/discovery/openapi.d.ts.map +1 -0
  29. package/dist/src/discovery/openapi.js +116 -0
  30. package/dist/src/discovery/openapi.js.map +1 -0
  31. package/dist/src/discovery/well-known.d.ts +22 -0
  32. package/dist/src/discovery/well-known.d.ts.map +1 -0
  33. package/dist/src/discovery/well-known.js +44 -0
  34. package/dist/src/discovery/well-known.js.map +1 -0
  35. package/dist/src/index.d.ts +15 -0
  36. package/dist/src/index.d.ts.map +1 -0
  37. package/dist/src/index.js +15 -0
  38. package/dist/src/index.js.map +1 -0
  39. package/dist/src/lightning.d.ts +12 -0
  40. package/dist/src/lightning.d.ts.map +1 -0
  41. package/dist/src/lightning.js +38 -0
  42. package/dist/src/lightning.js.map +1 -0
  43. package/dist/src/logger.d.ts +16 -0
  44. package/dist/src/logger.d.ts.map +1 -0
  45. package/dist/src/logger.js +99 -0
  46. package/dist/src/logger.js.map +1 -0
  47. package/dist/src/proxy/capacity.d.ts +15 -0
  48. package/dist/src/proxy/capacity.d.ts.map +1 -0
  49. package/dist/src/proxy/capacity.js +28 -0
  50. package/dist/src/proxy/capacity.js.map +1 -0
  51. package/dist/src/proxy/handler.d.ts +27 -0
  52. package/dist/src/proxy/handler.d.ts.map +1 -0
  53. package/dist/src/proxy/handler.js +165 -0
  54. package/dist/src/proxy/handler.js.map +1 -0
  55. package/dist/src/proxy/pricing.d.ts +17 -0
  56. package/dist/src/proxy/pricing.d.ts.map +1 -0
  57. package/dist/src/proxy/pricing.js +42 -0
  58. package/dist/src/proxy/pricing.js.map +1 -0
  59. package/dist/src/proxy/streaming.d.ts +12 -0
  60. package/dist/src/proxy/streaming.d.ts.map +1 -0
  61. package/dist/src/proxy/streaming.js +68 -0
  62. package/dist/src/proxy/streaming.js.map +1 -0
  63. package/dist/src/proxy/token-counter.d.ts +22 -0
  64. package/dist/src/proxy/token-counter.d.ts.map +1 -0
  65. package/dist/src/proxy/token-counter.js +66 -0
  66. package/dist/src/proxy/token-counter.js.map +1 -0
  67. package/dist/src/server.d.ts +9 -0
  68. package/dist/src/server.d.ts.map +1 -0
  69. package/dist/src/server.js +239 -0
  70. package/dist/src/server.js.map +1 -0
  71. package/dist/src/tunnel.d.ts +26 -0
  72. package/dist/src/tunnel.d.ts.map +1 -0
  73. package/dist/src/tunnel.js +78 -0
  74. package/dist/src/tunnel.js.map +1 -0
  75. package/dist/src/x402/facilitator.d.ts +7 -0
  76. package/dist/src/x402/facilitator.d.ts.map +1 -0
  77. package/dist/src/x402/facilitator.js +33 -0
  78. package/dist/src/x402/facilitator.js.map +1 -0
  79. package/package.json +70 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 TheCryptoDonkey
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.
package/README.md ADDED
@@ -0,0 +1,207 @@
1
+ # satgate
2
+
3
+ [![MIT licence](https://img.shields.io/badge/licence-MIT-blue.svg)](./LICENSE)
4
+ [![Nostr](https://img.shields.io/badge/Nostr-Zap%20me-purple)](https://primal.net/p/npub1mgvlrnf5hm9yf0n5mf9nqmvarhvxkc6remu5ec3vf8r0txqkuk7su0e7q2)
5
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.8-blue)](https://www.typescriptlang.org/)
6
+ [![Node](https://img.shields.io/badge/Node-%3E%3D22-green)](https://nodejs.org/)
7
+
8
+ **Your GPU is burning money. Make it earn money.**
9
+
10
+ satgate sits in front of Ollama, vLLM, llama.cpp — any OpenAI-compatible backend — and turns it into a pay-per-token API. No accounts. No API keys. No Stripe. Clients pay per token, you earn sats before the response finishes streaming.
11
+
12
+ ![satgate demo](demo/satgate-demo.gif)
13
+
14
+ ## Quick start
15
+
16
+ ```bash
17
+ npx satgate --upstream http://localhost:11434
18
+ ```
19
+
20
+ That's it. satgate auto-detects your models, starts accepting payments, and proxies inference requests. Clients pay per token, you earn sats.
21
+
22
+ ---
23
+
24
+ ## Try it live
25
+
26
+ A public instance is running at [satgate.trotters.dev](https://satgate.trotters.dev). Open it in a browser for the chat playground, or use curl:
27
+
28
+ ```bash
29
+ # 250 sats of free usage per day per IP — after that you'll get a 402 + invoice
30
+ curl -s -w '\n%{http_code}\n' https://satgate.trotters.dev/v1/chat/completions \
31
+ -H "Content-Type: application/json" \
32
+ -d '{"model":"qwen3:0.6b","messages":[{"role":"user","content":"What is Bitcoin?"}]}'
33
+
34
+ # Check pricing
35
+ curl -s https://satgate.trotters.dev/.well-known/l402 | jq .
36
+
37
+ # Machine-readable description
38
+ curl -s https://satgate.trotters.dev/llms.txt
39
+ ```
40
+
41
+ ---
42
+
43
+ ## The old way vs satgate
44
+
45
+ | | The old way | With satgate |
46
+ |---|---|---|
47
+ | **Sell GPU time** | Sign up for a marketplace (OpenRouter, Together). They set the price, take a cut, own the customer. | `npx satgate --upstream http://localhost:11434`. You set the price. You keep 100%. |
48
+ | **Handle billing** | Stripe account, KYC, usage tracking, invoices, chargebacks | Payments settle before the response finishes streaming. No accounts, no disputes. |
49
+ | **Serve AI agents** | OAuth flows, API key management, billing portals — none of which machines can use | Agents discover your endpoint, pay per token from their own wallet, no human in the loop. |
50
+ | **Price fairly** | Flat rate per request, regardless of whether it's 10 tokens or 10,000 | Actual tokens counted from the response. Overpayments credited back. |
51
+
52
+ ---
53
+
54
+ ## Built for machines
55
+
56
+ satgate doesn't just serve humans with `curl`. It's designed for AI agents that pay for their own resources.
57
+
58
+ Every satgate instance exposes three discovery endpoints — no auth required:
59
+
60
+ | Endpoint | Who reads it |
61
+ |---|---|
62
+ | `/.well-known/l402` | Machines — pricing, models, payment methods as structured JSON |
63
+ | `/llms.txt` | AI agents — plain-text description of what you're selling |
64
+ | `/openapi.json` | Code generators — full OpenAPI spec |
65
+
66
+ Pair with [l402-mcp](https://github.com/TheCryptoDonkey/l402-mcp) and an AI agent can autonomously discover your endpoint, check your prices, pay from its own wallet, and start prompting — no human involved.
67
+
68
+ ```mermaid
69
+ sequenceDiagram
70
+ participant A as AI Agent
71
+ participant M as l402-mcp
72
+ participant T as satgate
73
+ participant G as Your GPU
74
+
75
+ A->>M: "Use this inference endpoint"
76
+ M->>T: GET /.well-known/l402
77
+ T-->>M: Pricing, models, payment methods
78
+ M->>T: POST /v1/chat/completions
79
+ T-->>M: 402 + Lightning invoice
80
+ M->>M: Pay invoice from wallet
81
+ M->>T: Retry with L402 credential
82
+ T->>G: Proxy request
83
+ G-->>T: Stream response
84
+ T-->>M: Stream completion
85
+ M-->>A: Response
86
+ ```
87
+
88
+ ---
89
+
90
+ ## The secret
91
+
92
+ Everything you just saw — the payment gating, the multi-rail support, the credit system, the free tier, the macaroon credentials — that's not satgate. That's [toll-booth](https://github.com/TheCryptoDonkey/toll-booth).
93
+
94
+ satgate is ~400 lines of glue on top of toll-booth. It adds the AI-specific bits: token counting, model pricing, streaming reconciliation, capacity management. Everything else comes from the middleware.
95
+
96
+ **You could build your own satgate for your domain in an afternoon.**
97
+
98
+ Monetise a routing API. Gate a translation service. Sell weather data per request. toll-booth handles the payments — you just write the product logic.
99
+
100
+ → [**See toll-booth**](https://github.com/TheCryptoDonkey/toll-booth)
101
+
102
+ ```mermaid
103
+ graph TB
104
+ subgraph "satgate (~400 lines)"
105
+ TC[Token counting]
106
+ MP[Model pricing]
107
+ SR[Streaming reconciliation]
108
+ CM[Capacity management]
109
+ AD[Agent discovery]
110
+ end
111
+ subgraph "toll-booth"
112
+ L402[L402 protocol]
113
+ CR[Credit system]
114
+ FT[Free tier]
115
+ PR[Payment rails]
116
+ MA[Macaroon auth]
117
+ end
118
+ TC --> L402
119
+ MP --> CR
120
+ SR --> CR
121
+ CM --> L402
122
+ AD --> L402
123
+ ```
124
+
125
+ ---
126
+
127
+ ## What satgate adds
128
+
129
+ - **Pay-per-token** — actual token count from the response, not estimated. Streaming and buffered.
130
+ - **Model-specific pricing** — 1 sat/1k for Llama, 5 sats/1k for DeepSeek. You set the rates.
131
+ - **Streaming reconciliation** — estimated charge upfront, reconciled to actual usage after. Overpayments credited back.
132
+ - **Capacity management** — limit concurrent inference requests to protect your GPU.
133
+ - **Auto-detect models** — queries your upstream on startup. No manual model list.
134
+ - **Four payment rails** — Lightning, Cashu ecash, NWC, and x402 stablecoins. Operator picks what to accept.
135
+ - **Privacy by design** — no personal data collected or stored. No accounts, no cookies, no IP logging. GDPR-safe out of the box.
136
+ - **Instant public URL** — auto-spawns a Cloudflare tunnel. Your GPU is reachable from the internet in seconds.
137
+
138
+ ---
139
+
140
+ ## How it works
141
+
142
+ ```mermaid
143
+ sequenceDiagram
144
+ participant C as Client
145
+ participant T as satgate
146
+ participant G as Your GPU
147
+
148
+ C->>T: POST /v1/chat/completions
149
+ T-->>C: 402 + Lightning invoice (estimated cost)
150
+ C->>C: Pay invoice
151
+ C->>T: Retry with L402 credential
152
+ T->>G: Proxy request
153
+ G-->>T: Stream response
154
+ T->>T: Count actual tokens
155
+ T-->>C: Stream completion
156
+ T->>T: Reconcile: credit back overpayment
157
+ ```
158
+
159
+ Charges are estimated upfront based on model pricing, then reconciled to actual token usage after the response completes. Operators are never short-changed — costs round up. Overpayments are credited to the client's balance for the next request.
160
+
161
+ ---
162
+
163
+ ## Configuration
164
+
165
+ Zero config works (just `--upstream`). For production, create `satgate.yaml`:
166
+
167
+ ```yaml
168
+ upstream: http://localhost:11434
169
+ port: 3000
170
+ pricing:
171
+ default: 1 # 1 sat per 1k tokens
172
+ models:
173
+ llama3: 1
174
+ deepseek-r1: 5
175
+ freeTier:
176
+ creditsPerDay: 250
177
+ capacity:
178
+ maxConcurrent: 4
179
+ ```
180
+
181
+ CLI flags > environment variables > config file > defaults.
182
+
183
+ ---
184
+
185
+ ## Get started
186
+
187
+ ```bash
188
+ # Monetise your local Ollama
189
+ npx satgate --upstream http://localhost:11434
190
+
191
+ # Or point at any OpenAI-compatible backend
192
+ npx satgate --upstream http://your-vllm-server:8000
193
+ ```
194
+
195
+ → [**toll-booth**](https://github.com/TheCryptoDonkey/toll-booth) — the middleware that powers all of this. Build your own.
196
+ → [**l402-mcp**](https://github.com/TheCryptoDonkey/l402-mcp) — give AI agents a wallet. Let them pay for your GPU.
197
+
198
+ ---
199
+
200
+ Built by [@TheCryptoDonkey](https://github.com/TheCryptoDonkey).
201
+
202
+ - Lightning tips: `thedonkey@strike.me`
203
+ - Nostr: `npub1mgvlrnf5hm9yf0n5mf9nqmvarhvxkc6remu5ec3vf8r0txqkuk7su0e7q2`
204
+
205
+ ---
206
+
207
+ [MIT](./LICENSE)
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=satgate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"satgate.d.ts","sourceRoot":"","sources":["../../bin/satgate.ts"],"names":[],"mappings":""}
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env node
2
+ import { main } from '../src/cli.js';
3
+ main().catch((err) => {
4
+ console.error('[satgate] Fatal:', err.message);
5
+ process.exit(1);
6
+ });
7
+ //# sourceMappingURL=satgate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"satgate.js","sourceRoot":"","sources":["../../bin/satgate.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,IAAI,EAAE,MAAM,eAAe,CAAA;AACpC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,OAAO,CAAC,KAAK,CAAC,kBAAkB,EAAE,GAAG,CAAC,OAAO,CAAC,CAAA;IAC9C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;AACjB,CAAC,CAAC,CAAA"}
@@ -0,0 +1,24 @@
1
+ export interface AllowlistResult {
2
+ allowed: boolean;
3
+ identity?: string;
4
+ }
5
+ export interface RequestContext {
6
+ url: string;
7
+ method: string;
8
+ }
9
+ /**
10
+ * Checks whether the Authorization header matches an entry in the allowlist.
11
+ *
12
+ * Identity types:
13
+ * - Bearer <secret> — matched against non-pubkey entries (strings that are
14
+ * NOT 64-char hex and NOT npub1-prefixed)
15
+ * - Nostr <base64-event> — NIP-98 verification against pubkey entries
16
+ *
17
+ * Security: hex pubkeys and npub entries are NEVER treated as Bearer secrets.
18
+ * Only entries that don't look like pubkeys are valid Bearer secrets.
19
+ */
20
+ /**
21
+ * @param now - Optional Unix timestamp (seconds) for testing. Defaults to current time.
22
+ */
23
+ export declare function checkAllowlist(authHeader: string | undefined, allowlist: string[], request: RequestContext, now?: number): AllowlistResult;
24
+ //# sourceMappingURL=allowlist.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"allowlist.d.ts","sourceRoot":"","sources":["../../../src/auth/allowlist.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,OAAO,CAAA;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,CAAA;IACX,MAAM,EAAE,MAAM,CAAA;CACf;AA2DD;;;;;;;;;;GAUG;AACH;;GAEG;AACH,wBAAgB,cAAc,CAC5B,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,SAAS,EAAE,MAAM,EAAE,EACnB,OAAO,EAAE,cAAc,EACvB,GAAG,CAAC,EAAE,MAAM,GACX,eAAe,CA4BjB"}
@@ -0,0 +1,130 @@
1
+ import { schnorr } from '@noble/curves/secp256k1.js';
2
+ import { hexToBytes } from '@noble/curves/utils.js';
3
+ import { bech32 } from '@scure/base';
4
+ import { createHash, timingSafeEqual } from 'node:crypto';
5
+ const HEX_PUBKEY_RE = /^[0-9a-f]{64}$/;
6
+ /**
7
+ * Decode an npub (bech32-encoded) to a hex pubkey.
8
+ */
9
+ function npubToHex(npub) {
10
+ try {
11
+ const { prefix, words } = bech32.decode(npub);
12
+ if (prefix !== 'npub')
13
+ return null;
14
+ const bytes = bech32.fromWords(words);
15
+ return Buffer.from(bytes).toString('hex');
16
+ }
17
+ catch {
18
+ return null;
19
+ }
20
+ }
21
+ /**
22
+ * Normalise allowlist entries to hex pubkeys.
23
+ * Returns only the pubkey entries (npub or hex), not shared secrets.
24
+ */
25
+ function extractPubkeys(allowlist) {
26
+ const pubkeys = [];
27
+ for (const entry of allowlist) {
28
+ if (entry.startsWith('npub1')) {
29
+ const hex = npubToHex(entry);
30
+ if (hex)
31
+ pubkeys.push(hex);
32
+ }
33
+ else if (HEX_PUBKEY_RE.test(entry)) {
34
+ pubkeys.push(entry);
35
+ }
36
+ }
37
+ return pubkeys;
38
+ }
39
+ /**
40
+ * Constant-time string comparison to prevent timing attacks on Bearer tokens.
41
+ */
42
+ function constantTimeEqual(a, b) {
43
+ const bufA = Buffer.from(a);
44
+ const bufB = Buffer.from(b);
45
+ if (bufA.length !== bufB.length) {
46
+ // Compare against self to keep constant time even on length mismatch
47
+ timingSafeEqual(bufA, bufA);
48
+ return false;
49
+ }
50
+ return timingSafeEqual(bufA, bufB);
51
+ }
52
+ /**
53
+ * Checks whether the Authorization header matches an entry in the allowlist.
54
+ *
55
+ * Identity types:
56
+ * - Bearer <secret> — matched against non-pubkey entries (strings that are
57
+ * NOT 64-char hex and NOT npub1-prefixed)
58
+ * - Nostr <base64-event> — NIP-98 verification against pubkey entries
59
+ *
60
+ * Security: hex pubkeys and npub entries are NEVER treated as Bearer secrets.
61
+ * Only entries that don't look like pubkeys are valid Bearer secrets.
62
+ */
63
+ /**
64
+ * @param now - Optional Unix timestamp (seconds) for testing. Defaults to current time.
65
+ */
66
+ export function checkAllowlist(authHeader, allowlist, request, now) {
67
+ if (!authHeader || allowlist.length === 0) {
68
+ return { allowed: false };
69
+ }
70
+ const spaceIdx = authHeader.indexOf(' ');
71
+ if (spaceIdx === -1)
72
+ return { allowed: false };
73
+ const scheme = authHeader.slice(0, spaceIdx);
74
+ const credential = authHeader.slice(spaceIdx + 1);
75
+ if (scheme === 'Bearer') {
76
+ // Only match entries that are NOT pubkeys (hex or npub)
77
+ const secrets = allowlist.filter(entry => !entry.startsWith('npub1') && !HEX_PUBKEY_RE.test(entry));
78
+ const match = secrets.find(s => constantTimeEqual(s, credential));
79
+ if (match) {
80
+ return { allowed: true, identity: credential.slice(0, 8) + '...' };
81
+ }
82
+ return { allowed: false };
83
+ }
84
+ if (scheme === 'Nostr') {
85
+ return verifyNip98(credential, allowlist, request, now);
86
+ }
87
+ return { allowed: false };
88
+ }
89
+ /**
90
+ * Verify a NIP-98 HTTP Auth event against the allowlist of pubkeys.
91
+ */
92
+ function verifyNip98(base64Event, allowlist, request, nowOverride) {
93
+ try {
94
+ const json = Buffer.from(base64Event, 'base64').toString('utf-8');
95
+ const event = JSON.parse(json);
96
+ // Must be kind 27235 (NIP-98 HTTP Auth)
97
+ if (event.kind !== 27235)
98
+ return { allowed: false };
99
+ // Check created_at is within 60 seconds (injectable for testing)
100
+ const now = nowOverride ?? Math.floor(Date.now() / 1000);
101
+ if (Math.abs(now - event.created_at) > 60)
102
+ return { allowed: false };
103
+ // Validate URL and method tags match the actual request
104
+ const urlTag = event.tags.find(t => t[0] === 'u')?.[1];
105
+ const methodTag = event.tags.find(t => t[0] === 'method')?.[1];
106
+ if (urlTag !== request.url)
107
+ return { allowed: false };
108
+ if (methodTag?.toUpperCase() !== request.method.toUpperCase())
109
+ return { allowed: false };
110
+ // Verify event ID
111
+ const serialised = JSON.stringify([0, event.pubkey, event.created_at, event.kind, event.tags, event.content]);
112
+ const expectedId = createHash('sha256').update(serialised).digest('hex');
113
+ if (event.id !== expectedId)
114
+ return { allowed: false };
115
+ // Verify schnorr signature
116
+ const valid = schnorr.verify(hexToBytes(event.sig), hexToBytes(event.id), hexToBytes(event.pubkey));
117
+ if (!valid)
118
+ return { allowed: false };
119
+ // Check pubkey against allowlist (normalise npub → hex)
120
+ const allowedPubkeys = extractPubkeys(allowlist);
121
+ if (allowedPubkeys.includes(event.pubkey)) {
122
+ return { allowed: true, identity: event.pubkey.slice(0, 8) + '...' };
123
+ }
124
+ return { allowed: false };
125
+ }
126
+ catch {
127
+ return { allowed: false };
128
+ }
129
+ }
130
+ //# sourceMappingURL=allowlist.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"allowlist.js","sourceRoot":"","sources":["../../../src/auth/allowlist.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ import type { MiddlewareHandler } from 'hono';
2
+ export interface AuthMiddlewareConfig {
3
+ authMode: 'open' | 'lightning' | 'allowlist';
4
+ allowlist: string[];
5
+ }
6
+ /**
7
+ * Creates Hono middleware that handles auth based on the configured mode.
8
+ *
9
+ * - open: pass through (no checks)
10
+ * - allowlist: check Authorization header against allowlist
11
+ * - lightning: pass through — toll-booth's authMiddleware is mounted separately
12
+ * in server.ts for the lightning path
13
+ */
14
+ export declare function createAuthMiddleware(config: AuthMiddlewareConfig): MiddlewareHandler;
15
+ //# sourceMappingURL=middleware.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../../../src/auth/middleware.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAA;AAG7C,MAAM,WAAW,oBAAoB;IACnC,QAAQ,EAAE,MAAM,GAAG,WAAW,GAAG,WAAW,CAAA;IAC5C,SAAS,EAAE,MAAM,EAAE,CAAA;CACpB;AAED;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,oBAAoB,GAAG,iBAAiB,CAmBpF"}
@@ -0,0 +1,29 @@
1
+ import { checkAllowlist } from './allowlist.js';
2
+ /**
3
+ * Creates Hono middleware that handles auth based on the configured mode.
4
+ *
5
+ * - open: pass through (no checks)
6
+ * - allowlist: check Authorization header against allowlist
7
+ * - lightning: pass through — toll-booth's authMiddleware is mounted separately
8
+ * in server.ts for the lightning path
9
+ */
10
+ export function createAuthMiddleware(config) {
11
+ if (config.authMode === 'allowlist') {
12
+ return async (c, next) => {
13
+ const authHeader = c.req.header('Authorization');
14
+ const requestUrl = c.req.url;
15
+ const requestMethod = c.req.method;
16
+ const result = checkAllowlist(authHeader, config.allowlist, {
17
+ url: requestUrl,
18
+ method: requestMethod,
19
+ });
20
+ if (!result.allowed) {
21
+ return c.json({ error: 'Forbidden' }, 403);
22
+ }
23
+ await next();
24
+ };
25
+ }
26
+ // open mode and lightning mode: pass through
27
+ return async (_c, next) => { await next(); };
28
+ }
29
+ //# sourceMappingURL=middleware.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"middleware.js","sourceRoot":"","sources":["../../../src/auth/middleware.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAO/C;;;;;;;GAOG;AACH,MAAM,UAAU,oBAAoB,CAAC,MAA4B;IAC/D,IAAI,MAAM,CAAC,QAAQ,KAAK,WAAW,EAAE,CAAC;QACpC,OAAO,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE;YACvB,MAAM,UAAU,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,eAAe,CAAC,CAAA;YAChD,MAAM,UAAU,GAAG,CAAC,CAAC,GAAG,CAAC,GAAG,CAAA;YAC5B,MAAM,aAAa,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,CAAA;YAClC,MAAM,MAAM,GAAG,cAAc,CAAC,UAAU,EAAE,MAAM,CAAC,SAAS,EAAE;gBAC1D,GAAG,EAAE,UAAU;gBACf,MAAM,EAAE,aAAa;aACtB,CAAC,CAAA;YACF,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;gBACpB,OAAO,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,WAAW,EAAE,EAAE,GAAG,CAAC,CAAA;YAC5C,CAAC;YACD,MAAM,IAAI,EAAE,CAAA;QACd,CAAC,CAAA;IACH,CAAC;IAED,6CAA6C;IAC7C,OAAO,KAAK,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,GAAG,MAAM,IAAI,EAAE,CAAA,CAAC,CAAC,CAAA;AAC7C,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare function main(argv?: string[]): Promise<void>;
2
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../src/cli.ts"],"names":[],"mappings":"AAqHA,wBAAsB,IAAI,CAAC,IAAI,GAAE,MAAM,EAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAqHvE"}