@orangecheck/gate
Advanced tools
| import { G as GateOptions, M as MinimalReq, b as MinimalRes } from './types-BWQAPjI8.mjs'; | ||
| import '@orangecheck/sdk'; | ||
| declare function ocGate(opts: GateOptions): (req: MinimalReq, res: MinimalRes, next: (err?: unknown) => void) => Promise<void>; | ||
| export { ocGate }; |
| import { G as GateOptions, M as MinimalReq, b as MinimalRes } from './types-BWQAPjI8.js'; | ||
| import '@orangecheck/sdk'; | ||
| declare function ocGate(opts: GateOptions): (req: MinimalReq, res: MinimalRes, next: (err?: unknown) => void) => Promise<void>; | ||
| export { ocGate }; |
+247
| 'use strict'; | ||
| var sdk = require('@orangecheck/sdk'); | ||
| // src/core.ts | ||
| // src/cache.ts | ||
| var TtlLru = class { | ||
| constructor(max, ttlMs) { | ||
| this.max = max; | ||
| this.ttlMs = ttlMs; | ||
| this.store = /* @__PURE__ */ new Map(); | ||
| } | ||
| get(key) { | ||
| const entry = this.store.get(key); | ||
| if (!entry) return void 0; | ||
| if (Date.now() > entry.expires) { | ||
| this.store.delete(key); | ||
| return void 0; | ||
| } | ||
| return entry.value; | ||
| } | ||
| set(key, value) { | ||
| if (this.store.size >= this.max) { | ||
| const first = this.store.keys().next().value; | ||
| if (first !== void 0) this.store.delete(first); | ||
| } | ||
| this.store.set(key, { value, expires: Date.now() + this.ttlMs }); | ||
| } | ||
| clear() { | ||
| this.store.clear(); | ||
| } | ||
| }; | ||
| // src/core.ts | ||
| var caches = /* @__PURE__ */ new WeakMap(); | ||
| var MAX_CACHE_TTL_MS = 10 * 6e4; | ||
| var DEFAULT_CACHE_TTL_MS = 6e4; | ||
| var DEFAULT_LOOKUP_TIMEOUT_MS = 5e3; | ||
| var MAX_SUBJECT_LEN = 128; | ||
| function cacheFor(opts) { | ||
| let c = caches.get(opts); | ||
| if (!c) { | ||
| const ttl = Math.min(opts.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS, MAX_CACHE_TTL_MS); | ||
| c = new TtlLru(opts.cacheMax ?? 1e3, Math.max(0, ttl)); | ||
| caches.set(opts, c); | ||
| } | ||
| return c; | ||
| } | ||
| function normalizeSubject(kind, raw) { | ||
| const trimmed = raw.trim(); | ||
| if (kind === "attestation_id") return trimmed.toLowerCase(); | ||
| if (kind === "address") { | ||
| const low = trimmed.toLowerCase(); | ||
| if (low.startsWith("bc1") || low.startsWith("tb1") || low.startsWith("bcrt1")) return low; | ||
| return trimmed; | ||
| } | ||
| const colon = trimmed.indexOf(":"); | ||
| if (colon === -1) return trimmed; | ||
| return trimmed.slice(0, colon).toLowerCase() + ":" + trimmed.slice(colon + 1); | ||
| } | ||
| var warnedUntrusted = /* @__PURE__ */ new WeakSet(); | ||
| function warnUntrustedOnce(opts, src) { | ||
| if (opts.trustUnsafeSources || warnedUntrusted.has(opts)) return; | ||
| if (typeof src.from === "function") return; | ||
| if (src.from === "header" || src.from === "query" || src.from === "cookie" || src.from === "body") { | ||
| warnedUntrusted.add(opts); | ||
| console.warn( | ||
| `[orangecheck/gate] subject source "${src.from}" is caller-supplied \u2014 any client can set it. For real auth, resolve the address from a signed session (e.g., { from: (req) => req.session.verifiedAddress }) or pass { trustUnsafeSources: true } to silence this warning.` | ||
| ); | ||
| } | ||
| } | ||
| function readHeader(req, name) { | ||
| const raw = req.headers?.[name.toLowerCase()]; | ||
| if (Array.isArray(raw)) return raw[0]; | ||
| return raw; | ||
| } | ||
| function readCookie(req, name) { | ||
| if (req.cookies && name in req.cookies) return req.cookies[name]; | ||
| const raw = readHeader(req, "cookie"); | ||
| if (!raw) return void 0; | ||
| for (const part of raw.split(";")) { | ||
| const eq = part.indexOf("="); | ||
| if (eq === -1) continue; | ||
| const k = part.slice(0, eq).trim(); | ||
| if (k === name) return decodeURIComponent(part.slice(eq + 1).trim()); | ||
| } | ||
| return void 0; | ||
| } | ||
| function readQuery(req, name) { | ||
| const raw = req.query?.[name]; | ||
| if (Array.isArray(raw)) return raw[0]; | ||
| return raw; | ||
| } | ||
| function readBodyPath(req, path) { | ||
| if (!req.body || typeof req.body !== "object") return void 0; | ||
| const parts = path.split("."); | ||
| let cur = req.body; | ||
| for (const p of parts) { | ||
| if (cur == null) return void 0; | ||
| cur = cur[p]; | ||
| } | ||
| return typeof cur === "string" ? cur : void 0; | ||
| } | ||
| function resolve(src, req) { | ||
| if (!src) return void 0; | ||
| if (typeof src.from === "function") return src.from(req); | ||
| switch (src.from) { | ||
| case "header": | ||
| return readHeader(req, src.name ?? "x-oc-address"); | ||
| case "cookie": | ||
| return readCookie(req, src.name ?? "oc_addr"); | ||
| case "query": | ||
| return readQuery(req, src.name ?? "ocAddr"); | ||
| case "body": | ||
| return readBodyPath(req, src.path ?? "address"); | ||
| default: | ||
| return void 0; | ||
| } | ||
| } | ||
| async function assertOc(req, opts) { | ||
| const sources = [ | ||
| ["attestation_id", opts.attestationId], | ||
| ["address", opts.address], | ||
| ["identity", opts.identity] | ||
| ]; | ||
| let subject; | ||
| let subjectKind; | ||
| for (const [kind, src] of sources) { | ||
| if (!src) continue; | ||
| warnUntrustedOnce(opts, src); | ||
| const v = resolve(src, req); | ||
| if (v) { | ||
| subject = v; | ||
| subjectKind = kind; | ||
| break; | ||
| } | ||
| } | ||
| if (!subject || !subjectKind) { | ||
| return { ok: false, reason: "no_subject" }; | ||
| } | ||
| if (subject.length > MAX_SUBJECT_LEN) { | ||
| return { ok: false, reason: "no_subject" }; | ||
| } | ||
| subject = normalizeSubject(subjectKind, subject); | ||
| const cache = cacheFor(opts); | ||
| const cacheKey = `${subjectKind}:${subject}:${opts.minSats ?? 0}:${opts.minDays ?? 0}`; | ||
| const cached = cache.get(cacheKey); | ||
| if (cached) return { ...cached, subject, subjectKind }; | ||
| try { | ||
| const params = { | ||
| minSats: opts.minSats, | ||
| minDays: opts.minDays, | ||
| ...opts.relays ? { relays: opts.relays } : {} | ||
| }; | ||
| if (subjectKind === "attestation_id") params.id = subject; | ||
| else if (subjectKind === "address") params.addr = subject; | ||
| else if (subjectKind === "identity") { | ||
| const idx = subject.indexOf(":"); | ||
| if (idx === -1) { | ||
| const d = { | ||
| ok: false, | ||
| reason: "no_subject", | ||
| subject, | ||
| subjectKind | ||
| }; | ||
| cache.set(cacheKey, d); | ||
| return d; | ||
| } | ||
| params.identity = { | ||
| protocol: subject.slice(0, idx), | ||
| identifier: subject.slice(idx + 1) | ||
| }; | ||
| } | ||
| const timeoutMs = opts.lookupTimeoutMs ?? DEFAULT_LOOKUP_TIMEOUT_MS; | ||
| const result = await Promise.race([ | ||
| sdk.check(params), | ||
| new Promise( | ||
| (_, reject) => setTimeout(() => reject(new Error("lookup_timeout")), timeoutMs) | ||
| ) | ||
| ]); | ||
| let reason; | ||
| if (result.ok) reason = "ok"; | ||
| else if (result.reasons?.includes("not_found")) reason = "not_found"; | ||
| else if (result.reasons?.some((r) => r === "below_min_sats" || r === "below_min_days")) | ||
| reason = "below_threshold"; | ||
| else reason = "invalid_proof"; | ||
| const decision = { | ||
| ok: result.ok, | ||
| reason, | ||
| check: result, | ||
| subject, | ||
| subjectKind | ||
| }; | ||
| cache.set(cacheKey, decision); | ||
| return decision; | ||
| } catch (err) { | ||
| const decision = { | ||
| ok: Boolean(opts.failOpen), | ||
| reason: opts.failOpen ? "fail_open" : "lookup_error", | ||
| subject, | ||
| subjectKind | ||
| }; | ||
| if (opts.onDecision) opts.onDecision(req, decision); | ||
| console.warn( | ||
| "[orangecheck/gate] lookup failed:", | ||
| err instanceof Error ? err.message : String(err) | ||
| ); | ||
| return decision; | ||
| } | ||
| } | ||
| function sendBlockedDefault(res, decision, opts) { | ||
| res.setHeader("Content-Type", "application/json"); | ||
| res.status(decision.reason === "no_subject" ? 401 : 403); | ||
| const body = { | ||
| error: "orangecheck_gate_blocked", | ||
| reason: decision.reason | ||
| }; | ||
| if (opts.exposeSubject && decision.subject) { | ||
| body.subject = decision.subject; | ||
| body.subjectKind = decision.subjectKind; | ||
| } | ||
| if (decision.check?.reasons) body.reasons = decision.check.reasons; | ||
| res.json(body); | ||
| } | ||
| // src/express.ts | ||
| function ocGate(opts) { | ||
| return async function gateMiddleware(req, res, next) { | ||
| const decision = await assertOc(req, opts); | ||
| if (opts.onDecision) opts.onDecision(req, decision); | ||
| if (decision.ok) { | ||
| next(); | ||
| return; | ||
| } | ||
| if (opts.onBlocked) { | ||
| opts.onBlocked(req, res, decision); | ||
| return; | ||
| } | ||
| res.setHeader("Cache-Control", "no-store"); | ||
| sendBlockedDefault(res, decision, opts); | ||
| }; | ||
| } | ||
| exports.ocGate = ocGate; | ||
| //# sourceMappingURL=express.js.map | ||
| //# sourceMappingURL=express.js.map |
| {"version":3,"sources":["../src/cache.ts","../src/core.ts","../src/express.ts"],"names":["check"],"mappings":"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","file":"express.js","sourcesContent":["import type { GateDecision } from './types';\n\ninterface Entry {\n value: GateDecision;\n expires: number;\n}\n\n/**\n * Tiny LRU + TTL cache. No deps. Good enough for per-process gate state.\n *\n * Eviction: oldest-inserted key first once max is reached. Ties to real LRU\n * (touching on read) would be a nice-to-have; in practice gate callers hit\n * the same subject repeatedly inside the TTL window so insertion-order is\n * equivalent.\n */\nexport class TtlLru {\n private readonly store = new Map<string, Entry>();\n\n constructor(\n private readonly max: number,\n private readonly ttlMs: number\n ) {}\n\n get(key: string): GateDecision | undefined {\n const entry = this.store.get(key);\n if (!entry) return undefined;\n if (Date.now() > entry.expires) {\n this.store.delete(key);\n return undefined;\n }\n return entry.value;\n }\n\n set(key: string, value: GateDecision): void {\n if (this.store.size >= this.max) {\n const first = this.store.keys().next().value;\n if (first !== undefined) this.store.delete(first);\n }\n this.store.set(key, { value, expires: Date.now() + this.ttlMs });\n }\n\n clear(): void {\n this.store.clear();\n }\n}\n","import type { GateDecision, GateOptions, MinimalReq, MinimalRes, SubjectSource } from './types';\n\nimport { check } from '@orangecheck/sdk';\n\nimport { TtlLru } from './cache';\n\nconst caches = new WeakMap<GateOptions, TtlLru>();\n\n/** Hard ceiling on per-entry TTL. Callers can't ask for a permanent grant\n * by passing a huge number — the cache isn't a session store. */\nconst MAX_CACHE_TTL_MS = 10 * 60_000; // 10 minutes\nconst DEFAULT_CACHE_TTL_MS = 60_000;\nconst DEFAULT_LOOKUP_TIMEOUT_MS = 5_000;\n\n/** Subject strings > this length are almost certainly garbage — reject at\n * the gate instead of building a 10 KB cache key. */\nconst MAX_SUBJECT_LEN = 128;\n\nfunction cacheFor(opts: GateOptions): TtlLru {\n let c = caches.get(opts);\n if (!c) {\n const ttl = Math.min(opts.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS, MAX_CACHE_TTL_MS);\n c = new TtlLru(opts.cacheMax ?? 1_000, Math.max(0, ttl));\n caches.set(opts, c);\n }\n return c;\n}\n\n/**\n * Normalize a subject so that `bc1Q…` / `bc1q…` / ` bc1q… ` can't cache-key\n * to three separate rows against the same on-chain address. For bech32\n * (segwit) and attestation IDs, lowercase is correct; for legacy base58\n * addresses the case is significant, so only lowercase bc1/tb1.\n */\nfunction normalizeSubject(kind: GateDecision['subjectKind'], raw: string): string {\n const trimmed = raw.trim();\n if (kind === 'attestation_id') return trimmed.toLowerCase();\n if (kind === 'address') {\n const low = trimmed.toLowerCase();\n if (low.startsWith('bc1') || low.startsWith('tb1') || low.startsWith('bcrt1')) return low;\n return trimmed; // base58 — keep case\n }\n // identity: `protocol:identifier` — lowercase protocol only\n const colon = trimmed.indexOf(':');\n if (colon === -1) return trimmed;\n return trimmed.slice(0, colon).toLowerCase() + ':' + trimmed.slice(colon + 1);\n}\n\n/** Track untrusted-source warnings we've already logged so we don't spam\n * the console on every request. */\nconst warnedUntrusted = new WeakSet<GateOptions>();\n\nfunction warnUntrustedOnce(opts: GateOptions, src: SubjectSource): void {\n if (opts.trustUnsafeSources || warnedUntrusted.has(opts)) return;\n if (typeof src.from === 'function') return;\n if (src.from === 'header' || src.from === 'query' || src.from === 'cookie' || src.from === 'body') {\n warnedUntrusted.add(opts);\n // eslint-disable-next-line no-console\n console.warn(\n `[orangecheck/gate] subject source \"${src.from}\" is caller-supplied — any client can set it. ` +\n `For real auth, resolve the address from a signed session (e.g., { from: (req) => req.session.verifiedAddress }) ` +\n `or pass { trustUnsafeSources: true } to silence this warning.`\n );\n }\n}\n\nfunction readHeader(req: MinimalReq, name: string): string | undefined {\n const raw = req.headers?.[name.toLowerCase()];\n if (Array.isArray(raw)) return raw[0];\n return raw;\n}\n\nfunction readCookie(req: MinimalReq, name: string): string | undefined {\n // Prefer an already-parsed cookies jar (Express w/ cookie-parser, Next API).\n if (req.cookies && name in req.cookies) return req.cookies[name];\n // Fallback: parse the raw Cookie header.\n const raw = readHeader(req, 'cookie');\n if (!raw) return undefined;\n for (const part of raw.split(';')) {\n const eq = part.indexOf('=');\n if (eq === -1) continue;\n const k = part.slice(0, eq).trim();\n if (k === name) return decodeURIComponent(part.slice(eq + 1).trim());\n }\n return undefined;\n}\n\nfunction readQuery(req: MinimalReq, name: string): string | undefined {\n const raw = req.query?.[name];\n if (Array.isArray(raw)) return raw[0];\n return raw;\n}\n\nfunction readBodyPath(req: MinimalReq, path: string): string | undefined {\n if (!req.body || typeof req.body !== 'object') return undefined;\n const parts = path.split('.');\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let cur: any = req.body;\n for (const p of parts) {\n if (cur == null) return undefined;\n cur = cur[p];\n }\n return typeof cur === 'string' ? cur : undefined;\n}\n\nfunction resolve(src: SubjectSource | undefined, req: MinimalReq): string | undefined {\n if (!src) return undefined;\n if (typeof src.from === 'function') return src.from(req);\n switch (src.from) {\n case 'header':\n return readHeader(req, src.name ?? 'x-oc-address');\n case 'cookie':\n return readCookie(req, src.name ?? 'oc_addr');\n case 'query':\n return readQuery(req, src.name ?? 'ocAddr');\n case 'body':\n return readBodyPath(req, src.path ?? 'address');\n default:\n return undefined;\n }\n}\n\n/**\n * Run the OrangeCheck lookup + threshold comparison for a request, without\n * touching the response. Framework-agnostic. Use this directly in Fastify,\n * Hono, Workers, or anywhere Express middleware doesn't fit.\n *\n * const decision = await assertOc(req, { minSats: 100_000, address: { from: 'header' } });\n * if (!decision.ok) return res.status(403).json({ error: decision.reason });\n */\nexport async function assertOc(req: MinimalReq, opts: GateOptions): Promise<GateDecision> {\n const sources = [\n ['attestation_id', opts.attestationId] as const,\n ['address', opts.address] as const,\n ['identity', opts.identity] as const,\n ];\n\n let subject: string | undefined;\n let subjectKind: GateDecision['subjectKind'];\n for (const [kind, src] of sources) {\n if (!src) continue;\n warnUntrustedOnce(opts, src);\n const v = resolve(src, req);\n if (v) {\n subject = v;\n subjectKind = kind;\n break;\n }\n }\n\n if (!subject || !subjectKind) {\n return { ok: false, reason: 'no_subject' };\n }\n\n // Clamp obviously-junk subjects before they hit the cache or the network.\n if (subject.length > MAX_SUBJECT_LEN) {\n return { ok: false, reason: 'no_subject' };\n }\n subject = normalizeSubject(subjectKind, subject);\n\n const cache = cacheFor(opts);\n const cacheKey = `${subjectKind}:${subject}:${opts.minSats ?? 0}:${opts.minDays ?? 0}`;\n const cached = cache.get(cacheKey);\n if (cached) return { ...cached, subject, subjectKind };\n\n try {\n const params: Parameters<typeof check>[0] = {\n minSats: opts.minSats,\n minDays: opts.minDays,\n ...(opts.relays ? { relays: opts.relays } : {}),\n };\n if (subjectKind === 'attestation_id') params.id = subject;\n else if (subjectKind === 'address') params.addr = subject;\n else if (subjectKind === 'identity') {\n const idx = subject.indexOf(':');\n if (idx === -1) {\n const d: GateDecision = {\n ok: false,\n reason: 'no_subject',\n subject,\n subjectKind,\n };\n cache.set(cacheKey, d);\n return d;\n }\n params.identity = {\n protocol: subject.slice(0, idx),\n identifier: subject.slice(idx + 1),\n };\n }\n\n // Hard deadline. `check()` may call out to a relay/Esplora, and we\n // do not want the gate to hang a request indefinitely.\n const timeoutMs = opts.lookupTimeoutMs ?? DEFAULT_LOOKUP_TIMEOUT_MS;\n const result = await Promise.race([\n check(params),\n new Promise<never>((_, reject) =>\n setTimeout(() => reject(new Error('lookup_timeout')), timeoutMs)\n ),\n ]);\n\n let reason: GateDecision['reason'];\n if (result.ok) reason = 'ok';\n else if (result.reasons?.includes('not_found')) reason = 'not_found';\n else if (result.reasons?.some((r) => r === 'below_min_sats' || r === 'below_min_days'))\n reason = 'below_threshold';\n else reason = 'invalid_proof';\n\n const decision: GateDecision = {\n ok: result.ok,\n reason,\n check: result,\n subject,\n subjectKind,\n };\n cache.set(cacheKey, decision);\n return decision;\n } catch (err) {\n const decision: GateDecision = {\n ok: Boolean(opts.failOpen),\n reason: opts.failOpen ? 'fail_open' : 'lookup_error',\n subject,\n subjectKind,\n };\n // Do not cache errors — we want the next request to try again.\n if (opts.onDecision) opts.onDecision(req, decision);\n // Surface only the message, not the full stack, to avoid leaking\n // internals into caller-visible logs. Callers wanting stack traces\n // should wire `onDecision` and log `err` themselves.\n // eslint-disable-next-line no-console\n console.warn(\n '[orangecheck/gate] lookup failed:',\n err instanceof Error ? err.message : String(err)\n );\n return decision;\n }\n}\n\n/**\n * Default 403 body writer. Extracted so express.ts and next.ts don't drift.\n * Respects `opts.exposeSubject` so a cookie-sourced address never leaks back\n * into a response body that might be seen by a different caller.\n */\nexport function sendBlockedDefault(\n res: MinimalRes,\n decision: GateDecision,\n opts: GateOptions\n): void {\n res.setHeader('Content-Type', 'application/json');\n res.status(decision.reason === 'no_subject' ? 401 : 403);\n const body: Record<string, unknown> = {\n error: 'orangecheck_gate_blocked',\n reason: decision.reason,\n };\n if (opts.exposeSubject && decision.subject) {\n body.subject = decision.subject;\n body.subjectKind = decision.subjectKind;\n }\n if (decision.check?.reasons) body.reasons = decision.check.reasons;\n res.json(body);\n}\n","import type { GateOptions, MinimalReq, MinimalRes } from './types';\n\nimport { assertOc, sendBlockedDefault } from './core';\n\n/**\n * Express / Connect / Next-pages-API middleware.\n *\n * import { ocGate } from '@orangecheck/gate';\n *\n * app.post(\n * '/post',\n * ocGate({\n * minSats: 100_000,\n * minDays: 30,\n * address: { from: 'header' }, // reads X-OC-Address\n * }),\n * postHandler,\n * );\n *\n * On block: sends `403 { error: '<reason>', orangecheck: <CheckResult|null> }`\n * unless `opts.onBlocked` is provided.\n */\nexport function ocGate(opts: GateOptions) {\n return async function gateMiddleware(\n req: MinimalReq,\n res: MinimalRes,\n next: (err?: unknown) => void\n ): Promise<void> {\n const decision = await assertOc(req, opts);\n\n if (opts.onDecision) opts.onDecision(req, decision);\n\n if (decision.ok) {\n next();\n return;\n }\n\n if (opts.onBlocked) {\n opts.onBlocked(req, res, decision);\n return;\n }\n\n res.setHeader('Cache-Control', 'no-store');\n sendBlockedDefault(res, decision, opts);\n };\n}\n"]} |
+245
| import { check } from '@orangecheck/sdk'; | ||
| // src/core.ts | ||
| // src/cache.ts | ||
| var TtlLru = class { | ||
| constructor(max, ttlMs) { | ||
| this.max = max; | ||
| this.ttlMs = ttlMs; | ||
| this.store = /* @__PURE__ */ new Map(); | ||
| } | ||
| get(key) { | ||
| const entry = this.store.get(key); | ||
| if (!entry) return void 0; | ||
| if (Date.now() > entry.expires) { | ||
| this.store.delete(key); | ||
| return void 0; | ||
| } | ||
| return entry.value; | ||
| } | ||
| set(key, value) { | ||
| if (this.store.size >= this.max) { | ||
| const first = this.store.keys().next().value; | ||
| if (first !== void 0) this.store.delete(first); | ||
| } | ||
| this.store.set(key, { value, expires: Date.now() + this.ttlMs }); | ||
| } | ||
| clear() { | ||
| this.store.clear(); | ||
| } | ||
| }; | ||
| // src/core.ts | ||
| var caches = /* @__PURE__ */ new WeakMap(); | ||
| var MAX_CACHE_TTL_MS = 10 * 6e4; | ||
| var DEFAULT_CACHE_TTL_MS = 6e4; | ||
| var DEFAULT_LOOKUP_TIMEOUT_MS = 5e3; | ||
| var MAX_SUBJECT_LEN = 128; | ||
| function cacheFor(opts) { | ||
| let c = caches.get(opts); | ||
| if (!c) { | ||
| const ttl = Math.min(opts.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS, MAX_CACHE_TTL_MS); | ||
| c = new TtlLru(opts.cacheMax ?? 1e3, Math.max(0, ttl)); | ||
| caches.set(opts, c); | ||
| } | ||
| return c; | ||
| } | ||
| function normalizeSubject(kind, raw) { | ||
| const trimmed = raw.trim(); | ||
| if (kind === "attestation_id") return trimmed.toLowerCase(); | ||
| if (kind === "address") { | ||
| const low = trimmed.toLowerCase(); | ||
| if (low.startsWith("bc1") || low.startsWith("tb1") || low.startsWith("bcrt1")) return low; | ||
| return trimmed; | ||
| } | ||
| const colon = trimmed.indexOf(":"); | ||
| if (colon === -1) return trimmed; | ||
| return trimmed.slice(0, colon).toLowerCase() + ":" + trimmed.slice(colon + 1); | ||
| } | ||
| var warnedUntrusted = /* @__PURE__ */ new WeakSet(); | ||
| function warnUntrustedOnce(opts, src) { | ||
| if (opts.trustUnsafeSources || warnedUntrusted.has(opts)) return; | ||
| if (typeof src.from === "function") return; | ||
| if (src.from === "header" || src.from === "query" || src.from === "cookie" || src.from === "body") { | ||
| warnedUntrusted.add(opts); | ||
| console.warn( | ||
| `[orangecheck/gate] subject source "${src.from}" is caller-supplied \u2014 any client can set it. For real auth, resolve the address from a signed session (e.g., { from: (req) => req.session.verifiedAddress }) or pass { trustUnsafeSources: true } to silence this warning.` | ||
| ); | ||
| } | ||
| } | ||
| function readHeader(req, name) { | ||
| const raw = req.headers?.[name.toLowerCase()]; | ||
| if (Array.isArray(raw)) return raw[0]; | ||
| return raw; | ||
| } | ||
| function readCookie(req, name) { | ||
| if (req.cookies && name in req.cookies) return req.cookies[name]; | ||
| const raw = readHeader(req, "cookie"); | ||
| if (!raw) return void 0; | ||
| for (const part of raw.split(";")) { | ||
| const eq = part.indexOf("="); | ||
| if (eq === -1) continue; | ||
| const k = part.slice(0, eq).trim(); | ||
| if (k === name) return decodeURIComponent(part.slice(eq + 1).trim()); | ||
| } | ||
| return void 0; | ||
| } | ||
| function readQuery(req, name) { | ||
| const raw = req.query?.[name]; | ||
| if (Array.isArray(raw)) return raw[0]; | ||
| return raw; | ||
| } | ||
| function readBodyPath(req, path) { | ||
| if (!req.body || typeof req.body !== "object") return void 0; | ||
| const parts = path.split("."); | ||
| let cur = req.body; | ||
| for (const p of parts) { | ||
| if (cur == null) return void 0; | ||
| cur = cur[p]; | ||
| } | ||
| return typeof cur === "string" ? cur : void 0; | ||
| } | ||
| function resolve(src, req) { | ||
| if (!src) return void 0; | ||
| if (typeof src.from === "function") return src.from(req); | ||
| switch (src.from) { | ||
| case "header": | ||
| return readHeader(req, src.name ?? "x-oc-address"); | ||
| case "cookie": | ||
| return readCookie(req, src.name ?? "oc_addr"); | ||
| case "query": | ||
| return readQuery(req, src.name ?? "ocAddr"); | ||
| case "body": | ||
| return readBodyPath(req, src.path ?? "address"); | ||
| default: | ||
| return void 0; | ||
| } | ||
| } | ||
| async function assertOc(req, opts) { | ||
| const sources = [ | ||
| ["attestation_id", opts.attestationId], | ||
| ["address", opts.address], | ||
| ["identity", opts.identity] | ||
| ]; | ||
| let subject; | ||
| let subjectKind; | ||
| for (const [kind, src] of sources) { | ||
| if (!src) continue; | ||
| warnUntrustedOnce(opts, src); | ||
| const v = resolve(src, req); | ||
| if (v) { | ||
| subject = v; | ||
| subjectKind = kind; | ||
| break; | ||
| } | ||
| } | ||
| if (!subject || !subjectKind) { | ||
| return { ok: false, reason: "no_subject" }; | ||
| } | ||
| if (subject.length > MAX_SUBJECT_LEN) { | ||
| return { ok: false, reason: "no_subject" }; | ||
| } | ||
| subject = normalizeSubject(subjectKind, subject); | ||
| const cache = cacheFor(opts); | ||
| const cacheKey = `${subjectKind}:${subject}:${opts.minSats ?? 0}:${opts.minDays ?? 0}`; | ||
| const cached = cache.get(cacheKey); | ||
| if (cached) return { ...cached, subject, subjectKind }; | ||
| try { | ||
| const params = { | ||
| minSats: opts.minSats, | ||
| minDays: opts.minDays, | ||
| ...opts.relays ? { relays: opts.relays } : {} | ||
| }; | ||
| if (subjectKind === "attestation_id") params.id = subject; | ||
| else if (subjectKind === "address") params.addr = subject; | ||
| else if (subjectKind === "identity") { | ||
| const idx = subject.indexOf(":"); | ||
| if (idx === -1) { | ||
| const d = { | ||
| ok: false, | ||
| reason: "no_subject", | ||
| subject, | ||
| subjectKind | ||
| }; | ||
| cache.set(cacheKey, d); | ||
| return d; | ||
| } | ||
| params.identity = { | ||
| protocol: subject.slice(0, idx), | ||
| identifier: subject.slice(idx + 1) | ||
| }; | ||
| } | ||
| const timeoutMs = opts.lookupTimeoutMs ?? DEFAULT_LOOKUP_TIMEOUT_MS; | ||
| const result = await Promise.race([ | ||
| check(params), | ||
| new Promise( | ||
| (_, reject) => setTimeout(() => reject(new Error("lookup_timeout")), timeoutMs) | ||
| ) | ||
| ]); | ||
| let reason; | ||
| if (result.ok) reason = "ok"; | ||
| else if (result.reasons?.includes("not_found")) reason = "not_found"; | ||
| else if (result.reasons?.some((r) => r === "below_min_sats" || r === "below_min_days")) | ||
| reason = "below_threshold"; | ||
| else reason = "invalid_proof"; | ||
| const decision = { | ||
| ok: result.ok, | ||
| reason, | ||
| check: result, | ||
| subject, | ||
| subjectKind | ||
| }; | ||
| cache.set(cacheKey, decision); | ||
| return decision; | ||
| } catch (err) { | ||
| const decision = { | ||
| ok: Boolean(opts.failOpen), | ||
| reason: opts.failOpen ? "fail_open" : "lookup_error", | ||
| subject, | ||
| subjectKind | ||
| }; | ||
| if (opts.onDecision) opts.onDecision(req, decision); | ||
| console.warn( | ||
| "[orangecheck/gate] lookup failed:", | ||
| err instanceof Error ? err.message : String(err) | ||
| ); | ||
| return decision; | ||
| } | ||
| } | ||
| function sendBlockedDefault(res, decision, opts) { | ||
| res.setHeader("Content-Type", "application/json"); | ||
| res.status(decision.reason === "no_subject" ? 401 : 403); | ||
| const body = { | ||
| error: "orangecheck_gate_blocked", | ||
| reason: decision.reason | ||
| }; | ||
| if (opts.exposeSubject && decision.subject) { | ||
| body.subject = decision.subject; | ||
| body.subjectKind = decision.subjectKind; | ||
| } | ||
| if (decision.check?.reasons) body.reasons = decision.check.reasons; | ||
| res.json(body); | ||
| } | ||
| // src/express.ts | ||
| function ocGate(opts) { | ||
| return async function gateMiddleware(req, res, next) { | ||
| const decision = await assertOc(req, opts); | ||
| if (opts.onDecision) opts.onDecision(req, decision); | ||
| if (decision.ok) { | ||
| next(); | ||
| return; | ||
| } | ||
| if (opts.onBlocked) { | ||
| opts.onBlocked(req, res, decision); | ||
| return; | ||
| } | ||
| res.setHeader("Cache-Control", "no-store"); | ||
| sendBlockedDefault(res, decision, opts); | ||
| }; | ||
| } | ||
| export { ocGate }; | ||
| //# sourceMappingURL=express.mjs.map | ||
| //# sourceMappingURL=express.mjs.map |
| {"version":3,"sources":["../src/cache.ts","../src/core.ts","../src/express.ts"],"names":[],"mappings":"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","file":"express.mjs","sourcesContent":["import type { GateDecision } from './types';\n\ninterface Entry {\n value: GateDecision;\n expires: number;\n}\n\n/**\n * Tiny LRU + TTL cache. No deps. Good enough for per-process gate state.\n *\n * Eviction: oldest-inserted key first once max is reached. Ties to real LRU\n * (touching on read) would be a nice-to-have; in practice gate callers hit\n * the same subject repeatedly inside the TTL window so insertion-order is\n * equivalent.\n */\nexport class TtlLru {\n private readonly store = new Map<string, Entry>();\n\n constructor(\n private readonly max: number,\n private readonly ttlMs: number\n ) {}\n\n get(key: string): GateDecision | undefined {\n const entry = this.store.get(key);\n if (!entry) return undefined;\n if (Date.now() > entry.expires) {\n this.store.delete(key);\n return undefined;\n }\n return entry.value;\n }\n\n set(key: string, value: GateDecision): void {\n if (this.store.size >= this.max) {\n const first = this.store.keys().next().value;\n if (first !== undefined) this.store.delete(first);\n }\n this.store.set(key, { value, expires: Date.now() + this.ttlMs });\n }\n\n clear(): void {\n this.store.clear();\n }\n}\n","import type { GateDecision, GateOptions, MinimalReq, MinimalRes, SubjectSource } from './types';\n\nimport { check } from '@orangecheck/sdk';\n\nimport { TtlLru } from './cache';\n\nconst caches = new WeakMap<GateOptions, TtlLru>();\n\n/** Hard ceiling on per-entry TTL. Callers can't ask for a permanent grant\n * by passing a huge number — the cache isn't a session store. */\nconst MAX_CACHE_TTL_MS = 10 * 60_000; // 10 minutes\nconst DEFAULT_CACHE_TTL_MS = 60_000;\nconst DEFAULT_LOOKUP_TIMEOUT_MS = 5_000;\n\n/** Subject strings > this length are almost certainly garbage — reject at\n * the gate instead of building a 10 KB cache key. */\nconst MAX_SUBJECT_LEN = 128;\n\nfunction cacheFor(opts: GateOptions): TtlLru {\n let c = caches.get(opts);\n if (!c) {\n const ttl = Math.min(opts.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS, MAX_CACHE_TTL_MS);\n c = new TtlLru(opts.cacheMax ?? 1_000, Math.max(0, ttl));\n caches.set(opts, c);\n }\n return c;\n}\n\n/**\n * Normalize a subject so that `bc1Q…` / `bc1q…` / ` bc1q… ` can't cache-key\n * to three separate rows against the same on-chain address. For bech32\n * (segwit) and attestation IDs, lowercase is correct; for legacy base58\n * addresses the case is significant, so only lowercase bc1/tb1.\n */\nfunction normalizeSubject(kind: GateDecision['subjectKind'], raw: string): string {\n const trimmed = raw.trim();\n if (kind === 'attestation_id') return trimmed.toLowerCase();\n if (kind === 'address') {\n const low = trimmed.toLowerCase();\n if (low.startsWith('bc1') || low.startsWith('tb1') || low.startsWith('bcrt1')) return low;\n return trimmed; // base58 — keep case\n }\n // identity: `protocol:identifier` — lowercase protocol only\n const colon = trimmed.indexOf(':');\n if (colon === -1) return trimmed;\n return trimmed.slice(0, colon).toLowerCase() + ':' + trimmed.slice(colon + 1);\n}\n\n/** Track untrusted-source warnings we've already logged so we don't spam\n * the console on every request. */\nconst warnedUntrusted = new WeakSet<GateOptions>();\n\nfunction warnUntrustedOnce(opts: GateOptions, src: SubjectSource): void {\n if (opts.trustUnsafeSources || warnedUntrusted.has(opts)) return;\n if (typeof src.from === 'function') return;\n if (src.from === 'header' || src.from === 'query' || src.from === 'cookie' || src.from === 'body') {\n warnedUntrusted.add(opts);\n // eslint-disable-next-line no-console\n console.warn(\n `[orangecheck/gate] subject source \"${src.from}\" is caller-supplied — any client can set it. ` +\n `For real auth, resolve the address from a signed session (e.g., { from: (req) => req.session.verifiedAddress }) ` +\n `or pass { trustUnsafeSources: true } to silence this warning.`\n );\n }\n}\n\nfunction readHeader(req: MinimalReq, name: string): string | undefined {\n const raw = req.headers?.[name.toLowerCase()];\n if (Array.isArray(raw)) return raw[0];\n return raw;\n}\n\nfunction readCookie(req: MinimalReq, name: string): string | undefined {\n // Prefer an already-parsed cookies jar (Express w/ cookie-parser, Next API).\n if (req.cookies && name in req.cookies) return req.cookies[name];\n // Fallback: parse the raw Cookie header.\n const raw = readHeader(req, 'cookie');\n if (!raw) return undefined;\n for (const part of raw.split(';')) {\n const eq = part.indexOf('=');\n if (eq === -1) continue;\n const k = part.slice(0, eq).trim();\n if (k === name) return decodeURIComponent(part.slice(eq + 1).trim());\n }\n return undefined;\n}\n\nfunction readQuery(req: MinimalReq, name: string): string | undefined {\n const raw = req.query?.[name];\n if (Array.isArray(raw)) return raw[0];\n return raw;\n}\n\nfunction readBodyPath(req: MinimalReq, path: string): string | undefined {\n if (!req.body || typeof req.body !== 'object') return undefined;\n const parts = path.split('.');\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let cur: any = req.body;\n for (const p of parts) {\n if (cur == null) return undefined;\n cur = cur[p];\n }\n return typeof cur === 'string' ? cur : undefined;\n}\n\nfunction resolve(src: SubjectSource | undefined, req: MinimalReq): string | undefined {\n if (!src) return undefined;\n if (typeof src.from === 'function') return src.from(req);\n switch (src.from) {\n case 'header':\n return readHeader(req, src.name ?? 'x-oc-address');\n case 'cookie':\n return readCookie(req, src.name ?? 'oc_addr');\n case 'query':\n return readQuery(req, src.name ?? 'ocAddr');\n case 'body':\n return readBodyPath(req, src.path ?? 'address');\n default:\n return undefined;\n }\n}\n\n/**\n * Run the OrangeCheck lookup + threshold comparison for a request, without\n * touching the response. Framework-agnostic. Use this directly in Fastify,\n * Hono, Workers, or anywhere Express middleware doesn't fit.\n *\n * const decision = await assertOc(req, { minSats: 100_000, address: { from: 'header' } });\n * if (!decision.ok) return res.status(403).json({ error: decision.reason });\n */\nexport async function assertOc(req: MinimalReq, opts: GateOptions): Promise<GateDecision> {\n const sources = [\n ['attestation_id', opts.attestationId] as const,\n ['address', opts.address] as const,\n ['identity', opts.identity] as const,\n ];\n\n let subject: string | undefined;\n let subjectKind: GateDecision['subjectKind'];\n for (const [kind, src] of sources) {\n if (!src) continue;\n warnUntrustedOnce(opts, src);\n const v = resolve(src, req);\n if (v) {\n subject = v;\n subjectKind = kind;\n break;\n }\n }\n\n if (!subject || !subjectKind) {\n return { ok: false, reason: 'no_subject' };\n }\n\n // Clamp obviously-junk subjects before they hit the cache or the network.\n if (subject.length > MAX_SUBJECT_LEN) {\n return { ok: false, reason: 'no_subject' };\n }\n subject = normalizeSubject(subjectKind, subject);\n\n const cache = cacheFor(opts);\n const cacheKey = `${subjectKind}:${subject}:${opts.minSats ?? 0}:${opts.minDays ?? 0}`;\n const cached = cache.get(cacheKey);\n if (cached) return { ...cached, subject, subjectKind };\n\n try {\n const params: Parameters<typeof check>[0] = {\n minSats: opts.minSats,\n minDays: opts.minDays,\n ...(opts.relays ? { relays: opts.relays } : {}),\n };\n if (subjectKind === 'attestation_id') params.id = subject;\n else if (subjectKind === 'address') params.addr = subject;\n else if (subjectKind === 'identity') {\n const idx = subject.indexOf(':');\n if (idx === -1) {\n const d: GateDecision = {\n ok: false,\n reason: 'no_subject',\n subject,\n subjectKind,\n };\n cache.set(cacheKey, d);\n return d;\n }\n params.identity = {\n protocol: subject.slice(0, idx),\n identifier: subject.slice(idx + 1),\n };\n }\n\n // Hard deadline. `check()` may call out to a relay/Esplora, and we\n // do not want the gate to hang a request indefinitely.\n const timeoutMs = opts.lookupTimeoutMs ?? DEFAULT_LOOKUP_TIMEOUT_MS;\n const result = await Promise.race([\n check(params),\n new Promise<never>((_, reject) =>\n setTimeout(() => reject(new Error('lookup_timeout')), timeoutMs)\n ),\n ]);\n\n let reason: GateDecision['reason'];\n if (result.ok) reason = 'ok';\n else if (result.reasons?.includes('not_found')) reason = 'not_found';\n else if (result.reasons?.some((r) => r === 'below_min_sats' || r === 'below_min_days'))\n reason = 'below_threshold';\n else reason = 'invalid_proof';\n\n const decision: GateDecision = {\n ok: result.ok,\n reason,\n check: result,\n subject,\n subjectKind,\n };\n cache.set(cacheKey, decision);\n return decision;\n } catch (err) {\n const decision: GateDecision = {\n ok: Boolean(opts.failOpen),\n reason: opts.failOpen ? 'fail_open' : 'lookup_error',\n subject,\n subjectKind,\n };\n // Do not cache errors — we want the next request to try again.\n if (opts.onDecision) opts.onDecision(req, decision);\n // Surface only the message, not the full stack, to avoid leaking\n // internals into caller-visible logs. Callers wanting stack traces\n // should wire `onDecision` and log `err` themselves.\n // eslint-disable-next-line no-console\n console.warn(\n '[orangecheck/gate] lookup failed:',\n err instanceof Error ? err.message : String(err)\n );\n return decision;\n }\n}\n\n/**\n * Default 403 body writer. Extracted so express.ts and next.ts don't drift.\n * Respects `opts.exposeSubject` so a cookie-sourced address never leaks back\n * into a response body that might be seen by a different caller.\n */\nexport function sendBlockedDefault(\n res: MinimalRes,\n decision: GateDecision,\n opts: GateOptions\n): void {\n res.setHeader('Content-Type', 'application/json');\n res.status(decision.reason === 'no_subject' ? 401 : 403);\n const body: Record<string, unknown> = {\n error: 'orangecheck_gate_blocked',\n reason: decision.reason,\n };\n if (opts.exposeSubject && decision.subject) {\n body.subject = decision.subject;\n body.subjectKind = decision.subjectKind;\n }\n if (decision.check?.reasons) body.reasons = decision.check.reasons;\n res.json(body);\n}\n","import type { GateOptions, MinimalReq, MinimalRes } from './types';\n\nimport { assertOc, sendBlockedDefault } from './core';\n\n/**\n * Express / Connect / Next-pages-API middleware.\n *\n * import { ocGate } from '@orangecheck/gate';\n *\n * app.post(\n * '/post',\n * ocGate({\n * minSats: 100_000,\n * minDays: 30,\n * address: { from: 'header' }, // reads X-OC-Address\n * }),\n * postHandler,\n * );\n *\n * On block: sends `403 { error: '<reason>', orangecheck: <CheckResult|null> }`\n * unless `opts.onBlocked` is provided.\n */\nexport function ocGate(opts: GateOptions) {\n return async function gateMiddleware(\n req: MinimalReq,\n res: MinimalRes,\n next: (err?: unknown) => void\n ): Promise<void> {\n const decision = await assertOc(req, opts);\n\n if (opts.onDecision) opts.onDecision(req, decision);\n\n if (decision.ok) {\n next();\n return;\n }\n\n if (opts.onBlocked) {\n opts.onBlocked(req, res, decision);\n return;\n }\n\n res.setHeader('Cache-Control', 'no-store');\n sendBlockedDefault(res, decision, opts);\n };\n}\n"]} |
| import { G as GateOptions } from './types-BWQAPjI8.mjs'; | ||
| import '@orangecheck/sdk'; | ||
| type AnyFastifyReq = any; | ||
| type AnyFastifyReply = any; | ||
| declare function ocGateFastify(opts: GateOptions): (req: AnyFastifyReq, reply: AnyFastifyReply) => Promise<void>; | ||
| export { ocGateFastify }; |
| import { G as GateOptions } from './types-BWQAPjI8.js'; | ||
| import '@orangecheck/sdk'; | ||
| type AnyFastifyReq = any; | ||
| type AnyFastifyReply = any; | ||
| declare function ocGateFastify(opts: GateOptions): (req: AnyFastifyReq, reply: AnyFastifyReply) => Promise<void>; | ||
| export { ocGateFastify }; |
+280
| 'use strict'; | ||
| var sdk = require('@orangecheck/sdk'); | ||
| // src/core.ts | ||
| // src/cache.ts | ||
| var TtlLru = class { | ||
| constructor(max, ttlMs) { | ||
| this.max = max; | ||
| this.ttlMs = ttlMs; | ||
| this.store = /* @__PURE__ */ new Map(); | ||
| } | ||
| get(key) { | ||
| const entry = this.store.get(key); | ||
| if (!entry) return void 0; | ||
| if (Date.now() > entry.expires) { | ||
| this.store.delete(key); | ||
| return void 0; | ||
| } | ||
| return entry.value; | ||
| } | ||
| set(key, value) { | ||
| if (this.store.size >= this.max) { | ||
| const first = this.store.keys().next().value; | ||
| if (first !== void 0) this.store.delete(first); | ||
| } | ||
| this.store.set(key, { value, expires: Date.now() + this.ttlMs }); | ||
| } | ||
| clear() { | ||
| this.store.clear(); | ||
| } | ||
| }; | ||
| // src/core.ts | ||
| var caches = /* @__PURE__ */ new WeakMap(); | ||
| var MAX_CACHE_TTL_MS = 10 * 6e4; | ||
| var DEFAULT_CACHE_TTL_MS = 6e4; | ||
| var DEFAULT_LOOKUP_TIMEOUT_MS = 5e3; | ||
| var MAX_SUBJECT_LEN = 128; | ||
| function cacheFor(opts) { | ||
| let c = caches.get(opts); | ||
| if (!c) { | ||
| const ttl = Math.min(opts.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS, MAX_CACHE_TTL_MS); | ||
| c = new TtlLru(opts.cacheMax ?? 1e3, Math.max(0, ttl)); | ||
| caches.set(opts, c); | ||
| } | ||
| return c; | ||
| } | ||
| function normalizeSubject(kind, raw) { | ||
| const trimmed = raw.trim(); | ||
| if (kind === "attestation_id") return trimmed.toLowerCase(); | ||
| if (kind === "address") { | ||
| const low = trimmed.toLowerCase(); | ||
| if (low.startsWith("bc1") || low.startsWith("tb1") || low.startsWith("bcrt1")) return low; | ||
| return trimmed; | ||
| } | ||
| const colon = trimmed.indexOf(":"); | ||
| if (colon === -1) return trimmed; | ||
| return trimmed.slice(0, colon).toLowerCase() + ":" + trimmed.slice(colon + 1); | ||
| } | ||
| var warnedUntrusted = /* @__PURE__ */ new WeakSet(); | ||
| function warnUntrustedOnce(opts, src) { | ||
| if (opts.trustUnsafeSources || warnedUntrusted.has(opts)) return; | ||
| if (typeof src.from === "function") return; | ||
| if (src.from === "header" || src.from === "query" || src.from === "cookie" || src.from === "body") { | ||
| warnedUntrusted.add(opts); | ||
| console.warn( | ||
| `[orangecheck/gate] subject source "${src.from}" is caller-supplied \u2014 any client can set it. For real auth, resolve the address from a signed session (e.g., { from: (req) => req.session.verifiedAddress }) or pass { trustUnsafeSources: true } to silence this warning.` | ||
| ); | ||
| } | ||
| } | ||
| function readHeader(req, name) { | ||
| const raw = req.headers?.[name.toLowerCase()]; | ||
| if (Array.isArray(raw)) return raw[0]; | ||
| return raw; | ||
| } | ||
| function readCookie(req, name) { | ||
| if (req.cookies && name in req.cookies) return req.cookies[name]; | ||
| const raw = readHeader(req, "cookie"); | ||
| if (!raw) return void 0; | ||
| for (const part of raw.split(";")) { | ||
| const eq = part.indexOf("="); | ||
| if (eq === -1) continue; | ||
| const k = part.slice(0, eq).trim(); | ||
| if (k === name) return decodeURIComponent(part.slice(eq + 1).trim()); | ||
| } | ||
| return void 0; | ||
| } | ||
| function readQuery(req, name) { | ||
| const raw = req.query?.[name]; | ||
| if (Array.isArray(raw)) return raw[0]; | ||
| return raw; | ||
| } | ||
| function readBodyPath(req, path) { | ||
| if (!req.body || typeof req.body !== "object") return void 0; | ||
| const parts = path.split("."); | ||
| let cur = req.body; | ||
| for (const p of parts) { | ||
| if (cur == null) return void 0; | ||
| cur = cur[p]; | ||
| } | ||
| return typeof cur === "string" ? cur : void 0; | ||
| } | ||
| function resolve(src, req) { | ||
| if (!src) return void 0; | ||
| if (typeof src.from === "function") return src.from(req); | ||
| switch (src.from) { | ||
| case "header": | ||
| return readHeader(req, src.name ?? "x-oc-address"); | ||
| case "cookie": | ||
| return readCookie(req, src.name ?? "oc_addr"); | ||
| case "query": | ||
| return readQuery(req, src.name ?? "ocAddr"); | ||
| case "body": | ||
| return readBodyPath(req, src.path ?? "address"); | ||
| default: | ||
| return void 0; | ||
| } | ||
| } | ||
| async function assertOc(req, opts) { | ||
| const sources = [ | ||
| ["attestation_id", opts.attestationId], | ||
| ["address", opts.address], | ||
| ["identity", opts.identity] | ||
| ]; | ||
| let subject; | ||
| let subjectKind; | ||
| for (const [kind, src] of sources) { | ||
| if (!src) continue; | ||
| warnUntrustedOnce(opts, src); | ||
| const v = resolve(src, req); | ||
| if (v) { | ||
| subject = v; | ||
| subjectKind = kind; | ||
| break; | ||
| } | ||
| } | ||
| if (!subject || !subjectKind) { | ||
| return { ok: false, reason: "no_subject" }; | ||
| } | ||
| if (subject.length > MAX_SUBJECT_LEN) { | ||
| return { ok: false, reason: "no_subject" }; | ||
| } | ||
| subject = normalizeSubject(subjectKind, subject); | ||
| const cache = cacheFor(opts); | ||
| const cacheKey = `${subjectKind}:${subject}:${opts.minSats ?? 0}:${opts.minDays ?? 0}`; | ||
| const cached = cache.get(cacheKey); | ||
| if (cached) return { ...cached, subject, subjectKind }; | ||
| try { | ||
| const params = { | ||
| minSats: opts.minSats, | ||
| minDays: opts.minDays, | ||
| ...opts.relays ? { relays: opts.relays } : {} | ||
| }; | ||
| if (subjectKind === "attestation_id") params.id = subject; | ||
| else if (subjectKind === "address") params.addr = subject; | ||
| else if (subjectKind === "identity") { | ||
| const idx = subject.indexOf(":"); | ||
| if (idx === -1) { | ||
| const d = { | ||
| ok: false, | ||
| reason: "no_subject", | ||
| subject, | ||
| subjectKind | ||
| }; | ||
| cache.set(cacheKey, d); | ||
| return d; | ||
| } | ||
| params.identity = { | ||
| protocol: subject.slice(0, idx), | ||
| identifier: subject.slice(idx + 1) | ||
| }; | ||
| } | ||
| const timeoutMs = opts.lookupTimeoutMs ?? DEFAULT_LOOKUP_TIMEOUT_MS; | ||
| const result = await Promise.race([ | ||
| sdk.check(params), | ||
| new Promise( | ||
| (_, reject) => setTimeout(() => reject(new Error("lookup_timeout")), timeoutMs) | ||
| ) | ||
| ]); | ||
| let reason; | ||
| if (result.ok) reason = "ok"; | ||
| else if (result.reasons?.includes("not_found")) reason = "not_found"; | ||
| else if (result.reasons?.some((r) => r === "below_min_sats" || r === "below_min_days")) | ||
| reason = "below_threshold"; | ||
| else reason = "invalid_proof"; | ||
| const decision = { | ||
| ok: result.ok, | ||
| reason, | ||
| check: result, | ||
| subject, | ||
| subjectKind | ||
| }; | ||
| cache.set(cacheKey, decision); | ||
| return decision; | ||
| } catch (err) { | ||
| const decision = { | ||
| ok: Boolean(opts.failOpen), | ||
| reason: opts.failOpen ? "fail_open" : "lookup_error", | ||
| subject, | ||
| subjectKind | ||
| }; | ||
| if (opts.onDecision) opts.onDecision(req, decision); | ||
| console.warn( | ||
| "[orangecheck/gate] lookup failed:", | ||
| err instanceof Error ? err.message : String(err) | ||
| ); | ||
| return decision; | ||
| } | ||
| } | ||
| function sendBlockedDefault(res, decision, opts) { | ||
| res.setHeader("Content-Type", "application/json"); | ||
| res.status(decision.reason === "no_subject" ? 401 : 403); | ||
| const body = { | ||
| error: "orangecheck_gate_blocked", | ||
| reason: decision.reason | ||
| }; | ||
| if (opts.exposeSubject && decision.subject) { | ||
| body.subject = decision.subject; | ||
| body.subjectKind = decision.subjectKind; | ||
| } | ||
| if (decision.check?.reasons) body.reasons = decision.check.reasons; | ||
| res.json(body); | ||
| } | ||
| // src/fastify.ts | ||
| function ocGateFastify(opts) { | ||
| return async function ocPreHandler(req, reply) { | ||
| const minimal = { | ||
| headers: req.headers ?? {}, | ||
| query: req.query ?? {}, | ||
| body: req.body, | ||
| url: req.url, | ||
| method: req.method, | ||
| cookies: req.cookies | ||
| }; | ||
| const decision = await assertOc(minimal, opts); | ||
| if (opts.onDecision) opts.onDecision(minimal, decision); | ||
| if (decision.ok) return; | ||
| if (opts.onBlocked) { | ||
| const shim2 = { | ||
| status(code) { | ||
| reply.code(code); | ||
| return shim2; | ||
| }, | ||
| json(body) { | ||
| reply.send(body); | ||
| return shim2; | ||
| }, | ||
| setHeader(name, value) { | ||
| reply.header(name, value); | ||
| return shim2; | ||
| } | ||
| }; | ||
| opts.onBlocked(minimal, shim2, decision); | ||
| return; | ||
| } | ||
| reply.header("Cache-Control", "no-store"); | ||
| const shim = { | ||
| status(code) { | ||
| reply.code(code); | ||
| return shim; | ||
| }, | ||
| json(body) { | ||
| reply.send(body); | ||
| return shim; | ||
| }, | ||
| setHeader(name, value) { | ||
| reply.header(name, value); | ||
| return shim; | ||
| } | ||
| }; | ||
| sendBlockedDefault(shim, decision, opts); | ||
| }; | ||
| } | ||
| exports.ocGateFastify = ocGateFastify; | ||
| //# sourceMappingURL=fastify.js.map | ||
| //# sourceMappingURL=fastify.js.map |
| {"version":3,"sources":["../src/cache.ts","../src/core.ts","../src/fastify.ts"],"names":["check","shim"],"mappings":"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","file":"fastify.js","sourcesContent":["import type { GateDecision } from './types';\n\ninterface Entry {\n value: GateDecision;\n expires: number;\n}\n\n/**\n * Tiny LRU + TTL cache. No deps. Good enough for per-process gate state.\n *\n * Eviction: oldest-inserted key first once max is reached. Ties to real LRU\n * (touching on read) would be a nice-to-have; in practice gate callers hit\n * the same subject repeatedly inside the TTL window so insertion-order is\n * equivalent.\n */\nexport class TtlLru {\n private readonly store = new Map<string, Entry>();\n\n constructor(\n private readonly max: number,\n private readonly ttlMs: number\n ) {}\n\n get(key: string): GateDecision | undefined {\n const entry = this.store.get(key);\n if (!entry) return undefined;\n if (Date.now() > entry.expires) {\n this.store.delete(key);\n return undefined;\n }\n return entry.value;\n }\n\n set(key: string, value: GateDecision): void {\n if (this.store.size >= this.max) {\n const first = this.store.keys().next().value;\n if (first !== undefined) this.store.delete(first);\n }\n this.store.set(key, { value, expires: Date.now() + this.ttlMs });\n }\n\n clear(): void {\n this.store.clear();\n }\n}\n","import type { GateDecision, GateOptions, MinimalReq, MinimalRes, SubjectSource } from './types';\n\nimport { check } from '@orangecheck/sdk';\n\nimport { TtlLru } from './cache';\n\nconst caches = new WeakMap<GateOptions, TtlLru>();\n\n/** Hard ceiling on per-entry TTL. Callers can't ask for a permanent grant\n * by passing a huge number — the cache isn't a session store. */\nconst MAX_CACHE_TTL_MS = 10 * 60_000; // 10 minutes\nconst DEFAULT_CACHE_TTL_MS = 60_000;\nconst DEFAULT_LOOKUP_TIMEOUT_MS = 5_000;\n\n/** Subject strings > this length are almost certainly garbage — reject at\n * the gate instead of building a 10 KB cache key. */\nconst MAX_SUBJECT_LEN = 128;\n\nfunction cacheFor(opts: GateOptions): TtlLru {\n let c = caches.get(opts);\n if (!c) {\n const ttl = Math.min(opts.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS, MAX_CACHE_TTL_MS);\n c = new TtlLru(opts.cacheMax ?? 1_000, Math.max(0, ttl));\n caches.set(opts, c);\n }\n return c;\n}\n\n/**\n * Normalize a subject so that `bc1Q…` / `bc1q…` / ` bc1q… ` can't cache-key\n * to three separate rows against the same on-chain address. For bech32\n * (segwit) and attestation IDs, lowercase is correct; for legacy base58\n * addresses the case is significant, so only lowercase bc1/tb1.\n */\nfunction normalizeSubject(kind: GateDecision['subjectKind'], raw: string): string {\n const trimmed = raw.trim();\n if (kind === 'attestation_id') return trimmed.toLowerCase();\n if (kind === 'address') {\n const low = trimmed.toLowerCase();\n if (low.startsWith('bc1') || low.startsWith('tb1') || low.startsWith('bcrt1')) return low;\n return trimmed; // base58 — keep case\n }\n // identity: `protocol:identifier` — lowercase protocol only\n const colon = trimmed.indexOf(':');\n if (colon === -1) return trimmed;\n return trimmed.slice(0, colon).toLowerCase() + ':' + trimmed.slice(colon + 1);\n}\n\n/** Track untrusted-source warnings we've already logged so we don't spam\n * the console on every request. */\nconst warnedUntrusted = new WeakSet<GateOptions>();\n\nfunction warnUntrustedOnce(opts: GateOptions, src: SubjectSource): void {\n if (opts.trustUnsafeSources || warnedUntrusted.has(opts)) return;\n if (typeof src.from === 'function') return;\n if (src.from === 'header' || src.from === 'query' || src.from === 'cookie' || src.from === 'body') {\n warnedUntrusted.add(opts);\n // eslint-disable-next-line no-console\n console.warn(\n `[orangecheck/gate] subject source \"${src.from}\" is caller-supplied — any client can set it. ` +\n `For real auth, resolve the address from a signed session (e.g., { from: (req) => req.session.verifiedAddress }) ` +\n `or pass { trustUnsafeSources: true } to silence this warning.`\n );\n }\n}\n\nfunction readHeader(req: MinimalReq, name: string): string | undefined {\n const raw = req.headers?.[name.toLowerCase()];\n if (Array.isArray(raw)) return raw[0];\n return raw;\n}\n\nfunction readCookie(req: MinimalReq, name: string): string | undefined {\n // Prefer an already-parsed cookies jar (Express w/ cookie-parser, Next API).\n if (req.cookies && name in req.cookies) return req.cookies[name];\n // Fallback: parse the raw Cookie header.\n const raw = readHeader(req, 'cookie');\n if (!raw) return undefined;\n for (const part of raw.split(';')) {\n const eq = part.indexOf('=');\n if (eq === -1) continue;\n const k = part.slice(0, eq).trim();\n if (k === name) return decodeURIComponent(part.slice(eq + 1).trim());\n }\n return undefined;\n}\n\nfunction readQuery(req: MinimalReq, name: string): string | undefined {\n const raw = req.query?.[name];\n if (Array.isArray(raw)) return raw[0];\n return raw;\n}\n\nfunction readBodyPath(req: MinimalReq, path: string): string | undefined {\n if (!req.body || typeof req.body !== 'object') return undefined;\n const parts = path.split('.');\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let cur: any = req.body;\n for (const p of parts) {\n if (cur == null) return undefined;\n cur = cur[p];\n }\n return typeof cur === 'string' ? cur : undefined;\n}\n\nfunction resolve(src: SubjectSource | undefined, req: MinimalReq): string | undefined {\n if (!src) return undefined;\n if (typeof src.from === 'function') return src.from(req);\n switch (src.from) {\n case 'header':\n return readHeader(req, src.name ?? 'x-oc-address');\n case 'cookie':\n return readCookie(req, src.name ?? 'oc_addr');\n case 'query':\n return readQuery(req, src.name ?? 'ocAddr');\n case 'body':\n return readBodyPath(req, src.path ?? 'address');\n default:\n return undefined;\n }\n}\n\n/**\n * Run the OrangeCheck lookup + threshold comparison for a request, without\n * touching the response. Framework-agnostic. Use this directly in Fastify,\n * Hono, Workers, or anywhere Express middleware doesn't fit.\n *\n * const decision = await assertOc(req, { minSats: 100_000, address: { from: 'header' } });\n * if (!decision.ok) return res.status(403).json({ error: decision.reason });\n */\nexport async function assertOc(req: MinimalReq, opts: GateOptions): Promise<GateDecision> {\n const sources = [\n ['attestation_id', opts.attestationId] as const,\n ['address', opts.address] as const,\n ['identity', opts.identity] as const,\n ];\n\n let subject: string | undefined;\n let subjectKind: GateDecision['subjectKind'];\n for (const [kind, src] of sources) {\n if (!src) continue;\n warnUntrustedOnce(opts, src);\n const v = resolve(src, req);\n if (v) {\n subject = v;\n subjectKind = kind;\n break;\n }\n }\n\n if (!subject || !subjectKind) {\n return { ok: false, reason: 'no_subject' };\n }\n\n // Clamp obviously-junk subjects before they hit the cache or the network.\n if (subject.length > MAX_SUBJECT_LEN) {\n return { ok: false, reason: 'no_subject' };\n }\n subject = normalizeSubject(subjectKind, subject);\n\n const cache = cacheFor(opts);\n const cacheKey = `${subjectKind}:${subject}:${opts.minSats ?? 0}:${opts.minDays ?? 0}`;\n const cached = cache.get(cacheKey);\n if (cached) return { ...cached, subject, subjectKind };\n\n try {\n const params: Parameters<typeof check>[0] = {\n minSats: opts.minSats,\n minDays: opts.minDays,\n ...(opts.relays ? { relays: opts.relays } : {}),\n };\n if (subjectKind === 'attestation_id') params.id = subject;\n else if (subjectKind === 'address') params.addr = subject;\n else if (subjectKind === 'identity') {\n const idx = subject.indexOf(':');\n if (idx === -1) {\n const d: GateDecision = {\n ok: false,\n reason: 'no_subject',\n subject,\n subjectKind,\n };\n cache.set(cacheKey, d);\n return d;\n }\n params.identity = {\n protocol: subject.slice(0, idx),\n identifier: subject.slice(idx + 1),\n };\n }\n\n // Hard deadline. `check()` may call out to a relay/Esplora, and we\n // do not want the gate to hang a request indefinitely.\n const timeoutMs = opts.lookupTimeoutMs ?? DEFAULT_LOOKUP_TIMEOUT_MS;\n const result = await Promise.race([\n check(params),\n new Promise<never>((_, reject) =>\n setTimeout(() => reject(new Error('lookup_timeout')), timeoutMs)\n ),\n ]);\n\n let reason: GateDecision['reason'];\n if (result.ok) reason = 'ok';\n else if (result.reasons?.includes('not_found')) reason = 'not_found';\n else if (result.reasons?.some((r) => r === 'below_min_sats' || r === 'below_min_days'))\n reason = 'below_threshold';\n else reason = 'invalid_proof';\n\n const decision: GateDecision = {\n ok: result.ok,\n reason,\n check: result,\n subject,\n subjectKind,\n };\n cache.set(cacheKey, decision);\n return decision;\n } catch (err) {\n const decision: GateDecision = {\n ok: Boolean(opts.failOpen),\n reason: opts.failOpen ? 'fail_open' : 'lookup_error',\n subject,\n subjectKind,\n };\n // Do not cache errors — we want the next request to try again.\n if (opts.onDecision) opts.onDecision(req, decision);\n // Surface only the message, not the full stack, to avoid leaking\n // internals into caller-visible logs. Callers wanting stack traces\n // should wire `onDecision` and log `err` themselves.\n // eslint-disable-next-line no-console\n console.warn(\n '[orangecheck/gate] lookup failed:',\n err instanceof Error ? err.message : String(err)\n );\n return decision;\n }\n}\n\n/**\n * Default 403 body writer. Extracted so express.ts and next.ts don't drift.\n * Respects `opts.exposeSubject` so a cookie-sourced address never leaks back\n * into a response body that might be seen by a different caller.\n */\nexport function sendBlockedDefault(\n res: MinimalRes,\n decision: GateDecision,\n opts: GateOptions\n): void {\n res.setHeader('Content-Type', 'application/json');\n res.status(decision.reason === 'no_subject' ? 401 : 403);\n const body: Record<string, unknown> = {\n error: 'orangecheck_gate_blocked',\n reason: decision.reason,\n };\n if (opts.exposeSubject && decision.subject) {\n body.subject = decision.subject;\n body.subjectKind = decision.subjectKind;\n }\n if (decision.check?.reasons) body.reasons = decision.check.reasons;\n res.json(body);\n}\n","import type { GateOptions, MinimalReq } from './types';\n\nimport { assertOc, sendBlockedDefault } from './core';\n\n/**\n * Fastify plugin-friendly adapter. Usage:\n *\n * import Fastify from 'fastify';\n * import { ocGateFastify } from '@orangecheck/gate';\n *\n * const app = Fastify();\n *\n * app.post('/post', {\n * preHandler: ocGateFastify({\n * minSats: 100_000,\n * minDays: 30,\n * address: { from: 'header' },\n * }),\n * }, postHandler);\n *\n * Fastify's `req` shape differs slightly from Express (`req.query` is always\n * an object, `req.headers` keys are lowercased, `req.body` is parsed per the\n * declared schema). The adapter marshals it into the framework-agnostic\n * MinimalReq shape the gate core expects, then short-circuits with a 403 via\n * Fastify's `reply.code().send()` on block.\n */\n// The Fastify type surface is deliberately not imported — we don't want a\n// hard dependency on fastify just for a ~20-line wrapper. Use `unknown`-typed\n// request/reply and duck-type.\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype AnyFastifyReq = any;\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype AnyFastifyReply = any;\n\nexport function ocGateFastify(opts: GateOptions) {\n return async function ocPreHandler(\n req: AnyFastifyReq,\n reply: AnyFastifyReply\n ): Promise<void> {\n const minimal: MinimalReq = {\n headers: req.headers ?? {},\n query: (req.query ?? {}) as Record<string, string | string[] | undefined>,\n body: req.body,\n url: req.url,\n method: req.method,\n cookies: req.cookies,\n };\n const decision = await assertOc(minimal, opts);\n if (opts.onDecision) opts.onDecision(minimal, decision);\n if (decision.ok) return;\n\n if (opts.onBlocked) {\n // Best-effort: pass a shim Res compatible with MinimalRes.\n const shim = {\n status(code: number) {\n reply.code(code);\n return shim;\n },\n json(body: unknown) {\n reply.send(body);\n return shim;\n },\n setHeader(name: string, value: string | number | readonly string[]) {\n reply.header(name, value);\n return shim;\n },\n };\n opts.onBlocked(minimal, shim, decision);\n return;\n }\n\n reply.header('Cache-Control', 'no-store');\n const shim = {\n status(code: number) {\n reply.code(code);\n return shim;\n },\n json(body: unknown) {\n reply.send(body);\n return shim;\n },\n setHeader(name: string, value: string | number | readonly string[]) {\n reply.header(name, value);\n return shim;\n },\n };\n sendBlockedDefault(shim, decision, opts);\n };\n}\n"]} |
+278
| import { check } from '@orangecheck/sdk'; | ||
| // src/core.ts | ||
| // src/cache.ts | ||
| var TtlLru = class { | ||
| constructor(max, ttlMs) { | ||
| this.max = max; | ||
| this.ttlMs = ttlMs; | ||
| this.store = /* @__PURE__ */ new Map(); | ||
| } | ||
| get(key) { | ||
| const entry = this.store.get(key); | ||
| if (!entry) return void 0; | ||
| if (Date.now() > entry.expires) { | ||
| this.store.delete(key); | ||
| return void 0; | ||
| } | ||
| return entry.value; | ||
| } | ||
| set(key, value) { | ||
| if (this.store.size >= this.max) { | ||
| const first = this.store.keys().next().value; | ||
| if (first !== void 0) this.store.delete(first); | ||
| } | ||
| this.store.set(key, { value, expires: Date.now() + this.ttlMs }); | ||
| } | ||
| clear() { | ||
| this.store.clear(); | ||
| } | ||
| }; | ||
| // src/core.ts | ||
| var caches = /* @__PURE__ */ new WeakMap(); | ||
| var MAX_CACHE_TTL_MS = 10 * 6e4; | ||
| var DEFAULT_CACHE_TTL_MS = 6e4; | ||
| var DEFAULT_LOOKUP_TIMEOUT_MS = 5e3; | ||
| var MAX_SUBJECT_LEN = 128; | ||
| function cacheFor(opts) { | ||
| let c = caches.get(opts); | ||
| if (!c) { | ||
| const ttl = Math.min(opts.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS, MAX_CACHE_TTL_MS); | ||
| c = new TtlLru(opts.cacheMax ?? 1e3, Math.max(0, ttl)); | ||
| caches.set(opts, c); | ||
| } | ||
| return c; | ||
| } | ||
| function normalizeSubject(kind, raw) { | ||
| const trimmed = raw.trim(); | ||
| if (kind === "attestation_id") return trimmed.toLowerCase(); | ||
| if (kind === "address") { | ||
| const low = trimmed.toLowerCase(); | ||
| if (low.startsWith("bc1") || low.startsWith("tb1") || low.startsWith("bcrt1")) return low; | ||
| return trimmed; | ||
| } | ||
| const colon = trimmed.indexOf(":"); | ||
| if (colon === -1) return trimmed; | ||
| return trimmed.slice(0, colon).toLowerCase() + ":" + trimmed.slice(colon + 1); | ||
| } | ||
| var warnedUntrusted = /* @__PURE__ */ new WeakSet(); | ||
| function warnUntrustedOnce(opts, src) { | ||
| if (opts.trustUnsafeSources || warnedUntrusted.has(opts)) return; | ||
| if (typeof src.from === "function") return; | ||
| if (src.from === "header" || src.from === "query" || src.from === "cookie" || src.from === "body") { | ||
| warnedUntrusted.add(opts); | ||
| console.warn( | ||
| `[orangecheck/gate] subject source "${src.from}" is caller-supplied \u2014 any client can set it. For real auth, resolve the address from a signed session (e.g., { from: (req) => req.session.verifiedAddress }) or pass { trustUnsafeSources: true } to silence this warning.` | ||
| ); | ||
| } | ||
| } | ||
| function readHeader(req, name) { | ||
| const raw = req.headers?.[name.toLowerCase()]; | ||
| if (Array.isArray(raw)) return raw[0]; | ||
| return raw; | ||
| } | ||
| function readCookie(req, name) { | ||
| if (req.cookies && name in req.cookies) return req.cookies[name]; | ||
| const raw = readHeader(req, "cookie"); | ||
| if (!raw) return void 0; | ||
| for (const part of raw.split(";")) { | ||
| const eq = part.indexOf("="); | ||
| if (eq === -1) continue; | ||
| const k = part.slice(0, eq).trim(); | ||
| if (k === name) return decodeURIComponent(part.slice(eq + 1).trim()); | ||
| } | ||
| return void 0; | ||
| } | ||
| function readQuery(req, name) { | ||
| const raw = req.query?.[name]; | ||
| if (Array.isArray(raw)) return raw[0]; | ||
| return raw; | ||
| } | ||
| function readBodyPath(req, path) { | ||
| if (!req.body || typeof req.body !== "object") return void 0; | ||
| const parts = path.split("."); | ||
| let cur = req.body; | ||
| for (const p of parts) { | ||
| if (cur == null) return void 0; | ||
| cur = cur[p]; | ||
| } | ||
| return typeof cur === "string" ? cur : void 0; | ||
| } | ||
| function resolve(src, req) { | ||
| if (!src) return void 0; | ||
| if (typeof src.from === "function") return src.from(req); | ||
| switch (src.from) { | ||
| case "header": | ||
| return readHeader(req, src.name ?? "x-oc-address"); | ||
| case "cookie": | ||
| return readCookie(req, src.name ?? "oc_addr"); | ||
| case "query": | ||
| return readQuery(req, src.name ?? "ocAddr"); | ||
| case "body": | ||
| return readBodyPath(req, src.path ?? "address"); | ||
| default: | ||
| return void 0; | ||
| } | ||
| } | ||
| async function assertOc(req, opts) { | ||
| const sources = [ | ||
| ["attestation_id", opts.attestationId], | ||
| ["address", opts.address], | ||
| ["identity", opts.identity] | ||
| ]; | ||
| let subject; | ||
| let subjectKind; | ||
| for (const [kind, src] of sources) { | ||
| if (!src) continue; | ||
| warnUntrustedOnce(opts, src); | ||
| const v = resolve(src, req); | ||
| if (v) { | ||
| subject = v; | ||
| subjectKind = kind; | ||
| break; | ||
| } | ||
| } | ||
| if (!subject || !subjectKind) { | ||
| return { ok: false, reason: "no_subject" }; | ||
| } | ||
| if (subject.length > MAX_SUBJECT_LEN) { | ||
| return { ok: false, reason: "no_subject" }; | ||
| } | ||
| subject = normalizeSubject(subjectKind, subject); | ||
| const cache = cacheFor(opts); | ||
| const cacheKey = `${subjectKind}:${subject}:${opts.minSats ?? 0}:${opts.minDays ?? 0}`; | ||
| const cached = cache.get(cacheKey); | ||
| if (cached) return { ...cached, subject, subjectKind }; | ||
| try { | ||
| const params = { | ||
| minSats: opts.minSats, | ||
| minDays: opts.minDays, | ||
| ...opts.relays ? { relays: opts.relays } : {} | ||
| }; | ||
| if (subjectKind === "attestation_id") params.id = subject; | ||
| else if (subjectKind === "address") params.addr = subject; | ||
| else if (subjectKind === "identity") { | ||
| const idx = subject.indexOf(":"); | ||
| if (idx === -1) { | ||
| const d = { | ||
| ok: false, | ||
| reason: "no_subject", | ||
| subject, | ||
| subjectKind | ||
| }; | ||
| cache.set(cacheKey, d); | ||
| return d; | ||
| } | ||
| params.identity = { | ||
| protocol: subject.slice(0, idx), | ||
| identifier: subject.slice(idx + 1) | ||
| }; | ||
| } | ||
| const timeoutMs = opts.lookupTimeoutMs ?? DEFAULT_LOOKUP_TIMEOUT_MS; | ||
| const result = await Promise.race([ | ||
| check(params), | ||
| new Promise( | ||
| (_, reject) => setTimeout(() => reject(new Error("lookup_timeout")), timeoutMs) | ||
| ) | ||
| ]); | ||
| let reason; | ||
| if (result.ok) reason = "ok"; | ||
| else if (result.reasons?.includes("not_found")) reason = "not_found"; | ||
| else if (result.reasons?.some((r) => r === "below_min_sats" || r === "below_min_days")) | ||
| reason = "below_threshold"; | ||
| else reason = "invalid_proof"; | ||
| const decision = { | ||
| ok: result.ok, | ||
| reason, | ||
| check: result, | ||
| subject, | ||
| subjectKind | ||
| }; | ||
| cache.set(cacheKey, decision); | ||
| return decision; | ||
| } catch (err) { | ||
| const decision = { | ||
| ok: Boolean(opts.failOpen), | ||
| reason: opts.failOpen ? "fail_open" : "lookup_error", | ||
| subject, | ||
| subjectKind | ||
| }; | ||
| if (opts.onDecision) opts.onDecision(req, decision); | ||
| console.warn( | ||
| "[orangecheck/gate] lookup failed:", | ||
| err instanceof Error ? err.message : String(err) | ||
| ); | ||
| return decision; | ||
| } | ||
| } | ||
| function sendBlockedDefault(res, decision, opts) { | ||
| res.setHeader("Content-Type", "application/json"); | ||
| res.status(decision.reason === "no_subject" ? 401 : 403); | ||
| const body = { | ||
| error: "orangecheck_gate_blocked", | ||
| reason: decision.reason | ||
| }; | ||
| if (opts.exposeSubject && decision.subject) { | ||
| body.subject = decision.subject; | ||
| body.subjectKind = decision.subjectKind; | ||
| } | ||
| if (decision.check?.reasons) body.reasons = decision.check.reasons; | ||
| res.json(body); | ||
| } | ||
| // src/fastify.ts | ||
| function ocGateFastify(opts) { | ||
| return async function ocPreHandler(req, reply) { | ||
| const minimal = { | ||
| headers: req.headers ?? {}, | ||
| query: req.query ?? {}, | ||
| body: req.body, | ||
| url: req.url, | ||
| method: req.method, | ||
| cookies: req.cookies | ||
| }; | ||
| const decision = await assertOc(minimal, opts); | ||
| if (opts.onDecision) opts.onDecision(minimal, decision); | ||
| if (decision.ok) return; | ||
| if (opts.onBlocked) { | ||
| const shim2 = { | ||
| status(code) { | ||
| reply.code(code); | ||
| return shim2; | ||
| }, | ||
| json(body) { | ||
| reply.send(body); | ||
| return shim2; | ||
| }, | ||
| setHeader(name, value) { | ||
| reply.header(name, value); | ||
| return shim2; | ||
| } | ||
| }; | ||
| opts.onBlocked(minimal, shim2, decision); | ||
| return; | ||
| } | ||
| reply.header("Cache-Control", "no-store"); | ||
| const shim = { | ||
| status(code) { | ||
| reply.code(code); | ||
| return shim; | ||
| }, | ||
| json(body) { | ||
| reply.send(body); | ||
| return shim; | ||
| }, | ||
| setHeader(name, value) { | ||
| reply.header(name, value); | ||
| return shim; | ||
| } | ||
| }; | ||
| sendBlockedDefault(shim, decision, opts); | ||
| }; | ||
| } | ||
| export { ocGateFastify }; | ||
| //# sourceMappingURL=fastify.mjs.map | ||
| //# sourceMappingURL=fastify.mjs.map |
| {"version":3,"sources":["../src/cache.ts","../src/core.ts","../src/fastify.ts"],"names":["shim"],"mappings":"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","file":"fastify.mjs","sourcesContent":["import type { GateDecision } from './types';\n\ninterface Entry {\n value: GateDecision;\n expires: number;\n}\n\n/**\n * Tiny LRU + TTL cache. No deps. Good enough for per-process gate state.\n *\n * Eviction: oldest-inserted key first once max is reached. Ties to real LRU\n * (touching on read) would be a nice-to-have; in practice gate callers hit\n * the same subject repeatedly inside the TTL window so insertion-order is\n * equivalent.\n */\nexport class TtlLru {\n private readonly store = new Map<string, Entry>();\n\n constructor(\n private readonly max: number,\n private readonly ttlMs: number\n ) {}\n\n get(key: string): GateDecision | undefined {\n const entry = this.store.get(key);\n if (!entry) return undefined;\n if (Date.now() > entry.expires) {\n this.store.delete(key);\n return undefined;\n }\n return entry.value;\n }\n\n set(key: string, value: GateDecision): void {\n if (this.store.size >= this.max) {\n const first = this.store.keys().next().value;\n if (first !== undefined) this.store.delete(first);\n }\n this.store.set(key, { value, expires: Date.now() + this.ttlMs });\n }\n\n clear(): void {\n this.store.clear();\n }\n}\n","import type { GateDecision, GateOptions, MinimalReq, MinimalRes, SubjectSource } from './types';\n\nimport { check } from '@orangecheck/sdk';\n\nimport { TtlLru } from './cache';\n\nconst caches = new WeakMap<GateOptions, TtlLru>();\n\n/** Hard ceiling on per-entry TTL. Callers can't ask for a permanent grant\n * by passing a huge number — the cache isn't a session store. */\nconst MAX_CACHE_TTL_MS = 10 * 60_000; // 10 minutes\nconst DEFAULT_CACHE_TTL_MS = 60_000;\nconst DEFAULT_LOOKUP_TIMEOUT_MS = 5_000;\n\n/** Subject strings > this length are almost certainly garbage — reject at\n * the gate instead of building a 10 KB cache key. */\nconst MAX_SUBJECT_LEN = 128;\n\nfunction cacheFor(opts: GateOptions): TtlLru {\n let c = caches.get(opts);\n if (!c) {\n const ttl = Math.min(opts.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS, MAX_CACHE_TTL_MS);\n c = new TtlLru(opts.cacheMax ?? 1_000, Math.max(0, ttl));\n caches.set(opts, c);\n }\n return c;\n}\n\n/**\n * Normalize a subject so that `bc1Q…` / `bc1q…` / ` bc1q… ` can't cache-key\n * to three separate rows against the same on-chain address. For bech32\n * (segwit) and attestation IDs, lowercase is correct; for legacy base58\n * addresses the case is significant, so only lowercase bc1/tb1.\n */\nfunction normalizeSubject(kind: GateDecision['subjectKind'], raw: string): string {\n const trimmed = raw.trim();\n if (kind === 'attestation_id') return trimmed.toLowerCase();\n if (kind === 'address') {\n const low = trimmed.toLowerCase();\n if (low.startsWith('bc1') || low.startsWith('tb1') || low.startsWith('bcrt1')) return low;\n return trimmed; // base58 — keep case\n }\n // identity: `protocol:identifier` — lowercase protocol only\n const colon = trimmed.indexOf(':');\n if (colon === -1) return trimmed;\n return trimmed.slice(0, colon).toLowerCase() + ':' + trimmed.slice(colon + 1);\n}\n\n/** Track untrusted-source warnings we've already logged so we don't spam\n * the console on every request. */\nconst warnedUntrusted = new WeakSet<GateOptions>();\n\nfunction warnUntrustedOnce(opts: GateOptions, src: SubjectSource): void {\n if (opts.trustUnsafeSources || warnedUntrusted.has(opts)) return;\n if (typeof src.from === 'function') return;\n if (src.from === 'header' || src.from === 'query' || src.from === 'cookie' || src.from === 'body') {\n warnedUntrusted.add(opts);\n // eslint-disable-next-line no-console\n console.warn(\n `[orangecheck/gate] subject source \"${src.from}\" is caller-supplied — any client can set it. ` +\n `For real auth, resolve the address from a signed session (e.g., { from: (req) => req.session.verifiedAddress }) ` +\n `or pass { trustUnsafeSources: true } to silence this warning.`\n );\n }\n}\n\nfunction readHeader(req: MinimalReq, name: string): string | undefined {\n const raw = req.headers?.[name.toLowerCase()];\n if (Array.isArray(raw)) return raw[0];\n return raw;\n}\n\nfunction readCookie(req: MinimalReq, name: string): string | undefined {\n // Prefer an already-parsed cookies jar (Express w/ cookie-parser, Next API).\n if (req.cookies && name in req.cookies) return req.cookies[name];\n // Fallback: parse the raw Cookie header.\n const raw = readHeader(req, 'cookie');\n if (!raw) return undefined;\n for (const part of raw.split(';')) {\n const eq = part.indexOf('=');\n if (eq === -1) continue;\n const k = part.slice(0, eq).trim();\n if (k === name) return decodeURIComponent(part.slice(eq + 1).trim());\n }\n return undefined;\n}\n\nfunction readQuery(req: MinimalReq, name: string): string | undefined {\n const raw = req.query?.[name];\n if (Array.isArray(raw)) return raw[0];\n return raw;\n}\n\nfunction readBodyPath(req: MinimalReq, path: string): string | undefined {\n if (!req.body || typeof req.body !== 'object') return undefined;\n const parts = path.split('.');\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let cur: any = req.body;\n for (const p of parts) {\n if (cur == null) return undefined;\n cur = cur[p];\n }\n return typeof cur === 'string' ? cur : undefined;\n}\n\nfunction resolve(src: SubjectSource | undefined, req: MinimalReq): string | undefined {\n if (!src) return undefined;\n if (typeof src.from === 'function') return src.from(req);\n switch (src.from) {\n case 'header':\n return readHeader(req, src.name ?? 'x-oc-address');\n case 'cookie':\n return readCookie(req, src.name ?? 'oc_addr');\n case 'query':\n return readQuery(req, src.name ?? 'ocAddr');\n case 'body':\n return readBodyPath(req, src.path ?? 'address');\n default:\n return undefined;\n }\n}\n\n/**\n * Run the OrangeCheck lookup + threshold comparison for a request, without\n * touching the response. Framework-agnostic. Use this directly in Fastify,\n * Hono, Workers, or anywhere Express middleware doesn't fit.\n *\n * const decision = await assertOc(req, { minSats: 100_000, address: { from: 'header' } });\n * if (!decision.ok) return res.status(403).json({ error: decision.reason });\n */\nexport async function assertOc(req: MinimalReq, opts: GateOptions): Promise<GateDecision> {\n const sources = [\n ['attestation_id', opts.attestationId] as const,\n ['address', opts.address] as const,\n ['identity', opts.identity] as const,\n ];\n\n let subject: string | undefined;\n let subjectKind: GateDecision['subjectKind'];\n for (const [kind, src] of sources) {\n if (!src) continue;\n warnUntrustedOnce(opts, src);\n const v = resolve(src, req);\n if (v) {\n subject = v;\n subjectKind = kind;\n break;\n }\n }\n\n if (!subject || !subjectKind) {\n return { ok: false, reason: 'no_subject' };\n }\n\n // Clamp obviously-junk subjects before they hit the cache or the network.\n if (subject.length > MAX_SUBJECT_LEN) {\n return { ok: false, reason: 'no_subject' };\n }\n subject = normalizeSubject(subjectKind, subject);\n\n const cache = cacheFor(opts);\n const cacheKey = `${subjectKind}:${subject}:${opts.minSats ?? 0}:${opts.minDays ?? 0}`;\n const cached = cache.get(cacheKey);\n if (cached) return { ...cached, subject, subjectKind };\n\n try {\n const params: Parameters<typeof check>[0] = {\n minSats: opts.minSats,\n minDays: opts.minDays,\n ...(opts.relays ? { relays: opts.relays } : {}),\n };\n if (subjectKind === 'attestation_id') params.id = subject;\n else if (subjectKind === 'address') params.addr = subject;\n else if (subjectKind === 'identity') {\n const idx = subject.indexOf(':');\n if (idx === -1) {\n const d: GateDecision = {\n ok: false,\n reason: 'no_subject',\n subject,\n subjectKind,\n };\n cache.set(cacheKey, d);\n return d;\n }\n params.identity = {\n protocol: subject.slice(0, idx),\n identifier: subject.slice(idx + 1),\n };\n }\n\n // Hard deadline. `check()` may call out to a relay/Esplora, and we\n // do not want the gate to hang a request indefinitely.\n const timeoutMs = opts.lookupTimeoutMs ?? DEFAULT_LOOKUP_TIMEOUT_MS;\n const result = await Promise.race([\n check(params),\n new Promise<never>((_, reject) =>\n setTimeout(() => reject(new Error('lookup_timeout')), timeoutMs)\n ),\n ]);\n\n let reason: GateDecision['reason'];\n if (result.ok) reason = 'ok';\n else if (result.reasons?.includes('not_found')) reason = 'not_found';\n else if (result.reasons?.some((r) => r === 'below_min_sats' || r === 'below_min_days'))\n reason = 'below_threshold';\n else reason = 'invalid_proof';\n\n const decision: GateDecision = {\n ok: result.ok,\n reason,\n check: result,\n subject,\n subjectKind,\n };\n cache.set(cacheKey, decision);\n return decision;\n } catch (err) {\n const decision: GateDecision = {\n ok: Boolean(opts.failOpen),\n reason: opts.failOpen ? 'fail_open' : 'lookup_error',\n subject,\n subjectKind,\n };\n // Do not cache errors — we want the next request to try again.\n if (opts.onDecision) opts.onDecision(req, decision);\n // Surface only the message, not the full stack, to avoid leaking\n // internals into caller-visible logs. Callers wanting stack traces\n // should wire `onDecision` and log `err` themselves.\n // eslint-disable-next-line no-console\n console.warn(\n '[orangecheck/gate] lookup failed:',\n err instanceof Error ? err.message : String(err)\n );\n return decision;\n }\n}\n\n/**\n * Default 403 body writer. Extracted so express.ts and next.ts don't drift.\n * Respects `opts.exposeSubject` so a cookie-sourced address never leaks back\n * into a response body that might be seen by a different caller.\n */\nexport function sendBlockedDefault(\n res: MinimalRes,\n decision: GateDecision,\n opts: GateOptions\n): void {\n res.setHeader('Content-Type', 'application/json');\n res.status(decision.reason === 'no_subject' ? 401 : 403);\n const body: Record<string, unknown> = {\n error: 'orangecheck_gate_blocked',\n reason: decision.reason,\n };\n if (opts.exposeSubject && decision.subject) {\n body.subject = decision.subject;\n body.subjectKind = decision.subjectKind;\n }\n if (decision.check?.reasons) body.reasons = decision.check.reasons;\n res.json(body);\n}\n","import type { GateOptions, MinimalReq } from './types';\n\nimport { assertOc, sendBlockedDefault } from './core';\n\n/**\n * Fastify plugin-friendly adapter. Usage:\n *\n * import Fastify from 'fastify';\n * import { ocGateFastify } from '@orangecheck/gate';\n *\n * const app = Fastify();\n *\n * app.post('/post', {\n * preHandler: ocGateFastify({\n * minSats: 100_000,\n * minDays: 30,\n * address: { from: 'header' },\n * }),\n * }, postHandler);\n *\n * Fastify's `req` shape differs slightly from Express (`req.query` is always\n * an object, `req.headers` keys are lowercased, `req.body` is parsed per the\n * declared schema). The adapter marshals it into the framework-agnostic\n * MinimalReq shape the gate core expects, then short-circuits with a 403 via\n * Fastify's `reply.code().send()` on block.\n */\n// The Fastify type surface is deliberately not imported — we don't want a\n// hard dependency on fastify just for a ~20-line wrapper. Use `unknown`-typed\n// request/reply and duck-type.\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype AnyFastifyReq = any;\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype AnyFastifyReply = any;\n\nexport function ocGateFastify(opts: GateOptions) {\n return async function ocPreHandler(\n req: AnyFastifyReq,\n reply: AnyFastifyReply\n ): Promise<void> {\n const minimal: MinimalReq = {\n headers: req.headers ?? {},\n query: (req.query ?? {}) as Record<string, string | string[] | undefined>,\n body: req.body,\n url: req.url,\n method: req.method,\n cookies: req.cookies,\n };\n const decision = await assertOc(minimal, opts);\n if (opts.onDecision) opts.onDecision(minimal, decision);\n if (decision.ok) return;\n\n if (opts.onBlocked) {\n // Best-effort: pass a shim Res compatible with MinimalRes.\n const shim = {\n status(code: number) {\n reply.code(code);\n return shim;\n },\n json(body: unknown) {\n reply.send(body);\n return shim;\n },\n setHeader(name: string, value: string | number | readonly string[]) {\n reply.header(name, value);\n return shim;\n },\n };\n opts.onBlocked(minimal, shim, decision);\n return;\n }\n\n reply.header('Cache-Control', 'no-store');\n const shim = {\n status(code: number) {\n reply.code(code);\n return shim;\n },\n json(body: unknown) {\n reply.send(body);\n return shim;\n },\n setHeader(name: string, value: string | number | readonly string[]) {\n reply.header(name, value);\n return shim;\n },\n };\n sendBlockedDefault(shim, decision, opts);\n };\n}\n"]} |
| import { G as GateOptions } from './types-BWQAPjI8.mjs'; | ||
| import '@orangecheck/sdk'; | ||
| type AnyHonoContext = any; | ||
| type AnyNext = () => Promise<void>; | ||
| declare function ocGateHono(opts: GateOptions): (c: AnyHonoContext, next: AnyNext) => Promise<Response | void>; | ||
| export { ocGateHono }; |
| import { G as GateOptions } from './types-BWQAPjI8.js'; | ||
| import '@orangecheck/sdk'; | ||
| type AnyHonoContext = any; | ||
| type AnyNext = () => Promise<void>; | ||
| declare function ocGateHono(opts: GateOptions): (c: AnyHonoContext, next: AnyNext) => Promise<Response | void>; | ||
| export { ocGateHono }; |
+289
| 'use strict'; | ||
| var sdk = require('@orangecheck/sdk'); | ||
| // src/core.ts | ||
| // src/cache.ts | ||
| var TtlLru = class { | ||
| constructor(max, ttlMs) { | ||
| this.max = max; | ||
| this.ttlMs = ttlMs; | ||
| this.store = /* @__PURE__ */ new Map(); | ||
| } | ||
| get(key) { | ||
| const entry = this.store.get(key); | ||
| if (!entry) return void 0; | ||
| if (Date.now() > entry.expires) { | ||
| this.store.delete(key); | ||
| return void 0; | ||
| } | ||
| return entry.value; | ||
| } | ||
| set(key, value) { | ||
| if (this.store.size >= this.max) { | ||
| const first = this.store.keys().next().value; | ||
| if (first !== void 0) this.store.delete(first); | ||
| } | ||
| this.store.set(key, { value, expires: Date.now() + this.ttlMs }); | ||
| } | ||
| clear() { | ||
| this.store.clear(); | ||
| } | ||
| }; | ||
| // src/core.ts | ||
| var caches = /* @__PURE__ */ new WeakMap(); | ||
| var MAX_CACHE_TTL_MS = 10 * 6e4; | ||
| var DEFAULT_CACHE_TTL_MS = 6e4; | ||
| var DEFAULT_LOOKUP_TIMEOUT_MS = 5e3; | ||
| var MAX_SUBJECT_LEN = 128; | ||
| function cacheFor(opts) { | ||
| let c = caches.get(opts); | ||
| if (!c) { | ||
| const ttl = Math.min(opts.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS, MAX_CACHE_TTL_MS); | ||
| c = new TtlLru(opts.cacheMax ?? 1e3, Math.max(0, ttl)); | ||
| caches.set(opts, c); | ||
| } | ||
| return c; | ||
| } | ||
| function normalizeSubject(kind, raw) { | ||
| const trimmed = raw.trim(); | ||
| if (kind === "attestation_id") return trimmed.toLowerCase(); | ||
| if (kind === "address") { | ||
| const low = trimmed.toLowerCase(); | ||
| if (low.startsWith("bc1") || low.startsWith("tb1") || low.startsWith("bcrt1")) return low; | ||
| return trimmed; | ||
| } | ||
| const colon = trimmed.indexOf(":"); | ||
| if (colon === -1) return trimmed; | ||
| return trimmed.slice(0, colon).toLowerCase() + ":" + trimmed.slice(colon + 1); | ||
| } | ||
| var warnedUntrusted = /* @__PURE__ */ new WeakSet(); | ||
| function warnUntrustedOnce(opts, src) { | ||
| if (opts.trustUnsafeSources || warnedUntrusted.has(opts)) return; | ||
| if (typeof src.from === "function") return; | ||
| if (src.from === "header" || src.from === "query" || src.from === "cookie" || src.from === "body") { | ||
| warnedUntrusted.add(opts); | ||
| console.warn( | ||
| `[orangecheck/gate] subject source "${src.from}" is caller-supplied \u2014 any client can set it. For real auth, resolve the address from a signed session (e.g., { from: (req) => req.session.verifiedAddress }) or pass { trustUnsafeSources: true } to silence this warning.` | ||
| ); | ||
| } | ||
| } | ||
| function readHeader(req, name) { | ||
| const raw = req.headers?.[name.toLowerCase()]; | ||
| if (Array.isArray(raw)) return raw[0]; | ||
| return raw; | ||
| } | ||
| function readCookie(req, name) { | ||
| if (req.cookies && name in req.cookies) return req.cookies[name]; | ||
| const raw = readHeader(req, "cookie"); | ||
| if (!raw) return void 0; | ||
| for (const part of raw.split(";")) { | ||
| const eq = part.indexOf("="); | ||
| if (eq === -1) continue; | ||
| const k = part.slice(0, eq).trim(); | ||
| if (k === name) return decodeURIComponent(part.slice(eq + 1).trim()); | ||
| } | ||
| return void 0; | ||
| } | ||
| function readQuery(req, name) { | ||
| const raw = req.query?.[name]; | ||
| if (Array.isArray(raw)) return raw[0]; | ||
| return raw; | ||
| } | ||
| function readBodyPath(req, path) { | ||
| if (!req.body || typeof req.body !== "object") return void 0; | ||
| const parts = path.split("."); | ||
| let cur = req.body; | ||
| for (const p of parts) { | ||
| if (cur == null) return void 0; | ||
| cur = cur[p]; | ||
| } | ||
| return typeof cur === "string" ? cur : void 0; | ||
| } | ||
| function resolve(src, req) { | ||
| if (!src) return void 0; | ||
| if (typeof src.from === "function") return src.from(req); | ||
| switch (src.from) { | ||
| case "header": | ||
| return readHeader(req, src.name ?? "x-oc-address"); | ||
| case "cookie": | ||
| return readCookie(req, src.name ?? "oc_addr"); | ||
| case "query": | ||
| return readQuery(req, src.name ?? "ocAddr"); | ||
| case "body": | ||
| return readBodyPath(req, src.path ?? "address"); | ||
| default: | ||
| return void 0; | ||
| } | ||
| } | ||
| async function assertOc(req, opts) { | ||
| const sources = [ | ||
| ["attestation_id", opts.attestationId], | ||
| ["address", opts.address], | ||
| ["identity", opts.identity] | ||
| ]; | ||
| let subject; | ||
| let subjectKind; | ||
| for (const [kind, src] of sources) { | ||
| if (!src) continue; | ||
| warnUntrustedOnce(opts, src); | ||
| const v = resolve(src, req); | ||
| if (v) { | ||
| subject = v; | ||
| subjectKind = kind; | ||
| break; | ||
| } | ||
| } | ||
| if (!subject || !subjectKind) { | ||
| return { ok: false, reason: "no_subject" }; | ||
| } | ||
| if (subject.length > MAX_SUBJECT_LEN) { | ||
| return { ok: false, reason: "no_subject" }; | ||
| } | ||
| subject = normalizeSubject(subjectKind, subject); | ||
| const cache = cacheFor(opts); | ||
| const cacheKey = `${subjectKind}:${subject}:${opts.minSats ?? 0}:${opts.minDays ?? 0}`; | ||
| const cached = cache.get(cacheKey); | ||
| if (cached) return { ...cached, subject, subjectKind }; | ||
| try { | ||
| const params = { | ||
| minSats: opts.minSats, | ||
| minDays: opts.minDays, | ||
| ...opts.relays ? { relays: opts.relays } : {} | ||
| }; | ||
| if (subjectKind === "attestation_id") params.id = subject; | ||
| else if (subjectKind === "address") params.addr = subject; | ||
| else if (subjectKind === "identity") { | ||
| const idx = subject.indexOf(":"); | ||
| if (idx === -1) { | ||
| const d = { | ||
| ok: false, | ||
| reason: "no_subject", | ||
| subject, | ||
| subjectKind | ||
| }; | ||
| cache.set(cacheKey, d); | ||
| return d; | ||
| } | ||
| params.identity = { | ||
| protocol: subject.slice(0, idx), | ||
| identifier: subject.slice(idx + 1) | ||
| }; | ||
| } | ||
| const timeoutMs = opts.lookupTimeoutMs ?? DEFAULT_LOOKUP_TIMEOUT_MS; | ||
| const result = await Promise.race([ | ||
| sdk.check(params), | ||
| new Promise( | ||
| (_, reject) => setTimeout(() => reject(new Error("lookup_timeout")), timeoutMs) | ||
| ) | ||
| ]); | ||
| let reason; | ||
| if (result.ok) reason = "ok"; | ||
| else if (result.reasons?.includes("not_found")) reason = "not_found"; | ||
| else if (result.reasons?.some((r) => r === "below_min_sats" || r === "below_min_days")) | ||
| reason = "below_threshold"; | ||
| else reason = "invalid_proof"; | ||
| const decision = { | ||
| ok: result.ok, | ||
| reason, | ||
| check: result, | ||
| subject, | ||
| subjectKind | ||
| }; | ||
| cache.set(cacheKey, decision); | ||
| return decision; | ||
| } catch (err) { | ||
| const decision = { | ||
| ok: Boolean(opts.failOpen), | ||
| reason: opts.failOpen ? "fail_open" : "lookup_error", | ||
| subject, | ||
| subjectKind | ||
| }; | ||
| if (opts.onDecision) opts.onDecision(req, decision); | ||
| console.warn( | ||
| "[orangecheck/gate] lookup failed:", | ||
| err instanceof Error ? err.message : String(err) | ||
| ); | ||
| return decision; | ||
| } | ||
| } | ||
| // src/next.ts | ||
| var MAX_BODY_BYTES = 64 * 1024; | ||
| async function ocGateFetch(req, opts) { | ||
| const url = new URL(req.url); | ||
| const headers = {}; | ||
| req.headers.forEach((v, k) => { | ||
| headers[k.toLowerCase()] = v; | ||
| }); | ||
| const query = {}; | ||
| url.searchParams.forEach((v, k) => { | ||
| query[k] = v; | ||
| }); | ||
| let body = void 0; | ||
| if (opts.address?.from === "body" || opts.attestationId?.from === "body" || opts.identity?.from === "body") { | ||
| try { | ||
| const cloned = req.clone(); | ||
| const contentLen = Number(cloned.headers.get("content-length") ?? "0"); | ||
| if (contentLen > MAX_BODY_BYTES) { | ||
| body = void 0; | ||
| } else { | ||
| const text = await cloned.text(); | ||
| if (text.length <= MAX_BODY_BYTES) { | ||
| body = text ? JSON.parse(text) : void 0; | ||
| } | ||
| } | ||
| } catch { | ||
| body = void 0; | ||
| } | ||
| } | ||
| const minimal = { | ||
| headers, | ||
| query, | ||
| body, | ||
| url: req.url, | ||
| method: req.method | ||
| }; | ||
| return assertOc(minimal, opts); | ||
| } | ||
| // src/hono.ts | ||
| function ocGateHono(opts) { | ||
| return async function ocHonoMiddleware(c, next) { | ||
| const req = c.req.raw ?? c.req; | ||
| const decision = await ocGateFetch(req, opts); | ||
| if (opts.onDecision) { | ||
| const headers = {}; | ||
| req.headers.forEach((v, k) => { | ||
| headers[k.toLowerCase()] = v; | ||
| }); | ||
| opts.onDecision({ headers, url: req.url, method: req.method }, decision); | ||
| } | ||
| if (decision.ok) { | ||
| await next(); | ||
| return; | ||
| } | ||
| const body = { | ||
| error: "orangecheck_gate_blocked", | ||
| reason: decision.reason | ||
| }; | ||
| if (opts.exposeSubject && decision.subject) { | ||
| body.subject = decision.subject; | ||
| body.subjectKind = decision.subjectKind; | ||
| } | ||
| if (decision.check?.reasons) body.reasons = decision.check.reasons; | ||
| return new Response(JSON.stringify(body), { | ||
| status: decision.reason === "no_subject" ? 401 : 403, | ||
| headers: { | ||
| "Content-Type": "application/json", | ||
| "Cache-Control": "no-store" | ||
| } | ||
| }); | ||
| }; | ||
| } | ||
| exports.ocGateHono = ocGateHono; | ||
| //# sourceMappingURL=hono.js.map | ||
| //# sourceMappingURL=hono.js.map |
| {"version":3,"sources":["../src/cache.ts","../src/core.ts","../src/next.ts","../src/hono.ts"],"names":["check"],"mappings":"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","file":"hono.js","sourcesContent":["import type { GateDecision } from './types';\n\ninterface Entry {\n value: GateDecision;\n expires: number;\n}\n\n/**\n * Tiny LRU + TTL cache. No deps. Good enough for per-process gate state.\n *\n * Eviction: oldest-inserted key first once max is reached. Ties to real LRU\n * (touching on read) would be a nice-to-have; in practice gate callers hit\n * the same subject repeatedly inside the TTL window so insertion-order is\n * equivalent.\n */\nexport class TtlLru {\n private readonly store = new Map<string, Entry>();\n\n constructor(\n private readonly max: number,\n private readonly ttlMs: number\n ) {}\n\n get(key: string): GateDecision | undefined {\n const entry = this.store.get(key);\n if (!entry) return undefined;\n if (Date.now() > entry.expires) {\n this.store.delete(key);\n return undefined;\n }\n return entry.value;\n }\n\n set(key: string, value: GateDecision): void {\n if (this.store.size >= this.max) {\n const first = this.store.keys().next().value;\n if (first !== undefined) this.store.delete(first);\n }\n this.store.set(key, { value, expires: Date.now() + this.ttlMs });\n }\n\n clear(): void {\n this.store.clear();\n }\n}\n","import type { GateDecision, GateOptions, MinimalReq, MinimalRes, SubjectSource } from './types';\n\nimport { check } from '@orangecheck/sdk';\n\nimport { TtlLru } from './cache';\n\nconst caches = new WeakMap<GateOptions, TtlLru>();\n\n/** Hard ceiling on per-entry TTL. Callers can't ask for a permanent grant\n * by passing a huge number — the cache isn't a session store. */\nconst MAX_CACHE_TTL_MS = 10 * 60_000; // 10 minutes\nconst DEFAULT_CACHE_TTL_MS = 60_000;\nconst DEFAULT_LOOKUP_TIMEOUT_MS = 5_000;\n\n/** Subject strings > this length are almost certainly garbage — reject at\n * the gate instead of building a 10 KB cache key. */\nconst MAX_SUBJECT_LEN = 128;\n\nfunction cacheFor(opts: GateOptions): TtlLru {\n let c = caches.get(opts);\n if (!c) {\n const ttl = Math.min(opts.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS, MAX_CACHE_TTL_MS);\n c = new TtlLru(opts.cacheMax ?? 1_000, Math.max(0, ttl));\n caches.set(opts, c);\n }\n return c;\n}\n\n/**\n * Normalize a subject so that `bc1Q…` / `bc1q…` / ` bc1q… ` can't cache-key\n * to three separate rows against the same on-chain address. For bech32\n * (segwit) and attestation IDs, lowercase is correct; for legacy base58\n * addresses the case is significant, so only lowercase bc1/tb1.\n */\nfunction normalizeSubject(kind: GateDecision['subjectKind'], raw: string): string {\n const trimmed = raw.trim();\n if (kind === 'attestation_id') return trimmed.toLowerCase();\n if (kind === 'address') {\n const low = trimmed.toLowerCase();\n if (low.startsWith('bc1') || low.startsWith('tb1') || low.startsWith('bcrt1')) return low;\n return trimmed; // base58 — keep case\n }\n // identity: `protocol:identifier` — lowercase protocol only\n const colon = trimmed.indexOf(':');\n if (colon === -1) return trimmed;\n return trimmed.slice(0, colon).toLowerCase() + ':' + trimmed.slice(colon + 1);\n}\n\n/** Track untrusted-source warnings we've already logged so we don't spam\n * the console on every request. */\nconst warnedUntrusted = new WeakSet<GateOptions>();\n\nfunction warnUntrustedOnce(opts: GateOptions, src: SubjectSource): void {\n if (opts.trustUnsafeSources || warnedUntrusted.has(opts)) return;\n if (typeof src.from === 'function') return;\n if (src.from === 'header' || src.from === 'query' || src.from === 'cookie' || src.from === 'body') {\n warnedUntrusted.add(opts);\n // eslint-disable-next-line no-console\n console.warn(\n `[orangecheck/gate] subject source \"${src.from}\" is caller-supplied — any client can set it. ` +\n `For real auth, resolve the address from a signed session (e.g., { from: (req) => req.session.verifiedAddress }) ` +\n `or pass { trustUnsafeSources: true } to silence this warning.`\n );\n }\n}\n\nfunction readHeader(req: MinimalReq, name: string): string | undefined {\n const raw = req.headers?.[name.toLowerCase()];\n if (Array.isArray(raw)) return raw[0];\n return raw;\n}\n\nfunction readCookie(req: MinimalReq, name: string): string | undefined {\n // Prefer an already-parsed cookies jar (Express w/ cookie-parser, Next API).\n if (req.cookies && name in req.cookies) return req.cookies[name];\n // Fallback: parse the raw Cookie header.\n const raw = readHeader(req, 'cookie');\n if (!raw) return undefined;\n for (const part of raw.split(';')) {\n const eq = part.indexOf('=');\n if (eq === -1) continue;\n const k = part.slice(0, eq).trim();\n if (k === name) return decodeURIComponent(part.slice(eq + 1).trim());\n }\n return undefined;\n}\n\nfunction readQuery(req: MinimalReq, name: string): string | undefined {\n const raw = req.query?.[name];\n if (Array.isArray(raw)) return raw[0];\n return raw;\n}\n\nfunction readBodyPath(req: MinimalReq, path: string): string | undefined {\n if (!req.body || typeof req.body !== 'object') return undefined;\n const parts = path.split('.');\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let cur: any = req.body;\n for (const p of parts) {\n if (cur == null) return undefined;\n cur = cur[p];\n }\n return typeof cur === 'string' ? cur : undefined;\n}\n\nfunction resolve(src: SubjectSource | undefined, req: MinimalReq): string | undefined {\n if (!src) return undefined;\n if (typeof src.from === 'function') return src.from(req);\n switch (src.from) {\n case 'header':\n return readHeader(req, src.name ?? 'x-oc-address');\n case 'cookie':\n return readCookie(req, src.name ?? 'oc_addr');\n case 'query':\n return readQuery(req, src.name ?? 'ocAddr');\n case 'body':\n return readBodyPath(req, src.path ?? 'address');\n default:\n return undefined;\n }\n}\n\n/**\n * Run the OrangeCheck lookup + threshold comparison for a request, without\n * touching the response. Framework-agnostic. Use this directly in Fastify,\n * Hono, Workers, or anywhere Express middleware doesn't fit.\n *\n * const decision = await assertOc(req, { minSats: 100_000, address: { from: 'header' } });\n * if (!decision.ok) return res.status(403).json({ error: decision.reason });\n */\nexport async function assertOc(req: MinimalReq, opts: GateOptions): Promise<GateDecision> {\n const sources = [\n ['attestation_id', opts.attestationId] as const,\n ['address', opts.address] as const,\n ['identity', opts.identity] as const,\n ];\n\n let subject: string | undefined;\n let subjectKind: GateDecision['subjectKind'];\n for (const [kind, src] of sources) {\n if (!src) continue;\n warnUntrustedOnce(opts, src);\n const v = resolve(src, req);\n if (v) {\n subject = v;\n subjectKind = kind;\n break;\n }\n }\n\n if (!subject || !subjectKind) {\n return { ok: false, reason: 'no_subject' };\n }\n\n // Clamp obviously-junk subjects before they hit the cache or the network.\n if (subject.length > MAX_SUBJECT_LEN) {\n return { ok: false, reason: 'no_subject' };\n }\n subject = normalizeSubject(subjectKind, subject);\n\n const cache = cacheFor(opts);\n const cacheKey = `${subjectKind}:${subject}:${opts.minSats ?? 0}:${opts.minDays ?? 0}`;\n const cached = cache.get(cacheKey);\n if (cached) return { ...cached, subject, subjectKind };\n\n try {\n const params: Parameters<typeof check>[0] = {\n minSats: opts.minSats,\n minDays: opts.minDays,\n ...(opts.relays ? { relays: opts.relays } : {}),\n };\n if (subjectKind === 'attestation_id') params.id = subject;\n else if (subjectKind === 'address') params.addr = subject;\n else if (subjectKind === 'identity') {\n const idx = subject.indexOf(':');\n if (idx === -1) {\n const d: GateDecision = {\n ok: false,\n reason: 'no_subject',\n subject,\n subjectKind,\n };\n cache.set(cacheKey, d);\n return d;\n }\n params.identity = {\n protocol: subject.slice(0, idx),\n identifier: subject.slice(idx + 1),\n };\n }\n\n // Hard deadline. `check()` may call out to a relay/Esplora, and we\n // do not want the gate to hang a request indefinitely.\n const timeoutMs = opts.lookupTimeoutMs ?? DEFAULT_LOOKUP_TIMEOUT_MS;\n const result = await Promise.race([\n check(params),\n new Promise<never>((_, reject) =>\n setTimeout(() => reject(new Error('lookup_timeout')), timeoutMs)\n ),\n ]);\n\n let reason: GateDecision['reason'];\n if (result.ok) reason = 'ok';\n else if (result.reasons?.includes('not_found')) reason = 'not_found';\n else if (result.reasons?.some((r) => r === 'below_min_sats' || r === 'below_min_days'))\n reason = 'below_threshold';\n else reason = 'invalid_proof';\n\n const decision: GateDecision = {\n ok: result.ok,\n reason,\n check: result,\n subject,\n subjectKind,\n };\n cache.set(cacheKey, decision);\n return decision;\n } catch (err) {\n const decision: GateDecision = {\n ok: Boolean(opts.failOpen),\n reason: opts.failOpen ? 'fail_open' : 'lookup_error',\n subject,\n subjectKind,\n };\n // Do not cache errors — we want the next request to try again.\n if (opts.onDecision) opts.onDecision(req, decision);\n // Surface only the message, not the full stack, to avoid leaking\n // internals into caller-visible logs. Callers wanting stack traces\n // should wire `onDecision` and log `err` themselves.\n // eslint-disable-next-line no-console\n console.warn(\n '[orangecheck/gate] lookup failed:',\n err instanceof Error ? err.message : String(err)\n );\n return decision;\n }\n}\n\n/**\n * Default 403 body writer. Extracted so express.ts and next.ts don't drift.\n * Respects `opts.exposeSubject` so a cookie-sourced address never leaks back\n * into a response body that might be seen by a different caller.\n */\nexport function sendBlockedDefault(\n res: MinimalRes,\n decision: GateDecision,\n opts: GateOptions\n): void {\n res.setHeader('Content-Type', 'application/json');\n res.status(decision.reason === 'no_subject' ? 401 : 403);\n const body: Record<string, unknown> = {\n error: 'orangecheck_gate_blocked',\n reason: decision.reason,\n };\n if (opts.exposeSubject && decision.subject) {\n body.subject = decision.subject;\n body.subjectKind = decision.subjectKind;\n }\n if (decision.check?.reasons) body.reasons = decision.check.reasons;\n res.json(body);\n}\n","import type { GateDecision, GateOptions, MinimalReq, MinimalRes } from './types';\n\nimport { assertOc, sendBlockedDefault } from './core';\n\n/** Cap the amount of body we'll parse for a `body: ...` subject source.\n * Without this a 100 MB POST ties up the gate before any downstream limiter\n * runs. 64 KB is plenty for a subject field. */\nconst MAX_BODY_BYTES = 64 * 1024;\n\n/**\n * Next.js Pages Router API wrapper.\n *\n * import { withOcGate } from '@orangecheck/gate';\n *\n * async function handler(req, res) {\n * // The decision is attached to req when the gate lets the call through.\n * res.json({ hello: (req as any).orangecheck.sats });\n * }\n *\n * export default withOcGate(handler, {\n * minSats: 100_000,\n * minDays: 30,\n * address: { from: 'query', name: 'addr' },\n * });\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function withOcGate<H extends (req: any, res: any) => any>(\n handler: H,\n opts: GateOptions\n): H {\n const wrapped = async (req: MinimalReq, res: MinimalRes) => {\n const decision = await assertOc(req, opts);\n if (opts.onDecision) opts.onDecision(req, decision);\n\n if (decision.ok) {\n // Attach decision to the request for the downstream handler.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (req as any).orangecheck = decision.check;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return (handler as any)(req, res);\n }\n\n if (opts.onBlocked) {\n opts.onBlocked(req, res, decision);\n return;\n }\n\n res.setHeader('Cache-Control', 'no-store');\n sendBlockedDefault(res, decision, opts);\n };\n return wrapped as unknown as H;\n}\n\n/**\n * Framework-agnostic Fetch-style guard — for Next App Router route handlers,\n * Cloudflare Workers, Hono, Bun, and friends.\n *\n * export async function POST(req: Request) {\n * const decision = await ocGateFetch(req, {\n * minSats: 100_000,\n * address: { from: 'header' },\n * });\n * if (!decision.ok) {\n * return new Response(JSON.stringify({ error: decision.reason }), { status: 403 });\n * }\n * // ... proceed\n * }\n */\nexport async function ocGateFetch(req: Request, opts: GateOptions): Promise<GateDecision> {\n const url = new URL(req.url);\n\n // Build a MinimalReq adapter over the Fetch Request.\n const headers: Record<string, string> = {};\n req.headers.forEach((v, k) => {\n headers[k.toLowerCase()] = v;\n });\n\n const query: Record<string, string> = {};\n url.searchParams.forEach((v, k) => {\n query[k] = v;\n });\n\n // Body is opaque at guard time — only parsed if the caller asks for body: *.\n // Caps at MAX_BODY_BYTES so a large POST can't stall the gate.\n let body: unknown = undefined;\n if (\n opts.address?.from === 'body' ||\n opts.attestationId?.from === 'body' ||\n opts.identity?.from === 'body'\n ) {\n try {\n const cloned = req.clone();\n const contentLen = Number(cloned.headers.get('content-length') ?? '0');\n if (contentLen > MAX_BODY_BYTES) {\n body = undefined;\n } else {\n const text = await cloned.text();\n if (text.length <= MAX_BODY_BYTES) {\n body = text ? JSON.parse(text) : undefined;\n }\n }\n } catch {\n body = undefined;\n }\n }\n\n const minimal: MinimalReq = {\n headers,\n query,\n body,\n url: req.url,\n method: req.method,\n };\n return assertOc(minimal, opts);\n}\n","import type { GateOptions } from './types';\n\nimport { ocGateFetch } from './next';\n\n/**\n * Hono / edge-runtime adapter. Returns a Hono-compatible middleware function\n * that runs the OrangeCheck gate before `c.next()`.\n *\n * import { Hono } from 'hono';\n * import { ocGateHono } from '@orangecheck/gate';\n *\n * const app = new Hono();\n *\n * app.post(\n * '/post',\n * ocGateHono({ minSats: 100_000, address: { from: 'header' } }),\n * postHandler,\n * );\n *\n * Under the hood this is just `ocGateFetch` adapted to Hono's\n * `(c, next) => Promise<Response | void>` contract. The response on block\n * is a JSON 403 (401 on no_subject).\n */\n// Same rationale as the Fastify adapter: no hard dep on `hono`, just\n// duck-type its Context.\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype AnyHonoContext = any;\ntype AnyNext = () => Promise<void>;\n\nexport function ocGateHono(opts: GateOptions) {\n return async function ocHonoMiddleware(\n c: AnyHonoContext,\n next: AnyNext\n ): Promise<Response | void> {\n // Hono gives us `c.req.raw` (the underlying Fetch Request).\n const req: Request = c.req.raw ?? c.req;\n const decision = await ocGateFetch(req, opts);\n\n if (opts.onDecision) {\n // Synthesize a MinimalReq so the callback has something useful.\n const headers: Record<string, string> = {};\n req.headers.forEach((v, k) => {\n headers[k.toLowerCase()] = v;\n });\n opts.onDecision({ headers, url: req.url, method: req.method }, decision);\n }\n\n if (decision.ok) {\n await next();\n return;\n }\n\n const body: Record<string, unknown> = {\n error: 'orangecheck_gate_blocked',\n reason: decision.reason,\n };\n if (opts.exposeSubject && decision.subject) {\n body.subject = decision.subject;\n body.subjectKind = decision.subjectKind;\n }\n if (decision.check?.reasons) body.reasons = decision.check.reasons;\n\n return new Response(JSON.stringify(body), {\n status: decision.reason === 'no_subject' ? 401 : 403,\n headers: {\n 'Content-Type': 'application/json',\n 'Cache-Control': 'no-store',\n },\n });\n };\n}\n"]} |
+287
| import { check } from '@orangecheck/sdk'; | ||
| // src/core.ts | ||
| // src/cache.ts | ||
| var TtlLru = class { | ||
| constructor(max, ttlMs) { | ||
| this.max = max; | ||
| this.ttlMs = ttlMs; | ||
| this.store = /* @__PURE__ */ new Map(); | ||
| } | ||
| get(key) { | ||
| const entry = this.store.get(key); | ||
| if (!entry) return void 0; | ||
| if (Date.now() > entry.expires) { | ||
| this.store.delete(key); | ||
| return void 0; | ||
| } | ||
| return entry.value; | ||
| } | ||
| set(key, value) { | ||
| if (this.store.size >= this.max) { | ||
| const first = this.store.keys().next().value; | ||
| if (first !== void 0) this.store.delete(first); | ||
| } | ||
| this.store.set(key, { value, expires: Date.now() + this.ttlMs }); | ||
| } | ||
| clear() { | ||
| this.store.clear(); | ||
| } | ||
| }; | ||
| // src/core.ts | ||
| var caches = /* @__PURE__ */ new WeakMap(); | ||
| var MAX_CACHE_TTL_MS = 10 * 6e4; | ||
| var DEFAULT_CACHE_TTL_MS = 6e4; | ||
| var DEFAULT_LOOKUP_TIMEOUT_MS = 5e3; | ||
| var MAX_SUBJECT_LEN = 128; | ||
| function cacheFor(opts) { | ||
| let c = caches.get(opts); | ||
| if (!c) { | ||
| const ttl = Math.min(opts.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS, MAX_CACHE_TTL_MS); | ||
| c = new TtlLru(opts.cacheMax ?? 1e3, Math.max(0, ttl)); | ||
| caches.set(opts, c); | ||
| } | ||
| return c; | ||
| } | ||
| function normalizeSubject(kind, raw) { | ||
| const trimmed = raw.trim(); | ||
| if (kind === "attestation_id") return trimmed.toLowerCase(); | ||
| if (kind === "address") { | ||
| const low = trimmed.toLowerCase(); | ||
| if (low.startsWith("bc1") || low.startsWith("tb1") || low.startsWith("bcrt1")) return low; | ||
| return trimmed; | ||
| } | ||
| const colon = trimmed.indexOf(":"); | ||
| if (colon === -1) return trimmed; | ||
| return trimmed.slice(0, colon).toLowerCase() + ":" + trimmed.slice(colon + 1); | ||
| } | ||
| var warnedUntrusted = /* @__PURE__ */ new WeakSet(); | ||
| function warnUntrustedOnce(opts, src) { | ||
| if (opts.trustUnsafeSources || warnedUntrusted.has(opts)) return; | ||
| if (typeof src.from === "function") return; | ||
| if (src.from === "header" || src.from === "query" || src.from === "cookie" || src.from === "body") { | ||
| warnedUntrusted.add(opts); | ||
| console.warn( | ||
| `[orangecheck/gate] subject source "${src.from}" is caller-supplied \u2014 any client can set it. For real auth, resolve the address from a signed session (e.g., { from: (req) => req.session.verifiedAddress }) or pass { trustUnsafeSources: true } to silence this warning.` | ||
| ); | ||
| } | ||
| } | ||
| function readHeader(req, name) { | ||
| const raw = req.headers?.[name.toLowerCase()]; | ||
| if (Array.isArray(raw)) return raw[0]; | ||
| return raw; | ||
| } | ||
| function readCookie(req, name) { | ||
| if (req.cookies && name in req.cookies) return req.cookies[name]; | ||
| const raw = readHeader(req, "cookie"); | ||
| if (!raw) return void 0; | ||
| for (const part of raw.split(";")) { | ||
| const eq = part.indexOf("="); | ||
| if (eq === -1) continue; | ||
| const k = part.slice(0, eq).trim(); | ||
| if (k === name) return decodeURIComponent(part.slice(eq + 1).trim()); | ||
| } | ||
| return void 0; | ||
| } | ||
| function readQuery(req, name) { | ||
| const raw = req.query?.[name]; | ||
| if (Array.isArray(raw)) return raw[0]; | ||
| return raw; | ||
| } | ||
| function readBodyPath(req, path) { | ||
| if (!req.body || typeof req.body !== "object") return void 0; | ||
| const parts = path.split("."); | ||
| let cur = req.body; | ||
| for (const p of parts) { | ||
| if (cur == null) return void 0; | ||
| cur = cur[p]; | ||
| } | ||
| return typeof cur === "string" ? cur : void 0; | ||
| } | ||
| function resolve(src, req) { | ||
| if (!src) return void 0; | ||
| if (typeof src.from === "function") return src.from(req); | ||
| switch (src.from) { | ||
| case "header": | ||
| return readHeader(req, src.name ?? "x-oc-address"); | ||
| case "cookie": | ||
| return readCookie(req, src.name ?? "oc_addr"); | ||
| case "query": | ||
| return readQuery(req, src.name ?? "ocAddr"); | ||
| case "body": | ||
| return readBodyPath(req, src.path ?? "address"); | ||
| default: | ||
| return void 0; | ||
| } | ||
| } | ||
| async function assertOc(req, opts) { | ||
| const sources = [ | ||
| ["attestation_id", opts.attestationId], | ||
| ["address", opts.address], | ||
| ["identity", opts.identity] | ||
| ]; | ||
| let subject; | ||
| let subjectKind; | ||
| for (const [kind, src] of sources) { | ||
| if (!src) continue; | ||
| warnUntrustedOnce(opts, src); | ||
| const v = resolve(src, req); | ||
| if (v) { | ||
| subject = v; | ||
| subjectKind = kind; | ||
| break; | ||
| } | ||
| } | ||
| if (!subject || !subjectKind) { | ||
| return { ok: false, reason: "no_subject" }; | ||
| } | ||
| if (subject.length > MAX_SUBJECT_LEN) { | ||
| return { ok: false, reason: "no_subject" }; | ||
| } | ||
| subject = normalizeSubject(subjectKind, subject); | ||
| const cache = cacheFor(opts); | ||
| const cacheKey = `${subjectKind}:${subject}:${opts.minSats ?? 0}:${opts.minDays ?? 0}`; | ||
| const cached = cache.get(cacheKey); | ||
| if (cached) return { ...cached, subject, subjectKind }; | ||
| try { | ||
| const params = { | ||
| minSats: opts.minSats, | ||
| minDays: opts.minDays, | ||
| ...opts.relays ? { relays: opts.relays } : {} | ||
| }; | ||
| if (subjectKind === "attestation_id") params.id = subject; | ||
| else if (subjectKind === "address") params.addr = subject; | ||
| else if (subjectKind === "identity") { | ||
| const idx = subject.indexOf(":"); | ||
| if (idx === -1) { | ||
| const d = { | ||
| ok: false, | ||
| reason: "no_subject", | ||
| subject, | ||
| subjectKind | ||
| }; | ||
| cache.set(cacheKey, d); | ||
| return d; | ||
| } | ||
| params.identity = { | ||
| protocol: subject.slice(0, idx), | ||
| identifier: subject.slice(idx + 1) | ||
| }; | ||
| } | ||
| const timeoutMs = opts.lookupTimeoutMs ?? DEFAULT_LOOKUP_TIMEOUT_MS; | ||
| const result = await Promise.race([ | ||
| check(params), | ||
| new Promise( | ||
| (_, reject) => setTimeout(() => reject(new Error("lookup_timeout")), timeoutMs) | ||
| ) | ||
| ]); | ||
| let reason; | ||
| if (result.ok) reason = "ok"; | ||
| else if (result.reasons?.includes("not_found")) reason = "not_found"; | ||
| else if (result.reasons?.some((r) => r === "below_min_sats" || r === "below_min_days")) | ||
| reason = "below_threshold"; | ||
| else reason = "invalid_proof"; | ||
| const decision = { | ||
| ok: result.ok, | ||
| reason, | ||
| check: result, | ||
| subject, | ||
| subjectKind | ||
| }; | ||
| cache.set(cacheKey, decision); | ||
| return decision; | ||
| } catch (err) { | ||
| const decision = { | ||
| ok: Boolean(opts.failOpen), | ||
| reason: opts.failOpen ? "fail_open" : "lookup_error", | ||
| subject, | ||
| subjectKind | ||
| }; | ||
| if (opts.onDecision) opts.onDecision(req, decision); | ||
| console.warn( | ||
| "[orangecheck/gate] lookup failed:", | ||
| err instanceof Error ? err.message : String(err) | ||
| ); | ||
| return decision; | ||
| } | ||
| } | ||
| // src/next.ts | ||
| var MAX_BODY_BYTES = 64 * 1024; | ||
| async function ocGateFetch(req, opts) { | ||
| const url = new URL(req.url); | ||
| const headers = {}; | ||
| req.headers.forEach((v, k) => { | ||
| headers[k.toLowerCase()] = v; | ||
| }); | ||
| const query = {}; | ||
| url.searchParams.forEach((v, k) => { | ||
| query[k] = v; | ||
| }); | ||
| let body = void 0; | ||
| if (opts.address?.from === "body" || opts.attestationId?.from === "body" || opts.identity?.from === "body") { | ||
| try { | ||
| const cloned = req.clone(); | ||
| const contentLen = Number(cloned.headers.get("content-length") ?? "0"); | ||
| if (contentLen > MAX_BODY_BYTES) { | ||
| body = void 0; | ||
| } else { | ||
| const text = await cloned.text(); | ||
| if (text.length <= MAX_BODY_BYTES) { | ||
| body = text ? JSON.parse(text) : void 0; | ||
| } | ||
| } | ||
| } catch { | ||
| body = void 0; | ||
| } | ||
| } | ||
| const minimal = { | ||
| headers, | ||
| query, | ||
| body, | ||
| url: req.url, | ||
| method: req.method | ||
| }; | ||
| return assertOc(minimal, opts); | ||
| } | ||
| // src/hono.ts | ||
| function ocGateHono(opts) { | ||
| return async function ocHonoMiddleware(c, next) { | ||
| const req = c.req.raw ?? c.req; | ||
| const decision = await ocGateFetch(req, opts); | ||
| if (opts.onDecision) { | ||
| const headers = {}; | ||
| req.headers.forEach((v, k) => { | ||
| headers[k.toLowerCase()] = v; | ||
| }); | ||
| opts.onDecision({ headers, url: req.url, method: req.method }, decision); | ||
| } | ||
| if (decision.ok) { | ||
| await next(); | ||
| return; | ||
| } | ||
| const body = { | ||
| error: "orangecheck_gate_blocked", | ||
| reason: decision.reason | ||
| }; | ||
| if (opts.exposeSubject && decision.subject) { | ||
| body.subject = decision.subject; | ||
| body.subjectKind = decision.subjectKind; | ||
| } | ||
| if (decision.check?.reasons) body.reasons = decision.check.reasons; | ||
| return new Response(JSON.stringify(body), { | ||
| status: decision.reason === "no_subject" ? 401 : 403, | ||
| headers: { | ||
| "Content-Type": "application/json", | ||
| "Cache-Control": "no-store" | ||
| } | ||
| }); | ||
| }; | ||
| } | ||
| export { ocGateHono }; | ||
| //# sourceMappingURL=hono.mjs.map | ||
| //# sourceMappingURL=hono.mjs.map |
| {"version":3,"sources":["../src/cache.ts","../src/core.ts","../src/next.ts","../src/hono.ts"],"names":[],"mappings":"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","file":"hono.mjs","sourcesContent":["import type { GateDecision } from './types';\n\ninterface Entry {\n value: GateDecision;\n expires: number;\n}\n\n/**\n * Tiny LRU + TTL cache. No deps. Good enough for per-process gate state.\n *\n * Eviction: oldest-inserted key first once max is reached. Ties to real LRU\n * (touching on read) would be a nice-to-have; in practice gate callers hit\n * the same subject repeatedly inside the TTL window so insertion-order is\n * equivalent.\n */\nexport class TtlLru {\n private readonly store = new Map<string, Entry>();\n\n constructor(\n private readonly max: number,\n private readonly ttlMs: number\n ) {}\n\n get(key: string): GateDecision | undefined {\n const entry = this.store.get(key);\n if (!entry) return undefined;\n if (Date.now() > entry.expires) {\n this.store.delete(key);\n return undefined;\n }\n return entry.value;\n }\n\n set(key: string, value: GateDecision): void {\n if (this.store.size >= this.max) {\n const first = this.store.keys().next().value;\n if (first !== undefined) this.store.delete(first);\n }\n this.store.set(key, { value, expires: Date.now() + this.ttlMs });\n }\n\n clear(): void {\n this.store.clear();\n }\n}\n","import type { GateDecision, GateOptions, MinimalReq, MinimalRes, SubjectSource } from './types';\n\nimport { check } from '@orangecheck/sdk';\n\nimport { TtlLru } from './cache';\n\nconst caches = new WeakMap<GateOptions, TtlLru>();\n\n/** Hard ceiling on per-entry TTL. Callers can't ask for a permanent grant\n * by passing a huge number — the cache isn't a session store. */\nconst MAX_CACHE_TTL_MS = 10 * 60_000; // 10 minutes\nconst DEFAULT_CACHE_TTL_MS = 60_000;\nconst DEFAULT_LOOKUP_TIMEOUT_MS = 5_000;\n\n/** Subject strings > this length are almost certainly garbage — reject at\n * the gate instead of building a 10 KB cache key. */\nconst MAX_SUBJECT_LEN = 128;\n\nfunction cacheFor(opts: GateOptions): TtlLru {\n let c = caches.get(opts);\n if (!c) {\n const ttl = Math.min(opts.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS, MAX_CACHE_TTL_MS);\n c = new TtlLru(opts.cacheMax ?? 1_000, Math.max(0, ttl));\n caches.set(opts, c);\n }\n return c;\n}\n\n/**\n * Normalize a subject so that `bc1Q…` / `bc1q…` / ` bc1q… ` can't cache-key\n * to three separate rows against the same on-chain address. For bech32\n * (segwit) and attestation IDs, lowercase is correct; for legacy base58\n * addresses the case is significant, so only lowercase bc1/tb1.\n */\nfunction normalizeSubject(kind: GateDecision['subjectKind'], raw: string): string {\n const trimmed = raw.trim();\n if (kind === 'attestation_id') return trimmed.toLowerCase();\n if (kind === 'address') {\n const low = trimmed.toLowerCase();\n if (low.startsWith('bc1') || low.startsWith('tb1') || low.startsWith('bcrt1')) return low;\n return trimmed; // base58 — keep case\n }\n // identity: `protocol:identifier` — lowercase protocol only\n const colon = trimmed.indexOf(':');\n if (colon === -1) return trimmed;\n return trimmed.slice(0, colon).toLowerCase() + ':' + trimmed.slice(colon + 1);\n}\n\n/** Track untrusted-source warnings we've already logged so we don't spam\n * the console on every request. */\nconst warnedUntrusted = new WeakSet<GateOptions>();\n\nfunction warnUntrustedOnce(opts: GateOptions, src: SubjectSource): void {\n if (opts.trustUnsafeSources || warnedUntrusted.has(opts)) return;\n if (typeof src.from === 'function') return;\n if (src.from === 'header' || src.from === 'query' || src.from === 'cookie' || src.from === 'body') {\n warnedUntrusted.add(opts);\n // eslint-disable-next-line no-console\n console.warn(\n `[orangecheck/gate] subject source \"${src.from}\" is caller-supplied — any client can set it. ` +\n `For real auth, resolve the address from a signed session (e.g., { from: (req) => req.session.verifiedAddress }) ` +\n `or pass { trustUnsafeSources: true } to silence this warning.`\n );\n }\n}\n\nfunction readHeader(req: MinimalReq, name: string): string | undefined {\n const raw = req.headers?.[name.toLowerCase()];\n if (Array.isArray(raw)) return raw[0];\n return raw;\n}\n\nfunction readCookie(req: MinimalReq, name: string): string | undefined {\n // Prefer an already-parsed cookies jar (Express w/ cookie-parser, Next API).\n if (req.cookies && name in req.cookies) return req.cookies[name];\n // Fallback: parse the raw Cookie header.\n const raw = readHeader(req, 'cookie');\n if (!raw) return undefined;\n for (const part of raw.split(';')) {\n const eq = part.indexOf('=');\n if (eq === -1) continue;\n const k = part.slice(0, eq).trim();\n if (k === name) return decodeURIComponent(part.slice(eq + 1).trim());\n }\n return undefined;\n}\n\nfunction readQuery(req: MinimalReq, name: string): string | undefined {\n const raw = req.query?.[name];\n if (Array.isArray(raw)) return raw[0];\n return raw;\n}\n\nfunction readBodyPath(req: MinimalReq, path: string): string | undefined {\n if (!req.body || typeof req.body !== 'object') return undefined;\n const parts = path.split('.');\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let cur: any = req.body;\n for (const p of parts) {\n if (cur == null) return undefined;\n cur = cur[p];\n }\n return typeof cur === 'string' ? cur : undefined;\n}\n\nfunction resolve(src: SubjectSource | undefined, req: MinimalReq): string | undefined {\n if (!src) return undefined;\n if (typeof src.from === 'function') return src.from(req);\n switch (src.from) {\n case 'header':\n return readHeader(req, src.name ?? 'x-oc-address');\n case 'cookie':\n return readCookie(req, src.name ?? 'oc_addr');\n case 'query':\n return readQuery(req, src.name ?? 'ocAddr');\n case 'body':\n return readBodyPath(req, src.path ?? 'address');\n default:\n return undefined;\n }\n}\n\n/**\n * Run the OrangeCheck lookup + threshold comparison for a request, without\n * touching the response. Framework-agnostic. Use this directly in Fastify,\n * Hono, Workers, or anywhere Express middleware doesn't fit.\n *\n * const decision = await assertOc(req, { minSats: 100_000, address: { from: 'header' } });\n * if (!decision.ok) return res.status(403).json({ error: decision.reason });\n */\nexport async function assertOc(req: MinimalReq, opts: GateOptions): Promise<GateDecision> {\n const sources = [\n ['attestation_id', opts.attestationId] as const,\n ['address', opts.address] as const,\n ['identity', opts.identity] as const,\n ];\n\n let subject: string | undefined;\n let subjectKind: GateDecision['subjectKind'];\n for (const [kind, src] of sources) {\n if (!src) continue;\n warnUntrustedOnce(opts, src);\n const v = resolve(src, req);\n if (v) {\n subject = v;\n subjectKind = kind;\n break;\n }\n }\n\n if (!subject || !subjectKind) {\n return { ok: false, reason: 'no_subject' };\n }\n\n // Clamp obviously-junk subjects before they hit the cache or the network.\n if (subject.length > MAX_SUBJECT_LEN) {\n return { ok: false, reason: 'no_subject' };\n }\n subject = normalizeSubject(subjectKind, subject);\n\n const cache = cacheFor(opts);\n const cacheKey = `${subjectKind}:${subject}:${opts.minSats ?? 0}:${opts.minDays ?? 0}`;\n const cached = cache.get(cacheKey);\n if (cached) return { ...cached, subject, subjectKind };\n\n try {\n const params: Parameters<typeof check>[0] = {\n minSats: opts.minSats,\n minDays: opts.minDays,\n ...(opts.relays ? { relays: opts.relays } : {}),\n };\n if (subjectKind === 'attestation_id') params.id = subject;\n else if (subjectKind === 'address') params.addr = subject;\n else if (subjectKind === 'identity') {\n const idx = subject.indexOf(':');\n if (idx === -1) {\n const d: GateDecision = {\n ok: false,\n reason: 'no_subject',\n subject,\n subjectKind,\n };\n cache.set(cacheKey, d);\n return d;\n }\n params.identity = {\n protocol: subject.slice(0, idx),\n identifier: subject.slice(idx + 1),\n };\n }\n\n // Hard deadline. `check()` may call out to a relay/Esplora, and we\n // do not want the gate to hang a request indefinitely.\n const timeoutMs = opts.lookupTimeoutMs ?? DEFAULT_LOOKUP_TIMEOUT_MS;\n const result = await Promise.race([\n check(params),\n new Promise<never>((_, reject) =>\n setTimeout(() => reject(new Error('lookup_timeout')), timeoutMs)\n ),\n ]);\n\n let reason: GateDecision['reason'];\n if (result.ok) reason = 'ok';\n else if (result.reasons?.includes('not_found')) reason = 'not_found';\n else if (result.reasons?.some((r) => r === 'below_min_sats' || r === 'below_min_days'))\n reason = 'below_threshold';\n else reason = 'invalid_proof';\n\n const decision: GateDecision = {\n ok: result.ok,\n reason,\n check: result,\n subject,\n subjectKind,\n };\n cache.set(cacheKey, decision);\n return decision;\n } catch (err) {\n const decision: GateDecision = {\n ok: Boolean(opts.failOpen),\n reason: opts.failOpen ? 'fail_open' : 'lookup_error',\n subject,\n subjectKind,\n };\n // Do not cache errors — we want the next request to try again.\n if (opts.onDecision) opts.onDecision(req, decision);\n // Surface only the message, not the full stack, to avoid leaking\n // internals into caller-visible logs. Callers wanting stack traces\n // should wire `onDecision` and log `err` themselves.\n // eslint-disable-next-line no-console\n console.warn(\n '[orangecheck/gate] lookup failed:',\n err instanceof Error ? err.message : String(err)\n );\n return decision;\n }\n}\n\n/**\n * Default 403 body writer. Extracted so express.ts and next.ts don't drift.\n * Respects `opts.exposeSubject` so a cookie-sourced address never leaks back\n * into a response body that might be seen by a different caller.\n */\nexport function sendBlockedDefault(\n res: MinimalRes,\n decision: GateDecision,\n opts: GateOptions\n): void {\n res.setHeader('Content-Type', 'application/json');\n res.status(decision.reason === 'no_subject' ? 401 : 403);\n const body: Record<string, unknown> = {\n error: 'orangecheck_gate_blocked',\n reason: decision.reason,\n };\n if (opts.exposeSubject && decision.subject) {\n body.subject = decision.subject;\n body.subjectKind = decision.subjectKind;\n }\n if (decision.check?.reasons) body.reasons = decision.check.reasons;\n res.json(body);\n}\n","import type { GateDecision, GateOptions, MinimalReq, MinimalRes } from './types';\n\nimport { assertOc, sendBlockedDefault } from './core';\n\n/** Cap the amount of body we'll parse for a `body: ...` subject source.\n * Without this a 100 MB POST ties up the gate before any downstream limiter\n * runs. 64 KB is plenty for a subject field. */\nconst MAX_BODY_BYTES = 64 * 1024;\n\n/**\n * Next.js Pages Router API wrapper.\n *\n * import { withOcGate } from '@orangecheck/gate';\n *\n * async function handler(req, res) {\n * // The decision is attached to req when the gate lets the call through.\n * res.json({ hello: (req as any).orangecheck.sats });\n * }\n *\n * export default withOcGate(handler, {\n * minSats: 100_000,\n * minDays: 30,\n * address: { from: 'query', name: 'addr' },\n * });\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function withOcGate<H extends (req: any, res: any) => any>(\n handler: H,\n opts: GateOptions\n): H {\n const wrapped = async (req: MinimalReq, res: MinimalRes) => {\n const decision = await assertOc(req, opts);\n if (opts.onDecision) opts.onDecision(req, decision);\n\n if (decision.ok) {\n // Attach decision to the request for the downstream handler.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (req as any).orangecheck = decision.check;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return (handler as any)(req, res);\n }\n\n if (opts.onBlocked) {\n opts.onBlocked(req, res, decision);\n return;\n }\n\n res.setHeader('Cache-Control', 'no-store');\n sendBlockedDefault(res, decision, opts);\n };\n return wrapped as unknown as H;\n}\n\n/**\n * Framework-agnostic Fetch-style guard — for Next App Router route handlers,\n * Cloudflare Workers, Hono, Bun, and friends.\n *\n * export async function POST(req: Request) {\n * const decision = await ocGateFetch(req, {\n * minSats: 100_000,\n * address: { from: 'header' },\n * });\n * if (!decision.ok) {\n * return new Response(JSON.stringify({ error: decision.reason }), { status: 403 });\n * }\n * // ... proceed\n * }\n */\nexport async function ocGateFetch(req: Request, opts: GateOptions): Promise<GateDecision> {\n const url = new URL(req.url);\n\n // Build a MinimalReq adapter over the Fetch Request.\n const headers: Record<string, string> = {};\n req.headers.forEach((v, k) => {\n headers[k.toLowerCase()] = v;\n });\n\n const query: Record<string, string> = {};\n url.searchParams.forEach((v, k) => {\n query[k] = v;\n });\n\n // Body is opaque at guard time — only parsed if the caller asks for body: *.\n // Caps at MAX_BODY_BYTES so a large POST can't stall the gate.\n let body: unknown = undefined;\n if (\n opts.address?.from === 'body' ||\n opts.attestationId?.from === 'body' ||\n opts.identity?.from === 'body'\n ) {\n try {\n const cloned = req.clone();\n const contentLen = Number(cloned.headers.get('content-length') ?? '0');\n if (contentLen > MAX_BODY_BYTES) {\n body = undefined;\n } else {\n const text = await cloned.text();\n if (text.length <= MAX_BODY_BYTES) {\n body = text ? JSON.parse(text) : undefined;\n }\n }\n } catch {\n body = undefined;\n }\n }\n\n const minimal: MinimalReq = {\n headers,\n query,\n body,\n url: req.url,\n method: req.method,\n };\n return assertOc(minimal, opts);\n}\n","import type { GateOptions } from './types';\n\nimport { ocGateFetch } from './next';\n\n/**\n * Hono / edge-runtime adapter. Returns a Hono-compatible middleware function\n * that runs the OrangeCheck gate before `c.next()`.\n *\n * import { Hono } from 'hono';\n * import { ocGateHono } from '@orangecheck/gate';\n *\n * const app = new Hono();\n *\n * app.post(\n * '/post',\n * ocGateHono({ minSats: 100_000, address: { from: 'header' } }),\n * postHandler,\n * );\n *\n * Under the hood this is just `ocGateFetch` adapted to Hono's\n * `(c, next) => Promise<Response | void>` contract. The response on block\n * is a JSON 403 (401 on no_subject).\n */\n// Same rationale as the Fastify adapter: no hard dep on `hono`, just\n// duck-type its Context.\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype AnyHonoContext = any;\ntype AnyNext = () => Promise<void>;\n\nexport function ocGateHono(opts: GateOptions) {\n return async function ocHonoMiddleware(\n c: AnyHonoContext,\n next: AnyNext\n ): Promise<Response | void> {\n // Hono gives us `c.req.raw` (the underlying Fetch Request).\n const req: Request = c.req.raw ?? c.req;\n const decision = await ocGateFetch(req, opts);\n\n if (opts.onDecision) {\n // Synthesize a MinimalReq so the callback has something useful.\n const headers: Record<string, string> = {};\n req.headers.forEach((v, k) => {\n headers[k.toLowerCase()] = v;\n });\n opts.onDecision({ headers, url: req.url, method: req.method }, decision);\n }\n\n if (decision.ok) {\n await next();\n return;\n }\n\n const body: Record<string, unknown> = {\n error: 'orangecheck_gate_blocked',\n reason: decision.reason,\n };\n if (opts.exposeSubject && decision.subject) {\n body.subject = decision.subject;\n body.subjectKind = decision.subjectKind;\n }\n if (decision.check?.reasons) body.reasons = decision.check.reasons;\n\n return new Response(JSON.stringify(body), {\n status: decision.reason === 'no_subject' ? 401 : 403,\n headers: {\n 'Content-Type': 'application/json',\n 'Cache-Control': 'no-store',\n },\n });\n };\n}\n"]} |
| import { G as GateOptions, a as GateDecision } from './types-BWQAPjI8.mjs'; | ||
| import '@orangecheck/sdk'; | ||
| declare function withOcGate<H extends (req: any, res: any) => any>(handler: H, opts: GateOptions): H; | ||
| declare function ocGateFetch(req: Request, opts: GateOptions): Promise<GateDecision>; | ||
| export { ocGateFetch, withOcGate }; |
| import { G as GateOptions, a as GateDecision } from './types-BWQAPjI8.js'; | ||
| import '@orangecheck/sdk'; | ||
| declare function withOcGate<H extends (req: any, res: any) => any>(handler: H, opts: GateOptions): H; | ||
| declare function ocGateFetch(req: Request, opts: GateOptions): Promise<GateDecision>; | ||
| export { ocGateFetch, withOcGate }; |
+286
| 'use strict'; | ||
| var sdk = require('@orangecheck/sdk'); | ||
| // src/core.ts | ||
| // src/cache.ts | ||
| var TtlLru = class { | ||
| constructor(max, ttlMs) { | ||
| this.max = max; | ||
| this.ttlMs = ttlMs; | ||
| this.store = /* @__PURE__ */ new Map(); | ||
| } | ||
| get(key) { | ||
| const entry = this.store.get(key); | ||
| if (!entry) return void 0; | ||
| if (Date.now() > entry.expires) { | ||
| this.store.delete(key); | ||
| return void 0; | ||
| } | ||
| return entry.value; | ||
| } | ||
| set(key, value) { | ||
| if (this.store.size >= this.max) { | ||
| const first = this.store.keys().next().value; | ||
| if (first !== void 0) this.store.delete(first); | ||
| } | ||
| this.store.set(key, { value, expires: Date.now() + this.ttlMs }); | ||
| } | ||
| clear() { | ||
| this.store.clear(); | ||
| } | ||
| }; | ||
| // src/core.ts | ||
| var caches = /* @__PURE__ */ new WeakMap(); | ||
| var MAX_CACHE_TTL_MS = 10 * 6e4; | ||
| var DEFAULT_CACHE_TTL_MS = 6e4; | ||
| var DEFAULT_LOOKUP_TIMEOUT_MS = 5e3; | ||
| var MAX_SUBJECT_LEN = 128; | ||
| function cacheFor(opts) { | ||
| let c = caches.get(opts); | ||
| if (!c) { | ||
| const ttl = Math.min(opts.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS, MAX_CACHE_TTL_MS); | ||
| c = new TtlLru(opts.cacheMax ?? 1e3, Math.max(0, ttl)); | ||
| caches.set(opts, c); | ||
| } | ||
| return c; | ||
| } | ||
| function normalizeSubject(kind, raw) { | ||
| const trimmed = raw.trim(); | ||
| if (kind === "attestation_id") return trimmed.toLowerCase(); | ||
| if (kind === "address") { | ||
| const low = trimmed.toLowerCase(); | ||
| if (low.startsWith("bc1") || low.startsWith("tb1") || low.startsWith("bcrt1")) return low; | ||
| return trimmed; | ||
| } | ||
| const colon = trimmed.indexOf(":"); | ||
| if (colon === -1) return trimmed; | ||
| return trimmed.slice(0, colon).toLowerCase() + ":" + trimmed.slice(colon + 1); | ||
| } | ||
| var warnedUntrusted = /* @__PURE__ */ new WeakSet(); | ||
| function warnUntrustedOnce(opts, src) { | ||
| if (opts.trustUnsafeSources || warnedUntrusted.has(opts)) return; | ||
| if (typeof src.from === "function") return; | ||
| if (src.from === "header" || src.from === "query" || src.from === "cookie" || src.from === "body") { | ||
| warnedUntrusted.add(opts); | ||
| console.warn( | ||
| `[orangecheck/gate] subject source "${src.from}" is caller-supplied \u2014 any client can set it. For real auth, resolve the address from a signed session (e.g., { from: (req) => req.session.verifiedAddress }) or pass { trustUnsafeSources: true } to silence this warning.` | ||
| ); | ||
| } | ||
| } | ||
| function readHeader(req, name) { | ||
| const raw = req.headers?.[name.toLowerCase()]; | ||
| if (Array.isArray(raw)) return raw[0]; | ||
| return raw; | ||
| } | ||
| function readCookie(req, name) { | ||
| if (req.cookies && name in req.cookies) return req.cookies[name]; | ||
| const raw = readHeader(req, "cookie"); | ||
| if (!raw) return void 0; | ||
| for (const part of raw.split(";")) { | ||
| const eq = part.indexOf("="); | ||
| if (eq === -1) continue; | ||
| const k = part.slice(0, eq).trim(); | ||
| if (k === name) return decodeURIComponent(part.slice(eq + 1).trim()); | ||
| } | ||
| return void 0; | ||
| } | ||
| function readQuery(req, name) { | ||
| const raw = req.query?.[name]; | ||
| if (Array.isArray(raw)) return raw[0]; | ||
| return raw; | ||
| } | ||
| function readBodyPath(req, path) { | ||
| if (!req.body || typeof req.body !== "object") return void 0; | ||
| const parts = path.split("."); | ||
| let cur = req.body; | ||
| for (const p of parts) { | ||
| if (cur == null) return void 0; | ||
| cur = cur[p]; | ||
| } | ||
| return typeof cur === "string" ? cur : void 0; | ||
| } | ||
| function resolve(src, req) { | ||
| if (!src) return void 0; | ||
| if (typeof src.from === "function") return src.from(req); | ||
| switch (src.from) { | ||
| case "header": | ||
| return readHeader(req, src.name ?? "x-oc-address"); | ||
| case "cookie": | ||
| return readCookie(req, src.name ?? "oc_addr"); | ||
| case "query": | ||
| return readQuery(req, src.name ?? "ocAddr"); | ||
| case "body": | ||
| return readBodyPath(req, src.path ?? "address"); | ||
| default: | ||
| return void 0; | ||
| } | ||
| } | ||
| async function assertOc(req, opts) { | ||
| const sources = [ | ||
| ["attestation_id", opts.attestationId], | ||
| ["address", opts.address], | ||
| ["identity", opts.identity] | ||
| ]; | ||
| let subject; | ||
| let subjectKind; | ||
| for (const [kind, src] of sources) { | ||
| if (!src) continue; | ||
| warnUntrustedOnce(opts, src); | ||
| const v = resolve(src, req); | ||
| if (v) { | ||
| subject = v; | ||
| subjectKind = kind; | ||
| break; | ||
| } | ||
| } | ||
| if (!subject || !subjectKind) { | ||
| return { ok: false, reason: "no_subject" }; | ||
| } | ||
| if (subject.length > MAX_SUBJECT_LEN) { | ||
| return { ok: false, reason: "no_subject" }; | ||
| } | ||
| subject = normalizeSubject(subjectKind, subject); | ||
| const cache = cacheFor(opts); | ||
| const cacheKey = `${subjectKind}:${subject}:${opts.minSats ?? 0}:${opts.minDays ?? 0}`; | ||
| const cached = cache.get(cacheKey); | ||
| if (cached) return { ...cached, subject, subjectKind }; | ||
| try { | ||
| const params = { | ||
| minSats: opts.minSats, | ||
| minDays: opts.minDays, | ||
| ...opts.relays ? { relays: opts.relays } : {} | ||
| }; | ||
| if (subjectKind === "attestation_id") params.id = subject; | ||
| else if (subjectKind === "address") params.addr = subject; | ||
| else if (subjectKind === "identity") { | ||
| const idx = subject.indexOf(":"); | ||
| if (idx === -1) { | ||
| const d = { | ||
| ok: false, | ||
| reason: "no_subject", | ||
| subject, | ||
| subjectKind | ||
| }; | ||
| cache.set(cacheKey, d); | ||
| return d; | ||
| } | ||
| params.identity = { | ||
| protocol: subject.slice(0, idx), | ||
| identifier: subject.slice(idx + 1) | ||
| }; | ||
| } | ||
| const timeoutMs = opts.lookupTimeoutMs ?? DEFAULT_LOOKUP_TIMEOUT_MS; | ||
| const result = await Promise.race([ | ||
| sdk.check(params), | ||
| new Promise( | ||
| (_, reject) => setTimeout(() => reject(new Error("lookup_timeout")), timeoutMs) | ||
| ) | ||
| ]); | ||
| let reason; | ||
| if (result.ok) reason = "ok"; | ||
| else if (result.reasons?.includes("not_found")) reason = "not_found"; | ||
| else if (result.reasons?.some((r) => r === "below_min_sats" || r === "below_min_days")) | ||
| reason = "below_threshold"; | ||
| else reason = "invalid_proof"; | ||
| const decision = { | ||
| ok: result.ok, | ||
| reason, | ||
| check: result, | ||
| subject, | ||
| subjectKind | ||
| }; | ||
| cache.set(cacheKey, decision); | ||
| return decision; | ||
| } catch (err) { | ||
| const decision = { | ||
| ok: Boolean(opts.failOpen), | ||
| reason: opts.failOpen ? "fail_open" : "lookup_error", | ||
| subject, | ||
| subjectKind | ||
| }; | ||
| if (opts.onDecision) opts.onDecision(req, decision); | ||
| console.warn( | ||
| "[orangecheck/gate] lookup failed:", | ||
| err instanceof Error ? err.message : String(err) | ||
| ); | ||
| return decision; | ||
| } | ||
| } | ||
| function sendBlockedDefault(res, decision, opts) { | ||
| res.setHeader("Content-Type", "application/json"); | ||
| res.status(decision.reason === "no_subject" ? 401 : 403); | ||
| const body = { | ||
| error: "orangecheck_gate_blocked", | ||
| reason: decision.reason | ||
| }; | ||
| if (opts.exposeSubject && decision.subject) { | ||
| body.subject = decision.subject; | ||
| body.subjectKind = decision.subjectKind; | ||
| } | ||
| if (decision.check?.reasons) body.reasons = decision.check.reasons; | ||
| res.json(body); | ||
| } | ||
| // src/next.ts | ||
| var MAX_BODY_BYTES = 64 * 1024; | ||
| function withOcGate(handler, opts) { | ||
| const wrapped = async (req, res) => { | ||
| const decision = await assertOc(req, opts); | ||
| if (opts.onDecision) opts.onDecision(req, decision); | ||
| if (decision.ok) { | ||
| req.orangecheck = decision.check; | ||
| return handler(req, res); | ||
| } | ||
| if (opts.onBlocked) { | ||
| opts.onBlocked(req, res, decision); | ||
| return; | ||
| } | ||
| res.setHeader("Cache-Control", "no-store"); | ||
| sendBlockedDefault(res, decision, opts); | ||
| }; | ||
| return wrapped; | ||
| } | ||
| async function ocGateFetch(req, opts) { | ||
| const url = new URL(req.url); | ||
| const headers = {}; | ||
| req.headers.forEach((v, k) => { | ||
| headers[k.toLowerCase()] = v; | ||
| }); | ||
| const query = {}; | ||
| url.searchParams.forEach((v, k) => { | ||
| query[k] = v; | ||
| }); | ||
| let body = void 0; | ||
| if (opts.address?.from === "body" || opts.attestationId?.from === "body" || opts.identity?.from === "body") { | ||
| try { | ||
| const cloned = req.clone(); | ||
| const contentLen = Number(cloned.headers.get("content-length") ?? "0"); | ||
| if (contentLen > MAX_BODY_BYTES) { | ||
| body = void 0; | ||
| } else { | ||
| const text = await cloned.text(); | ||
| if (text.length <= MAX_BODY_BYTES) { | ||
| body = text ? JSON.parse(text) : void 0; | ||
| } | ||
| } | ||
| } catch { | ||
| body = void 0; | ||
| } | ||
| } | ||
| const minimal = { | ||
| headers, | ||
| query, | ||
| body, | ||
| url: req.url, | ||
| method: req.method | ||
| }; | ||
| return assertOc(minimal, opts); | ||
| } | ||
| exports.ocGateFetch = ocGateFetch; | ||
| exports.withOcGate = withOcGate; | ||
| //# sourceMappingURL=next.js.map | ||
| //# sourceMappingURL=next.js.map |
| {"version":3,"sources":["../src/cache.ts","../src/core.ts","../src/next.ts"],"names":["check"],"mappings":"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","file":"next.js","sourcesContent":["import type { GateDecision } from './types';\n\ninterface Entry {\n value: GateDecision;\n expires: number;\n}\n\n/**\n * Tiny LRU + TTL cache. No deps. Good enough for per-process gate state.\n *\n * Eviction: oldest-inserted key first once max is reached. Ties to real LRU\n * (touching on read) would be a nice-to-have; in practice gate callers hit\n * the same subject repeatedly inside the TTL window so insertion-order is\n * equivalent.\n */\nexport class TtlLru {\n private readonly store = new Map<string, Entry>();\n\n constructor(\n private readonly max: number,\n private readonly ttlMs: number\n ) {}\n\n get(key: string): GateDecision | undefined {\n const entry = this.store.get(key);\n if (!entry) return undefined;\n if (Date.now() > entry.expires) {\n this.store.delete(key);\n return undefined;\n }\n return entry.value;\n }\n\n set(key: string, value: GateDecision): void {\n if (this.store.size >= this.max) {\n const first = this.store.keys().next().value;\n if (first !== undefined) this.store.delete(first);\n }\n this.store.set(key, { value, expires: Date.now() + this.ttlMs });\n }\n\n clear(): void {\n this.store.clear();\n }\n}\n","import type { GateDecision, GateOptions, MinimalReq, MinimalRes, SubjectSource } from './types';\n\nimport { check } from '@orangecheck/sdk';\n\nimport { TtlLru } from './cache';\n\nconst caches = new WeakMap<GateOptions, TtlLru>();\n\n/** Hard ceiling on per-entry TTL. Callers can't ask for a permanent grant\n * by passing a huge number — the cache isn't a session store. */\nconst MAX_CACHE_TTL_MS = 10 * 60_000; // 10 minutes\nconst DEFAULT_CACHE_TTL_MS = 60_000;\nconst DEFAULT_LOOKUP_TIMEOUT_MS = 5_000;\n\n/** Subject strings > this length are almost certainly garbage — reject at\n * the gate instead of building a 10 KB cache key. */\nconst MAX_SUBJECT_LEN = 128;\n\nfunction cacheFor(opts: GateOptions): TtlLru {\n let c = caches.get(opts);\n if (!c) {\n const ttl = Math.min(opts.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS, MAX_CACHE_TTL_MS);\n c = new TtlLru(opts.cacheMax ?? 1_000, Math.max(0, ttl));\n caches.set(opts, c);\n }\n return c;\n}\n\n/**\n * Normalize a subject so that `bc1Q…` / `bc1q…` / ` bc1q… ` can't cache-key\n * to three separate rows against the same on-chain address. For bech32\n * (segwit) and attestation IDs, lowercase is correct; for legacy base58\n * addresses the case is significant, so only lowercase bc1/tb1.\n */\nfunction normalizeSubject(kind: GateDecision['subjectKind'], raw: string): string {\n const trimmed = raw.trim();\n if (kind === 'attestation_id') return trimmed.toLowerCase();\n if (kind === 'address') {\n const low = trimmed.toLowerCase();\n if (low.startsWith('bc1') || low.startsWith('tb1') || low.startsWith('bcrt1')) return low;\n return trimmed; // base58 — keep case\n }\n // identity: `protocol:identifier` — lowercase protocol only\n const colon = trimmed.indexOf(':');\n if (colon === -1) return trimmed;\n return trimmed.slice(0, colon).toLowerCase() + ':' + trimmed.slice(colon + 1);\n}\n\n/** Track untrusted-source warnings we've already logged so we don't spam\n * the console on every request. */\nconst warnedUntrusted = new WeakSet<GateOptions>();\n\nfunction warnUntrustedOnce(opts: GateOptions, src: SubjectSource): void {\n if (opts.trustUnsafeSources || warnedUntrusted.has(opts)) return;\n if (typeof src.from === 'function') return;\n if (src.from === 'header' || src.from === 'query' || src.from === 'cookie' || src.from === 'body') {\n warnedUntrusted.add(opts);\n // eslint-disable-next-line no-console\n console.warn(\n `[orangecheck/gate] subject source \"${src.from}\" is caller-supplied — any client can set it. ` +\n `For real auth, resolve the address from a signed session (e.g., { from: (req) => req.session.verifiedAddress }) ` +\n `or pass { trustUnsafeSources: true } to silence this warning.`\n );\n }\n}\n\nfunction readHeader(req: MinimalReq, name: string): string | undefined {\n const raw = req.headers?.[name.toLowerCase()];\n if (Array.isArray(raw)) return raw[0];\n return raw;\n}\n\nfunction readCookie(req: MinimalReq, name: string): string | undefined {\n // Prefer an already-parsed cookies jar (Express w/ cookie-parser, Next API).\n if (req.cookies && name in req.cookies) return req.cookies[name];\n // Fallback: parse the raw Cookie header.\n const raw = readHeader(req, 'cookie');\n if (!raw) return undefined;\n for (const part of raw.split(';')) {\n const eq = part.indexOf('=');\n if (eq === -1) continue;\n const k = part.slice(0, eq).trim();\n if (k === name) return decodeURIComponent(part.slice(eq + 1).trim());\n }\n return undefined;\n}\n\nfunction readQuery(req: MinimalReq, name: string): string | undefined {\n const raw = req.query?.[name];\n if (Array.isArray(raw)) return raw[0];\n return raw;\n}\n\nfunction readBodyPath(req: MinimalReq, path: string): string | undefined {\n if (!req.body || typeof req.body !== 'object') return undefined;\n const parts = path.split('.');\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let cur: any = req.body;\n for (const p of parts) {\n if (cur == null) return undefined;\n cur = cur[p];\n }\n return typeof cur === 'string' ? cur : undefined;\n}\n\nfunction resolve(src: SubjectSource | undefined, req: MinimalReq): string | undefined {\n if (!src) return undefined;\n if (typeof src.from === 'function') return src.from(req);\n switch (src.from) {\n case 'header':\n return readHeader(req, src.name ?? 'x-oc-address');\n case 'cookie':\n return readCookie(req, src.name ?? 'oc_addr');\n case 'query':\n return readQuery(req, src.name ?? 'ocAddr');\n case 'body':\n return readBodyPath(req, src.path ?? 'address');\n default:\n return undefined;\n }\n}\n\n/**\n * Run the OrangeCheck lookup + threshold comparison for a request, without\n * touching the response. Framework-agnostic. Use this directly in Fastify,\n * Hono, Workers, or anywhere Express middleware doesn't fit.\n *\n * const decision = await assertOc(req, { minSats: 100_000, address: { from: 'header' } });\n * if (!decision.ok) return res.status(403).json({ error: decision.reason });\n */\nexport async function assertOc(req: MinimalReq, opts: GateOptions): Promise<GateDecision> {\n const sources = [\n ['attestation_id', opts.attestationId] as const,\n ['address', opts.address] as const,\n ['identity', opts.identity] as const,\n ];\n\n let subject: string | undefined;\n let subjectKind: GateDecision['subjectKind'];\n for (const [kind, src] of sources) {\n if (!src) continue;\n warnUntrustedOnce(opts, src);\n const v = resolve(src, req);\n if (v) {\n subject = v;\n subjectKind = kind;\n break;\n }\n }\n\n if (!subject || !subjectKind) {\n return { ok: false, reason: 'no_subject' };\n }\n\n // Clamp obviously-junk subjects before they hit the cache or the network.\n if (subject.length > MAX_SUBJECT_LEN) {\n return { ok: false, reason: 'no_subject' };\n }\n subject = normalizeSubject(subjectKind, subject);\n\n const cache = cacheFor(opts);\n const cacheKey = `${subjectKind}:${subject}:${opts.minSats ?? 0}:${opts.minDays ?? 0}`;\n const cached = cache.get(cacheKey);\n if (cached) return { ...cached, subject, subjectKind };\n\n try {\n const params: Parameters<typeof check>[0] = {\n minSats: opts.minSats,\n minDays: opts.minDays,\n ...(opts.relays ? { relays: opts.relays } : {}),\n };\n if (subjectKind === 'attestation_id') params.id = subject;\n else if (subjectKind === 'address') params.addr = subject;\n else if (subjectKind === 'identity') {\n const idx = subject.indexOf(':');\n if (idx === -1) {\n const d: GateDecision = {\n ok: false,\n reason: 'no_subject',\n subject,\n subjectKind,\n };\n cache.set(cacheKey, d);\n return d;\n }\n params.identity = {\n protocol: subject.slice(0, idx),\n identifier: subject.slice(idx + 1),\n };\n }\n\n // Hard deadline. `check()` may call out to a relay/Esplora, and we\n // do not want the gate to hang a request indefinitely.\n const timeoutMs = opts.lookupTimeoutMs ?? DEFAULT_LOOKUP_TIMEOUT_MS;\n const result = await Promise.race([\n check(params),\n new Promise<never>((_, reject) =>\n setTimeout(() => reject(new Error('lookup_timeout')), timeoutMs)\n ),\n ]);\n\n let reason: GateDecision['reason'];\n if (result.ok) reason = 'ok';\n else if (result.reasons?.includes('not_found')) reason = 'not_found';\n else if (result.reasons?.some((r) => r === 'below_min_sats' || r === 'below_min_days'))\n reason = 'below_threshold';\n else reason = 'invalid_proof';\n\n const decision: GateDecision = {\n ok: result.ok,\n reason,\n check: result,\n subject,\n subjectKind,\n };\n cache.set(cacheKey, decision);\n return decision;\n } catch (err) {\n const decision: GateDecision = {\n ok: Boolean(opts.failOpen),\n reason: opts.failOpen ? 'fail_open' : 'lookup_error',\n subject,\n subjectKind,\n };\n // Do not cache errors — we want the next request to try again.\n if (opts.onDecision) opts.onDecision(req, decision);\n // Surface only the message, not the full stack, to avoid leaking\n // internals into caller-visible logs. Callers wanting stack traces\n // should wire `onDecision` and log `err` themselves.\n // eslint-disable-next-line no-console\n console.warn(\n '[orangecheck/gate] lookup failed:',\n err instanceof Error ? err.message : String(err)\n );\n return decision;\n }\n}\n\n/**\n * Default 403 body writer. Extracted so express.ts and next.ts don't drift.\n * Respects `opts.exposeSubject` so a cookie-sourced address never leaks back\n * into a response body that might be seen by a different caller.\n */\nexport function sendBlockedDefault(\n res: MinimalRes,\n decision: GateDecision,\n opts: GateOptions\n): void {\n res.setHeader('Content-Type', 'application/json');\n res.status(decision.reason === 'no_subject' ? 401 : 403);\n const body: Record<string, unknown> = {\n error: 'orangecheck_gate_blocked',\n reason: decision.reason,\n };\n if (opts.exposeSubject && decision.subject) {\n body.subject = decision.subject;\n body.subjectKind = decision.subjectKind;\n }\n if (decision.check?.reasons) body.reasons = decision.check.reasons;\n res.json(body);\n}\n","import type { GateDecision, GateOptions, MinimalReq, MinimalRes } from './types';\n\nimport { assertOc, sendBlockedDefault } from './core';\n\n/** Cap the amount of body we'll parse for a `body: ...` subject source.\n * Without this a 100 MB POST ties up the gate before any downstream limiter\n * runs. 64 KB is plenty for a subject field. */\nconst MAX_BODY_BYTES = 64 * 1024;\n\n/**\n * Next.js Pages Router API wrapper.\n *\n * import { withOcGate } from '@orangecheck/gate';\n *\n * async function handler(req, res) {\n * // The decision is attached to req when the gate lets the call through.\n * res.json({ hello: (req as any).orangecheck.sats });\n * }\n *\n * export default withOcGate(handler, {\n * minSats: 100_000,\n * minDays: 30,\n * address: { from: 'query', name: 'addr' },\n * });\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function withOcGate<H extends (req: any, res: any) => any>(\n handler: H,\n opts: GateOptions\n): H {\n const wrapped = async (req: MinimalReq, res: MinimalRes) => {\n const decision = await assertOc(req, opts);\n if (opts.onDecision) opts.onDecision(req, decision);\n\n if (decision.ok) {\n // Attach decision to the request for the downstream handler.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (req as any).orangecheck = decision.check;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return (handler as any)(req, res);\n }\n\n if (opts.onBlocked) {\n opts.onBlocked(req, res, decision);\n return;\n }\n\n res.setHeader('Cache-Control', 'no-store');\n sendBlockedDefault(res, decision, opts);\n };\n return wrapped as unknown as H;\n}\n\n/**\n * Framework-agnostic Fetch-style guard — for Next App Router route handlers,\n * Cloudflare Workers, Hono, Bun, and friends.\n *\n * export async function POST(req: Request) {\n * const decision = await ocGateFetch(req, {\n * minSats: 100_000,\n * address: { from: 'header' },\n * });\n * if (!decision.ok) {\n * return new Response(JSON.stringify({ error: decision.reason }), { status: 403 });\n * }\n * // ... proceed\n * }\n */\nexport async function ocGateFetch(req: Request, opts: GateOptions): Promise<GateDecision> {\n const url = new URL(req.url);\n\n // Build a MinimalReq adapter over the Fetch Request.\n const headers: Record<string, string> = {};\n req.headers.forEach((v, k) => {\n headers[k.toLowerCase()] = v;\n });\n\n const query: Record<string, string> = {};\n url.searchParams.forEach((v, k) => {\n query[k] = v;\n });\n\n // Body is opaque at guard time — only parsed if the caller asks for body: *.\n // Caps at MAX_BODY_BYTES so a large POST can't stall the gate.\n let body: unknown = undefined;\n if (\n opts.address?.from === 'body' ||\n opts.attestationId?.from === 'body' ||\n opts.identity?.from === 'body'\n ) {\n try {\n const cloned = req.clone();\n const contentLen = Number(cloned.headers.get('content-length') ?? '0');\n if (contentLen > MAX_BODY_BYTES) {\n body = undefined;\n } else {\n const text = await cloned.text();\n if (text.length <= MAX_BODY_BYTES) {\n body = text ? JSON.parse(text) : undefined;\n }\n }\n } catch {\n body = undefined;\n }\n }\n\n const minimal: MinimalReq = {\n headers,\n query,\n body,\n url: req.url,\n method: req.method,\n };\n return assertOc(minimal, opts);\n}\n"]} |
+283
| import { check } from '@orangecheck/sdk'; | ||
| // src/core.ts | ||
| // src/cache.ts | ||
| var TtlLru = class { | ||
| constructor(max, ttlMs) { | ||
| this.max = max; | ||
| this.ttlMs = ttlMs; | ||
| this.store = /* @__PURE__ */ new Map(); | ||
| } | ||
| get(key) { | ||
| const entry = this.store.get(key); | ||
| if (!entry) return void 0; | ||
| if (Date.now() > entry.expires) { | ||
| this.store.delete(key); | ||
| return void 0; | ||
| } | ||
| return entry.value; | ||
| } | ||
| set(key, value) { | ||
| if (this.store.size >= this.max) { | ||
| const first = this.store.keys().next().value; | ||
| if (first !== void 0) this.store.delete(first); | ||
| } | ||
| this.store.set(key, { value, expires: Date.now() + this.ttlMs }); | ||
| } | ||
| clear() { | ||
| this.store.clear(); | ||
| } | ||
| }; | ||
| // src/core.ts | ||
| var caches = /* @__PURE__ */ new WeakMap(); | ||
| var MAX_CACHE_TTL_MS = 10 * 6e4; | ||
| var DEFAULT_CACHE_TTL_MS = 6e4; | ||
| var DEFAULT_LOOKUP_TIMEOUT_MS = 5e3; | ||
| var MAX_SUBJECT_LEN = 128; | ||
| function cacheFor(opts) { | ||
| let c = caches.get(opts); | ||
| if (!c) { | ||
| const ttl = Math.min(opts.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS, MAX_CACHE_TTL_MS); | ||
| c = new TtlLru(opts.cacheMax ?? 1e3, Math.max(0, ttl)); | ||
| caches.set(opts, c); | ||
| } | ||
| return c; | ||
| } | ||
| function normalizeSubject(kind, raw) { | ||
| const trimmed = raw.trim(); | ||
| if (kind === "attestation_id") return trimmed.toLowerCase(); | ||
| if (kind === "address") { | ||
| const low = trimmed.toLowerCase(); | ||
| if (low.startsWith("bc1") || low.startsWith("tb1") || low.startsWith("bcrt1")) return low; | ||
| return trimmed; | ||
| } | ||
| const colon = trimmed.indexOf(":"); | ||
| if (colon === -1) return trimmed; | ||
| return trimmed.slice(0, colon).toLowerCase() + ":" + trimmed.slice(colon + 1); | ||
| } | ||
| var warnedUntrusted = /* @__PURE__ */ new WeakSet(); | ||
| function warnUntrustedOnce(opts, src) { | ||
| if (opts.trustUnsafeSources || warnedUntrusted.has(opts)) return; | ||
| if (typeof src.from === "function") return; | ||
| if (src.from === "header" || src.from === "query" || src.from === "cookie" || src.from === "body") { | ||
| warnedUntrusted.add(opts); | ||
| console.warn( | ||
| `[orangecheck/gate] subject source "${src.from}" is caller-supplied \u2014 any client can set it. For real auth, resolve the address from a signed session (e.g., { from: (req) => req.session.verifiedAddress }) or pass { trustUnsafeSources: true } to silence this warning.` | ||
| ); | ||
| } | ||
| } | ||
| function readHeader(req, name) { | ||
| const raw = req.headers?.[name.toLowerCase()]; | ||
| if (Array.isArray(raw)) return raw[0]; | ||
| return raw; | ||
| } | ||
| function readCookie(req, name) { | ||
| if (req.cookies && name in req.cookies) return req.cookies[name]; | ||
| const raw = readHeader(req, "cookie"); | ||
| if (!raw) return void 0; | ||
| for (const part of raw.split(";")) { | ||
| const eq = part.indexOf("="); | ||
| if (eq === -1) continue; | ||
| const k = part.slice(0, eq).trim(); | ||
| if (k === name) return decodeURIComponent(part.slice(eq + 1).trim()); | ||
| } | ||
| return void 0; | ||
| } | ||
| function readQuery(req, name) { | ||
| const raw = req.query?.[name]; | ||
| if (Array.isArray(raw)) return raw[0]; | ||
| return raw; | ||
| } | ||
| function readBodyPath(req, path) { | ||
| if (!req.body || typeof req.body !== "object") return void 0; | ||
| const parts = path.split("."); | ||
| let cur = req.body; | ||
| for (const p of parts) { | ||
| if (cur == null) return void 0; | ||
| cur = cur[p]; | ||
| } | ||
| return typeof cur === "string" ? cur : void 0; | ||
| } | ||
| function resolve(src, req) { | ||
| if (!src) return void 0; | ||
| if (typeof src.from === "function") return src.from(req); | ||
| switch (src.from) { | ||
| case "header": | ||
| return readHeader(req, src.name ?? "x-oc-address"); | ||
| case "cookie": | ||
| return readCookie(req, src.name ?? "oc_addr"); | ||
| case "query": | ||
| return readQuery(req, src.name ?? "ocAddr"); | ||
| case "body": | ||
| return readBodyPath(req, src.path ?? "address"); | ||
| default: | ||
| return void 0; | ||
| } | ||
| } | ||
| async function assertOc(req, opts) { | ||
| const sources = [ | ||
| ["attestation_id", opts.attestationId], | ||
| ["address", opts.address], | ||
| ["identity", opts.identity] | ||
| ]; | ||
| let subject; | ||
| let subjectKind; | ||
| for (const [kind, src] of sources) { | ||
| if (!src) continue; | ||
| warnUntrustedOnce(opts, src); | ||
| const v = resolve(src, req); | ||
| if (v) { | ||
| subject = v; | ||
| subjectKind = kind; | ||
| break; | ||
| } | ||
| } | ||
| if (!subject || !subjectKind) { | ||
| return { ok: false, reason: "no_subject" }; | ||
| } | ||
| if (subject.length > MAX_SUBJECT_LEN) { | ||
| return { ok: false, reason: "no_subject" }; | ||
| } | ||
| subject = normalizeSubject(subjectKind, subject); | ||
| const cache = cacheFor(opts); | ||
| const cacheKey = `${subjectKind}:${subject}:${opts.minSats ?? 0}:${opts.minDays ?? 0}`; | ||
| const cached = cache.get(cacheKey); | ||
| if (cached) return { ...cached, subject, subjectKind }; | ||
| try { | ||
| const params = { | ||
| minSats: opts.minSats, | ||
| minDays: opts.minDays, | ||
| ...opts.relays ? { relays: opts.relays } : {} | ||
| }; | ||
| if (subjectKind === "attestation_id") params.id = subject; | ||
| else if (subjectKind === "address") params.addr = subject; | ||
| else if (subjectKind === "identity") { | ||
| const idx = subject.indexOf(":"); | ||
| if (idx === -1) { | ||
| const d = { | ||
| ok: false, | ||
| reason: "no_subject", | ||
| subject, | ||
| subjectKind | ||
| }; | ||
| cache.set(cacheKey, d); | ||
| return d; | ||
| } | ||
| params.identity = { | ||
| protocol: subject.slice(0, idx), | ||
| identifier: subject.slice(idx + 1) | ||
| }; | ||
| } | ||
| const timeoutMs = opts.lookupTimeoutMs ?? DEFAULT_LOOKUP_TIMEOUT_MS; | ||
| const result = await Promise.race([ | ||
| check(params), | ||
| new Promise( | ||
| (_, reject) => setTimeout(() => reject(new Error("lookup_timeout")), timeoutMs) | ||
| ) | ||
| ]); | ||
| let reason; | ||
| if (result.ok) reason = "ok"; | ||
| else if (result.reasons?.includes("not_found")) reason = "not_found"; | ||
| else if (result.reasons?.some((r) => r === "below_min_sats" || r === "below_min_days")) | ||
| reason = "below_threshold"; | ||
| else reason = "invalid_proof"; | ||
| const decision = { | ||
| ok: result.ok, | ||
| reason, | ||
| check: result, | ||
| subject, | ||
| subjectKind | ||
| }; | ||
| cache.set(cacheKey, decision); | ||
| return decision; | ||
| } catch (err) { | ||
| const decision = { | ||
| ok: Boolean(opts.failOpen), | ||
| reason: opts.failOpen ? "fail_open" : "lookup_error", | ||
| subject, | ||
| subjectKind | ||
| }; | ||
| if (opts.onDecision) opts.onDecision(req, decision); | ||
| console.warn( | ||
| "[orangecheck/gate] lookup failed:", | ||
| err instanceof Error ? err.message : String(err) | ||
| ); | ||
| return decision; | ||
| } | ||
| } | ||
| function sendBlockedDefault(res, decision, opts) { | ||
| res.setHeader("Content-Type", "application/json"); | ||
| res.status(decision.reason === "no_subject" ? 401 : 403); | ||
| const body = { | ||
| error: "orangecheck_gate_blocked", | ||
| reason: decision.reason | ||
| }; | ||
| if (opts.exposeSubject && decision.subject) { | ||
| body.subject = decision.subject; | ||
| body.subjectKind = decision.subjectKind; | ||
| } | ||
| if (decision.check?.reasons) body.reasons = decision.check.reasons; | ||
| res.json(body); | ||
| } | ||
| // src/next.ts | ||
| var MAX_BODY_BYTES = 64 * 1024; | ||
| function withOcGate(handler, opts) { | ||
| const wrapped = async (req, res) => { | ||
| const decision = await assertOc(req, opts); | ||
| if (opts.onDecision) opts.onDecision(req, decision); | ||
| if (decision.ok) { | ||
| req.orangecheck = decision.check; | ||
| return handler(req, res); | ||
| } | ||
| if (opts.onBlocked) { | ||
| opts.onBlocked(req, res, decision); | ||
| return; | ||
| } | ||
| res.setHeader("Cache-Control", "no-store"); | ||
| sendBlockedDefault(res, decision, opts); | ||
| }; | ||
| return wrapped; | ||
| } | ||
| async function ocGateFetch(req, opts) { | ||
| const url = new URL(req.url); | ||
| const headers = {}; | ||
| req.headers.forEach((v, k) => { | ||
| headers[k.toLowerCase()] = v; | ||
| }); | ||
| const query = {}; | ||
| url.searchParams.forEach((v, k) => { | ||
| query[k] = v; | ||
| }); | ||
| let body = void 0; | ||
| if (opts.address?.from === "body" || opts.attestationId?.from === "body" || opts.identity?.from === "body") { | ||
| try { | ||
| const cloned = req.clone(); | ||
| const contentLen = Number(cloned.headers.get("content-length") ?? "0"); | ||
| if (contentLen > MAX_BODY_BYTES) { | ||
| body = void 0; | ||
| } else { | ||
| const text = await cloned.text(); | ||
| if (text.length <= MAX_BODY_BYTES) { | ||
| body = text ? JSON.parse(text) : void 0; | ||
| } | ||
| } | ||
| } catch { | ||
| body = void 0; | ||
| } | ||
| } | ||
| const minimal = { | ||
| headers, | ||
| query, | ||
| body, | ||
| url: req.url, | ||
| method: req.method | ||
| }; | ||
| return assertOc(minimal, opts); | ||
| } | ||
| export { ocGateFetch, withOcGate }; | ||
| //# sourceMappingURL=next.mjs.map | ||
| //# sourceMappingURL=next.mjs.map |
| {"version":3,"sources":["../src/cache.ts","../src/core.ts","../src/next.ts"],"names":[],"mappings":"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","file":"next.mjs","sourcesContent":["import type { GateDecision } from './types';\n\ninterface Entry {\n value: GateDecision;\n expires: number;\n}\n\n/**\n * Tiny LRU + TTL cache. No deps. Good enough for per-process gate state.\n *\n * Eviction: oldest-inserted key first once max is reached. Ties to real LRU\n * (touching on read) would be a nice-to-have; in practice gate callers hit\n * the same subject repeatedly inside the TTL window so insertion-order is\n * equivalent.\n */\nexport class TtlLru {\n private readonly store = new Map<string, Entry>();\n\n constructor(\n private readonly max: number,\n private readonly ttlMs: number\n ) {}\n\n get(key: string): GateDecision | undefined {\n const entry = this.store.get(key);\n if (!entry) return undefined;\n if (Date.now() > entry.expires) {\n this.store.delete(key);\n return undefined;\n }\n return entry.value;\n }\n\n set(key: string, value: GateDecision): void {\n if (this.store.size >= this.max) {\n const first = this.store.keys().next().value;\n if (first !== undefined) this.store.delete(first);\n }\n this.store.set(key, { value, expires: Date.now() + this.ttlMs });\n }\n\n clear(): void {\n this.store.clear();\n }\n}\n","import type { GateDecision, GateOptions, MinimalReq, MinimalRes, SubjectSource } from './types';\n\nimport { check } from '@orangecheck/sdk';\n\nimport { TtlLru } from './cache';\n\nconst caches = new WeakMap<GateOptions, TtlLru>();\n\n/** Hard ceiling on per-entry TTL. Callers can't ask for a permanent grant\n * by passing a huge number — the cache isn't a session store. */\nconst MAX_CACHE_TTL_MS = 10 * 60_000; // 10 minutes\nconst DEFAULT_CACHE_TTL_MS = 60_000;\nconst DEFAULT_LOOKUP_TIMEOUT_MS = 5_000;\n\n/** Subject strings > this length are almost certainly garbage — reject at\n * the gate instead of building a 10 KB cache key. */\nconst MAX_SUBJECT_LEN = 128;\n\nfunction cacheFor(opts: GateOptions): TtlLru {\n let c = caches.get(opts);\n if (!c) {\n const ttl = Math.min(opts.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS, MAX_CACHE_TTL_MS);\n c = new TtlLru(opts.cacheMax ?? 1_000, Math.max(0, ttl));\n caches.set(opts, c);\n }\n return c;\n}\n\n/**\n * Normalize a subject so that `bc1Q…` / `bc1q…` / ` bc1q… ` can't cache-key\n * to three separate rows against the same on-chain address. For bech32\n * (segwit) and attestation IDs, lowercase is correct; for legacy base58\n * addresses the case is significant, so only lowercase bc1/tb1.\n */\nfunction normalizeSubject(kind: GateDecision['subjectKind'], raw: string): string {\n const trimmed = raw.trim();\n if (kind === 'attestation_id') return trimmed.toLowerCase();\n if (kind === 'address') {\n const low = trimmed.toLowerCase();\n if (low.startsWith('bc1') || low.startsWith('tb1') || low.startsWith('bcrt1')) return low;\n return trimmed; // base58 — keep case\n }\n // identity: `protocol:identifier` — lowercase protocol only\n const colon = trimmed.indexOf(':');\n if (colon === -1) return trimmed;\n return trimmed.slice(0, colon).toLowerCase() + ':' + trimmed.slice(colon + 1);\n}\n\n/** Track untrusted-source warnings we've already logged so we don't spam\n * the console on every request. */\nconst warnedUntrusted = new WeakSet<GateOptions>();\n\nfunction warnUntrustedOnce(opts: GateOptions, src: SubjectSource): void {\n if (opts.trustUnsafeSources || warnedUntrusted.has(opts)) return;\n if (typeof src.from === 'function') return;\n if (src.from === 'header' || src.from === 'query' || src.from === 'cookie' || src.from === 'body') {\n warnedUntrusted.add(opts);\n // eslint-disable-next-line no-console\n console.warn(\n `[orangecheck/gate] subject source \"${src.from}\" is caller-supplied — any client can set it. ` +\n `For real auth, resolve the address from a signed session (e.g., { from: (req) => req.session.verifiedAddress }) ` +\n `or pass { trustUnsafeSources: true } to silence this warning.`\n );\n }\n}\n\nfunction readHeader(req: MinimalReq, name: string): string | undefined {\n const raw = req.headers?.[name.toLowerCase()];\n if (Array.isArray(raw)) return raw[0];\n return raw;\n}\n\nfunction readCookie(req: MinimalReq, name: string): string | undefined {\n // Prefer an already-parsed cookies jar (Express w/ cookie-parser, Next API).\n if (req.cookies && name in req.cookies) return req.cookies[name];\n // Fallback: parse the raw Cookie header.\n const raw = readHeader(req, 'cookie');\n if (!raw) return undefined;\n for (const part of raw.split(';')) {\n const eq = part.indexOf('=');\n if (eq === -1) continue;\n const k = part.slice(0, eq).trim();\n if (k === name) return decodeURIComponent(part.slice(eq + 1).trim());\n }\n return undefined;\n}\n\nfunction readQuery(req: MinimalReq, name: string): string | undefined {\n const raw = req.query?.[name];\n if (Array.isArray(raw)) return raw[0];\n return raw;\n}\n\nfunction readBodyPath(req: MinimalReq, path: string): string | undefined {\n if (!req.body || typeof req.body !== 'object') return undefined;\n const parts = path.split('.');\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let cur: any = req.body;\n for (const p of parts) {\n if (cur == null) return undefined;\n cur = cur[p];\n }\n return typeof cur === 'string' ? cur : undefined;\n}\n\nfunction resolve(src: SubjectSource | undefined, req: MinimalReq): string | undefined {\n if (!src) return undefined;\n if (typeof src.from === 'function') return src.from(req);\n switch (src.from) {\n case 'header':\n return readHeader(req, src.name ?? 'x-oc-address');\n case 'cookie':\n return readCookie(req, src.name ?? 'oc_addr');\n case 'query':\n return readQuery(req, src.name ?? 'ocAddr');\n case 'body':\n return readBodyPath(req, src.path ?? 'address');\n default:\n return undefined;\n }\n}\n\n/**\n * Run the OrangeCheck lookup + threshold comparison for a request, without\n * touching the response. Framework-agnostic. Use this directly in Fastify,\n * Hono, Workers, or anywhere Express middleware doesn't fit.\n *\n * const decision = await assertOc(req, { minSats: 100_000, address: { from: 'header' } });\n * if (!decision.ok) return res.status(403).json({ error: decision.reason });\n */\nexport async function assertOc(req: MinimalReq, opts: GateOptions): Promise<GateDecision> {\n const sources = [\n ['attestation_id', opts.attestationId] as const,\n ['address', opts.address] as const,\n ['identity', opts.identity] as const,\n ];\n\n let subject: string | undefined;\n let subjectKind: GateDecision['subjectKind'];\n for (const [kind, src] of sources) {\n if (!src) continue;\n warnUntrustedOnce(opts, src);\n const v = resolve(src, req);\n if (v) {\n subject = v;\n subjectKind = kind;\n break;\n }\n }\n\n if (!subject || !subjectKind) {\n return { ok: false, reason: 'no_subject' };\n }\n\n // Clamp obviously-junk subjects before they hit the cache or the network.\n if (subject.length > MAX_SUBJECT_LEN) {\n return { ok: false, reason: 'no_subject' };\n }\n subject = normalizeSubject(subjectKind, subject);\n\n const cache = cacheFor(opts);\n const cacheKey = `${subjectKind}:${subject}:${opts.minSats ?? 0}:${opts.minDays ?? 0}`;\n const cached = cache.get(cacheKey);\n if (cached) return { ...cached, subject, subjectKind };\n\n try {\n const params: Parameters<typeof check>[0] = {\n minSats: opts.minSats,\n minDays: opts.minDays,\n ...(opts.relays ? { relays: opts.relays } : {}),\n };\n if (subjectKind === 'attestation_id') params.id = subject;\n else if (subjectKind === 'address') params.addr = subject;\n else if (subjectKind === 'identity') {\n const idx = subject.indexOf(':');\n if (idx === -1) {\n const d: GateDecision = {\n ok: false,\n reason: 'no_subject',\n subject,\n subjectKind,\n };\n cache.set(cacheKey, d);\n return d;\n }\n params.identity = {\n protocol: subject.slice(0, idx),\n identifier: subject.slice(idx + 1),\n };\n }\n\n // Hard deadline. `check()` may call out to a relay/Esplora, and we\n // do not want the gate to hang a request indefinitely.\n const timeoutMs = opts.lookupTimeoutMs ?? DEFAULT_LOOKUP_TIMEOUT_MS;\n const result = await Promise.race([\n check(params),\n new Promise<never>((_, reject) =>\n setTimeout(() => reject(new Error('lookup_timeout')), timeoutMs)\n ),\n ]);\n\n let reason: GateDecision['reason'];\n if (result.ok) reason = 'ok';\n else if (result.reasons?.includes('not_found')) reason = 'not_found';\n else if (result.reasons?.some((r) => r === 'below_min_sats' || r === 'below_min_days'))\n reason = 'below_threshold';\n else reason = 'invalid_proof';\n\n const decision: GateDecision = {\n ok: result.ok,\n reason,\n check: result,\n subject,\n subjectKind,\n };\n cache.set(cacheKey, decision);\n return decision;\n } catch (err) {\n const decision: GateDecision = {\n ok: Boolean(opts.failOpen),\n reason: opts.failOpen ? 'fail_open' : 'lookup_error',\n subject,\n subjectKind,\n };\n // Do not cache errors — we want the next request to try again.\n if (opts.onDecision) opts.onDecision(req, decision);\n // Surface only the message, not the full stack, to avoid leaking\n // internals into caller-visible logs. Callers wanting stack traces\n // should wire `onDecision` and log `err` themselves.\n // eslint-disable-next-line no-console\n console.warn(\n '[orangecheck/gate] lookup failed:',\n err instanceof Error ? err.message : String(err)\n );\n return decision;\n }\n}\n\n/**\n * Default 403 body writer. Extracted so express.ts and next.ts don't drift.\n * Respects `opts.exposeSubject` so a cookie-sourced address never leaks back\n * into a response body that might be seen by a different caller.\n */\nexport function sendBlockedDefault(\n res: MinimalRes,\n decision: GateDecision,\n opts: GateOptions\n): void {\n res.setHeader('Content-Type', 'application/json');\n res.status(decision.reason === 'no_subject' ? 401 : 403);\n const body: Record<string, unknown> = {\n error: 'orangecheck_gate_blocked',\n reason: decision.reason,\n };\n if (opts.exposeSubject && decision.subject) {\n body.subject = decision.subject;\n body.subjectKind = decision.subjectKind;\n }\n if (decision.check?.reasons) body.reasons = decision.check.reasons;\n res.json(body);\n}\n","import type { GateDecision, GateOptions, MinimalReq, MinimalRes } from './types';\n\nimport { assertOc, sendBlockedDefault } from './core';\n\n/** Cap the amount of body we'll parse for a `body: ...` subject source.\n * Without this a 100 MB POST ties up the gate before any downstream limiter\n * runs. 64 KB is plenty for a subject field. */\nconst MAX_BODY_BYTES = 64 * 1024;\n\n/**\n * Next.js Pages Router API wrapper.\n *\n * import { withOcGate } from '@orangecheck/gate';\n *\n * async function handler(req, res) {\n * // The decision is attached to req when the gate lets the call through.\n * res.json({ hello: (req as any).orangecheck.sats });\n * }\n *\n * export default withOcGate(handler, {\n * minSats: 100_000,\n * minDays: 30,\n * address: { from: 'query', name: 'addr' },\n * });\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function withOcGate<H extends (req: any, res: any) => any>(\n handler: H,\n opts: GateOptions\n): H {\n const wrapped = async (req: MinimalReq, res: MinimalRes) => {\n const decision = await assertOc(req, opts);\n if (opts.onDecision) opts.onDecision(req, decision);\n\n if (decision.ok) {\n // Attach decision to the request for the downstream handler.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (req as any).orangecheck = decision.check;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return (handler as any)(req, res);\n }\n\n if (opts.onBlocked) {\n opts.onBlocked(req, res, decision);\n return;\n }\n\n res.setHeader('Cache-Control', 'no-store');\n sendBlockedDefault(res, decision, opts);\n };\n return wrapped as unknown as H;\n}\n\n/**\n * Framework-agnostic Fetch-style guard — for Next App Router route handlers,\n * Cloudflare Workers, Hono, Bun, and friends.\n *\n * export async function POST(req: Request) {\n * const decision = await ocGateFetch(req, {\n * minSats: 100_000,\n * address: { from: 'header' },\n * });\n * if (!decision.ok) {\n * return new Response(JSON.stringify({ error: decision.reason }), { status: 403 });\n * }\n * // ... proceed\n * }\n */\nexport async function ocGateFetch(req: Request, opts: GateOptions): Promise<GateDecision> {\n const url = new URL(req.url);\n\n // Build a MinimalReq adapter over the Fetch Request.\n const headers: Record<string, string> = {};\n req.headers.forEach((v, k) => {\n headers[k.toLowerCase()] = v;\n });\n\n const query: Record<string, string> = {};\n url.searchParams.forEach((v, k) => {\n query[k] = v;\n });\n\n // Body is opaque at guard time — only parsed if the caller asks for body: *.\n // Caps at MAX_BODY_BYTES so a large POST can't stall the gate.\n let body: unknown = undefined;\n if (\n opts.address?.from === 'body' ||\n opts.attestationId?.from === 'body' ||\n opts.identity?.from === 'body'\n ) {\n try {\n const cloned = req.clone();\n const contentLen = Number(cloned.headers.get('content-length') ?? '0');\n if (contentLen > MAX_BODY_BYTES) {\n body = undefined;\n } else {\n const text = await cloned.text();\n if (text.length <= MAX_BODY_BYTES) {\n body = text ? JSON.parse(text) : undefined;\n }\n }\n } catch {\n body = undefined;\n }\n }\n\n const minimal: MinimalReq = {\n headers,\n query,\n body,\n url: req.url,\n method: req.method,\n };\n return assertOc(minimal, opts);\n}\n"]} |
| import { CheckResult } from '@orangecheck/sdk'; | ||
| type SubjectSource = { | ||
| from: 'header'; | ||
| name?: string; | ||
| } | { | ||
| from: 'cookie'; | ||
| name?: string; | ||
| } | { | ||
| from: 'query'; | ||
| name?: string; | ||
| } | { | ||
| from: 'body'; | ||
| path?: string; | ||
| } | { | ||
| from: (req: any) => string | undefined; | ||
| }; | ||
| interface MinimalReq { | ||
| headers?: Record<string, string | string[] | undefined>; | ||
| cookies?: Record<string, string | undefined>; | ||
| query?: Record<string, string | string[] | undefined>; | ||
| body?: any; | ||
| url?: string; | ||
| method?: string; | ||
| } | ||
| interface MinimalRes { | ||
| status(code: number): MinimalRes; | ||
| json(body: unknown): MinimalRes | void; | ||
| setHeader(name: string, value: string | number | readonly string[]): MinimalRes | void; | ||
| end?: (...args: any[]) => void; | ||
| } | ||
| interface GateOptions { | ||
| minSats?: number; | ||
| minDays?: number; | ||
| address?: SubjectSource; | ||
| attestationId?: SubjectSource; | ||
| identity?: SubjectSource; | ||
| cacheTtlMs?: number; | ||
| cacheMax?: number; | ||
| failOpen?: boolean; | ||
| relays?: string[]; | ||
| lookupTimeoutMs?: number; | ||
| trustUnsafeSources?: boolean; | ||
| exposeSubject?: boolean; | ||
| onDecision?: (req: MinimalReq, decision: GateDecision) => void; | ||
| onBlocked?: (req: MinimalReq, res: MinimalRes, decision: GateDecision) => void; | ||
| } | ||
| interface GateDecision { | ||
| ok: boolean; | ||
| reason: 'ok' | 'no_subject' | 'below_threshold' | 'invalid_proof' | 'not_found' | 'lookup_error' | 'fail_open'; | ||
| check?: CheckResult; | ||
| subject?: string; | ||
| subjectKind?: 'address' | 'attestation_id' | 'identity'; | ||
| } | ||
| export type { GateOptions as G, MinimalReq as M, SubjectSource as S, GateDecision as a, MinimalRes as b }; |
| import { CheckResult } from '@orangecheck/sdk'; | ||
| type SubjectSource = { | ||
| from: 'header'; | ||
| name?: string; | ||
| } | { | ||
| from: 'cookie'; | ||
| name?: string; | ||
| } | { | ||
| from: 'query'; | ||
| name?: string; | ||
| } | { | ||
| from: 'body'; | ||
| path?: string; | ||
| } | { | ||
| from: (req: any) => string | undefined; | ||
| }; | ||
| interface MinimalReq { | ||
| headers?: Record<string, string | string[] | undefined>; | ||
| cookies?: Record<string, string | undefined>; | ||
| query?: Record<string, string | string[] | undefined>; | ||
| body?: any; | ||
| url?: string; | ||
| method?: string; | ||
| } | ||
| interface MinimalRes { | ||
| status(code: number): MinimalRes; | ||
| json(body: unknown): MinimalRes | void; | ||
| setHeader(name: string, value: string | number | readonly string[]): MinimalRes | void; | ||
| end?: (...args: any[]) => void; | ||
| } | ||
| interface GateOptions { | ||
| minSats?: number; | ||
| minDays?: number; | ||
| address?: SubjectSource; | ||
| attestationId?: SubjectSource; | ||
| identity?: SubjectSource; | ||
| cacheTtlMs?: number; | ||
| cacheMax?: number; | ||
| failOpen?: boolean; | ||
| relays?: string[]; | ||
| lookupTimeoutMs?: number; | ||
| trustUnsafeSources?: boolean; | ||
| exposeSubject?: boolean; | ||
| onDecision?: (req: MinimalReq, decision: GateDecision) => void; | ||
| onBlocked?: (req: MinimalReq, res: MinimalRes, decision: GateDecision) => void; | ||
| } | ||
| interface GateDecision { | ||
| ok: boolean; | ||
| reason: 'ok' | 'no_subject' | 'below_threshold' | 'invalid_proof' | 'not_found' | 'lookup_error' | 'fail_open'; | ||
| check?: CheckResult; | ||
| subject?: string; | ||
| subjectKind?: 'address' | 'attestation_id' | 'identity'; | ||
| } | ||
| export type { GateOptions as G, MinimalReq as M, SubjectSource as S, GateDecision as a, MinimalRes as b }; |
+8
-60
@@ -1,63 +0,11 @@ | ||
| import { CheckResult } from '@orangecheck/sdk'; | ||
| import { M as MinimalReq, G as GateOptions, a as GateDecision } from './types-BWQAPjI8.mjs'; | ||
| export { b as MinimalRes, S as SubjectSource } from './types-BWQAPjI8.mjs'; | ||
| export { ocGate } from './express.mjs'; | ||
| export { ocGateFastify } from './fastify.mjs'; | ||
| export { ocGateHono } from './hono.mjs'; | ||
| export { ocGateFetch, withOcGate } from './next.mjs'; | ||
| import '@orangecheck/sdk'; | ||
| type SubjectSource = { | ||
| from: 'header'; | ||
| name?: string; | ||
| } | { | ||
| from: 'cookie'; | ||
| name?: string; | ||
| } | { | ||
| from: 'query'; | ||
| name?: string; | ||
| } | { | ||
| from: 'body'; | ||
| path?: string; | ||
| } | { | ||
| from: (req: any) => string | undefined; | ||
| }; | ||
| interface MinimalReq { | ||
| headers?: Record<string, string | string[] | undefined>; | ||
| cookies?: Record<string, string | undefined>; | ||
| query?: Record<string, string | string[] | undefined>; | ||
| body?: any; | ||
| url?: string; | ||
| method?: string; | ||
| } | ||
| interface MinimalRes { | ||
| status(code: number): MinimalRes; | ||
| json(body: unknown): MinimalRes | void; | ||
| setHeader(name: string, value: string | number | readonly string[]): MinimalRes | void; | ||
| end?: (...args: any[]) => void; | ||
| } | ||
| interface GateOptions { | ||
| minSats?: number; | ||
| minDays?: number; | ||
| address?: SubjectSource; | ||
| attestationId?: SubjectSource; | ||
| identity?: SubjectSource; | ||
| cacheTtlMs?: number; | ||
| cacheMax?: number; | ||
| failOpen?: boolean; | ||
| relays?: string[]; | ||
| lookupTimeoutMs?: number; | ||
| trustUnsafeSources?: boolean; | ||
| exposeSubject?: boolean; | ||
| onDecision?: (req: MinimalReq, decision: GateDecision) => void; | ||
| onBlocked?: (req: MinimalReq, res: MinimalRes, decision: GateDecision) => void; | ||
| } | ||
| interface GateDecision { | ||
| ok: boolean; | ||
| reason: 'ok' | 'no_subject' | 'below_threshold' | 'invalid_proof' | 'not_found' | 'lookup_error' | 'fail_open'; | ||
| check?: CheckResult; | ||
| subject?: string; | ||
| subjectKind?: 'address' | 'attestation_id' | 'identity'; | ||
| } | ||
| declare function assertOc(req: MinimalReq, opts: GateOptions): Promise<GateDecision>; | ||
| declare function ocGate(opts: GateOptions): (req: MinimalReq, res: MinimalRes, next: (err?: unknown) => void) => Promise<void>; | ||
| declare function withOcGate<H extends (req: any, res: any) => any>(handler: H, opts: GateOptions): H; | ||
| declare function ocGateFetch(req: Request, opts: GateOptions): Promise<GateDecision>; | ||
| export { type GateDecision, type GateOptions, type MinimalReq, type MinimalRes, type SubjectSource, assertOc, ocGate, ocGateFetch, withOcGate }; | ||
| export { GateDecision, GateOptions, MinimalReq, assertOc }; |
+8
-60
@@ -1,63 +0,11 @@ | ||
| import { CheckResult } from '@orangecheck/sdk'; | ||
| import { M as MinimalReq, G as GateOptions, a as GateDecision } from './types-BWQAPjI8.js'; | ||
| export { b as MinimalRes, S as SubjectSource } from './types-BWQAPjI8.js'; | ||
| export { ocGate } from './express.js'; | ||
| export { ocGateFastify } from './fastify.js'; | ||
| export { ocGateHono } from './hono.js'; | ||
| export { ocGateFetch, withOcGate } from './next.js'; | ||
| import '@orangecheck/sdk'; | ||
| type SubjectSource = { | ||
| from: 'header'; | ||
| name?: string; | ||
| } | { | ||
| from: 'cookie'; | ||
| name?: string; | ||
| } | { | ||
| from: 'query'; | ||
| name?: string; | ||
| } | { | ||
| from: 'body'; | ||
| path?: string; | ||
| } | { | ||
| from: (req: any) => string | undefined; | ||
| }; | ||
| interface MinimalReq { | ||
| headers?: Record<string, string | string[] | undefined>; | ||
| cookies?: Record<string, string | undefined>; | ||
| query?: Record<string, string | string[] | undefined>; | ||
| body?: any; | ||
| url?: string; | ||
| method?: string; | ||
| } | ||
| interface MinimalRes { | ||
| status(code: number): MinimalRes; | ||
| json(body: unknown): MinimalRes | void; | ||
| setHeader(name: string, value: string | number | readonly string[]): MinimalRes | void; | ||
| end?: (...args: any[]) => void; | ||
| } | ||
| interface GateOptions { | ||
| minSats?: number; | ||
| minDays?: number; | ||
| address?: SubjectSource; | ||
| attestationId?: SubjectSource; | ||
| identity?: SubjectSource; | ||
| cacheTtlMs?: number; | ||
| cacheMax?: number; | ||
| failOpen?: boolean; | ||
| relays?: string[]; | ||
| lookupTimeoutMs?: number; | ||
| trustUnsafeSources?: boolean; | ||
| exposeSubject?: boolean; | ||
| onDecision?: (req: MinimalReq, decision: GateDecision) => void; | ||
| onBlocked?: (req: MinimalReq, res: MinimalRes, decision: GateDecision) => void; | ||
| } | ||
| interface GateDecision { | ||
| ok: boolean; | ||
| reason: 'ok' | 'no_subject' | 'below_threshold' | 'invalid_proof' | 'not_found' | 'lookup_error' | 'fail_open'; | ||
| check?: CheckResult; | ||
| subject?: string; | ||
| subjectKind?: 'address' | 'attestation_id' | 'identity'; | ||
| } | ||
| declare function assertOc(req: MinimalReq, opts: GateOptions): Promise<GateDecision>; | ||
| declare function ocGate(opts: GateOptions): (req: MinimalReq, res: MinimalRes, next: (err?: unknown) => void) => Promise<void>; | ||
| declare function withOcGate<H extends (req: any, res: any) => any>(handler: H, opts: GateOptions): H; | ||
| declare function ocGateFetch(req: Request, opts: GateOptions): Promise<GateDecision>; | ||
| export { type GateDecision, type GateOptions, type MinimalReq, type MinimalRes, type SubjectSource, assertOc, ocGate, ocGateFetch, withOcGate }; | ||
| export { GateDecision, GateOptions, MinimalReq, assertOc }; |
+88
-0
@@ -245,2 +245,53 @@ 'use strict'; | ||
| // src/fastify.ts | ||
| function ocGateFastify(opts) { | ||
| return async function ocPreHandler(req, reply) { | ||
| const minimal = { | ||
| headers: req.headers ?? {}, | ||
| query: req.query ?? {}, | ||
| body: req.body, | ||
| url: req.url, | ||
| method: req.method, | ||
| cookies: req.cookies | ||
| }; | ||
| const decision = await assertOc(minimal, opts); | ||
| if (opts.onDecision) opts.onDecision(minimal, decision); | ||
| if (decision.ok) return; | ||
| if (opts.onBlocked) { | ||
| const shim2 = { | ||
| status(code) { | ||
| reply.code(code); | ||
| return shim2; | ||
| }, | ||
| json(body) { | ||
| reply.send(body); | ||
| return shim2; | ||
| }, | ||
| setHeader(name, value) { | ||
| reply.header(name, value); | ||
| return shim2; | ||
| } | ||
| }; | ||
| opts.onBlocked(minimal, shim2, decision); | ||
| return; | ||
| } | ||
| reply.header("Cache-Control", "no-store"); | ||
| const shim = { | ||
| status(code) { | ||
| reply.code(code); | ||
| return shim; | ||
| }, | ||
| json(body) { | ||
| reply.send(body); | ||
| return shim; | ||
| }, | ||
| setHeader(name, value) { | ||
| reply.header(name, value); | ||
| return shim; | ||
| } | ||
| }; | ||
| sendBlockedDefault(shim, decision, opts); | ||
| }; | ||
| } | ||
| // src/next.ts | ||
@@ -302,7 +353,44 @@ var MAX_BODY_BYTES = 64 * 1024; | ||
| // src/hono.ts | ||
| function ocGateHono(opts) { | ||
| return async function ocHonoMiddleware(c, next) { | ||
| const req = c.req.raw ?? c.req; | ||
| const decision = await ocGateFetch(req, opts); | ||
| if (opts.onDecision) { | ||
| const headers = {}; | ||
| req.headers.forEach((v, k) => { | ||
| headers[k.toLowerCase()] = v; | ||
| }); | ||
| opts.onDecision({ headers, url: req.url, method: req.method }, decision); | ||
| } | ||
| if (decision.ok) { | ||
| await next(); | ||
| return; | ||
| } | ||
| const body = { | ||
| error: "orangecheck_gate_blocked", | ||
| reason: decision.reason | ||
| }; | ||
| if (opts.exposeSubject && decision.subject) { | ||
| body.subject = decision.subject; | ||
| body.subjectKind = decision.subjectKind; | ||
| } | ||
| if (decision.check?.reasons) body.reasons = decision.check.reasons; | ||
| return new Response(JSON.stringify(body), { | ||
| status: decision.reason === "no_subject" ? 401 : 403, | ||
| headers: { | ||
| "Content-Type": "application/json", | ||
| "Cache-Control": "no-store" | ||
| } | ||
| }); | ||
| }; | ||
| } | ||
| exports.assertOc = assertOc; | ||
| exports.ocGate = ocGate; | ||
| exports.ocGateFastify = ocGateFastify; | ||
| exports.ocGateFetch = ocGateFetch; | ||
| exports.ocGateHono = ocGateHono; | ||
| exports.withOcGate = withOcGate; | ||
| //# sourceMappingURL=index.js.map | ||
| //# sourceMappingURL=index.js.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"sources":["../src/cache.ts","../src/core.ts","../src/express.ts","../src/next.ts"],"names":["check"],"mappings":"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","file":"index.js","sourcesContent":["import type { GateDecision } from './types';\n\ninterface Entry {\n value: GateDecision;\n expires: number;\n}\n\n/**\n * Tiny LRU + TTL cache. No deps. Good enough for per-process gate state.\n *\n * Eviction: oldest-inserted key first once max is reached. Ties to real LRU\n * (touching on read) would be a nice-to-have; in practice gate callers hit\n * the same subject repeatedly inside the TTL window so insertion-order is\n * equivalent.\n */\nexport class TtlLru {\n private readonly store = new Map<string, Entry>();\n\n constructor(\n private readonly max: number,\n private readonly ttlMs: number\n ) {}\n\n get(key: string): GateDecision | undefined {\n const entry = this.store.get(key);\n if (!entry) return undefined;\n if (Date.now() > entry.expires) {\n this.store.delete(key);\n return undefined;\n }\n return entry.value;\n }\n\n set(key: string, value: GateDecision): void {\n if (this.store.size >= this.max) {\n const first = this.store.keys().next().value;\n if (first !== undefined) this.store.delete(first);\n }\n this.store.set(key, { value, expires: Date.now() + this.ttlMs });\n }\n\n clear(): void {\n this.store.clear();\n }\n}\n","import type { GateDecision, GateOptions, MinimalReq, MinimalRes, SubjectSource } from './types';\n\nimport { check } from '@orangecheck/sdk';\n\nimport { TtlLru } from './cache';\n\nconst caches = new WeakMap<GateOptions, TtlLru>();\n\n/** Hard ceiling on per-entry TTL. Callers can't ask for a permanent grant\n * by passing a huge number — the cache isn't a session store. */\nconst MAX_CACHE_TTL_MS = 10 * 60_000; // 10 minutes\nconst DEFAULT_CACHE_TTL_MS = 60_000;\nconst DEFAULT_LOOKUP_TIMEOUT_MS = 5_000;\n\n/** Subject strings > this length are almost certainly garbage — reject at\n * the gate instead of building a 10 KB cache key. */\nconst MAX_SUBJECT_LEN = 128;\n\nfunction cacheFor(opts: GateOptions): TtlLru {\n let c = caches.get(opts);\n if (!c) {\n const ttl = Math.min(opts.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS, MAX_CACHE_TTL_MS);\n c = new TtlLru(opts.cacheMax ?? 1_000, Math.max(0, ttl));\n caches.set(opts, c);\n }\n return c;\n}\n\n/**\n * Normalize a subject so that `bc1Q…` / `bc1q…` / ` bc1q… ` can't cache-key\n * to three separate rows against the same on-chain address. For bech32\n * (segwit) and attestation IDs, lowercase is correct; for legacy base58\n * addresses the case is significant, so only lowercase bc1/tb1.\n */\nfunction normalizeSubject(kind: GateDecision['subjectKind'], raw: string): string {\n const trimmed = raw.trim();\n if (kind === 'attestation_id') return trimmed.toLowerCase();\n if (kind === 'address') {\n const low = trimmed.toLowerCase();\n if (low.startsWith('bc1') || low.startsWith('tb1') || low.startsWith('bcrt1')) return low;\n return trimmed; // base58 — keep case\n }\n // identity: `protocol:identifier` — lowercase protocol only\n const colon = trimmed.indexOf(':');\n if (colon === -1) return trimmed;\n return trimmed.slice(0, colon).toLowerCase() + ':' + trimmed.slice(colon + 1);\n}\n\n/** Track untrusted-source warnings we've already logged so we don't spam\n * the console on every request. */\nconst warnedUntrusted = new WeakSet<GateOptions>();\n\nfunction warnUntrustedOnce(opts: GateOptions, src: SubjectSource): void {\n if (opts.trustUnsafeSources || warnedUntrusted.has(opts)) return;\n if (typeof src.from === 'function') return;\n if (src.from === 'header' || src.from === 'query' || src.from === 'cookie' || src.from === 'body') {\n warnedUntrusted.add(opts);\n // eslint-disable-next-line no-console\n console.warn(\n `[orangecheck/gate] subject source \"${src.from}\" is caller-supplied — any client can set it. ` +\n `For real auth, resolve the address from a signed session (e.g., { from: (req) => req.session.verifiedAddress }) ` +\n `or pass { trustUnsafeSources: true } to silence this warning.`\n );\n }\n}\n\nfunction readHeader(req: MinimalReq, name: string): string | undefined {\n const raw = req.headers?.[name.toLowerCase()];\n if (Array.isArray(raw)) return raw[0];\n return raw;\n}\n\nfunction readCookie(req: MinimalReq, name: string): string | undefined {\n // Prefer an already-parsed cookies jar (Express w/ cookie-parser, Next API).\n if (req.cookies && name in req.cookies) return req.cookies[name];\n // Fallback: parse the raw Cookie header.\n const raw = readHeader(req, 'cookie');\n if (!raw) return undefined;\n for (const part of raw.split(';')) {\n const eq = part.indexOf('=');\n if (eq === -1) continue;\n const k = part.slice(0, eq).trim();\n if (k === name) return decodeURIComponent(part.slice(eq + 1).trim());\n }\n return undefined;\n}\n\nfunction readQuery(req: MinimalReq, name: string): string | undefined {\n const raw = req.query?.[name];\n if (Array.isArray(raw)) return raw[0];\n return raw;\n}\n\nfunction readBodyPath(req: MinimalReq, path: string): string | undefined {\n if (!req.body || typeof req.body !== 'object') return undefined;\n const parts = path.split('.');\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let cur: any = req.body;\n for (const p of parts) {\n if (cur == null) return undefined;\n cur = cur[p];\n }\n return typeof cur === 'string' ? cur : undefined;\n}\n\nfunction resolve(src: SubjectSource | undefined, req: MinimalReq): string | undefined {\n if (!src) return undefined;\n if (typeof src.from === 'function') return src.from(req);\n switch (src.from) {\n case 'header':\n return readHeader(req, src.name ?? 'x-oc-address');\n case 'cookie':\n return readCookie(req, src.name ?? 'oc_addr');\n case 'query':\n return readQuery(req, src.name ?? 'ocAddr');\n case 'body':\n return readBodyPath(req, src.path ?? 'address');\n default:\n return undefined;\n }\n}\n\n/**\n * Run the OrangeCheck lookup + threshold comparison for a request, without\n * touching the response. Framework-agnostic. Use this directly in Fastify,\n * Hono, Workers, or anywhere Express middleware doesn't fit.\n *\n * const decision = await assertOc(req, { minSats: 100_000, address: { from: 'header' } });\n * if (!decision.ok) return res.status(403).json({ error: decision.reason });\n */\nexport async function assertOc(req: MinimalReq, opts: GateOptions): Promise<GateDecision> {\n const sources = [\n ['attestation_id', opts.attestationId] as const,\n ['address', opts.address] as const,\n ['identity', opts.identity] as const,\n ];\n\n let subject: string | undefined;\n let subjectKind: GateDecision['subjectKind'];\n for (const [kind, src] of sources) {\n if (!src) continue;\n warnUntrustedOnce(opts, src);\n const v = resolve(src, req);\n if (v) {\n subject = v;\n subjectKind = kind;\n break;\n }\n }\n\n if (!subject || !subjectKind) {\n return { ok: false, reason: 'no_subject' };\n }\n\n // Clamp obviously-junk subjects before they hit the cache or the network.\n if (subject.length > MAX_SUBJECT_LEN) {\n return { ok: false, reason: 'no_subject' };\n }\n subject = normalizeSubject(subjectKind, subject);\n\n const cache = cacheFor(opts);\n const cacheKey = `${subjectKind}:${subject}:${opts.minSats ?? 0}:${opts.minDays ?? 0}`;\n const cached = cache.get(cacheKey);\n if (cached) return { ...cached, subject, subjectKind };\n\n try {\n const params: Parameters<typeof check>[0] = {\n minSats: opts.minSats,\n minDays: opts.minDays,\n ...(opts.relays ? { relays: opts.relays } : {}),\n };\n if (subjectKind === 'attestation_id') params.id = subject;\n else if (subjectKind === 'address') params.addr = subject;\n else if (subjectKind === 'identity') {\n const idx = subject.indexOf(':');\n if (idx === -1) {\n const d: GateDecision = {\n ok: false,\n reason: 'no_subject',\n subject,\n subjectKind,\n };\n cache.set(cacheKey, d);\n return d;\n }\n params.identity = {\n protocol: subject.slice(0, idx),\n identifier: subject.slice(idx + 1),\n };\n }\n\n // Hard deadline. `check()` may call out to a relay/Esplora, and we\n // do not want the gate to hang a request indefinitely.\n const timeoutMs = opts.lookupTimeoutMs ?? DEFAULT_LOOKUP_TIMEOUT_MS;\n const result = await Promise.race([\n check(params),\n new Promise<never>((_, reject) =>\n setTimeout(() => reject(new Error('lookup_timeout')), timeoutMs)\n ),\n ]);\n\n let reason: GateDecision['reason'];\n if (result.ok) reason = 'ok';\n else if (result.reasons?.includes('not_found')) reason = 'not_found';\n else if (result.reasons?.some((r) => r === 'below_min_sats' || r === 'below_min_days'))\n reason = 'below_threshold';\n else reason = 'invalid_proof';\n\n const decision: GateDecision = {\n ok: result.ok,\n reason,\n check: result,\n subject,\n subjectKind,\n };\n cache.set(cacheKey, decision);\n return decision;\n } catch (err) {\n const decision: GateDecision = {\n ok: Boolean(opts.failOpen),\n reason: opts.failOpen ? 'fail_open' : 'lookup_error',\n subject,\n subjectKind,\n };\n // Do not cache errors — we want the next request to try again.\n if (opts.onDecision) opts.onDecision(req, decision);\n // Surface only the message, not the full stack, to avoid leaking\n // internals into caller-visible logs. Callers wanting stack traces\n // should wire `onDecision` and log `err` themselves.\n // eslint-disable-next-line no-console\n console.warn(\n '[orangecheck/gate] lookup failed:',\n err instanceof Error ? err.message : String(err)\n );\n return decision;\n }\n}\n\n/**\n * Default 403 body writer. Extracted so express.ts and next.ts don't drift.\n * Respects `opts.exposeSubject` so a cookie-sourced address never leaks back\n * into a response body that might be seen by a different caller.\n */\nexport function sendBlockedDefault(\n res: MinimalRes,\n decision: GateDecision,\n opts: GateOptions\n): void {\n res.setHeader('Content-Type', 'application/json');\n res.status(decision.reason === 'no_subject' ? 401 : 403);\n const body: Record<string, unknown> = {\n error: 'orangecheck_gate_blocked',\n reason: decision.reason,\n };\n if (opts.exposeSubject && decision.subject) {\n body.subject = decision.subject;\n body.subjectKind = decision.subjectKind;\n }\n if (decision.check?.reasons) body.reasons = decision.check.reasons;\n res.json(body);\n}\n","import type { GateOptions, MinimalReq, MinimalRes } from './types';\n\nimport { assertOc, sendBlockedDefault } from './core';\n\n/**\n * Express / Connect / Next-pages-API middleware.\n *\n * import { ocGate } from '@orangecheck/gate';\n *\n * app.post(\n * '/post',\n * ocGate({\n * minSats: 100_000,\n * minDays: 30,\n * address: { from: 'header' }, // reads X-OC-Address\n * }),\n * postHandler,\n * );\n *\n * On block: sends `403 { error: '<reason>', orangecheck: <CheckResult|null> }`\n * unless `opts.onBlocked` is provided.\n */\nexport function ocGate(opts: GateOptions) {\n return async function gateMiddleware(\n req: MinimalReq,\n res: MinimalRes,\n next: (err?: unknown) => void\n ): Promise<void> {\n const decision = await assertOc(req, opts);\n\n if (opts.onDecision) opts.onDecision(req, decision);\n\n if (decision.ok) {\n next();\n return;\n }\n\n if (opts.onBlocked) {\n opts.onBlocked(req, res, decision);\n return;\n }\n\n res.setHeader('Cache-Control', 'no-store');\n sendBlockedDefault(res, decision, opts);\n };\n}\n","import type { GateDecision, GateOptions, MinimalReq, MinimalRes } from './types';\n\nimport { assertOc, sendBlockedDefault } from './core';\n\n/** Cap the amount of body we'll parse for a `body: ...` subject source.\n * Without this a 100 MB POST ties up the gate before any downstream limiter\n * runs. 64 KB is plenty for a subject field. */\nconst MAX_BODY_BYTES = 64 * 1024;\n\n/**\n * Next.js Pages Router API wrapper.\n *\n * import { withOcGate } from '@orangecheck/gate';\n *\n * async function handler(req, res) {\n * // The decision is attached to req when the gate lets the call through.\n * res.json({ hello: (req as any).orangecheck.sats });\n * }\n *\n * export default withOcGate(handler, {\n * minSats: 100_000,\n * minDays: 30,\n * address: { from: 'query', name: 'addr' },\n * });\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function withOcGate<H extends (req: any, res: any) => any>(\n handler: H,\n opts: GateOptions\n): H {\n const wrapped = async (req: MinimalReq, res: MinimalRes) => {\n const decision = await assertOc(req, opts);\n if (opts.onDecision) opts.onDecision(req, decision);\n\n if (decision.ok) {\n // Attach decision to the request for the downstream handler.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (req as any).orangecheck = decision.check;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return (handler as any)(req, res);\n }\n\n if (opts.onBlocked) {\n opts.onBlocked(req, res, decision);\n return;\n }\n\n res.setHeader('Cache-Control', 'no-store');\n sendBlockedDefault(res, decision, opts);\n };\n return wrapped as unknown as H;\n}\n\n/**\n * Framework-agnostic Fetch-style guard — for Next App Router route handlers,\n * Cloudflare Workers, Hono, Bun, and friends.\n *\n * export async function POST(req: Request) {\n * const decision = await ocGateFetch(req, {\n * minSats: 100_000,\n * address: { from: 'header' },\n * });\n * if (!decision.ok) {\n * return new Response(JSON.stringify({ error: decision.reason }), { status: 403 });\n * }\n * // ... proceed\n * }\n */\nexport async function ocGateFetch(req: Request, opts: GateOptions): Promise<GateDecision> {\n const url = new URL(req.url);\n\n // Build a MinimalReq adapter over the Fetch Request.\n const headers: Record<string, string> = {};\n req.headers.forEach((v, k) => {\n headers[k.toLowerCase()] = v;\n });\n\n const query: Record<string, string> = {};\n url.searchParams.forEach((v, k) => {\n query[k] = v;\n });\n\n // Body is opaque at guard time — only parsed if the caller asks for body: *.\n // Caps at MAX_BODY_BYTES so a large POST can't stall the gate.\n let body: unknown = undefined;\n if (\n opts.address?.from === 'body' ||\n opts.attestationId?.from === 'body' ||\n opts.identity?.from === 'body'\n ) {\n try {\n const cloned = req.clone();\n const contentLen = Number(cloned.headers.get('content-length') ?? '0');\n if (contentLen > MAX_BODY_BYTES) {\n body = undefined;\n } else {\n const text = await cloned.text();\n if (text.length <= MAX_BODY_BYTES) {\n body = text ? JSON.parse(text) : undefined;\n }\n }\n } catch {\n body = undefined;\n }\n }\n\n const minimal: MinimalReq = {\n headers,\n query,\n body,\n url: req.url,\n method: req.method,\n };\n return assertOc(minimal, opts);\n}\n"]} | ||
| {"version":3,"sources":["../src/cache.ts","../src/core.ts","../src/express.ts","../src/fastify.ts","../src/next.ts","../src/hono.ts"],"names":["check","shim"],"mappings":"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","file":"index.js","sourcesContent":["import type { GateDecision } from './types';\n\ninterface Entry {\n value: GateDecision;\n expires: number;\n}\n\n/**\n * Tiny LRU + TTL cache. No deps. Good enough for per-process gate state.\n *\n * Eviction: oldest-inserted key first once max is reached. Ties to real LRU\n * (touching on read) would be a nice-to-have; in practice gate callers hit\n * the same subject repeatedly inside the TTL window so insertion-order is\n * equivalent.\n */\nexport class TtlLru {\n private readonly store = new Map<string, Entry>();\n\n constructor(\n private readonly max: number,\n private readonly ttlMs: number\n ) {}\n\n get(key: string): GateDecision | undefined {\n const entry = this.store.get(key);\n if (!entry) return undefined;\n if (Date.now() > entry.expires) {\n this.store.delete(key);\n return undefined;\n }\n return entry.value;\n }\n\n set(key: string, value: GateDecision): void {\n if (this.store.size >= this.max) {\n const first = this.store.keys().next().value;\n if (first !== undefined) this.store.delete(first);\n }\n this.store.set(key, { value, expires: Date.now() + this.ttlMs });\n }\n\n clear(): void {\n this.store.clear();\n }\n}\n","import type { GateDecision, GateOptions, MinimalReq, MinimalRes, SubjectSource } from './types';\n\nimport { check } from '@orangecheck/sdk';\n\nimport { TtlLru } from './cache';\n\nconst caches = new WeakMap<GateOptions, TtlLru>();\n\n/** Hard ceiling on per-entry TTL. Callers can't ask for a permanent grant\n * by passing a huge number — the cache isn't a session store. */\nconst MAX_CACHE_TTL_MS = 10 * 60_000; // 10 minutes\nconst DEFAULT_CACHE_TTL_MS = 60_000;\nconst DEFAULT_LOOKUP_TIMEOUT_MS = 5_000;\n\n/** Subject strings > this length are almost certainly garbage — reject at\n * the gate instead of building a 10 KB cache key. */\nconst MAX_SUBJECT_LEN = 128;\n\nfunction cacheFor(opts: GateOptions): TtlLru {\n let c = caches.get(opts);\n if (!c) {\n const ttl = Math.min(opts.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS, MAX_CACHE_TTL_MS);\n c = new TtlLru(opts.cacheMax ?? 1_000, Math.max(0, ttl));\n caches.set(opts, c);\n }\n return c;\n}\n\n/**\n * Normalize a subject so that `bc1Q…` / `bc1q…` / ` bc1q… ` can't cache-key\n * to three separate rows against the same on-chain address. For bech32\n * (segwit) and attestation IDs, lowercase is correct; for legacy base58\n * addresses the case is significant, so only lowercase bc1/tb1.\n */\nfunction normalizeSubject(kind: GateDecision['subjectKind'], raw: string): string {\n const trimmed = raw.trim();\n if (kind === 'attestation_id') return trimmed.toLowerCase();\n if (kind === 'address') {\n const low = trimmed.toLowerCase();\n if (low.startsWith('bc1') || low.startsWith('tb1') || low.startsWith('bcrt1')) return low;\n return trimmed; // base58 — keep case\n }\n // identity: `protocol:identifier` — lowercase protocol only\n const colon = trimmed.indexOf(':');\n if (colon === -1) return trimmed;\n return trimmed.slice(0, colon).toLowerCase() + ':' + trimmed.slice(colon + 1);\n}\n\n/** Track untrusted-source warnings we've already logged so we don't spam\n * the console on every request. */\nconst warnedUntrusted = new WeakSet<GateOptions>();\n\nfunction warnUntrustedOnce(opts: GateOptions, src: SubjectSource): void {\n if (opts.trustUnsafeSources || warnedUntrusted.has(opts)) return;\n if (typeof src.from === 'function') return;\n if (src.from === 'header' || src.from === 'query' || src.from === 'cookie' || src.from === 'body') {\n warnedUntrusted.add(opts);\n // eslint-disable-next-line no-console\n console.warn(\n `[orangecheck/gate] subject source \"${src.from}\" is caller-supplied — any client can set it. ` +\n `For real auth, resolve the address from a signed session (e.g., { from: (req) => req.session.verifiedAddress }) ` +\n `or pass { trustUnsafeSources: true } to silence this warning.`\n );\n }\n}\n\nfunction readHeader(req: MinimalReq, name: string): string | undefined {\n const raw = req.headers?.[name.toLowerCase()];\n if (Array.isArray(raw)) return raw[0];\n return raw;\n}\n\nfunction readCookie(req: MinimalReq, name: string): string | undefined {\n // Prefer an already-parsed cookies jar (Express w/ cookie-parser, Next API).\n if (req.cookies && name in req.cookies) return req.cookies[name];\n // Fallback: parse the raw Cookie header.\n const raw = readHeader(req, 'cookie');\n if (!raw) return undefined;\n for (const part of raw.split(';')) {\n const eq = part.indexOf('=');\n if (eq === -1) continue;\n const k = part.slice(0, eq).trim();\n if (k === name) return decodeURIComponent(part.slice(eq + 1).trim());\n }\n return undefined;\n}\n\nfunction readQuery(req: MinimalReq, name: string): string | undefined {\n const raw = req.query?.[name];\n if (Array.isArray(raw)) return raw[0];\n return raw;\n}\n\nfunction readBodyPath(req: MinimalReq, path: string): string | undefined {\n if (!req.body || typeof req.body !== 'object') return undefined;\n const parts = path.split('.');\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let cur: any = req.body;\n for (const p of parts) {\n if (cur == null) return undefined;\n cur = cur[p];\n }\n return typeof cur === 'string' ? cur : undefined;\n}\n\nfunction resolve(src: SubjectSource | undefined, req: MinimalReq): string | undefined {\n if (!src) return undefined;\n if (typeof src.from === 'function') return src.from(req);\n switch (src.from) {\n case 'header':\n return readHeader(req, src.name ?? 'x-oc-address');\n case 'cookie':\n return readCookie(req, src.name ?? 'oc_addr');\n case 'query':\n return readQuery(req, src.name ?? 'ocAddr');\n case 'body':\n return readBodyPath(req, src.path ?? 'address');\n default:\n return undefined;\n }\n}\n\n/**\n * Run the OrangeCheck lookup + threshold comparison for a request, without\n * touching the response. Framework-agnostic. Use this directly in Fastify,\n * Hono, Workers, or anywhere Express middleware doesn't fit.\n *\n * const decision = await assertOc(req, { minSats: 100_000, address: { from: 'header' } });\n * if (!decision.ok) return res.status(403).json({ error: decision.reason });\n */\nexport async function assertOc(req: MinimalReq, opts: GateOptions): Promise<GateDecision> {\n const sources = [\n ['attestation_id', opts.attestationId] as const,\n ['address', opts.address] as const,\n ['identity', opts.identity] as const,\n ];\n\n let subject: string | undefined;\n let subjectKind: GateDecision['subjectKind'];\n for (const [kind, src] of sources) {\n if (!src) continue;\n warnUntrustedOnce(opts, src);\n const v = resolve(src, req);\n if (v) {\n subject = v;\n subjectKind = kind;\n break;\n }\n }\n\n if (!subject || !subjectKind) {\n return { ok: false, reason: 'no_subject' };\n }\n\n // Clamp obviously-junk subjects before they hit the cache or the network.\n if (subject.length > MAX_SUBJECT_LEN) {\n return { ok: false, reason: 'no_subject' };\n }\n subject = normalizeSubject(subjectKind, subject);\n\n const cache = cacheFor(opts);\n const cacheKey = `${subjectKind}:${subject}:${opts.minSats ?? 0}:${opts.minDays ?? 0}`;\n const cached = cache.get(cacheKey);\n if (cached) return { ...cached, subject, subjectKind };\n\n try {\n const params: Parameters<typeof check>[0] = {\n minSats: opts.minSats,\n minDays: opts.minDays,\n ...(opts.relays ? { relays: opts.relays } : {}),\n };\n if (subjectKind === 'attestation_id') params.id = subject;\n else if (subjectKind === 'address') params.addr = subject;\n else if (subjectKind === 'identity') {\n const idx = subject.indexOf(':');\n if (idx === -1) {\n const d: GateDecision = {\n ok: false,\n reason: 'no_subject',\n subject,\n subjectKind,\n };\n cache.set(cacheKey, d);\n return d;\n }\n params.identity = {\n protocol: subject.slice(0, idx),\n identifier: subject.slice(idx + 1),\n };\n }\n\n // Hard deadline. `check()` may call out to a relay/Esplora, and we\n // do not want the gate to hang a request indefinitely.\n const timeoutMs = opts.lookupTimeoutMs ?? DEFAULT_LOOKUP_TIMEOUT_MS;\n const result = await Promise.race([\n check(params),\n new Promise<never>((_, reject) =>\n setTimeout(() => reject(new Error('lookup_timeout')), timeoutMs)\n ),\n ]);\n\n let reason: GateDecision['reason'];\n if (result.ok) reason = 'ok';\n else if (result.reasons?.includes('not_found')) reason = 'not_found';\n else if (result.reasons?.some((r) => r === 'below_min_sats' || r === 'below_min_days'))\n reason = 'below_threshold';\n else reason = 'invalid_proof';\n\n const decision: GateDecision = {\n ok: result.ok,\n reason,\n check: result,\n subject,\n subjectKind,\n };\n cache.set(cacheKey, decision);\n return decision;\n } catch (err) {\n const decision: GateDecision = {\n ok: Boolean(opts.failOpen),\n reason: opts.failOpen ? 'fail_open' : 'lookup_error',\n subject,\n subjectKind,\n };\n // Do not cache errors — we want the next request to try again.\n if (opts.onDecision) opts.onDecision(req, decision);\n // Surface only the message, not the full stack, to avoid leaking\n // internals into caller-visible logs. Callers wanting stack traces\n // should wire `onDecision` and log `err` themselves.\n // eslint-disable-next-line no-console\n console.warn(\n '[orangecheck/gate] lookup failed:',\n err instanceof Error ? err.message : String(err)\n );\n return decision;\n }\n}\n\n/**\n * Default 403 body writer. Extracted so express.ts and next.ts don't drift.\n * Respects `opts.exposeSubject` so a cookie-sourced address never leaks back\n * into a response body that might be seen by a different caller.\n */\nexport function sendBlockedDefault(\n res: MinimalRes,\n decision: GateDecision,\n opts: GateOptions\n): void {\n res.setHeader('Content-Type', 'application/json');\n res.status(decision.reason === 'no_subject' ? 401 : 403);\n const body: Record<string, unknown> = {\n error: 'orangecheck_gate_blocked',\n reason: decision.reason,\n };\n if (opts.exposeSubject && decision.subject) {\n body.subject = decision.subject;\n body.subjectKind = decision.subjectKind;\n }\n if (decision.check?.reasons) body.reasons = decision.check.reasons;\n res.json(body);\n}\n","import type { GateOptions, MinimalReq, MinimalRes } from './types';\n\nimport { assertOc, sendBlockedDefault } from './core';\n\n/**\n * Express / Connect / Next-pages-API middleware.\n *\n * import { ocGate } from '@orangecheck/gate';\n *\n * app.post(\n * '/post',\n * ocGate({\n * minSats: 100_000,\n * minDays: 30,\n * address: { from: 'header' }, // reads X-OC-Address\n * }),\n * postHandler,\n * );\n *\n * On block: sends `403 { error: '<reason>', orangecheck: <CheckResult|null> }`\n * unless `opts.onBlocked` is provided.\n */\nexport function ocGate(opts: GateOptions) {\n return async function gateMiddleware(\n req: MinimalReq,\n res: MinimalRes,\n next: (err?: unknown) => void\n ): Promise<void> {\n const decision = await assertOc(req, opts);\n\n if (opts.onDecision) opts.onDecision(req, decision);\n\n if (decision.ok) {\n next();\n return;\n }\n\n if (opts.onBlocked) {\n opts.onBlocked(req, res, decision);\n return;\n }\n\n res.setHeader('Cache-Control', 'no-store');\n sendBlockedDefault(res, decision, opts);\n };\n}\n","import type { GateOptions, MinimalReq } from './types';\n\nimport { assertOc, sendBlockedDefault } from './core';\n\n/**\n * Fastify plugin-friendly adapter. Usage:\n *\n * import Fastify from 'fastify';\n * import { ocGateFastify } from '@orangecheck/gate';\n *\n * const app = Fastify();\n *\n * app.post('/post', {\n * preHandler: ocGateFastify({\n * minSats: 100_000,\n * minDays: 30,\n * address: { from: 'header' },\n * }),\n * }, postHandler);\n *\n * Fastify's `req` shape differs slightly from Express (`req.query` is always\n * an object, `req.headers` keys are lowercased, `req.body` is parsed per the\n * declared schema). The adapter marshals it into the framework-agnostic\n * MinimalReq shape the gate core expects, then short-circuits with a 403 via\n * Fastify's `reply.code().send()` on block.\n */\n// The Fastify type surface is deliberately not imported — we don't want a\n// hard dependency on fastify just for a ~20-line wrapper. Use `unknown`-typed\n// request/reply and duck-type.\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype AnyFastifyReq = any;\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype AnyFastifyReply = any;\n\nexport function ocGateFastify(opts: GateOptions) {\n return async function ocPreHandler(\n req: AnyFastifyReq,\n reply: AnyFastifyReply\n ): Promise<void> {\n const minimal: MinimalReq = {\n headers: req.headers ?? {},\n query: (req.query ?? {}) as Record<string, string | string[] | undefined>,\n body: req.body,\n url: req.url,\n method: req.method,\n cookies: req.cookies,\n };\n const decision = await assertOc(minimal, opts);\n if (opts.onDecision) opts.onDecision(minimal, decision);\n if (decision.ok) return;\n\n if (opts.onBlocked) {\n // Best-effort: pass a shim Res compatible with MinimalRes.\n const shim = {\n status(code: number) {\n reply.code(code);\n return shim;\n },\n json(body: unknown) {\n reply.send(body);\n return shim;\n },\n setHeader(name: string, value: string | number | readonly string[]) {\n reply.header(name, value);\n return shim;\n },\n };\n opts.onBlocked(minimal, shim, decision);\n return;\n }\n\n reply.header('Cache-Control', 'no-store');\n const shim = {\n status(code: number) {\n reply.code(code);\n return shim;\n },\n json(body: unknown) {\n reply.send(body);\n return shim;\n },\n setHeader(name: string, value: string | number | readonly string[]) {\n reply.header(name, value);\n return shim;\n },\n };\n sendBlockedDefault(shim, decision, opts);\n };\n}\n","import type { GateDecision, GateOptions, MinimalReq, MinimalRes } from './types';\n\nimport { assertOc, sendBlockedDefault } from './core';\n\n/** Cap the amount of body we'll parse for a `body: ...` subject source.\n * Without this a 100 MB POST ties up the gate before any downstream limiter\n * runs. 64 KB is plenty for a subject field. */\nconst MAX_BODY_BYTES = 64 * 1024;\n\n/**\n * Next.js Pages Router API wrapper.\n *\n * import { withOcGate } from '@orangecheck/gate';\n *\n * async function handler(req, res) {\n * // The decision is attached to req when the gate lets the call through.\n * res.json({ hello: (req as any).orangecheck.sats });\n * }\n *\n * export default withOcGate(handler, {\n * minSats: 100_000,\n * minDays: 30,\n * address: { from: 'query', name: 'addr' },\n * });\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function withOcGate<H extends (req: any, res: any) => any>(\n handler: H,\n opts: GateOptions\n): H {\n const wrapped = async (req: MinimalReq, res: MinimalRes) => {\n const decision = await assertOc(req, opts);\n if (opts.onDecision) opts.onDecision(req, decision);\n\n if (decision.ok) {\n // Attach decision to the request for the downstream handler.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (req as any).orangecheck = decision.check;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return (handler as any)(req, res);\n }\n\n if (opts.onBlocked) {\n opts.onBlocked(req, res, decision);\n return;\n }\n\n res.setHeader('Cache-Control', 'no-store');\n sendBlockedDefault(res, decision, opts);\n };\n return wrapped as unknown as H;\n}\n\n/**\n * Framework-agnostic Fetch-style guard — for Next App Router route handlers,\n * Cloudflare Workers, Hono, Bun, and friends.\n *\n * export async function POST(req: Request) {\n * const decision = await ocGateFetch(req, {\n * minSats: 100_000,\n * address: { from: 'header' },\n * });\n * if (!decision.ok) {\n * return new Response(JSON.stringify({ error: decision.reason }), { status: 403 });\n * }\n * // ... proceed\n * }\n */\nexport async function ocGateFetch(req: Request, opts: GateOptions): Promise<GateDecision> {\n const url = new URL(req.url);\n\n // Build a MinimalReq adapter over the Fetch Request.\n const headers: Record<string, string> = {};\n req.headers.forEach((v, k) => {\n headers[k.toLowerCase()] = v;\n });\n\n const query: Record<string, string> = {};\n url.searchParams.forEach((v, k) => {\n query[k] = v;\n });\n\n // Body is opaque at guard time — only parsed if the caller asks for body: *.\n // Caps at MAX_BODY_BYTES so a large POST can't stall the gate.\n let body: unknown = undefined;\n if (\n opts.address?.from === 'body' ||\n opts.attestationId?.from === 'body' ||\n opts.identity?.from === 'body'\n ) {\n try {\n const cloned = req.clone();\n const contentLen = Number(cloned.headers.get('content-length') ?? '0');\n if (contentLen > MAX_BODY_BYTES) {\n body = undefined;\n } else {\n const text = await cloned.text();\n if (text.length <= MAX_BODY_BYTES) {\n body = text ? JSON.parse(text) : undefined;\n }\n }\n } catch {\n body = undefined;\n }\n }\n\n const minimal: MinimalReq = {\n headers,\n query,\n body,\n url: req.url,\n method: req.method,\n };\n return assertOc(minimal, opts);\n}\n","import type { GateOptions } from './types';\n\nimport { ocGateFetch } from './next';\n\n/**\n * Hono / edge-runtime adapter. Returns a Hono-compatible middleware function\n * that runs the OrangeCheck gate before `c.next()`.\n *\n * import { Hono } from 'hono';\n * import { ocGateHono } from '@orangecheck/gate';\n *\n * const app = new Hono();\n *\n * app.post(\n * '/post',\n * ocGateHono({ minSats: 100_000, address: { from: 'header' } }),\n * postHandler,\n * );\n *\n * Under the hood this is just `ocGateFetch` adapted to Hono's\n * `(c, next) => Promise<Response | void>` contract. The response on block\n * is a JSON 403 (401 on no_subject).\n */\n// Same rationale as the Fastify adapter: no hard dep on `hono`, just\n// duck-type its Context.\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype AnyHonoContext = any;\ntype AnyNext = () => Promise<void>;\n\nexport function ocGateHono(opts: GateOptions) {\n return async function ocHonoMiddleware(\n c: AnyHonoContext,\n next: AnyNext\n ): Promise<Response | void> {\n // Hono gives us `c.req.raw` (the underlying Fetch Request).\n const req: Request = c.req.raw ?? c.req;\n const decision = await ocGateFetch(req, opts);\n\n if (opts.onDecision) {\n // Synthesize a MinimalReq so the callback has something useful.\n const headers: Record<string, string> = {};\n req.headers.forEach((v, k) => {\n headers[k.toLowerCase()] = v;\n });\n opts.onDecision({ headers, url: req.url, method: req.method }, decision);\n }\n\n if (decision.ok) {\n await next();\n return;\n }\n\n const body: Record<string, unknown> = {\n error: 'orangecheck_gate_blocked',\n reason: decision.reason,\n };\n if (opts.exposeSubject && decision.subject) {\n body.subject = decision.subject;\n body.subjectKind = decision.subjectKind;\n }\n if (decision.check?.reasons) body.reasons = decision.check.reasons;\n\n return new Response(JSON.stringify(body), {\n status: decision.reason === 'no_subject' ? 401 : 403,\n headers: {\n 'Content-Type': 'application/json',\n 'Cache-Control': 'no-store',\n },\n });\n };\n}\n"]} |
+87
-1
@@ -243,2 +243,53 @@ import { check } from '@orangecheck/sdk'; | ||
| // src/fastify.ts | ||
| function ocGateFastify(opts) { | ||
| return async function ocPreHandler(req, reply) { | ||
| const minimal = { | ||
| headers: req.headers ?? {}, | ||
| query: req.query ?? {}, | ||
| body: req.body, | ||
| url: req.url, | ||
| method: req.method, | ||
| cookies: req.cookies | ||
| }; | ||
| const decision = await assertOc(minimal, opts); | ||
| if (opts.onDecision) opts.onDecision(minimal, decision); | ||
| if (decision.ok) return; | ||
| if (opts.onBlocked) { | ||
| const shim2 = { | ||
| status(code) { | ||
| reply.code(code); | ||
| return shim2; | ||
| }, | ||
| json(body) { | ||
| reply.send(body); | ||
| return shim2; | ||
| }, | ||
| setHeader(name, value) { | ||
| reply.header(name, value); | ||
| return shim2; | ||
| } | ||
| }; | ||
| opts.onBlocked(minimal, shim2, decision); | ||
| return; | ||
| } | ||
| reply.header("Cache-Control", "no-store"); | ||
| const shim = { | ||
| status(code) { | ||
| reply.code(code); | ||
| return shim; | ||
| }, | ||
| json(body) { | ||
| reply.send(body); | ||
| return shim; | ||
| }, | ||
| setHeader(name, value) { | ||
| reply.header(name, value); | ||
| return shim; | ||
| } | ||
| }; | ||
| sendBlockedDefault(shim, decision, opts); | ||
| }; | ||
| } | ||
| // src/next.ts | ||
@@ -300,4 +351,39 @@ var MAX_BODY_BYTES = 64 * 1024; | ||
| export { assertOc, ocGate, ocGateFetch, withOcGate }; | ||
| // src/hono.ts | ||
| function ocGateHono(opts) { | ||
| return async function ocHonoMiddleware(c, next) { | ||
| const req = c.req.raw ?? c.req; | ||
| const decision = await ocGateFetch(req, opts); | ||
| if (opts.onDecision) { | ||
| const headers = {}; | ||
| req.headers.forEach((v, k) => { | ||
| headers[k.toLowerCase()] = v; | ||
| }); | ||
| opts.onDecision({ headers, url: req.url, method: req.method }, decision); | ||
| } | ||
| if (decision.ok) { | ||
| await next(); | ||
| return; | ||
| } | ||
| const body = { | ||
| error: "orangecheck_gate_blocked", | ||
| reason: decision.reason | ||
| }; | ||
| if (opts.exposeSubject && decision.subject) { | ||
| body.subject = decision.subject; | ||
| body.subjectKind = decision.subjectKind; | ||
| } | ||
| if (decision.check?.reasons) body.reasons = decision.check.reasons; | ||
| return new Response(JSON.stringify(body), { | ||
| status: decision.reason === "no_subject" ? 401 : 403, | ||
| headers: { | ||
| "Content-Type": "application/json", | ||
| "Cache-Control": "no-store" | ||
| } | ||
| }); | ||
| }; | ||
| } | ||
| export { assertOc, ocGate, ocGateFastify, ocGateFetch, ocGateHono, withOcGate }; | ||
| //# sourceMappingURL=index.mjs.map | ||
| //# sourceMappingURL=index.mjs.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"sources":["../src/cache.ts","../src/core.ts","../src/express.ts","../src/next.ts"],"names":[],"mappings":";;;;;AAeO,IAAM,SAAN,MAAa;AAAA,EAGhB,WAAA,CACqB,KACA,KAAA,EACnB;AAFmB,IAAA,IAAA,CAAA,GAAA,GAAA,GAAA;AACA,IAAA,IAAA,CAAA,KAAA,GAAA,KAAA;AAJrB,IAAA,IAAA,CAAiB,KAAA,uBAAY,GAAA,EAAmB;AAAA,EAK7C;AAAA,EAEH,IAAI,GAAA,EAAuC;AACvC,IAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,KAAA,CAAM,GAAA,CAAI,GAAG,CAAA;AAChC,IAAA,IAAI,CAAC,OAAO,OAAO,MAAA;AACnB,IAAA,IAAI,IAAA,CAAK,GAAA,EAAI,GAAI,KAAA,CAAM,OAAA,EAAS;AAC5B,MAAA,IAAA,CAAK,KAAA,CAAM,OAAO,GAAG,CAAA;AACrB,MAAA,OAAO,MAAA;AAAA,IACX;AACA,IAAA,OAAO,KAAA,CAAM,KAAA;AAAA,EACjB;AAAA,EAEA,GAAA,CAAI,KAAa,KAAA,EAA2B;AACxC,IAAA,IAAI,IAAA,CAAK,KAAA,CAAM,IAAA,IAAQ,IAAA,CAAK,GAAA,EAAK;AAC7B,MAAA,MAAM,QAAQ,IAAA,CAAK,KAAA,CAAM,IAAA,EAAK,CAAE,MAAK,CAAE,KAAA;AACvC,MAAA,IAAI,KAAA,KAAU,MAAA,EAAW,IAAA,CAAK,KAAA,CAAM,OAAO,KAAK,CAAA;AAAA,IACpD;AACA,IAAA,IAAA,CAAK,KAAA,CAAM,GAAA,CAAI,GAAA,EAAK,EAAE,KAAA,EAAO,OAAA,EAAS,IAAA,CAAK,GAAA,EAAI,GAAI,IAAA,CAAK,KAAA,EAAO,CAAA;AAAA,EACnE;AAAA,EAEA,KAAA,GAAc;AACV,IAAA,IAAA,CAAK,MAAM,KAAA,EAAM;AAAA,EACrB;AACJ,CAAA;;;ACtCA,IAAM,MAAA,uBAAa,OAAA,EAA6B;AAIhD,IAAM,mBAAmB,EAAA,GAAK,GAAA;AAC9B,IAAM,oBAAA,GAAuB,GAAA;AAC7B,IAAM,yBAAA,GAA4B,GAAA;AAIlC,IAAM,eAAA,GAAkB,GAAA;AAExB,SAAS,SAAS,IAAA,EAA2B;AACzC,EAAA,IAAI,CAAA,GAAI,MAAA,CAAO,GAAA,CAAI,IAAI,CAAA;AACvB,EAAA,IAAI,CAAC,CAAA,EAAG;AACJ,IAAA,MAAM,MAAM,IAAA,CAAK,GAAA,CAAI,IAAA,CAAK,UAAA,IAAc,sBAAsB,gBAAgB,CAAA;AAC9E,IAAA,CAAA,GAAI,IAAI,OAAO,IAAA,CAAK,QAAA,IAAY,KAAO,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,GAAG,CAAC,CAAA;AACvD,IAAA,MAAA,CAAO,GAAA,CAAI,MAAM,CAAC,CAAA;AAAA,EACtB;AACA,EAAA,OAAO,CAAA;AACX;AAQA,SAAS,gBAAA,CAAiB,MAAmC,GAAA,EAAqB;AAC9E,EAAA,MAAM,OAAA,GAAU,IAAI,IAAA,EAAK;AACzB,EAAA,IAAI,IAAA,KAAS,gBAAA,EAAkB,OAAO,OAAA,CAAQ,WAAA,EAAY;AAC1D,EAAA,IAAI,SAAS,SAAA,EAAW;AACpB,IAAA,MAAM,GAAA,GAAM,QAAQ,WAAA,EAAY;AAChC,IAAA,IAAI,GAAA,CAAI,UAAA,CAAW,KAAK,CAAA,IAAK,GAAA,CAAI,UAAA,CAAW,KAAK,CAAA,IAAK,GAAA,CAAI,UAAA,CAAW,OAAO,CAAA,EAAG,OAAO,GAAA;AACtF,IAAA,OAAO,OAAA;AAAA,EACX;AAEA,EAAA,MAAM,KAAA,GAAQ,OAAA,CAAQ,OAAA,CAAQ,GAAG,CAAA;AACjC,EAAA,IAAI,KAAA,KAAU,IAAI,OAAO,OAAA;AACzB,EAAA,OAAO,OAAA,CAAQ,KAAA,CAAM,CAAA,EAAG,KAAK,CAAA,CAAE,WAAA,EAAY,GAAI,GAAA,GAAM,OAAA,CAAQ,KAAA,CAAM,KAAA,GAAQ,CAAC,CAAA;AAChF;AAIA,IAAM,eAAA,uBAAsB,OAAA,EAAqB;AAEjD,SAAS,iBAAA,CAAkB,MAAmB,GAAA,EAA0B;AACpE,EAAA,IAAI,IAAA,CAAK,kBAAA,IAAsB,eAAA,CAAgB,GAAA,CAAI,IAAI,CAAA,EAAG;AAC1D,EAAA,IAAI,OAAO,GAAA,CAAI,IAAA,KAAS,UAAA,EAAY;AACpC,EAAA,IAAI,GAAA,CAAI,IAAA,KAAS,QAAA,IAAY,GAAA,CAAI,IAAA,KAAS,OAAA,IAAW,GAAA,CAAI,IAAA,KAAS,QAAA,IAAY,GAAA,CAAI,IAAA,KAAS,MAAA,EAAQ;AAC/F,IAAA,eAAA,CAAgB,IAAI,IAAI,CAAA;AAExB,IAAA,OAAA,CAAQ,IAAA;AAAA,MACJ,CAAA,mCAAA,EAAsC,IAAI,IAAI,CAAA,gOAAA;AAAA,KAGlD;AAAA,EACJ;AACJ;AAEA,SAAS,UAAA,CAAW,KAAiB,IAAA,EAAkC;AACnE,EAAA,MAAM,GAAA,GAAM,GAAA,CAAI,OAAA,GAAU,IAAA,CAAK,aAAa,CAAA;AAC5C,EAAA,IAAI,MAAM,OAAA,CAAQ,GAAG,CAAA,EAAG,OAAO,IAAI,CAAC,CAAA;AACpC,EAAA,OAAO,GAAA;AACX;AAEA,SAAS,UAAA,CAAW,KAAiB,IAAA,EAAkC;AAEnE,EAAA,IAAI,GAAA,CAAI,WAAW,IAAA,IAAQ,GAAA,CAAI,SAAS,OAAO,GAAA,CAAI,QAAQ,IAAI,CAAA;AAE/D,EAAA,MAAM,GAAA,GAAM,UAAA,CAAW,GAAA,EAAK,QAAQ,CAAA;AACpC,EAAA,IAAI,CAAC,KAAK,OAAO,MAAA;AACjB,EAAA,KAAA,MAAW,IAAA,IAAQ,GAAA,CAAI,KAAA,CAAM,GAAG,CAAA,EAAG;AAC/B,IAAA,MAAM,EAAA,GAAK,IAAA,CAAK,OAAA,CAAQ,GAAG,CAAA;AAC3B,IAAA,IAAI,OAAO,EAAA,EAAI;AACf,IAAA,MAAM,IAAI,IAAA,CAAK,KAAA,CAAM,CAAA,EAAG,EAAE,EAAE,IAAA,EAAK;AACjC,IAAA,IAAI,CAAA,KAAM,IAAA,EAAM,OAAO,kBAAA,CAAmB,IAAA,CAAK,MAAM,EAAA,GAAK,CAAC,CAAA,CAAE,IAAA,EAAM,CAAA;AAAA,EACvE;AACA,EAAA,OAAO,MAAA;AACX;AAEA,SAAS,SAAA,CAAU,KAAiB,IAAA,EAAkC;AAClE,EAAA,MAAM,GAAA,GAAM,GAAA,CAAI,KAAA,GAAQ,IAAI,CAAA;AAC5B,EAAA,IAAI,MAAM,OAAA,CAAQ,GAAG,CAAA,EAAG,OAAO,IAAI,CAAC,CAAA;AACpC,EAAA,OAAO,GAAA;AACX;AAEA,SAAS,YAAA,CAAa,KAAiB,IAAA,EAAkC;AACrE,EAAA,IAAI,CAAC,GAAA,CAAI,IAAA,IAAQ,OAAO,GAAA,CAAI,IAAA,KAAS,UAAU,OAAO,MAAA;AACtD,EAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,KAAA,CAAM,GAAG,CAAA;AAE5B,EAAA,IAAI,MAAW,GAAA,CAAI,IAAA;AACnB,EAAA,KAAA,MAAW,KAAK,KAAA,EAAO;AACnB,IAAA,IAAI,GAAA,IAAO,MAAM,OAAO,MAAA;AACxB,IAAA,GAAA,GAAM,IAAI,CAAC,CAAA;AAAA,EACf;AACA,EAAA,OAAO,OAAO,GAAA,KAAQ,QAAA,GAAW,GAAA,GAAM,MAAA;AAC3C;AAEA,SAAS,OAAA,CAAQ,KAAgC,GAAA,EAAqC;AAClF,EAAA,IAAI,CAAC,KAAK,OAAO,MAAA;AACjB,EAAA,IAAI,OAAO,GAAA,CAAI,IAAA,KAAS,YAAY,OAAO,GAAA,CAAI,KAAK,GAAG,CAAA;AACvD,EAAA,QAAQ,IAAI,IAAA;AAAM,IACd,KAAK,QAAA;AACD,MAAA,OAAO,UAAA,CAAW,GAAA,EAAK,GAAA,CAAI,IAAA,IAAQ,cAAc,CAAA;AAAA,IACrD,KAAK,QAAA;AACD,MAAA,OAAO,UAAA,CAAW,GAAA,EAAK,GAAA,CAAI,IAAA,IAAQ,SAAS,CAAA;AAAA,IAChD,KAAK,OAAA;AACD,MAAA,OAAO,SAAA,CAAU,GAAA,EAAK,GAAA,CAAI,IAAA,IAAQ,QAAQ,CAAA;AAAA,IAC9C,KAAK,MAAA;AACD,MAAA,OAAO,YAAA,CAAa,GAAA,EAAK,GAAA,CAAI,IAAA,IAAQ,SAAS,CAAA;AAAA,IAClD;AACI,MAAA,OAAO,MAAA;AAAA;AAEnB;AAUA,eAAsB,QAAA,CAAS,KAAiB,IAAA,EAA0C;AACtF,EAAA,MAAM,OAAA,GAAU;AAAA,IACZ,CAAC,gBAAA,EAAkB,IAAA,CAAK,aAAa,CAAA;AAAA,IACrC,CAAC,SAAA,EAAW,IAAA,CAAK,OAAO,CAAA;AAAA,IACxB,CAAC,UAAA,EAAY,IAAA,CAAK,QAAQ;AAAA,GAC9B;AAEA,EAAA,IAAI,OAAA;AACJ,EAAA,IAAI,WAAA;AACJ,EAAA,KAAA,MAAW,CAAC,IAAA,EAAM,GAAG,CAAA,IAAK,OAAA,EAAS;AAC/B,IAAA,IAAI,CAAC,GAAA,EAAK;AACV,IAAA,iBAAA,CAAkB,MAAM,GAAG,CAAA;AAC3B,IAAA,MAAM,CAAA,GAAI,OAAA,CAAQ,GAAA,EAAK,GAAG,CAAA;AAC1B,IAAA,IAAI,CAAA,EAAG;AACH,MAAA,OAAA,GAAU,CAAA;AACV,MAAA,WAAA,GAAc,IAAA;AACd,MAAA;AAAA,IACJ;AAAA,EACJ;AAEA,EAAA,IAAI,CAAC,OAAA,IAAW,CAAC,WAAA,EAAa;AAC1B,IAAA,OAAO,EAAE,EAAA,EAAI,KAAA,EAAO,MAAA,EAAQ,YAAA,EAAa;AAAA,EAC7C;AAGA,EAAA,IAAI,OAAA,CAAQ,SAAS,eAAA,EAAiB;AAClC,IAAA,OAAO,EAAE,EAAA,EAAI,KAAA,EAAO,MAAA,EAAQ,YAAA,EAAa;AAAA,EAC7C;AACA,EAAA,OAAA,GAAU,gBAAA,CAAiB,aAAa,OAAO,CAAA;AAE/C,EAAA,MAAM,KAAA,GAAQ,SAAS,IAAI,CAAA;AAC3B,EAAA,MAAM,QAAA,GAAW,CAAA,EAAG,WAAW,CAAA,CAAA,EAAI,OAAO,CAAA,CAAA,EAAI,IAAA,CAAK,OAAA,IAAW,CAAC,CAAA,CAAA,EAAI,IAAA,CAAK,OAAA,IAAW,CAAC,CAAA,CAAA;AACpF,EAAA,MAAM,MAAA,GAAS,KAAA,CAAM,GAAA,CAAI,QAAQ,CAAA;AACjC,EAAA,IAAI,QAAQ,OAAO,EAAE,GAAG,MAAA,EAAQ,SAAS,WAAA,EAAY;AAErD,EAAA,IAAI;AACA,IAAA,MAAM,MAAA,GAAsC;AAAA,MACxC,SAAS,IAAA,CAAK,OAAA;AAAA,MACd,SAAS,IAAA,CAAK,OAAA;AAAA,MACd,GAAI,KAAK,MAAA,GAAS,EAAE,QAAQ,IAAA,CAAK,MAAA,KAAW;AAAC,KACjD;AACA,IAAA,IAAI,WAAA,KAAgB,gBAAA,EAAkB,MAAA,CAAO,EAAA,GAAK,OAAA;AAAA,SAAA,IACzC,WAAA,KAAgB,SAAA,EAAW,MAAA,CAAO,IAAA,GAAO,OAAA;AAAA,SAAA,IACzC,gBAAgB,UAAA,EAAY;AACjC,MAAA,MAAM,GAAA,GAAM,OAAA,CAAQ,OAAA,CAAQ,GAAG,CAAA;AAC/B,MAAA,IAAI,QAAQ,CAAA,CAAA,EAAI;AACZ,QAAA,MAAM,CAAA,GAAkB;AAAA,UACpB,EAAA,EAAI,KAAA;AAAA,UACJ,MAAA,EAAQ,YAAA;AAAA,UACR,OAAA;AAAA,UACA;AAAA,SACJ;AACA,QAAA,KAAA,CAAM,GAAA,CAAI,UAAU,CAAC,CAAA;AACrB,QAAA,OAAO,CAAA;AAAA,MACX;AACA,MAAA,MAAA,CAAO,QAAA,GAAW;AAAA,QACd,QAAA,EAAU,OAAA,CAAQ,KAAA,CAAM,CAAA,EAAG,GAAG,CAAA;AAAA,QAC9B,UAAA,EAAY,OAAA,CAAQ,KAAA,CAAM,GAAA,GAAM,CAAC;AAAA,OACrC;AAAA,IACJ;AAIA,IAAA,MAAM,SAAA,GAAY,KAAK,eAAA,IAAmB,yBAAA;AAC1C,IAAA,MAAM,MAAA,GAAS,MAAM,OAAA,CAAQ,IAAA,CAAK;AAAA,MAC9B,MAAM,MAAM,CAAA;AAAA,MACZ,IAAI,OAAA;AAAA,QAAe,CAAC,CAAA,EAAG,MAAA,KACnB,UAAA,CAAW,MAAM,MAAA,CAAO,IAAI,KAAA,CAAM,gBAAgB,CAAC,CAAA,EAAG,SAAS;AAAA;AACnE,KACH,CAAA;AAED,IAAA,IAAI,MAAA;AACJ,IAAA,IAAI,MAAA,CAAO,IAAI,MAAA,GAAS,IAAA;AAAA,SAAA,IACf,MAAA,CAAO,OAAA,EAAS,QAAA,CAAS,WAAW,GAAG,MAAA,GAAS,WAAA;AAAA,SAAA,IAChD,MAAA,CAAO,SAAS,IAAA,CAAK,CAAC,MAAM,CAAA,KAAM,gBAAA,IAAoB,MAAM,gBAAgB,CAAA;AACjF,MAAA,MAAA,GAAS,iBAAA;AAAA,SACR,MAAA,GAAS,eAAA;AAEd,IAAA,MAAM,QAAA,GAAyB;AAAA,MAC3B,IAAI,MAAA,CAAO,EAAA;AAAA,MACX,MAAA;AAAA,MACA,KAAA,EAAO,MAAA;AAAA,MACP,OAAA;AAAA,MACA;AAAA,KACJ;AACA,IAAA,KAAA,CAAM,GAAA,CAAI,UAAU,QAAQ,CAAA;AAC5B,IAAA,OAAO,QAAA;AAAA,EACX,SAAS,GAAA,EAAK;AACV,IAAA,MAAM,QAAA,GAAyB;AAAA,MAC3B,EAAA,EAAI,OAAA,CAAQ,IAAA,CAAK,QAAQ,CAAA;AAAA,MACzB,MAAA,EAAQ,IAAA,CAAK,QAAA,GAAW,WAAA,GAAc,cAAA;AAAA,MACtC,OAAA;AAAA,MACA;AAAA,KACJ;AAEA,IAAA,IAAI,IAAA,CAAK,UAAA,EAAY,IAAA,CAAK,UAAA,CAAW,KAAK,QAAQ,CAAA;AAKlD,IAAA,OAAA,CAAQ,IAAA;AAAA,MACJ,mCAAA;AAAA,MACA,GAAA,YAAe,KAAA,GAAQ,GAAA,CAAI,OAAA,GAAU,OAAO,GAAG;AAAA,KACnD;AACA,IAAA,OAAO,QAAA;AAAA,EACX;AACJ;AAOO,SAAS,kBAAA,CACZ,GAAA,EACA,QAAA,EACA,IAAA,EACI;AACJ,EAAA,GAAA,CAAI,SAAA,CAAU,gBAAgB,kBAAkB,CAAA;AAChD,EAAA,GAAA,CAAI,MAAA,CAAO,QAAA,CAAS,MAAA,KAAW,YAAA,GAAe,MAAM,GAAG,CAAA;AACvD,EAAA,MAAM,IAAA,GAAgC;AAAA,IAClC,KAAA,EAAO,0BAAA;AAAA,IACP,QAAQ,QAAA,CAAS;AAAA,GACrB;AACA,EAAA,IAAI,IAAA,CAAK,aAAA,IAAiB,QAAA,CAAS,OAAA,EAAS;AACxC,IAAA,IAAA,CAAK,UAAU,QAAA,CAAS,OAAA;AACxB,IAAA,IAAA,CAAK,cAAc,QAAA,CAAS,WAAA;AAAA,EAChC;AACA,EAAA,IAAI,SAAS,KAAA,EAAO,OAAA,EAAS,IAAA,CAAK,OAAA,GAAU,SAAS,KAAA,CAAM,OAAA;AAC3D,EAAA,GAAA,CAAI,KAAK,IAAI,CAAA;AACjB;;;AC9OO,SAAS,OAAO,IAAA,EAAmB;AACtC,EAAA,OAAO,eAAe,cAAA,CAClB,GAAA,EACA,GAAA,EACA,IAAA,EACa;AACb,IAAA,MAAM,QAAA,GAAW,MAAM,QAAA,CAAS,GAAA,EAAK,IAAI,CAAA;AAEzC,IAAA,IAAI,IAAA,CAAK,UAAA,EAAY,IAAA,CAAK,UAAA,CAAW,KAAK,QAAQ,CAAA;AAElD,IAAA,IAAI,SAAS,EAAA,EAAI;AACb,MAAA,IAAA,EAAK;AACL,MAAA;AAAA,IACJ;AAEA,IAAA,IAAI,KAAK,SAAA,EAAW;AAChB,MAAA,IAAA,CAAK,SAAA,CAAU,GAAA,EAAK,GAAA,EAAK,QAAQ,CAAA;AACjC,MAAA;AAAA,IACJ;AAEA,IAAA,GAAA,CAAI,SAAA,CAAU,iBAAiB,UAAU,CAAA;AACzC,IAAA,kBAAA,CAAmB,GAAA,EAAK,UAAU,IAAI,CAAA;AAAA,EAC1C,CAAA;AACJ;;;ACtCA,IAAM,iBAAiB,EAAA,GAAK,IAAA;AAmBrB,SAAS,UAAA,CACZ,SACA,IAAA,EACC;AACD,EAAA,MAAM,OAAA,GAAU,OAAO,GAAA,EAAiB,GAAA,KAAoB;AACxD,IAAA,MAAM,QAAA,GAAW,MAAM,QAAA,CAAS,GAAA,EAAK,IAAI,CAAA;AACzC,IAAA,IAAI,IAAA,CAAK,UAAA,EAAY,IAAA,CAAK,UAAA,CAAW,KAAK,QAAQ,CAAA;AAElD,IAAA,IAAI,SAAS,EAAA,EAAI;AAGb,MAAC,GAAA,CAAY,cAAc,QAAA,CAAS,KAAA;AAEpC,MAAA,OAAQ,OAAA,CAAgB,KAAK,GAAG,CAAA;AAAA,IACpC;AAEA,IAAA,IAAI,KAAK,SAAA,EAAW;AAChB,MAAA,IAAA,CAAK,SAAA,CAAU,GAAA,EAAK,GAAA,EAAK,QAAQ,CAAA;AACjC,MAAA;AAAA,IACJ;AAEA,IAAA,GAAA,CAAI,SAAA,CAAU,iBAAiB,UAAU,CAAA;AACzC,IAAA,kBAAA,CAAmB,GAAA,EAAK,UAAU,IAAI,CAAA;AAAA,EAC1C,CAAA;AACA,EAAA,OAAO,OAAA;AACX;AAiBA,eAAsB,WAAA,CAAY,KAAc,IAAA,EAA0C;AACtF,EAAA,MAAM,GAAA,GAAM,IAAI,GAAA,CAAI,GAAA,CAAI,GAAG,CAAA;AAG3B,EAAA,MAAM,UAAkC,EAAC;AACzC,EAAA,GAAA,CAAI,OAAA,CAAQ,OAAA,CAAQ,CAAC,CAAA,EAAG,CAAA,KAAM;AAC1B,IAAA,OAAA,CAAQ,CAAA,CAAE,WAAA,EAAa,CAAA,GAAI,CAAA;AAAA,EAC/B,CAAC,CAAA;AAED,EAAA,MAAM,QAAgC,EAAC;AACvC,EAAA,GAAA,CAAI,YAAA,CAAa,OAAA,CAAQ,CAAC,CAAA,EAAG,CAAA,KAAM;AAC/B,IAAA,KAAA,CAAM,CAAC,CAAA,GAAI,CAAA;AAAA,EACf,CAAC,CAAA;AAID,EAAA,IAAI,IAAA,GAAgB,MAAA;AACpB,EAAA,IACI,IAAA,CAAK,OAAA,EAAS,IAAA,KAAS,MAAA,IACvB,IAAA,CAAK,aAAA,EAAe,IAAA,KAAS,MAAA,IAC7B,IAAA,CAAK,QAAA,EAAU,IAAA,KAAS,MAAA,EAC1B;AACE,IAAA,IAAI;AACA,MAAA,MAAM,MAAA,GAAS,IAAI,KAAA,EAAM;AACzB,MAAA,MAAM,aAAa,MAAA,CAAO,MAAA,CAAO,QAAQ,GAAA,CAAI,gBAAgB,KAAK,GAAG,CAAA;AACrE,MAAA,IAAI,aAAa,cAAA,EAAgB;AAC7B,QAAA,IAAA,GAAO,KAAA,CAAA;AAAA,MACX,CAAA,MAAO;AACH,QAAA,MAAM,IAAA,GAAO,MAAM,MAAA,CAAO,IAAA,EAAK;AAC/B,QAAA,IAAI,IAAA,CAAK,UAAU,cAAA,EAAgB;AAC/B,UAAA,IAAA,GAAO,IAAA,GAAO,IAAA,CAAK,KAAA,CAAM,IAAI,CAAA,GAAI,KAAA,CAAA;AAAA,QACrC;AAAA,MACJ;AAAA,IACJ,CAAA,CAAA,MAAQ;AACJ,MAAA,IAAA,GAAO,MAAA;AAAA,IACX;AAAA,EACJ;AAEA,EAAA,MAAM,OAAA,GAAsB;AAAA,IACxB,OAAA;AAAA,IACA,KAAA;AAAA,IACA,IAAA;AAAA,IACA,KAAK,GAAA,CAAI,GAAA;AAAA,IACT,QAAQ,GAAA,CAAI;AAAA,GAChB;AACA,EAAA,OAAO,QAAA,CAAS,SAAS,IAAI,CAAA;AACjC","file":"index.mjs","sourcesContent":["import type { GateDecision } from './types';\n\ninterface Entry {\n value: GateDecision;\n expires: number;\n}\n\n/**\n * Tiny LRU + TTL cache. No deps. Good enough for per-process gate state.\n *\n * Eviction: oldest-inserted key first once max is reached. Ties to real LRU\n * (touching on read) would be a nice-to-have; in practice gate callers hit\n * the same subject repeatedly inside the TTL window so insertion-order is\n * equivalent.\n */\nexport class TtlLru {\n private readonly store = new Map<string, Entry>();\n\n constructor(\n private readonly max: number,\n private readonly ttlMs: number\n ) {}\n\n get(key: string): GateDecision | undefined {\n const entry = this.store.get(key);\n if (!entry) return undefined;\n if (Date.now() > entry.expires) {\n this.store.delete(key);\n return undefined;\n }\n return entry.value;\n }\n\n set(key: string, value: GateDecision): void {\n if (this.store.size >= this.max) {\n const first = this.store.keys().next().value;\n if (first !== undefined) this.store.delete(first);\n }\n this.store.set(key, { value, expires: Date.now() + this.ttlMs });\n }\n\n clear(): void {\n this.store.clear();\n }\n}\n","import type { GateDecision, GateOptions, MinimalReq, MinimalRes, SubjectSource } from './types';\n\nimport { check } from '@orangecheck/sdk';\n\nimport { TtlLru } from './cache';\n\nconst caches = new WeakMap<GateOptions, TtlLru>();\n\n/** Hard ceiling on per-entry TTL. Callers can't ask for a permanent grant\n * by passing a huge number — the cache isn't a session store. */\nconst MAX_CACHE_TTL_MS = 10 * 60_000; // 10 minutes\nconst DEFAULT_CACHE_TTL_MS = 60_000;\nconst DEFAULT_LOOKUP_TIMEOUT_MS = 5_000;\n\n/** Subject strings > this length are almost certainly garbage — reject at\n * the gate instead of building a 10 KB cache key. */\nconst MAX_SUBJECT_LEN = 128;\n\nfunction cacheFor(opts: GateOptions): TtlLru {\n let c = caches.get(opts);\n if (!c) {\n const ttl = Math.min(opts.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS, MAX_CACHE_TTL_MS);\n c = new TtlLru(opts.cacheMax ?? 1_000, Math.max(0, ttl));\n caches.set(opts, c);\n }\n return c;\n}\n\n/**\n * Normalize a subject so that `bc1Q…` / `bc1q…` / ` bc1q… ` can't cache-key\n * to three separate rows against the same on-chain address. For bech32\n * (segwit) and attestation IDs, lowercase is correct; for legacy base58\n * addresses the case is significant, so only lowercase bc1/tb1.\n */\nfunction normalizeSubject(kind: GateDecision['subjectKind'], raw: string): string {\n const trimmed = raw.trim();\n if (kind === 'attestation_id') return trimmed.toLowerCase();\n if (kind === 'address') {\n const low = trimmed.toLowerCase();\n if (low.startsWith('bc1') || low.startsWith('tb1') || low.startsWith('bcrt1')) return low;\n return trimmed; // base58 — keep case\n }\n // identity: `protocol:identifier` — lowercase protocol only\n const colon = trimmed.indexOf(':');\n if (colon === -1) return trimmed;\n return trimmed.slice(0, colon).toLowerCase() + ':' + trimmed.slice(colon + 1);\n}\n\n/** Track untrusted-source warnings we've already logged so we don't spam\n * the console on every request. */\nconst warnedUntrusted = new WeakSet<GateOptions>();\n\nfunction warnUntrustedOnce(opts: GateOptions, src: SubjectSource): void {\n if (opts.trustUnsafeSources || warnedUntrusted.has(opts)) return;\n if (typeof src.from === 'function') return;\n if (src.from === 'header' || src.from === 'query' || src.from === 'cookie' || src.from === 'body') {\n warnedUntrusted.add(opts);\n // eslint-disable-next-line no-console\n console.warn(\n `[orangecheck/gate] subject source \"${src.from}\" is caller-supplied — any client can set it. ` +\n `For real auth, resolve the address from a signed session (e.g., { from: (req) => req.session.verifiedAddress }) ` +\n `or pass { trustUnsafeSources: true } to silence this warning.`\n );\n }\n}\n\nfunction readHeader(req: MinimalReq, name: string): string | undefined {\n const raw = req.headers?.[name.toLowerCase()];\n if (Array.isArray(raw)) return raw[0];\n return raw;\n}\n\nfunction readCookie(req: MinimalReq, name: string): string | undefined {\n // Prefer an already-parsed cookies jar (Express w/ cookie-parser, Next API).\n if (req.cookies && name in req.cookies) return req.cookies[name];\n // Fallback: parse the raw Cookie header.\n const raw = readHeader(req, 'cookie');\n if (!raw) return undefined;\n for (const part of raw.split(';')) {\n const eq = part.indexOf('=');\n if (eq === -1) continue;\n const k = part.slice(0, eq).trim();\n if (k === name) return decodeURIComponent(part.slice(eq + 1).trim());\n }\n return undefined;\n}\n\nfunction readQuery(req: MinimalReq, name: string): string | undefined {\n const raw = req.query?.[name];\n if (Array.isArray(raw)) return raw[0];\n return raw;\n}\n\nfunction readBodyPath(req: MinimalReq, path: string): string | undefined {\n if (!req.body || typeof req.body !== 'object') return undefined;\n const parts = path.split('.');\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let cur: any = req.body;\n for (const p of parts) {\n if (cur == null) return undefined;\n cur = cur[p];\n }\n return typeof cur === 'string' ? cur : undefined;\n}\n\nfunction resolve(src: SubjectSource | undefined, req: MinimalReq): string | undefined {\n if (!src) return undefined;\n if (typeof src.from === 'function') return src.from(req);\n switch (src.from) {\n case 'header':\n return readHeader(req, src.name ?? 'x-oc-address');\n case 'cookie':\n return readCookie(req, src.name ?? 'oc_addr');\n case 'query':\n return readQuery(req, src.name ?? 'ocAddr');\n case 'body':\n return readBodyPath(req, src.path ?? 'address');\n default:\n return undefined;\n }\n}\n\n/**\n * Run the OrangeCheck lookup + threshold comparison for a request, without\n * touching the response. Framework-agnostic. Use this directly in Fastify,\n * Hono, Workers, or anywhere Express middleware doesn't fit.\n *\n * const decision = await assertOc(req, { minSats: 100_000, address: { from: 'header' } });\n * if (!decision.ok) return res.status(403).json({ error: decision.reason });\n */\nexport async function assertOc(req: MinimalReq, opts: GateOptions): Promise<GateDecision> {\n const sources = [\n ['attestation_id', opts.attestationId] as const,\n ['address', opts.address] as const,\n ['identity', opts.identity] as const,\n ];\n\n let subject: string | undefined;\n let subjectKind: GateDecision['subjectKind'];\n for (const [kind, src] of sources) {\n if (!src) continue;\n warnUntrustedOnce(opts, src);\n const v = resolve(src, req);\n if (v) {\n subject = v;\n subjectKind = kind;\n break;\n }\n }\n\n if (!subject || !subjectKind) {\n return { ok: false, reason: 'no_subject' };\n }\n\n // Clamp obviously-junk subjects before they hit the cache or the network.\n if (subject.length > MAX_SUBJECT_LEN) {\n return { ok: false, reason: 'no_subject' };\n }\n subject = normalizeSubject(subjectKind, subject);\n\n const cache = cacheFor(opts);\n const cacheKey = `${subjectKind}:${subject}:${opts.minSats ?? 0}:${opts.minDays ?? 0}`;\n const cached = cache.get(cacheKey);\n if (cached) return { ...cached, subject, subjectKind };\n\n try {\n const params: Parameters<typeof check>[0] = {\n minSats: opts.minSats,\n minDays: opts.minDays,\n ...(opts.relays ? { relays: opts.relays } : {}),\n };\n if (subjectKind === 'attestation_id') params.id = subject;\n else if (subjectKind === 'address') params.addr = subject;\n else if (subjectKind === 'identity') {\n const idx = subject.indexOf(':');\n if (idx === -1) {\n const d: GateDecision = {\n ok: false,\n reason: 'no_subject',\n subject,\n subjectKind,\n };\n cache.set(cacheKey, d);\n return d;\n }\n params.identity = {\n protocol: subject.slice(0, idx),\n identifier: subject.slice(idx + 1),\n };\n }\n\n // Hard deadline. `check()` may call out to a relay/Esplora, and we\n // do not want the gate to hang a request indefinitely.\n const timeoutMs = opts.lookupTimeoutMs ?? DEFAULT_LOOKUP_TIMEOUT_MS;\n const result = await Promise.race([\n check(params),\n new Promise<never>((_, reject) =>\n setTimeout(() => reject(new Error('lookup_timeout')), timeoutMs)\n ),\n ]);\n\n let reason: GateDecision['reason'];\n if (result.ok) reason = 'ok';\n else if (result.reasons?.includes('not_found')) reason = 'not_found';\n else if (result.reasons?.some((r) => r === 'below_min_sats' || r === 'below_min_days'))\n reason = 'below_threshold';\n else reason = 'invalid_proof';\n\n const decision: GateDecision = {\n ok: result.ok,\n reason,\n check: result,\n subject,\n subjectKind,\n };\n cache.set(cacheKey, decision);\n return decision;\n } catch (err) {\n const decision: GateDecision = {\n ok: Boolean(opts.failOpen),\n reason: opts.failOpen ? 'fail_open' : 'lookup_error',\n subject,\n subjectKind,\n };\n // Do not cache errors — we want the next request to try again.\n if (opts.onDecision) opts.onDecision(req, decision);\n // Surface only the message, not the full stack, to avoid leaking\n // internals into caller-visible logs. Callers wanting stack traces\n // should wire `onDecision` and log `err` themselves.\n // eslint-disable-next-line no-console\n console.warn(\n '[orangecheck/gate] lookup failed:',\n err instanceof Error ? err.message : String(err)\n );\n return decision;\n }\n}\n\n/**\n * Default 403 body writer. Extracted so express.ts and next.ts don't drift.\n * Respects `opts.exposeSubject` so a cookie-sourced address never leaks back\n * into a response body that might be seen by a different caller.\n */\nexport function sendBlockedDefault(\n res: MinimalRes,\n decision: GateDecision,\n opts: GateOptions\n): void {\n res.setHeader('Content-Type', 'application/json');\n res.status(decision.reason === 'no_subject' ? 401 : 403);\n const body: Record<string, unknown> = {\n error: 'orangecheck_gate_blocked',\n reason: decision.reason,\n };\n if (opts.exposeSubject && decision.subject) {\n body.subject = decision.subject;\n body.subjectKind = decision.subjectKind;\n }\n if (decision.check?.reasons) body.reasons = decision.check.reasons;\n res.json(body);\n}\n","import type { GateOptions, MinimalReq, MinimalRes } from './types';\n\nimport { assertOc, sendBlockedDefault } from './core';\n\n/**\n * Express / Connect / Next-pages-API middleware.\n *\n * import { ocGate } from '@orangecheck/gate';\n *\n * app.post(\n * '/post',\n * ocGate({\n * minSats: 100_000,\n * minDays: 30,\n * address: { from: 'header' }, // reads X-OC-Address\n * }),\n * postHandler,\n * );\n *\n * On block: sends `403 { error: '<reason>', orangecheck: <CheckResult|null> }`\n * unless `opts.onBlocked` is provided.\n */\nexport function ocGate(opts: GateOptions) {\n return async function gateMiddleware(\n req: MinimalReq,\n res: MinimalRes,\n next: (err?: unknown) => void\n ): Promise<void> {\n const decision = await assertOc(req, opts);\n\n if (opts.onDecision) opts.onDecision(req, decision);\n\n if (decision.ok) {\n next();\n return;\n }\n\n if (opts.onBlocked) {\n opts.onBlocked(req, res, decision);\n return;\n }\n\n res.setHeader('Cache-Control', 'no-store');\n sendBlockedDefault(res, decision, opts);\n };\n}\n","import type { GateDecision, GateOptions, MinimalReq, MinimalRes } from './types';\n\nimport { assertOc, sendBlockedDefault } from './core';\n\n/** Cap the amount of body we'll parse for a `body: ...` subject source.\n * Without this a 100 MB POST ties up the gate before any downstream limiter\n * runs. 64 KB is plenty for a subject field. */\nconst MAX_BODY_BYTES = 64 * 1024;\n\n/**\n * Next.js Pages Router API wrapper.\n *\n * import { withOcGate } from '@orangecheck/gate';\n *\n * async function handler(req, res) {\n * // The decision is attached to req when the gate lets the call through.\n * res.json({ hello: (req as any).orangecheck.sats });\n * }\n *\n * export default withOcGate(handler, {\n * minSats: 100_000,\n * minDays: 30,\n * address: { from: 'query', name: 'addr' },\n * });\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function withOcGate<H extends (req: any, res: any) => any>(\n handler: H,\n opts: GateOptions\n): H {\n const wrapped = async (req: MinimalReq, res: MinimalRes) => {\n const decision = await assertOc(req, opts);\n if (opts.onDecision) opts.onDecision(req, decision);\n\n if (decision.ok) {\n // Attach decision to the request for the downstream handler.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (req as any).orangecheck = decision.check;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return (handler as any)(req, res);\n }\n\n if (opts.onBlocked) {\n opts.onBlocked(req, res, decision);\n return;\n }\n\n res.setHeader('Cache-Control', 'no-store');\n sendBlockedDefault(res, decision, opts);\n };\n return wrapped as unknown as H;\n}\n\n/**\n * Framework-agnostic Fetch-style guard — for Next App Router route handlers,\n * Cloudflare Workers, Hono, Bun, and friends.\n *\n * export async function POST(req: Request) {\n * const decision = await ocGateFetch(req, {\n * minSats: 100_000,\n * address: { from: 'header' },\n * });\n * if (!decision.ok) {\n * return new Response(JSON.stringify({ error: decision.reason }), { status: 403 });\n * }\n * // ... proceed\n * }\n */\nexport async function ocGateFetch(req: Request, opts: GateOptions): Promise<GateDecision> {\n const url = new URL(req.url);\n\n // Build a MinimalReq adapter over the Fetch Request.\n const headers: Record<string, string> = {};\n req.headers.forEach((v, k) => {\n headers[k.toLowerCase()] = v;\n });\n\n const query: Record<string, string> = {};\n url.searchParams.forEach((v, k) => {\n query[k] = v;\n });\n\n // Body is opaque at guard time — only parsed if the caller asks for body: *.\n // Caps at MAX_BODY_BYTES so a large POST can't stall the gate.\n let body: unknown = undefined;\n if (\n opts.address?.from === 'body' ||\n opts.attestationId?.from === 'body' ||\n opts.identity?.from === 'body'\n ) {\n try {\n const cloned = req.clone();\n const contentLen = Number(cloned.headers.get('content-length') ?? '0');\n if (contentLen > MAX_BODY_BYTES) {\n body = undefined;\n } else {\n const text = await cloned.text();\n if (text.length <= MAX_BODY_BYTES) {\n body = text ? JSON.parse(text) : undefined;\n }\n }\n } catch {\n body = undefined;\n }\n }\n\n const minimal: MinimalReq = {\n headers,\n query,\n body,\n url: req.url,\n method: req.method,\n };\n return assertOc(minimal, opts);\n}\n"]} | ||
| {"version":3,"sources":["../src/cache.ts","../src/core.ts","../src/express.ts","../src/fastify.ts","../src/next.ts","../src/hono.ts"],"names":["shim"],"mappings":"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","file":"index.mjs","sourcesContent":["import type { GateDecision } from './types';\n\ninterface Entry {\n value: GateDecision;\n expires: number;\n}\n\n/**\n * Tiny LRU + TTL cache. No deps. Good enough for per-process gate state.\n *\n * Eviction: oldest-inserted key first once max is reached. Ties to real LRU\n * (touching on read) would be a nice-to-have; in practice gate callers hit\n * the same subject repeatedly inside the TTL window so insertion-order is\n * equivalent.\n */\nexport class TtlLru {\n private readonly store = new Map<string, Entry>();\n\n constructor(\n private readonly max: number,\n private readonly ttlMs: number\n ) {}\n\n get(key: string): GateDecision | undefined {\n const entry = this.store.get(key);\n if (!entry) return undefined;\n if (Date.now() > entry.expires) {\n this.store.delete(key);\n return undefined;\n }\n return entry.value;\n }\n\n set(key: string, value: GateDecision): void {\n if (this.store.size >= this.max) {\n const first = this.store.keys().next().value;\n if (first !== undefined) this.store.delete(first);\n }\n this.store.set(key, { value, expires: Date.now() + this.ttlMs });\n }\n\n clear(): void {\n this.store.clear();\n }\n}\n","import type { GateDecision, GateOptions, MinimalReq, MinimalRes, SubjectSource } from './types';\n\nimport { check } from '@orangecheck/sdk';\n\nimport { TtlLru } from './cache';\n\nconst caches = new WeakMap<GateOptions, TtlLru>();\n\n/** Hard ceiling on per-entry TTL. Callers can't ask for a permanent grant\n * by passing a huge number — the cache isn't a session store. */\nconst MAX_CACHE_TTL_MS = 10 * 60_000; // 10 minutes\nconst DEFAULT_CACHE_TTL_MS = 60_000;\nconst DEFAULT_LOOKUP_TIMEOUT_MS = 5_000;\n\n/** Subject strings > this length are almost certainly garbage — reject at\n * the gate instead of building a 10 KB cache key. */\nconst MAX_SUBJECT_LEN = 128;\n\nfunction cacheFor(opts: GateOptions): TtlLru {\n let c = caches.get(opts);\n if (!c) {\n const ttl = Math.min(opts.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS, MAX_CACHE_TTL_MS);\n c = new TtlLru(opts.cacheMax ?? 1_000, Math.max(0, ttl));\n caches.set(opts, c);\n }\n return c;\n}\n\n/**\n * Normalize a subject so that `bc1Q…` / `bc1q…` / ` bc1q… ` can't cache-key\n * to three separate rows against the same on-chain address. For bech32\n * (segwit) and attestation IDs, lowercase is correct; for legacy base58\n * addresses the case is significant, so only lowercase bc1/tb1.\n */\nfunction normalizeSubject(kind: GateDecision['subjectKind'], raw: string): string {\n const trimmed = raw.trim();\n if (kind === 'attestation_id') return trimmed.toLowerCase();\n if (kind === 'address') {\n const low = trimmed.toLowerCase();\n if (low.startsWith('bc1') || low.startsWith('tb1') || low.startsWith('bcrt1')) return low;\n return trimmed; // base58 — keep case\n }\n // identity: `protocol:identifier` — lowercase protocol only\n const colon = trimmed.indexOf(':');\n if (colon === -1) return trimmed;\n return trimmed.slice(0, colon).toLowerCase() + ':' + trimmed.slice(colon + 1);\n}\n\n/** Track untrusted-source warnings we've already logged so we don't spam\n * the console on every request. */\nconst warnedUntrusted = new WeakSet<GateOptions>();\n\nfunction warnUntrustedOnce(opts: GateOptions, src: SubjectSource): void {\n if (opts.trustUnsafeSources || warnedUntrusted.has(opts)) return;\n if (typeof src.from === 'function') return;\n if (src.from === 'header' || src.from === 'query' || src.from === 'cookie' || src.from === 'body') {\n warnedUntrusted.add(opts);\n // eslint-disable-next-line no-console\n console.warn(\n `[orangecheck/gate] subject source \"${src.from}\" is caller-supplied — any client can set it. ` +\n `For real auth, resolve the address from a signed session (e.g., { from: (req) => req.session.verifiedAddress }) ` +\n `or pass { trustUnsafeSources: true } to silence this warning.`\n );\n }\n}\n\nfunction readHeader(req: MinimalReq, name: string): string | undefined {\n const raw = req.headers?.[name.toLowerCase()];\n if (Array.isArray(raw)) return raw[0];\n return raw;\n}\n\nfunction readCookie(req: MinimalReq, name: string): string | undefined {\n // Prefer an already-parsed cookies jar (Express w/ cookie-parser, Next API).\n if (req.cookies && name in req.cookies) return req.cookies[name];\n // Fallback: parse the raw Cookie header.\n const raw = readHeader(req, 'cookie');\n if (!raw) return undefined;\n for (const part of raw.split(';')) {\n const eq = part.indexOf('=');\n if (eq === -1) continue;\n const k = part.slice(0, eq).trim();\n if (k === name) return decodeURIComponent(part.slice(eq + 1).trim());\n }\n return undefined;\n}\n\nfunction readQuery(req: MinimalReq, name: string): string | undefined {\n const raw = req.query?.[name];\n if (Array.isArray(raw)) return raw[0];\n return raw;\n}\n\nfunction readBodyPath(req: MinimalReq, path: string): string | undefined {\n if (!req.body || typeof req.body !== 'object') return undefined;\n const parts = path.split('.');\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let cur: any = req.body;\n for (const p of parts) {\n if (cur == null) return undefined;\n cur = cur[p];\n }\n return typeof cur === 'string' ? cur : undefined;\n}\n\nfunction resolve(src: SubjectSource | undefined, req: MinimalReq): string | undefined {\n if (!src) return undefined;\n if (typeof src.from === 'function') return src.from(req);\n switch (src.from) {\n case 'header':\n return readHeader(req, src.name ?? 'x-oc-address');\n case 'cookie':\n return readCookie(req, src.name ?? 'oc_addr');\n case 'query':\n return readQuery(req, src.name ?? 'ocAddr');\n case 'body':\n return readBodyPath(req, src.path ?? 'address');\n default:\n return undefined;\n }\n}\n\n/**\n * Run the OrangeCheck lookup + threshold comparison for a request, without\n * touching the response. Framework-agnostic. Use this directly in Fastify,\n * Hono, Workers, or anywhere Express middleware doesn't fit.\n *\n * const decision = await assertOc(req, { minSats: 100_000, address: { from: 'header' } });\n * if (!decision.ok) return res.status(403).json({ error: decision.reason });\n */\nexport async function assertOc(req: MinimalReq, opts: GateOptions): Promise<GateDecision> {\n const sources = [\n ['attestation_id', opts.attestationId] as const,\n ['address', opts.address] as const,\n ['identity', opts.identity] as const,\n ];\n\n let subject: string | undefined;\n let subjectKind: GateDecision['subjectKind'];\n for (const [kind, src] of sources) {\n if (!src) continue;\n warnUntrustedOnce(opts, src);\n const v = resolve(src, req);\n if (v) {\n subject = v;\n subjectKind = kind;\n break;\n }\n }\n\n if (!subject || !subjectKind) {\n return { ok: false, reason: 'no_subject' };\n }\n\n // Clamp obviously-junk subjects before they hit the cache or the network.\n if (subject.length > MAX_SUBJECT_LEN) {\n return { ok: false, reason: 'no_subject' };\n }\n subject = normalizeSubject(subjectKind, subject);\n\n const cache = cacheFor(opts);\n const cacheKey = `${subjectKind}:${subject}:${opts.minSats ?? 0}:${opts.minDays ?? 0}`;\n const cached = cache.get(cacheKey);\n if (cached) return { ...cached, subject, subjectKind };\n\n try {\n const params: Parameters<typeof check>[0] = {\n minSats: opts.minSats,\n minDays: opts.minDays,\n ...(opts.relays ? { relays: opts.relays } : {}),\n };\n if (subjectKind === 'attestation_id') params.id = subject;\n else if (subjectKind === 'address') params.addr = subject;\n else if (subjectKind === 'identity') {\n const idx = subject.indexOf(':');\n if (idx === -1) {\n const d: GateDecision = {\n ok: false,\n reason: 'no_subject',\n subject,\n subjectKind,\n };\n cache.set(cacheKey, d);\n return d;\n }\n params.identity = {\n protocol: subject.slice(0, idx),\n identifier: subject.slice(idx + 1),\n };\n }\n\n // Hard deadline. `check()` may call out to a relay/Esplora, and we\n // do not want the gate to hang a request indefinitely.\n const timeoutMs = opts.lookupTimeoutMs ?? DEFAULT_LOOKUP_TIMEOUT_MS;\n const result = await Promise.race([\n check(params),\n new Promise<never>((_, reject) =>\n setTimeout(() => reject(new Error('lookup_timeout')), timeoutMs)\n ),\n ]);\n\n let reason: GateDecision['reason'];\n if (result.ok) reason = 'ok';\n else if (result.reasons?.includes('not_found')) reason = 'not_found';\n else if (result.reasons?.some((r) => r === 'below_min_sats' || r === 'below_min_days'))\n reason = 'below_threshold';\n else reason = 'invalid_proof';\n\n const decision: GateDecision = {\n ok: result.ok,\n reason,\n check: result,\n subject,\n subjectKind,\n };\n cache.set(cacheKey, decision);\n return decision;\n } catch (err) {\n const decision: GateDecision = {\n ok: Boolean(opts.failOpen),\n reason: opts.failOpen ? 'fail_open' : 'lookup_error',\n subject,\n subjectKind,\n };\n // Do not cache errors — we want the next request to try again.\n if (opts.onDecision) opts.onDecision(req, decision);\n // Surface only the message, not the full stack, to avoid leaking\n // internals into caller-visible logs. Callers wanting stack traces\n // should wire `onDecision` and log `err` themselves.\n // eslint-disable-next-line no-console\n console.warn(\n '[orangecheck/gate] lookup failed:',\n err instanceof Error ? err.message : String(err)\n );\n return decision;\n }\n}\n\n/**\n * Default 403 body writer. Extracted so express.ts and next.ts don't drift.\n * Respects `opts.exposeSubject` so a cookie-sourced address never leaks back\n * into a response body that might be seen by a different caller.\n */\nexport function sendBlockedDefault(\n res: MinimalRes,\n decision: GateDecision,\n opts: GateOptions\n): void {\n res.setHeader('Content-Type', 'application/json');\n res.status(decision.reason === 'no_subject' ? 401 : 403);\n const body: Record<string, unknown> = {\n error: 'orangecheck_gate_blocked',\n reason: decision.reason,\n };\n if (opts.exposeSubject && decision.subject) {\n body.subject = decision.subject;\n body.subjectKind = decision.subjectKind;\n }\n if (decision.check?.reasons) body.reasons = decision.check.reasons;\n res.json(body);\n}\n","import type { GateOptions, MinimalReq, MinimalRes } from './types';\n\nimport { assertOc, sendBlockedDefault } from './core';\n\n/**\n * Express / Connect / Next-pages-API middleware.\n *\n * import { ocGate } from '@orangecheck/gate';\n *\n * app.post(\n * '/post',\n * ocGate({\n * minSats: 100_000,\n * minDays: 30,\n * address: { from: 'header' }, // reads X-OC-Address\n * }),\n * postHandler,\n * );\n *\n * On block: sends `403 { error: '<reason>', orangecheck: <CheckResult|null> }`\n * unless `opts.onBlocked` is provided.\n */\nexport function ocGate(opts: GateOptions) {\n return async function gateMiddleware(\n req: MinimalReq,\n res: MinimalRes,\n next: (err?: unknown) => void\n ): Promise<void> {\n const decision = await assertOc(req, opts);\n\n if (opts.onDecision) opts.onDecision(req, decision);\n\n if (decision.ok) {\n next();\n return;\n }\n\n if (opts.onBlocked) {\n opts.onBlocked(req, res, decision);\n return;\n }\n\n res.setHeader('Cache-Control', 'no-store');\n sendBlockedDefault(res, decision, opts);\n };\n}\n","import type { GateOptions, MinimalReq } from './types';\n\nimport { assertOc, sendBlockedDefault } from './core';\n\n/**\n * Fastify plugin-friendly adapter. Usage:\n *\n * import Fastify from 'fastify';\n * import { ocGateFastify } from '@orangecheck/gate';\n *\n * const app = Fastify();\n *\n * app.post('/post', {\n * preHandler: ocGateFastify({\n * minSats: 100_000,\n * minDays: 30,\n * address: { from: 'header' },\n * }),\n * }, postHandler);\n *\n * Fastify's `req` shape differs slightly from Express (`req.query` is always\n * an object, `req.headers` keys are lowercased, `req.body` is parsed per the\n * declared schema). The adapter marshals it into the framework-agnostic\n * MinimalReq shape the gate core expects, then short-circuits with a 403 via\n * Fastify's `reply.code().send()` on block.\n */\n// The Fastify type surface is deliberately not imported — we don't want a\n// hard dependency on fastify just for a ~20-line wrapper. Use `unknown`-typed\n// request/reply and duck-type.\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype AnyFastifyReq = any;\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype AnyFastifyReply = any;\n\nexport function ocGateFastify(opts: GateOptions) {\n return async function ocPreHandler(\n req: AnyFastifyReq,\n reply: AnyFastifyReply\n ): Promise<void> {\n const minimal: MinimalReq = {\n headers: req.headers ?? {},\n query: (req.query ?? {}) as Record<string, string | string[] | undefined>,\n body: req.body,\n url: req.url,\n method: req.method,\n cookies: req.cookies,\n };\n const decision = await assertOc(minimal, opts);\n if (opts.onDecision) opts.onDecision(minimal, decision);\n if (decision.ok) return;\n\n if (opts.onBlocked) {\n // Best-effort: pass a shim Res compatible with MinimalRes.\n const shim = {\n status(code: number) {\n reply.code(code);\n return shim;\n },\n json(body: unknown) {\n reply.send(body);\n return shim;\n },\n setHeader(name: string, value: string | number | readonly string[]) {\n reply.header(name, value);\n return shim;\n },\n };\n opts.onBlocked(minimal, shim, decision);\n return;\n }\n\n reply.header('Cache-Control', 'no-store');\n const shim = {\n status(code: number) {\n reply.code(code);\n return shim;\n },\n json(body: unknown) {\n reply.send(body);\n return shim;\n },\n setHeader(name: string, value: string | number | readonly string[]) {\n reply.header(name, value);\n return shim;\n },\n };\n sendBlockedDefault(shim, decision, opts);\n };\n}\n","import type { GateDecision, GateOptions, MinimalReq, MinimalRes } from './types';\n\nimport { assertOc, sendBlockedDefault } from './core';\n\n/** Cap the amount of body we'll parse for a `body: ...` subject source.\n * Without this a 100 MB POST ties up the gate before any downstream limiter\n * runs. 64 KB is plenty for a subject field. */\nconst MAX_BODY_BYTES = 64 * 1024;\n\n/**\n * Next.js Pages Router API wrapper.\n *\n * import { withOcGate } from '@orangecheck/gate';\n *\n * async function handler(req, res) {\n * // The decision is attached to req when the gate lets the call through.\n * res.json({ hello: (req as any).orangecheck.sats });\n * }\n *\n * export default withOcGate(handler, {\n * minSats: 100_000,\n * minDays: 30,\n * address: { from: 'query', name: 'addr' },\n * });\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function withOcGate<H extends (req: any, res: any) => any>(\n handler: H,\n opts: GateOptions\n): H {\n const wrapped = async (req: MinimalReq, res: MinimalRes) => {\n const decision = await assertOc(req, opts);\n if (opts.onDecision) opts.onDecision(req, decision);\n\n if (decision.ok) {\n // Attach decision to the request for the downstream handler.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (req as any).orangecheck = decision.check;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return (handler as any)(req, res);\n }\n\n if (opts.onBlocked) {\n opts.onBlocked(req, res, decision);\n return;\n }\n\n res.setHeader('Cache-Control', 'no-store');\n sendBlockedDefault(res, decision, opts);\n };\n return wrapped as unknown as H;\n}\n\n/**\n * Framework-agnostic Fetch-style guard — for Next App Router route handlers,\n * Cloudflare Workers, Hono, Bun, and friends.\n *\n * export async function POST(req: Request) {\n * const decision = await ocGateFetch(req, {\n * minSats: 100_000,\n * address: { from: 'header' },\n * });\n * if (!decision.ok) {\n * return new Response(JSON.stringify({ error: decision.reason }), { status: 403 });\n * }\n * // ... proceed\n * }\n */\nexport async function ocGateFetch(req: Request, opts: GateOptions): Promise<GateDecision> {\n const url = new URL(req.url);\n\n // Build a MinimalReq adapter over the Fetch Request.\n const headers: Record<string, string> = {};\n req.headers.forEach((v, k) => {\n headers[k.toLowerCase()] = v;\n });\n\n const query: Record<string, string> = {};\n url.searchParams.forEach((v, k) => {\n query[k] = v;\n });\n\n // Body is opaque at guard time — only parsed if the caller asks for body: *.\n // Caps at MAX_BODY_BYTES so a large POST can't stall the gate.\n let body: unknown = undefined;\n if (\n opts.address?.from === 'body' ||\n opts.attestationId?.from === 'body' ||\n opts.identity?.from === 'body'\n ) {\n try {\n const cloned = req.clone();\n const contentLen = Number(cloned.headers.get('content-length') ?? '0');\n if (contentLen > MAX_BODY_BYTES) {\n body = undefined;\n } else {\n const text = await cloned.text();\n if (text.length <= MAX_BODY_BYTES) {\n body = text ? JSON.parse(text) : undefined;\n }\n }\n } catch {\n body = undefined;\n }\n }\n\n const minimal: MinimalReq = {\n headers,\n query,\n body,\n url: req.url,\n method: req.method,\n };\n return assertOc(minimal, opts);\n}\n","import type { GateOptions } from './types';\n\nimport { ocGateFetch } from './next';\n\n/**\n * Hono / edge-runtime adapter. Returns a Hono-compatible middleware function\n * that runs the OrangeCheck gate before `c.next()`.\n *\n * import { Hono } from 'hono';\n * import { ocGateHono } from '@orangecheck/gate';\n *\n * const app = new Hono();\n *\n * app.post(\n * '/post',\n * ocGateHono({ minSats: 100_000, address: { from: 'header' } }),\n * postHandler,\n * );\n *\n * Under the hood this is just `ocGateFetch` adapted to Hono's\n * `(c, next) => Promise<Response | void>` contract. The response on block\n * is a JSON 403 (401 on no_subject).\n */\n// Same rationale as the Fastify adapter: no hard dep on `hono`, just\n// duck-type its Context.\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype AnyHonoContext = any;\ntype AnyNext = () => Promise<void>;\n\nexport function ocGateHono(opts: GateOptions) {\n return async function ocHonoMiddleware(\n c: AnyHonoContext,\n next: AnyNext\n ): Promise<Response | void> {\n // Hono gives us `c.req.raw` (the underlying Fetch Request).\n const req: Request = c.req.raw ?? c.req;\n const decision = await ocGateFetch(req, opts);\n\n if (opts.onDecision) {\n // Synthesize a MinimalReq so the callback has something useful.\n const headers: Record<string, string> = {};\n req.headers.forEach((v, k) => {\n headers[k.toLowerCase()] = v;\n });\n opts.onDecision({ headers, url: req.url, method: req.method }, decision);\n }\n\n if (decision.ok) {\n await next();\n return;\n }\n\n const body: Record<string, unknown> = {\n error: 'orangecheck_gate_blocked',\n reason: decision.reason,\n };\n if (opts.exposeSubject && decision.subject) {\n body.subject = decision.subject;\n body.subjectKind = decision.subjectKind;\n }\n if (decision.check?.reasons) body.reasons = decision.check.reasons;\n\n return new Response(JSON.stringify(body), {\n status: decision.reason === 'no_subject' ? 401 : 403,\n headers: {\n 'Content-Type': 'application/json',\n 'Cache-Control': 'no-store',\n },\n });\n };\n}\n"]} |
+24
-3
| { | ||
| "name": "@orangecheck/gate", | ||
| "version": "0.1.2", | ||
| "version": "0.1.3", | ||
| "description": "Drop-in sybil-resistance middleware for Express, Next.js, Fastify, and any Node HTTP framework. Built on OrangeCheck.", | ||
@@ -13,3 +13,4 @@ "keywords": [ | ||
| "fastify", | ||
| "rate-limiting", | ||
| "hono", | ||
| "workers", | ||
| "bip322", | ||
@@ -37,2 +38,22 @@ "nostr" | ||
| "require": "./dist/index.js" | ||
| }, | ||
| "./express": { | ||
| "types": "./dist/express.d.ts", | ||
| "import": "./dist/express.mjs", | ||
| "require": "./dist/express.js" | ||
| }, | ||
| "./next": { | ||
| "types": "./dist/next.d.ts", | ||
| "import": "./dist/next.mjs", | ||
| "require": "./dist/next.js" | ||
| }, | ||
| "./fastify": { | ||
| "types": "./dist/fastify.d.ts", | ||
| "import": "./dist/fastify.mjs", | ||
| "require": "./dist/fastify.js" | ||
| }, | ||
| "./hono": { | ||
| "types": "./dist/hono.d.ts", | ||
| "import": "./dist/hono.mjs", | ||
| "require": "./dist/hono.js" | ||
| } | ||
@@ -55,3 +76,3 @@ }, | ||
| "dependencies": { | ||
| "@orangecheck/sdk": "^0.1.3" | ||
| "@orangecheck/sdk": "^0.1.4" | ||
| }, | ||
@@ -58,0 +79,0 @@ "devDependencies": { |
+61
-3
@@ -64,5 +64,46 @@ # `@orangecheck/gate` | ||
| ## Fetch-style — App Router, Hono, Cloudflare Workers, Bun | ||
| ## Fastify | ||
| ```ts | ||
| import Fastify from 'fastify'; | ||
| import { ocGateFastify } from '@orangecheck/gate/fastify'; | ||
| const app = Fastify(); | ||
| app.post('/post', { | ||
| preHandler: ocGateFastify({ | ||
| minSats: 100_000, | ||
| minDays: 30, | ||
| address: { from: 'header' }, | ||
| }), | ||
| }, postHandler); | ||
| ``` | ||
| --- | ||
| ## Hono / Cloudflare Workers / Bun / Deno | ||
| ```ts | ||
| import { Hono } from 'hono'; | ||
| import { ocGateHono } from '@orangecheck/gate/hono'; | ||
| const app = new Hono(); | ||
| app.post( | ||
| '/post', | ||
| ocGateHono({ | ||
| minSats: 100_000, | ||
| address: { from: 'header' }, | ||
| }), | ||
| postHandler | ||
| ); | ||
| ``` | ||
| Same middleware works on every edge-runtime Hono supports. | ||
| --- | ||
| ## Fetch-style — App Router route handlers, raw Workers | ||
| ```ts | ||
| import { ocGateFetch } from '@orangecheck/gate'; | ||
@@ -92,3 +133,3 @@ | ||
| // Fastify, Hono, tRPC, raw http.createServer, whatever. | ||
| // tRPC, raw http.createServer, Elysia, whatever. | ||
| const decision = await assertOc(req, { | ||
@@ -146,8 +187,25 @@ minSats: 100_000, | ||
| // In-process cache. Matches the /api/check 60s cache by default. | ||
| cacheTtlMs?: number; // default 60_000 | ||
| // Hard-clamped to 10 minutes — a very large value does NOT produce | ||
| // a permanent grant. | ||
| cacheTtlMs?: number; // default 60_000, max 600_000 | ||
| cacheMax?: number; // default 1_000 entries | ||
| // Hard deadline on the upstream lookup. Past this, the gate returns | ||
| // lookup_error (fail-closed unless failOpen is set). | ||
| lookupTimeoutMs?: number; // default 5_000 | ||
| // Degrade gracefully when relays are unreachable. | ||
| failOpen?: boolean; // default false — closed by default | ||
| // `header` / `query` / `cookie` / `body` sources are caller-supplied | ||
| // and spoofable. The gate emits a one-time startup warning unless | ||
| // trustUnsafeSources is set — silence it only when you've verified | ||
| // the address via a signed session. | ||
| trustUnsafeSources?: boolean; | ||
| // Include subject/subjectKind in the default 403 body. Default false | ||
| // so cookie-bound addresses don't leak back to the caller. | ||
| exposeSubject?: boolean; | ||
| // Override Nostr discovery relays. | ||
@@ -154,0 +212,0 @@ relays?: string[]; |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
374306
334.55%35
288.89%2987
359.54%294
24.58%1
Infinity%Updated