🚀 Socket Launch Week Day 4:Socket MCP Adds Org Alerts, Threat Feed Review, and Package Inspection.Learn more
Sign In

@orangecheck/gate

Package Overview
Dependencies
Maintainers
1
Versions
4
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@orangecheck/gate - npm Package Compare versions

Comparing version
0.1.2
to
0.1.3
+6
dist/express.d.mts
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 };
'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"]}
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 };
'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"]}
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 };
'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"]}
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":";;;;;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;;;ACrOA,IAAM,iBAAiB,EAAA,GAAK,IAAA;AA6D5B,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;;;ACrFO,SAAS,WAAW,IAAA,EAAmB;AAC1C,EAAA,OAAO,eAAe,gBAAA,CAClB,CAAA,EACA,IAAA,EACwB;AAExB,IAAA,MAAM,GAAA,GAAe,CAAA,CAAE,GAAA,CAAI,GAAA,IAAO,CAAA,CAAE,GAAA;AACpC,IAAA,MAAM,QAAA,GAAW,MAAM,WAAA,CAAY,GAAA,EAAK,IAAI,CAAA;AAE5C,IAAA,IAAI,KAAK,UAAA,EAAY;AAEjB,MAAA,MAAM,UAAkC,EAAC;AACzC,MAAA,GAAA,CAAI,OAAA,CAAQ,OAAA,CAAQ,CAAC,CAAA,EAAG,CAAA,KAAM;AAC1B,QAAA,OAAA,CAAQ,CAAA,CAAE,WAAA,EAAa,CAAA,GAAI,CAAA;AAAA,MAC/B,CAAC,CAAA;AACD,MAAA,IAAA,CAAK,UAAA,CAAW,EAAE,OAAA,EAAS,GAAA,EAAK,GAAA,CAAI,KAAK,MAAA,EAAQ,GAAA,CAAI,MAAA,EAAO,EAAG,QAAQ,CAAA;AAAA,IAC3E;AAEA,IAAA,IAAI,SAAS,EAAA,EAAI;AACb,MAAA,MAAM,IAAA,EAAK;AACX,MAAA;AAAA,IACJ;AAEA,IAAA,MAAM,IAAA,GAAgC;AAAA,MAClC,KAAA,EAAO,0BAAA;AAAA,MACP,QAAQ,QAAA,CAAS;AAAA,KACrB;AACA,IAAA,IAAI,IAAA,CAAK,aAAA,IAAiB,QAAA,CAAS,OAAA,EAAS;AACxC,MAAA,IAAA,CAAK,UAAU,QAAA,CAAS,OAAA;AACxB,MAAA,IAAA,CAAK,cAAc,QAAA,CAAS,WAAA;AAAA,IAChC;AACA,IAAA,IAAI,SAAS,KAAA,EAAO,OAAA,EAAS,IAAA,CAAK,OAAA,GAAU,SAAS,KAAA,CAAM,OAAA;AAE3D,IAAA,OAAO,IAAI,QAAA,CAAS,IAAA,CAAK,SAAA,CAAU,IAAI,CAAA,EAAG;AAAA,MACtC,MAAA,EAAQ,QAAA,CAAS,MAAA,KAAW,YAAA,GAAe,GAAA,GAAM,GAAA;AAAA,MACjD,OAAA,EAAS;AAAA,QACL,cAAA,EAAgB,kBAAA;AAAA,QAChB,eAAA,EAAiB;AAAA;AACrB,KACH,CAAA;AAAA,EACL,CAAA;AACJ","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 };
'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"]}
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 };

@@ -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 };

@@ -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"]}

@@ -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":"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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 { 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"]}
{
"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": {

@@ -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[];