@agentronics/sdk 0.5.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +96 -76
- package/dist/chunk-3WPDIMJP.js +18 -0
- package/dist/chunk-3WPDIMJP.js.map +1 -0
- package/dist/chunk-6QQS26AP.js +1200 -0
- package/dist/chunk-6QQS26AP.js.map +1 -0
- package/dist/chunk-BPDWJCPY.js +2 -0
- package/dist/chunk-BPDWJCPY.js.map +1 -0
- package/dist/index.cjs +8 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +51 -12
- package/dist/index.d.ts +51 -12
- package/dist/index.global.js +8 -7
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/{init-only-7f8_LQAI.d.ts → init-only-C80qhujz.d.ts} +227 -30
- package/dist/{init-only-Cwll8MGz.d.cts → init-only-s9E9bVC0.d.cts} +227 -30
- package/dist/init-only.cjs +8 -7
- package/dist/init-only.cjs.map +1 -1
- package/dist/init-only.d.cts +2 -2
- package/dist/init-only.d.ts +2 -2
- package/dist/init-only.global.js +8 -7
- package/dist/init-only.global.js.map +1 -1
- package/dist/init-only.js +1 -1
- package/dist/lite.cjs +1 -1
- package/dist/lite.cjs.map +1 -1
- package/dist/lite.d.cts +2 -2
- package/dist/lite.d.ts +2 -2
- package/dist/lite.global.js +1 -1
- package/dist/lite.global.js.map +1 -1
- package/dist/lite.js +1 -1
- package/dist/middleware-Dd2T772u.d.cts +429 -0
- package/dist/middleware-Dd2T772u.d.ts +429 -0
- package/dist/next.cjs +1137 -0
- package/dist/next.cjs.map +1 -0
- package/dist/next.d.cts +20 -0
- package/dist/next.d.ts +20 -0
- package/dist/next.js +20 -0
- package/dist/next.js.map +1 -0
- package/dist/server.cjs +1321 -0
- package/dist/server.cjs.map +1 -0
- package/dist/server.d.cts +131 -0
- package/dist/server.d.ts +131 -0
- package/dist/server.js +95 -0
- package/dist/server.js.map +1 -0
- package/dist/webmcp-BgKzHeQ3.d.cts +97 -0
- package/dist/webmcp-BgKzHeQ3.d.ts +97 -0
- package/package.json +38 -6
- package/dist/chunk-77VJ65X2.js +0 -17
- package/dist/chunk-77VJ65X2.js.map +0 -1
- package/dist/chunk-LLD3Y2LZ.js +0 -2
- package/dist/chunk-LLD3Y2LZ.js.map +0 -1
- package/dist/webmcp-C4ikhM4g.d.cts +0 -12
- package/dist/webmcp-C4ikhM4g.d.ts +0 -12
package/dist/next.cjs
ADDED
|
@@ -0,0 +1,1137 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/detection/crawler.ts
|
|
4
|
+
var CRAWLER_SIGNATURES = [
|
|
5
|
+
// AI / LLM crawlers
|
|
6
|
+
{ name: "GPTBot", category: "ai", pattern: /GPTBot/i },
|
|
7
|
+
{ name: "OAI-SearchBot", category: "ai", pattern: /OAI-SearchBot/i },
|
|
8
|
+
{ name: "ChatGPT-User", category: "ai", pattern: /ChatGPT-User/i },
|
|
9
|
+
{ name: "Claude-User", category: "ai", pattern: /Claude-User/i },
|
|
10
|
+
{ name: "Claude-SearchBot", category: "ai", pattern: /Claude-SearchBot/i },
|
|
11
|
+
{ name: "ClaudeBot", category: "ai", pattern: /ClaudeBot/i },
|
|
12
|
+
{ name: "Claude-Web", category: "ai", pattern: /Claude-Web/i },
|
|
13
|
+
{ name: "anthropic-ai", category: "ai", pattern: /anthropic-ai/i },
|
|
14
|
+
{ name: "Perplexity-User", category: "ai", pattern: /Perplexity-User/i },
|
|
15
|
+
{ name: "PerplexityBot", category: "ai", pattern: /PerplexityBot/i },
|
|
16
|
+
{ name: "Google-Extended", category: "ai", pattern: /Google-Extended/i },
|
|
17
|
+
{ name: "Applebot-Extended", category: "ai", pattern: /Applebot-Extended/i },
|
|
18
|
+
{ name: "Amazonbot", category: "ai", pattern: /Amazonbot/i },
|
|
19
|
+
{ name: "Bytespider", category: "ai", pattern: /Bytespider/i },
|
|
20
|
+
{ name: "CCBot", category: "ai", pattern: /CCBot/i },
|
|
21
|
+
{ name: "Meta-ExternalAgent", category: "ai", pattern: /Meta-ExternalAgent/i },
|
|
22
|
+
{ name: "cohere-ai", category: "ai", pattern: /cohere-ai/i },
|
|
23
|
+
// Search crawlers
|
|
24
|
+
{ name: "Googlebot", category: "search", pattern: /Googlebot/i },
|
|
25
|
+
{ name: "Bingbot", category: "search", pattern: /bingbot/i },
|
|
26
|
+
{ name: "DuckDuckBot", category: "search", pattern: /DuckDuckBot/i },
|
|
27
|
+
{ name: "YandexBot", category: "search", pattern: /YandexBot/i },
|
|
28
|
+
{ name: "Baiduspider", category: "search", pattern: /Baiduspider/i },
|
|
29
|
+
{ name: "Applebot", category: "search", pattern: /Applebot/i }
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
// src/server/ipRanges.ts
|
|
33
|
+
var PUBLISHED_IP_RANGES = {
|
|
34
|
+
GPTBot: ["https://openai.com/gptbot.json"],
|
|
35
|
+
"OAI-SearchBot": ["https://openai.com/searchbot.json"],
|
|
36
|
+
"ChatGPT-User": ["https://openai.com/chatgpt-user.json"],
|
|
37
|
+
PerplexityBot: ["https://www.perplexity.ai/perplexitybot.json"],
|
|
38
|
+
"Perplexity-User": ["https://www.perplexity.ai/perplexity-user.json"],
|
|
39
|
+
Googlebot: ["https://developers.google.com/static/crawling/ipranges/common-crawlers.json"],
|
|
40
|
+
Bingbot: ["https://www.bing.com/toolbox/bingbot.json"],
|
|
41
|
+
DuckDuckBot: ["https://duckduckgo.com/duckduckbot.json"],
|
|
42
|
+
CCBot: ["https://index.commoncrawl.org/ccbot.json"]
|
|
43
|
+
};
|
|
44
|
+
var TTL_MS = 24 * 36e5;
|
|
45
|
+
var FAILED_TTL_MS = 10 * 6e4;
|
|
46
|
+
var cache = /* @__PURE__ */ new Map();
|
|
47
|
+
async function loadUrl(url, fetcher) {
|
|
48
|
+
const hit = cache.get(url);
|
|
49
|
+
if (hit && hit.until > Date.now()) return hit.cidrs;
|
|
50
|
+
try {
|
|
51
|
+
const res = await fetcher(url, { signal: AbortSignal.timeout(5e3), redirect: "follow" });
|
|
52
|
+
if (!res.ok) throw new Error(String(res.status));
|
|
53
|
+
const body = await res.json();
|
|
54
|
+
const cidrs = (body.prefixes ?? []).flatMap((p) => [p.ipv4Prefix, p.ipv6Prefix]).filter((c) => typeof c === "string");
|
|
55
|
+
cache.set(url, { cidrs, until: Date.now() + TTL_MS });
|
|
56
|
+
return cidrs;
|
|
57
|
+
} catch {
|
|
58
|
+
const cidrs = hit?.cidrs ?? [];
|
|
59
|
+
cache.set(url, { cidrs, until: Date.now() + FAILED_TTL_MS });
|
|
60
|
+
return cidrs;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
async function rangesFor(name, options = {}) {
|
|
64
|
+
const fixed = options.ipRanges?.[name];
|
|
65
|
+
if (fixed) return fixed;
|
|
66
|
+
const urls = PUBLISHED_IP_RANGES[name];
|
|
67
|
+
if (!urls) return null;
|
|
68
|
+
const fetcher = options.fetcher ?? fetch;
|
|
69
|
+
return (await Promise.all(urls.map((u) => loadUrl(u, fetcher)))).flat();
|
|
70
|
+
}
|
|
71
|
+
function v4ToBigInt(ip) {
|
|
72
|
+
const parts = ip.split(".");
|
|
73
|
+
if (parts.length !== 4) return null;
|
|
74
|
+
let n = 0n;
|
|
75
|
+
for (const p of parts) {
|
|
76
|
+
if (!/^\d{1,3}$/.test(p) || Number(p) > 255) return null;
|
|
77
|
+
n = n << 8n | BigInt(p);
|
|
78
|
+
}
|
|
79
|
+
return n;
|
|
80
|
+
}
|
|
81
|
+
function v6ToBigInt(ip) {
|
|
82
|
+
let s = ip.toLowerCase().replace(/^\[|\]$/g, "");
|
|
83
|
+
const zone = s.indexOf("%");
|
|
84
|
+
if (zone >= 0) s = s.slice(0, zone);
|
|
85
|
+
const v4 = s.match(/(\d+\.\d+\.\d+\.\d+)$/);
|
|
86
|
+
if (v4) {
|
|
87
|
+
const n2 = v4ToBigInt(v4[1]);
|
|
88
|
+
if (n2 === null) return null;
|
|
89
|
+
s = s.slice(0, -v4[1].length) + (n2 >> 16n & 0xffffn).toString(16) + ":" + (n2 & 0xffffn).toString(16);
|
|
90
|
+
}
|
|
91
|
+
const halves = s.split("::");
|
|
92
|
+
if (halves.length > 2) return null;
|
|
93
|
+
const head = halves[0] ? halves[0].split(":") : [];
|
|
94
|
+
const tail = halves.length === 2 && halves[1] ? halves[1].split(":") : [];
|
|
95
|
+
const missing = 8 - head.length - tail.length;
|
|
96
|
+
if (halves.length === 1 ? missing !== 0 : missing < 1) return null;
|
|
97
|
+
const groups = [...head, ...Array(halves.length === 2 ? missing : 0).fill("0"), ...tail];
|
|
98
|
+
let n = 0n;
|
|
99
|
+
for (const g of groups) {
|
|
100
|
+
if (!/^[0-9a-f]{1,4}$/.test(g)) return null;
|
|
101
|
+
n = n << 16n | BigInt(parseInt(g, 16));
|
|
102
|
+
}
|
|
103
|
+
return n;
|
|
104
|
+
}
|
|
105
|
+
function ipInCidr(ip, cidr) {
|
|
106
|
+
const [range, bitsRaw] = cidr.split("/");
|
|
107
|
+
if (!range) return false;
|
|
108
|
+
const isV6 = range.includes(":");
|
|
109
|
+
const mapped = ip.toLowerCase().match(/^::ffff:(\d+\.\d+\.\d+\.\d+)$/);
|
|
110
|
+
const candidate = !isV6 && mapped ? mapped[1] : ip;
|
|
111
|
+
const width = isV6 ? 128 : 32;
|
|
112
|
+
const bits = bitsRaw === void 0 ? width : Number(bitsRaw);
|
|
113
|
+
if (!Number.isInteger(bits) || bits < 0 || bits > width) return false;
|
|
114
|
+
const a = isV6 ? v6ToBigInt(candidate) : v4ToBigInt(candidate);
|
|
115
|
+
const b = isV6 ? v6ToBigInt(range) : v4ToBigInt(range);
|
|
116
|
+
if (a === null || b === null) return false;
|
|
117
|
+
const shift = BigInt(width - bits);
|
|
118
|
+
return a >> shift === b >> shift;
|
|
119
|
+
}
|
|
120
|
+
var ipInRanges = (ip, cidrs) => cidrs.some((c) => ipInCidr(ip, c));
|
|
121
|
+
|
|
122
|
+
// src/server/crawlers.ts
|
|
123
|
+
var RDNS_DOMAINS = {
|
|
124
|
+
Googlebot: ["googlebot.com", "google.com", "googleusercontent.com"],
|
|
125
|
+
Bingbot: ["search.msn.com"],
|
|
126
|
+
Applebot: ["applebot.apple.com"],
|
|
127
|
+
"Applebot-Extended": ["applebot.apple.com"],
|
|
128
|
+
YandexBot: ["yandex.ru", "yandex.net", "yandex.com"],
|
|
129
|
+
Baiduspider: ["baidu.com", "baidu.jp"],
|
|
130
|
+
Amazonbot: ["crawl.amazonbot.amazon"]
|
|
131
|
+
};
|
|
132
|
+
function classifyUserAgent(ua, signatures = CRAWLER_SIGNATURES) {
|
|
133
|
+
if (!ua) return null;
|
|
134
|
+
const hit = signatures.find((s) => s.pattern.test(ua));
|
|
135
|
+
return hit ? { name: hit.name, category: hit.category } : null;
|
|
136
|
+
}
|
|
137
|
+
function clientIp(request, source = "auto") {
|
|
138
|
+
if (typeof source === "function") return source(request);
|
|
139
|
+
const h = request.headers;
|
|
140
|
+
if (source === "cloudflare") return h.get("cf-connecting-ip");
|
|
141
|
+
if (source === "vercel") return h.get("x-real-ip") ?? h.get("x-vercel-forwarded-for");
|
|
142
|
+
return h.get("cf-connecting-ip") ?? h.get("x-real-ip") ?? h.get("x-vercel-forwarded-for");
|
|
143
|
+
}
|
|
144
|
+
async function nodeResolver() {
|
|
145
|
+
try {
|
|
146
|
+
const dns = await import(
|
|
147
|
+
/* webpackIgnore: true */
|
|
148
|
+
/* @vite-ignore */
|
|
149
|
+
'dns/promises'
|
|
150
|
+
);
|
|
151
|
+
return {
|
|
152
|
+
reverse: (ip) => dns.reverse(ip),
|
|
153
|
+
lookup: async (host) => (await dns.lookup(host, { all: true })).map((a) => a.address)
|
|
154
|
+
};
|
|
155
|
+
} catch {
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
var rdnsCache = /* @__PURE__ */ new Map();
|
|
160
|
+
async function verifyCrawler(claim, ip, resolver, ranges = {}) {
|
|
161
|
+
const domains = RDNS_DOMAINS[claim.name];
|
|
162
|
+
const cidrs = await rangesFor(claim.name, ranges);
|
|
163
|
+
if (!domains && !cidrs) return { status: "unverified", name: claim.name, reason: "no_verification_method_for_vendor" };
|
|
164
|
+
if (!ip) return { status: "unverified", name: claim.name, reason: "client_ip_unknown" };
|
|
165
|
+
if (cidrs?.length && ipInRanges(ip, cidrs)) {
|
|
166
|
+
return { status: "verified", name: claim.name, host: ip, ip, via: "ip-range" };
|
|
167
|
+
}
|
|
168
|
+
if (!domains) return { status: "unverified", name: claim.name, reason: "ip_outside_published_ranges" };
|
|
169
|
+
const key = `${claim.name}|${ip}`;
|
|
170
|
+
const hit = rdnsCache.get(key);
|
|
171
|
+
if (hit && hit.until > Date.now()) {
|
|
172
|
+
return hit.ok ? { status: "verified", name: claim.name, host: hit.host, ip, via: "rdns" } : { status: "unverified", name: claim.name, reason: "rdns_mismatch" };
|
|
173
|
+
}
|
|
174
|
+
const dns = resolver ?? await nodeResolver();
|
|
175
|
+
if (!dns) return { status: "unverified", name: claim.name, reason: "dns_unavailable_in_runtime" };
|
|
176
|
+
const remember = (ok, host) => {
|
|
177
|
+
if (rdnsCache.size > 5e3) rdnsCache.delete(rdnsCache.keys().next().value);
|
|
178
|
+
rdnsCache.set(key, { ok, ...host ? { host } : {}, until: Date.now() + (ok ? 36e5 : 6e5) });
|
|
179
|
+
};
|
|
180
|
+
try {
|
|
181
|
+
const names = await dns.reverse(ip);
|
|
182
|
+
for (const name of names) {
|
|
183
|
+
const host = name.toLowerCase().replace(/\.$/, "");
|
|
184
|
+
if (!domains.some((d) => host === d || host.endsWith(`.${d}`))) continue;
|
|
185
|
+
const forward = await dns.lookup(host);
|
|
186
|
+
if (forward.includes(ip)) {
|
|
187
|
+
remember(true, host);
|
|
188
|
+
return { status: "verified", name: claim.name, host, ip, via: "rdns" };
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
remember(false);
|
|
192
|
+
return { status: "unverified", name: claim.name, reason: "rdns_mismatch" };
|
|
193
|
+
} catch {
|
|
194
|
+
return { status: "unverified", name: claim.name, reason: "rdns_lookup_failed" };
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// src/server/structuredFields.ts
|
|
199
|
+
var SfToken = class {
|
|
200
|
+
constructor(value) {
|
|
201
|
+
this.value = value;
|
|
202
|
+
}
|
|
203
|
+
value;
|
|
204
|
+
};
|
|
205
|
+
var SfDecimal = class {
|
|
206
|
+
constructor(value) {
|
|
207
|
+
this.value = value;
|
|
208
|
+
}
|
|
209
|
+
value;
|
|
210
|
+
};
|
|
211
|
+
var isInnerList = (m) => "items" in m;
|
|
212
|
+
var SfParseError = class extends Error {
|
|
213
|
+
};
|
|
214
|
+
var Parser = class {
|
|
215
|
+
constructor(s) {
|
|
216
|
+
this.s = s;
|
|
217
|
+
}
|
|
218
|
+
s;
|
|
219
|
+
i = 0;
|
|
220
|
+
get done() {
|
|
221
|
+
return this.i >= this.s.length;
|
|
222
|
+
}
|
|
223
|
+
peek() {
|
|
224
|
+
return this.s[this.i] ?? "";
|
|
225
|
+
}
|
|
226
|
+
fail(what) {
|
|
227
|
+
throw new SfParseError(`${what} at ${this.i}`);
|
|
228
|
+
}
|
|
229
|
+
sp() {
|
|
230
|
+
while (this.peek() === " ") this.i++;
|
|
231
|
+
}
|
|
232
|
+
ows() {
|
|
233
|
+
while (this.peek() === " " || this.peek() === " ") this.i++;
|
|
234
|
+
}
|
|
235
|
+
key() {
|
|
236
|
+
const c = this.peek();
|
|
237
|
+
if (!/[a-z*]/.test(c)) this.fail("invalid key");
|
|
238
|
+
let out = "";
|
|
239
|
+
while (/[a-z0-9_\-.*]/.test(this.peek())) out += this.s[this.i++];
|
|
240
|
+
return out;
|
|
241
|
+
}
|
|
242
|
+
params() {
|
|
243
|
+
const out = [];
|
|
244
|
+
while (this.peek() === ";") {
|
|
245
|
+
this.i++;
|
|
246
|
+
this.sp();
|
|
247
|
+
const k = this.key();
|
|
248
|
+
let v = true;
|
|
249
|
+
if (this.peek() === "=") {
|
|
250
|
+
this.i++;
|
|
251
|
+
v = this.bareItem();
|
|
252
|
+
}
|
|
253
|
+
const existing = out.findIndex(([ek]) => ek === k);
|
|
254
|
+
if (existing >= 0) out[existing] = [k, v];
|
|
255
|
+
else out.push([k, v]);
|
|
256
|
+
}
|
|
257
|
+
return out;
|
|
258
|
+
}
|
|
259
|
+
bareItem() {
|
|
260
|
+
const c = this.peek();
|
|
261
|
+
if (c === "-" || /[0-9]/.test(c)) return this.number();
|
|
262
|
+
if (c === '"') return this.string();
|
|
263
|
+
if (c === ":") return this.bytes();
|
|
264
|
+
if (c === "?") return this.boolean();
|
|
265
|
+
if (/[A-Za-z*]/.test(c)) return this.token();
|
|
266
|
+
return this.fail("invalid bare item");
|
|
267
|
+
}
|
|
268
|
+
number() {
|
|
269
|
+
let sign = 1;
|
|
270
|
+
if (this.peek() === "-") {
|
|
271
|
+
sign = -1;
|
|
272
|
+
this.i++;
|
|
273
|
+
}
|
|
274
|
+
let num = "";
|
|
275
|
+
let isDecimal = false;
|
|
276
|
+
while (!this.done) {
|
|
277
|
+
const c = this.peek();
|
|
278
|
+
if (/[0-9]/.test(c)) num += c;
|
|
279
|
+
else if (c === "." && !isDecimal) {
|
|
280
|
+
if (num.length > 12) this.fail("decimal integer part too long");
|
|
281
|
+
isDecimal = true;
|
|
282
|
+
num += c;
|
|
283
|
+
} else break;
|
|
284
|
+
this.i++;
|
|
285
|
+
if (!isDecimal && num.length > 15) this.fail("integer too long");
|
|
286
|
+
if (isDecimal && num.length > 16) this.fail("decimal too long");
|
|
287
|
+
}
|
|
288
|
+
if (!/[0-9]/.test(num[0] ?? "")) this.fail("invalid number");
|
|
289
|
+
if (isDecimal) {
|
|
290
|
+
if (num.endsWith(".")) this.fail("decimal ends with dot");
|
|
291
|
+
if (num.split(".")[1].length > 3) this.fail("decimal fraction too long");
|
|
292
|
+
return new SfDecimal(sign * Number(num));
|
|
293
|
+
}
|
|
294
|
+
return sign * Number(num);
|
|
295
|
+
}
|
|
296
|
+
string() {
|
|
297
|
+
this.i++;
|
|
298
|
+
let out = "";
|
|
299
|
+
while (!this.done) {
|
|
300
|
+
const c = this.s[this.i++];
|
|
301
|
+
if (c === "\\") {
|
|
302
|
+
const n = this.s[this.i++];
|
|
303
|
+
if (n !== '"' && n !== "\\") this.fail("invalid escape");
|
|
304
|
+
out += n;
|
|
305
|
+
} else if (c === '"') return out;
|
|
306
|
+
else {
|
|
307
|
+
const code = c.charCodeAt(0);
|
|
308
|
+
if (code < 32 || code > 126) this.fail("invalid string char");
|
|
309
|
+
out += c;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
return this.fail("unterminated string");
|
|
313
|
+
}
|
|
314
|
+
token() {
|
|
315
|
+
let out = "";
|
|
316
|
+
while (/[A-Za-z0-9!#$%&'*+\-.^_`|~:/]/.test(this.peek()) && !this.done) out += this.s[this.i++];
|
|
317
|
+
return new SfToken(out);
|
|
318
|
+
}
|
|
319
|
+
bytes() {
|
|
320
|
+
this.i++;
|
|
321
|
+
const end = this.s.indexOf(":", this.i);
|
|
322
|
+
if (end < 0) this.fail("unterminated byte sequence");
|
|
323
|
+
const b64 = this.s.slice(this.i, end);
|
|
324
|
+
if (!/^[A-Za-z0-9+/=]*$/.test(b64)) this.fail("invalid base64");
|
|
325
|
+
this.i = end + 1;
|
|
326
|
+
return base64ToBytes(b64);
|
|
327
|
+
}
|
|
328
|
+
boolean() {
|
|
329
|
+
this.i++;
|
|
330
|
+
const c = this.s[this.i++];
|
|
331
|
+
if (c === "1") return true;
|
|
332
|
+
if (c === "0") return false;
|
|
333
|
+
return this.fail("invalid boolean");
|
|
334
|
+
}
|
|
335
|
+
item() {
|
|
336
|
+
const value = this.bareItem();
|
|
337
|
+
return { value, params: this.params() };
|
|
338
|
+
}
|
|
339
|
+
innerList() {
|
|
340
|
+
this.i++;
|
|
341
|
+
const items = [];
|
|
342
|
+
while (!this.done) {
|
|
343
|
+
this.sp();
|
|
344
|
+
if (this.peek() === ")") {
|
|
345
|
+
this.i++;
|
|
346
|
+
return { items, params: this.params() };
|
|
347
|
+
}
|
|
348
|
+
items.push(this.item());
|
|
349
|
+
const c = this.peek();
|
|
350
|
+
if (c !== " " && c !== ")") this.fail("invalid inner list");
|
|
351
|
+
}
|
|
352
|
+
return this.fail("unterminated inner list");
|
|
353
|
+
}
|
|
354
|
+
member() {
|
|
355
|
+
return this.peek() === "(" ? this.innerList() : this.item();
|
|
356
|
+
}
|
|
357
|
+
dictionary() {
|
|
358
|
+
const out = /* @__PURE__ */ new Map();
|
|
359
|
+
this.sp();
|
|
360
|
+
while (!this.done) {
|
|
361
|
+
const k = this.key();
|
|
362
|
+
let m;
|
|
363
|
+
if (this.peek() === "=") {
|
|
364
|
+
this.i++;
|
|
365
|
+
m = this.member();
|
|
366
|
+
} else {
|
|
367
|
+
m = { value: true, params: this.params() };
|
|
368
|
+
}
|
|
369
|
+
out.set(k, m);
|
|
370
|
+
this.ows();
|
|
371
|
+
if (this.done) return out;
|
|
372
|
+
if (this.peek() !== ",") this.fail("expected comma");
|
|
373
|
+
this.i++;
|
|
374
|
+
this.ows();
|
|
375
|
+
if (this.done) this.fail("trailing comma");
|
|
376
|
+
}
|
|
377
|
+
return out;
|
|
378
|
+
}
|
|
379
|
+
};
|
|
380
|
+
function parseTop(input, fn) {
|
|
381
|
+
const p = new Parser(input.trim());
|
|
382
|
+
const out = fn(p);
|
|
383
|
+
p.sp();
|
|
384
|
+
if (!p.done) p.fail("trailing characters");
|
|
385
|
+
return out;
|
|
386
|
+
}
|
|
387
|
+
var parseDictionary = (input) => parseTop(input, (p) => p.dictionary());
|
|
388
|
+
var parseItem = (input) => parseTop(input, (p) => p.item());
|
|
389
|
+
function serializeBareItem(v) {
|
|
390
|
+
if (typeof v === "boolean") return v ? "?1" : "?0";
|
|
391
|
+
if (typeof v === "number") {
|
|
392
|
+
if (!Number.isInteger(v)) throw new SfParseError("non-integer number; use SfDecimal");
|
|
393
|
+
return String(v);
|
|
394
|
+
}
|
|
395
|
+
if (v instanceof SfDecimal) {
|
|
396
|
+
const s = (Math.round(v.value * 1e3) / 1e3).toString();
|
|
397
|
+
return s.includes(".") ? s : `${s}.0`;
|
|
398
|
+
}
|
|
399
|
+
if (v instanceof SfToken) return v.value;
|
|
400
|
+
if (v instanceof Uint8Array) return `:${bytesToBase64(v)}:`;
|
|
401
|
+
return `"${v.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
402
|
+
}
|
|
403
|
+
var serializeParams = (params) => params.map(([k, v]) => v === true ? `;${k}` : `;${k}=${serializeBareItem(v)}`).join("");
|
|
404
|
+
var serializeItem = (item) => serializeBareItem(item.value) + serializeParams(item.params);
|
|
405
|
+
var serializeInnerList = (list) => `(${list.items.map(serializeItem).join(" ")})${serializeParams(list.params)}`;
|
|
406
|
+
var serializeMember = (m) => isInnerList(m) ? serializeInnerList(m) : serializeItem(m);
|
|
407
|
+
var getParam = (params, key) => params.find(([k]) => k === key)?.[1];
|
|
408
|
+
function base64ToBytes(b64) {
|
|
409
|
+
const bin = atob(b64);
|
|
410
|
+
const out = new Uint8Array(bin.length);
|
|
411
|
+
for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);
|
|
412
|
+
return out;
|
|
413
|
+
}
|
|
414
|
+
function bytesToBase64(bytes) {
|
|
415
|
+
let bin = "";
|
|
416
|
+
for (const b of bytes) bin += String.fromCharCode(b);
|
|
417
|
+
return btoa(bin);
|
|
418
|
+
}
|
|
419
|
+
var bytesToBase64Url = (bytes) => bytesToBase64(bytes).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
420
|
+
|
|
421
|
+
// src/server/oauth.ts
|
|
422
|
+
var josePromise;
|
|
423
|
+
var loadJose = () => josePromise ??= import('jose');
|
|
424
|
+
var remoteSets = /* @__PURE__ */ new Map();
|
|
425
|
+
var ASYMMETRIC = ["RS256", "PS256", "ES256", "EdDSA"];
|
|
426
|
+
function unverifiedIssuer(token) {
|
|
427
|
+
try {
|
|
428
|
+
const payload = token.split(".")[1];
|
|
429
|
+
const json = atob(payload.replace(/-/g, "+").replace(/_/g, "/"));
|
|
430
|
+
const iss = JSON.parse(json).iss;
|
|
431
|
+
return typeof iss === "string" ? iss : null;
|
|
432
|
+
} catch {
|
|
433
|
+
return null;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
var str = (v) => typeof v === "string" && v ? v : void 0;
|
|
437
|
+
async function sha256B64u(text) {
|
|
438
|
+
const d = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(text));
|
|
439
|
+
return bytesToBase64Url(new Uint8Array(d));
|
|
440
|
+
}
|
|
441
|
+
var htuOf = (url) => {
|
|
442
|
+
const u = new URL(url);
|
|
443
|
+
return `${u.origin}${u.pathname}`;
|
|
444
|
+
};
|
|
445
|
+
async function verifyDpopProof(jose, proof, token, jkt, request, replay) {
|
|
446
|
+
try {
|
|
447
|
+
const { payload, protectedHeader } = await jose.jwtVerify(proof, jose.EmbeddedJWK, {
|
|
448
|
+
typ: "dpop+jwt",
|
|
449
|
+
algorithms: ASYMMETRIC
|
|
450
|
+
});
|
|
451
|
+
const jwk = protectedHeader.jwk;
|
|
452
|
+
if (!jwk) return "dpop_missing_jwk";
|
|
453
|
+
if (await jose.calculateJwkThumbprint(jwk, "sha256") !== jkt) return "dpop_key_mismatch";
|
|
454
|
+
if (str(payload.htm)?.toUpperCase() !== request.method.toUpperCase()) return "dpop_htm_mismatch";
|
|
455
|
+
if (str(payload.htu) === void 0 || htuOf(str(payload.htu)) !== htuOf(request.url)) return "dpop_htu_mismatch";
|
|
456
|
+
const iat = typeof payload.iat === "number" ? payload.iat : NaN;
|
|
457
|
+
if (!Number.isFinite(iat) || Math.abs(Date.now() / 1e3 - iat) > 300) return "dpop_stale";
|
|
458
|
+
if (str(payload.ath) !== await sha256B64u(token)) return "dpop_ath_mismatch";
|
|
459
|
+
const jti = str(payload.jti);
|
|
460
|
+
if (!jti) return "dpop_missing_jti";
|
|
461
|
+
if (replay) {
|
|
462
|
+
const key = `dpop:${jkt}:${jti}`;
|
|
463
|
+
if (await replay.has(key)) return "dpop_replayed";
|
|
464
|
+
await replay.add(key, 600);
|
|
465
|
+
}
|
|
466
|
+
return null;
|
|
467
|
+
} catch (e) {
|
|
468
|
+
return `dpop_invalid:${e.code ?? e.message}`;
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
async function verifyOAuthToken(token, o, ctx) {
|
|
472
|
+
const jose = await loadJose();
|
|
473
|
+
const keySet = o.jwks ? jose.createLocalJWKSet(o.jwks) : (() => {
|
|
474
|
+
const uri = o.jwksUri ?? `${o.issuer.replace(/\/$/, "")}/.well-known/jwks.json`;
|
|
475
|
+
let s = remoteSets.get(uri);
|
|
476
|
+
if (!s) {
|
|
477
|
+
s = jose.createRemoteJWKSet(new URL(uri), { cacheMaxAge: 36e5, cooldownDuration: 3e4 });
|
|
478
|
+
remoteSets.set(uri, s);
|
|
479
|
+
}
|
|
480
|
+
return s;
|
|
481
|
+
})();
|
|
482
|
+
let payload;
|
|
483
|
+
try {
|
|
484
|
+
;
|
|
485
|
+
({ payload } = await jose.jwtVerify(token, keySet, {
|
|
486
|
+
issuer: o.issuer,
|
|
487
|
+
audience: o.audience,
|
|
488
|
+
algorithms: o.algorithms ?? ASYMMETRIC,
|
|
489
|
+
clockTolerance: o.clockToleranceSec ?? 30
|
|
490
|
+
}));
|
|
491
|
+
} catch (e) {
|
|
492
|
+
return { error: `invalid_token:${e.code ?? e.message}` };
|
|
493
|
+
}
|
|
494
|
+
const jkt = str(payload.cnf?.jkt);
|
|
495
|
+
let dpop = false;
|
|
496
|
+
if (jkt) {
|
|
497
|
+
const proof = ctx.request.headers.get("dpop");
|
|
498
|
+
if (ctx.scheme !== "dpop" || !proof) return { error: "dpop_proof_required" };
|
|
499
|
+
const bad = await verifyDpopProof(jose, proof, token, jkt, ctx.request, ctx.replayCache);
|
|
500
|
+
if (bad) return { error: bad };
|
|
501
|
+
dpop = true;
|
|
502
|
+
} else if (o.requireDpop) {
|
|
503
|
+
return { error: "token_not_dpop_bound" };
|
|
504
|
+
}
|
|
505
|
+
if (o.requiredScopes?.length) {
|
|
506
|
+
const granted = new Set(String(payload.scope ?? payload.scp ?? "").split(/\s+/));
|
|
507
|
+
const missing = o.requiredScopes.filter((s) => !granted.has(s));
|
|
508
|
+
if (missing.length) return { error: `missing_scopes:${missing.join(",")}` };
|
|
509
|
+
}
|
|
510
|
+
const act = payload.act;
|
|
511
|
+
const clientId = str(payload.client_id) ?? str(payload.azp);
|
|
512
|
+
const sub = str(payload.sub);
|
|
513
|
+
const agentId = str(act?.client_id) ?? str(act?.sub) ?? clientId ?? sub;
|
|
514
|
+
if (!agentId) return { error: "token_has_no_client_id" };
|
|
515
|
+
const user = act ? sub : sub && clientId && sub !== clientId ? sub : void 0;
|
|
516
|
+
const agent = {
|
|
517
|
+
id: `oauth2:${agentId}`,
|
|
518
|
+
name: str(payload.agent_name) ?? str(payload.client_name) ?? agentId,
|
|
519
|
+
vendor: str(payload.agent_vendor) ?? null,
|
|
520
|
+
claims: { iss: payload.iss, sub, scope: payload.scope ?? payload.scp, ...act ? { act } : {} },
|
|
521
|
+
dpop
|
|
522
|
+
};
|
|
523
|
+
if (user) {
|
|
524
|
+
const onBehalfOf = { sub: user };
|
|
525
|
+
if (str(payload.email)) onBehalfOf.email = str(payload.email);
|
|
526
|
+
if (str(payload.name)) onBehalfOf.name = str(payload.name);
|
|
527
|
+
agent.onBehalfOf = onBehalfOf;
|
|
528
|
+
}
|
|
529
|
+
return agent;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
// src/server/httpSignatures.ts
|
|
533
|
+
var SignatureError = class extends Error {
|
|
534
|
+
constructor(code, message) {
|
|
535
|
+
super(message ?? code);
|
|
536
|
+
this.code = code;
|
|
537
|
+
}
|
|
538
|
+
code;
|
|
539
|
+
};
|
|
540
|
+
var DERIVED = /* @__PURE__ */ new Set([
|
|
541
|
+
"@method",
|
|
542
|
+
"@target-uri",
|
|
543
|
+
"@authority",
|
|
544
|
+
"@scheme",
|
|
545
|
+
"@request-target",
|
|
546
|
+
"@path",
|
|
547
|
+
"@query",
|
|
548
|
+
"@query-param"
|
|
549
|
+
]);
|
|
550
|
+
function componentValue(id, ctx) {
|
|
551
|
+
if (typeof id.value !== "string") throw new SignatureError("invalid_component", "component id must be a string");
|
|
552
|
+
const name = id.value;
|
|
553
|
+
const { url } = ctx;
|
|
554
|
+
if (name.startsWith("@")) {
|
|
555
|
+
if (!DERIVED.has(name)) throw new SignatureError("unsupported_component", name);
|
|
556
|
+
switch (name) {
|
|
557
|
+
case "@method":
|
|
558
|
+
return ctx.request.method.toUpperCase();
|
|
559
|
+
case "@target-uri":
|
|
560
|
+
return url.href;
|
|
561
|
+
case "@authority":
|
|
562
|
+
return (ctx.authority ?? url.host).toLowerCase();
|
|
563
|
+
case "@scheme":
|
|
564
|
+
return url.protocol.replace(/:$/, "").toLowerCase();
|
|
565
|
+
case "@request-target":
|
|
566
|
+
return url.pathname + url.search;
|
|
567
|
+
case "@path":
|
|
568
|
+
return url.pathname || "/";
|
|
569
|
+
case "@query":
|
|
570
|
+
return url.search || "?";
|
|
571
|
+
case "@query-param": {
|
|
572
|
+
const pname = getParam(id.params, "name");
|
|
573
|
+
if (typeof pname !== "string") throw new SignatureError("invalid_component", "@query-param needs name");
|
|
574
|
+
const values = url.searchParams.getAll(pname);
|
|
575
|
+
if (values.length !== 1) throw new SignatureError("invalid_component", `@query-param ${pname}`);
|
|
576
|
+
return encodeURIComponent(values[0]);
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
for (const [k] of id.params) {
|
|
581
|
+
if (k !== "key") throw new SignatureError("unsupported_component", `param ${k}`);
|
|
582
|
+
}
|
|
583
|
+
const raw = ctx.request.headers.get(name);
|
|
584
|
+
if (raw === null) throw new SignatureError("missing_component", name);
|
|
585
|
+
const value = raw.trim();
|
|
586
|
+
const key = getParam(id.params, "key");
|
|
587
|
+
if (typeof key === "string") {
|
|
588
|
+
let dict;
|
|
589
|
+
try {
|
|
590
|
+
dict = parseDictionary(value);
|
|
591
|
+
} catch (e) {
|
|
592
|
+
throw new SignatureError("invalid_component", `${name} is not a dictionary: ${e.message}`);
|
|
593
|
+
}
|
|
594
|
+
const member = dict.get(key);
|
|
595
|
+
if (!member) throw new SignatureError("missing_component", `${name};key=${key}`);
|
|
596
|
+
return serializeMember(member);
|
|
597
|
+
}
|
|
598
|
+
return value;
|
|
599
|
+
}
|
|
600
|
+
function signatureBase(input, ctx) {
|
|
601
|
+
const seen = /* @__PURE__ */ new Set();
|
|
602
|
+
const lines = [];
|
|
603
|
+
for (const id of input.items) {
|
|
604
|
+
const ident = serializeItem(id);
|
|
605
|
+
if (seen.has(ident)) throw new SignatureError("duplicate_component", ident);
|
|
606
|
+
seen.add(ident);
|
|
607
|
+
lines.push(`${ident}: ${componentValue(id, ctx)}`);
|
|
608
|
+
}
|
|
609
|
+
lines.push(`"@signature-params": ${serializeInnerList(input)}`);
|
|
610
|
+
return lines.join("\n");
|
|
611
|
+
}
|
|
612
|
+
var ALGS = {
|
|
613
|
+
ed25519: { kty: "OKP", crv: "Ed25519", importAlg: { name: "Ed25519" }, verifyAlg: { name: "Ed25519" } },
|
|
614
|
+
"rsa-pss-sha512": {
|
|
615
|
+
kty: "RSA",
|
|
616
|
+
importAlg: { name: "RSA-PSS", hash: "SHA-512" },
|
|
617
|
+
verifyAlg: { name: "RSA-PSS", saltLength: 64 }
|
|
618
|
+
},
|
|
619
|
+
"rsa-v1_5-sha256": {
|
|
620
|
+
kty: "RSA",
|
|
621
|
+
importAlg: { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" },
|
|
622
|
+
verifyAlg: { name: "RSASSA-PKCS1-v1_5" }
|
|
623
|
+
},
|
|
624
|
+
"ecdsa-p256-sha256": {
|
|
625
|
+
kty: "EC",
|
|
626
|
+
crv: "P-256",
|
|
627
|
+
importAlg: { name: "ECDSA", namedCurve: "P-256" },
|
|
628
|
+
verifyAlg: { name: "ECDSA", hash: "SHA-256" }
|
|
629
|
+
},
|
|
630
|
+
"ecdsa-p384-sha384": {
|
|
631
|
+
kty: "EC",
|
|
632
|
+
crv: "P-384",
|
|
633
|
+
importAlg: { name: "ECDSA", namedCurve: "P-384" },
|
|
634
|
+
verifyAlg: { name: "ECDSA", hash: "SHA-384" }
|
|
635
|
+
}
|
|
636
|
+
};
|
|
637
|
+
function resolveAlgorithm(jwk, algParam) {
|
|
638
|
+
if (typeof algParam === "string") {
|
|
639
|
+
const spec = ALGS[algParam];
|
|
640
|
+
if (!spec) throw new SignatureError("unsupported_alg", algParam);
|
|
641
|
+
if (spec.kty !== jwk.kty || spec.crv && spec.crv !== jwk.crv) {
|
|
642
|
+
throw new SignatureError("alg_key_mismatch", `${algParam} vs ${jwk.kty}/${jwk.crv ?? ""}`);
|
|
643
|
+
}
|
|
644
|
+
return algParam;
|
|
645
|
+
}
|
|
646
|
+
if (jwk.kty === "OKP" && jwk.crv === "Ed25519") return "ed25519";
|
|
647
|
+
if (jwk.kty === "EC" && jwk.crv === "P-256") return "ecdsa-p256-sha256";
|
|
648
|
+
if (jwk.kty === "EC" && jwk.crv === "P-384") return "ecdsa-p384-sha384";
|
|
649
|
+
throw new SignatureError("alg_required", `cannot infer algorithm for ${jwk.kty}`);
|
|
650
|
+
}
|
|
651
|
+
async function verifySignature(alg, jwk, base, signature) {
|
|
652
|
+
const spec = ALGS[alg];
|
|
653
|
+
const { kid: _kid, alg: _alg, use: _use, nbf: _nbf, exp: _exp, ...material } = jwk;
|
|
654
|
+
let key;
|
|
655
|
+
try {
|
|
656
|
+
key = await crypto.subtle.importKey("jwk", material, spec.importAlg, false, ["verify"]);
|
|
657
|
+
} catch (e) {
|
|
658
|
+
throw new SignatureError("invalid_key", e.message);
|
|
659
|
+
}
|
|
660
|
+
return crypto.subtle.verify(spec.verifyAlg, key, new Uint8Array(signature), new TextEncoder().encode(base));
|
|
661
|
+
}
|
|
662
|
+
async function jwkThumbprint(jwk) {
|
|
663
|
+
let canonical;
|
|
664
|
+
if (jwk.kty === "OKP") canonical = JSON.stringify({ crv: jwk.crv, kty: jwk.kty, x: jwk.x });
|
|
665
|
+
else if (jwk.kty === "EC") canonical = JSON.stringify({ crv: jwk.crv, kty: jwk.kty, x: jwk.x, y: jwk.y });
|
|
666
|
+
else if (jwk.kty === "RSA") canonical = JSON.stringify({ e: jwk.e, kty: jwk.kty, n: jwk.n });
|
|
667
|
+
else throw new SignatureError("invalid_key", `unsupported kty ${jwk.kty}`);
|
|
668
|
+
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(canonical));
|
|
669
|
+
return bytesToBase64Url(new Uint8Array(digest));
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
// src/server/safeFetch.ts
|
|
673
|
+
var UnsafeUrlError = class extends Error {
|
|
674
|
+
};
|
|
675
|
+
var BLOCKED_SUFFIXES = [".localhost", ".local", ".internal", ".home.arpa", ".lan", ".corp", ".intranet"];
|
|
676
|
+
function isPrivateIpv4(ip) {
|
|
677
|
+
const m = ip.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/);
|
|
678
|
+
if (!m) return false;
|
|
679
|
+
const [a, b] = [Number(m[1]), Number(m[2])];
|
|
680
|
+
return a === 0 || a === 10 || a === 127 || a === 100 && b >= 64 && b <= 127 || // CGNAT
|
|
681
|
+
a === 169 && b === 254 || // link-local / cloud metadata
|
|
682
|
+
a === 172 && b >= 16 && b <= 31 || a === 192 && b === 168 || a === 192 && b === 0 || a === 198 && (b === 18 || b === 19) || a >= 224;
|
|
683
|
+
}
|
|
684
|
+
function isPrivateIpv6(ip) {
|
|
685
|
+
const s = ip.toLowerCase().replace(/^\[|\]$/g, "");
|
|
686
|
+
if (s === "::" || s === "::1") return true;
|
|
687
|
+
const mapped = s.match(/^::ffff:(\d+\.\d+\.\d+\.\d+)$/);
|
|
688
|
+
if (mapped) return isPrivateIpv4(mapped[1]);
|
|
689
|
+
return /^(fc|fd|fe8|fe9|fea|feb|ff)/.test(s);
|
|
690
|
+
}
|
|
691
|
+
var isIpLiteral = (host) => /^\d+\.\d+\.\d+\.\d+$/.test(host) || host.includes(":") || host.startsWith("[");
|
|
692
|
+
function assertSafeUrl(raw) {
|
|
693
|
+
let url;
|
|
694
|
+
try {
|
|
695
|
+
url = new URL(raw);
|
|
696
|
+
} catch {
|
|
697
|
+
throw new UnsafeUrlError("invalid URL");
|
|
698
|
+
}
|
|
699
|
+
if (url.protocol !== "https:") throw new UnsafeUrlError("HTTPS required");
|
|
700
|
+
if (url.username || url.password) throw new UnsafeUrlError("credentials in URL");
|
|
701
|
+
if (url.port && url.port !== "443") throw new UnsafeUrlError("non-standard port");
|
|
702
|
+
const host = url.hostname.toLowerCase();
|
|
703
|
+
if (isIpLiteral(host)) throw new UnsafeUrlError("IP-literal hosts are not allowed");
|
|
704
|
+
if (host === "localhost" || !host.includes(".") || BLOCKED_SUFFIXES.some((s) => host.endsWith(s))) {
|
|
705
|
+
throw new UnsafeUrlError("internal hostname");
|
|
706
|
+
}
|
|
707
|
+
return url;
|
|
708
|
+
}
|
|
709
|
+
async function assertPublicDns(host) {
|
|
710
|
+
let dns;
|
|
711
|
+
try {
|
|
712
|
+
dns = await import(
|
|
713
|
+
/* webpackIgnore: true */
|
|
714
|
+
/* @vite-ignore */
|
|
715
|
+
'dns/promises'
|
|
716
|
+
);
|
|
717
|
+
} catch {
|
|
718
|
+
return;
|
|
719
|
+
}
|
|
720
|
+
const addrs = await dns.lookup(host, { all: true }).catch(() => {
|
|
721
|
+
throw new UnsafeUrlError("DNS lookup failed");
|
|
722
|
+
});
|
|
723
|
+
for (const { address, family } of addrs) {
|
|
724
|
+
if (family === 4 ? isPrivateIpv4(address) : isPrivateIpv6(address)) {
|
|
725
|
+
throw new UnsafeUrlError("host resolves to a private address");
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
async function safeFetch(raw, opts = {}) {
|
|
730
|
+
const url = assertSafeUrl(raw);
|
|
731
|
+
if (opts.resolveDns) await assertPublicDns(url.hostname);
|
|
732
|
+
const doFetch = opts.fetch ?? fetch;
|
|
733
|
+
const res = await doFetch(url.href, {
|
|
734
|
+
method: "GET",
|
|
735
|
+
redirect: "manual",
|
|
736
|
+
headers: { accept: opts.accept ?? "application/json" },
|
|
737
|
+
signal: AbortSignal.timeout(opts.timeoutMs ?? 3e3)
|
|
738
|
+
});
|
|
739
|
+
const maxBytes = opts.maxBytes ?? 64 * 1024;
|
|
740
|
+
const declared = Number(res.headers.get("content-length") ?? 0);
|
|
741
|
+
if (declared > maxBytes) throw new UnsafeUrlError("response too large");
|
|
742
|
+
const reader = res.body?.getReader();
|
|
743
|
+
const chunks = [];
|
|
744
|
+
let total = 0;
|
|
745
|
+
if (reader) {
|
|
746
|
+
for (; ; ) {
|
|
747
|
+
const { done, value } = await reader.read();
|
|
748
|
+
if (done) break;
|
|
749
|
+
total += value.byteLength;
|
|
750
|
+
if (total > maxBytes) {
|
|
751
|
+
await reader.cancel().catch(() => void 0);
|
|
752
|
+
throw new UnsafeUrlError("response too large");
|
|
753
|
+
}
|
|
754
|
+
chunks.push(value);
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
const body = new Uint8Array(total);
|
|
758
|
+
let o = 0;
|
|
759
|
+
for (const c of chunks) {
|
|
760
|
+
body.set(c, o);
|
|
761
|
+
o += c.byteLength;
|
|
762
|
+
}
|
|
763
|
+
return { status: res.status, headers: res.headers, body: new TextDecoder().decode(body) };
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
// src/server/webBotAuth.ts
|
|
767
|
+
var WEB_BOT_AUTH_TAG = "web-bot-auth";
|
|
768
|
+
var DIRECTORY_PATH = "/.well-known/http-message-signatures-directory";
|
|
769
|
+
var DIRECTORY_MEDIA_TYPE = "application/http-message-signatures-directory+json";
|
|
770
|
+
function memoryReplayCache(maxEntries = 1e4) {
|
|
771
|
+
const m = /* @__PURE__ */ new Map();
|
|
772
|
+
return {
|
|
773
|
+
has(key) {
|
|
774
|
+
const exp = m.get(key);
|
|
775
|
+
if (exp === void 0) return false;
|
|
776
|
+
if (exp < Date.now()) {
|
|
777
|
+
m.delete(key);
|
|
778
|
+
return false;
|
|
779
|
+
}
|
|
780
|
+
return true;
|
|
781
|
+
},
|
|
782
|
+
add(key, ttl) {
|
|
783
|
+
if (m.size >= maxEntries) m.delete(m.keys().next().value);
|
|
784
|
+
m.set(key, Date.now() + ttl * 1e3);
|
|
785
|
+
}
|
|
786
|
+
};
|
|
787
|
+
}
|
|
788
|
+
var directoryCache = /* @__PURE__ */ new Map();
|
|
789
|
+
var MAX_CACHED = 256;
|
|
790
|
+
var NEGATIVE_TTL = 5 * 60 * 1e3;
|
|
791
|
+
function cacheTtlMs(headers) {
|
|
792
|
+
const cc = headers.get("cache-control") ?? "";
|
|
793
|
+
const m = cc.match(/max-age=(\d+)/i);
|
|
794
|
+
const secs = m ? Number(m[1]) : 3600;
|
|
795
|
+
return Math.min(Math.max(secs, 60), 86400) * 1e3;
|
|
796
|
+
}
|
|
797
|
+
async function loadDirectory(url, opts) {
|
|
798
|
+
const cached = directoryCache.get(url);
|
|
799
|
+
if (cached && "failedUntil" in cached && cached.failedUntil > Date.now()) {
|
|
800
|
+
throw new SignatureError("discovery_failed", "recent discovery failure (cached)");
|
|
801
|
+
}
|
|
802
|
+
if (cached && "keys" in cached && cached.until > Date.now()) return cached;
|
|
803
|
+
try {
|
|
804
|
+
const res = await safeFetch(url, {
|
|
805
|
+
...opts.fetch ? { fetch: opts.fetch } : {},
|
|
806
|
+
accept: `${DIRECTORY_MEDIA_TYPE}, application/jwk-set+json, application/json`,
|
|
807
|
+
resolveDns: opts.resolveDns ?? true
|
|
808
|
+
});
|
|
809
|
+
if (res.status !== 200) throw new SignatureError("discovery_failed", `directory returned ${res.status}`);
|
|
810
|
+
let doc;
|
|
811
|
+
try {
|
|
812
|
+
doc = JSON.parse(res.body);
|
|
813
|
+
} catch {
|
|
814
|
+
throw new SignatureError("discovery_failed", "directory is not JSON");
|
|
815
|
+
}
|
|
816
|
+
const raw = doc.keys;
|
|
817
|
+
if (!Array.isArray(raw)) throw new SignatureError("discovery_failed", "directory has no keys array");
|
|
818
|
+
const now = opts.now?.() ?? Date.now() / 1e3;
|
|
819
|
+
const keys = raw.filter(
|
|
820
|
+
(k) => typeof k === "object" && k !== null && typeof k.kty === "string" && (k.nbf === void 0 || k.nbf <= now) && (k.exp === void 0 || k.exp > now)
|
|
821
|
+
);
|
|
822
|
+
const thumbprints = await Promise.all(keys.map((k) => jwkThumbprint(k).catch(() => "")));
|
|
823
|
+
const entry = { keys, thumbprints, until: Date.now() + cacheTtlMs(res.headers) };
|
|
824
|
+
if (directoryCache.size >= MAX_CACHED) directoryCache.delete(directoryCache.keys().next().value);
|
|
825
|
+
directoryCache.set(url, entry);
|
|
826
|
+
return entry;
|
|
827
|
+
} catch (e) {
|
|
828
|
+
directoryCache.set(url, { failedUntil: Date.now() + NEGATIVE_TTL });
|
|
829
|
+
if (e instanceof SignatureError) throw e;
|
|
830
|
+
throw new SignatureError("discovery_failed", e.message);
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
function directoryUrlFor(member) {
|
|
834
|
+
if (typeof member.value !== "string") throw new SignatureError("invalid_signature_agent", "must be a string");
|
|
835
|
+
let url;
|
|
836
|
+
try {
|
|
837
|
+
url = new URL(member.value);
|
|
838
|
+
} catch {
|
|
839
|
+
throw new SignatureError("invalid_signature_agent", "not a URL");
|
|
840
|
+
}
|
|
841
|
+
if (url.protocol !== "https:") throw new SignatureError("invalid_signature_agent", "HTTPS required");
|
|
842
|
+
const typeParam = getParam(member.params, "type");
|
|
843
|
+
const type = typeParam instanceof SfToken ? typeParam.value : typeof typeParam === "string" ? typeParam : "directory";
|
|
844
|
+
if (type === "directory") return { signer: url.origin, directoryUrl: url.origin + DIRECTORY_PATH };
|
|
845
|
+
if (type === "jwks_uri") return { signer: url.href, directoryUrl: url.href };
|
|
846
|
+
throw new SignatureError("unsupported_signature_agent_type", type);
|
|
847
|
+
}
|
|
848
|
+
function isAllowed(signer, allowed) {
|
|
849
|
+
if (!allowed || allowed === "any") return true;
|
|
850
|
+
return allowed.some((a) => {
|
|
851
|
+
try {
|
|
852
|
+
const u = new URL(a);
|
|
853
|
+
return u.origin === signer || u.href === signer || new URL(signer).origin === u.origin;
|
|
854
|
+
} catch {
|
|
855
|
+
return false;
|
|
856
|
+
}
|
|
857
|
+
});
|
|
858
|
+
}
|
|
859
|
+
async function verifyWebBotAuth(request, opts = {}) {
|
|
860
|
+
const sigHeader = request.headers.get("signature");
|
|
861
|
+
const inputHeader = request.headers.get("signature-input");
|
|
862
|
+
if (!sigHeader && !inputHeader) return { status: "absent" };
|
|
863
|
+
if (!sigHeader || !inputHeader) return { status: "invalid", reason: "signature_headers_incomplete" };
|
|
864
|
+
let inputs, sigs;
|
|
865
|
+
try {
|
|
866
|
+
inputs = parseDictionary(inputHeader);
|
|
867
|
+
sigs = parseDictionary(sigHeader);
|
|
868
|
+
} catch (e) {
|
|
869
|
+
return { status: "invalid", reason: `malformed_signature_headers: ${e.message}` };
|
|
870
|
+
}
|
|
871
|
+
const candidates = [...inputs.entries()].filter(
|
|
872
|
+
([, m]) => isInnerList(m) && getParam(m.params, "tag") === WEB_BOT_AUTH_TAG
|
|
873
|
+
);
|
|
874
|
+
if (candidates.length === 0) return { status: "absent" };
|
|
875
|
+
let lastFailure = { status: "invalid", reason: "no_valid_signature" };
|
|
876
|
+
for (const [label, input] of candidates) {
|
|
877
|
+
const r = await verifyOne(request, label, input, sigs.get(label), opts);
|
|
878
|
+
if (r.status === "verified") return r;
|
|
879
|
+
lastFailure = r;
|
|
880
|
+
}
|
|
881
|
+
return lastFailure;
|
|
882
|
+
}
|
|
883
|
+
async function verifyOne(request, label, input, sigMember, opts) {
|
|
884
|
+
const fail = (reason, extra = {}) => ({
|
|
885
|
+
status: "invalid",
|
|
886
|
+
reason,
|
|
887
|
+
...extra
|
|
888
|
+
});
|
|
889
|
+
if (!sigMember || isInnerList(sigMember) || !(sigMember.value instanceof Uint8Array)) {
|
|
890
|
+
return fail("signature_missing_for_label");
|
|
891
|
+
}
|
|
892
|
+
const signature = sigMember.value;
|
|
893
|
+
const created = getParam(input.params, "created");
|
|
894
|
+
const expires = getParam(input.params, "expires");
|
|
895
|
+
const keyid = getParam(input.params, "keyid");
|
|
896
|
+
const nonce = getParam(input.params, "nonce");
|
|
897
|
+
if (typeof created !== "number" || typeof expires !== "number") return fail("created_and_expires_required");
|
|
898
|
+
if (typeof keyid !== "string") return fail("keyid_required");
|
|
899
|
+
if (nonce !== void 0 && typeof nonce !== "string") return fail("invalid_nonce");
|
|
900
|
+
if (opts.requireNonce && nonce === void 0) return fail("nonce_required", { keyid });
|
|
901
|
+
const now = Math.floor(opts.now?.() ?? Date.now() / 1e3);
|
|
902
|
+
const skew = opts.clockSkewSec ?? 60;
|
|
903
|
+
if (created > now + skew) return fail("created_in_future", { keyid });
|
|
904
|
+
if (expires < now - skew) return fail("expired", { keyid });
|
|
905
|
+
if (expires <= created) return fail("expires_before_created", { keyid });
|
|
906
|
+
if (expires - created > (opts.maxValiditySec ?? 86400)) return fail("validity_window_too_long", { keyid });
|
|
907
|
+
const names = input.items.map((i) => i.value);
|
|
908
|
+
if (!names.includes("@authority") && !names.includes("@target-uri")) {
|
|
909
|
+
return fail("authority_or_target_uri_not_covered", { keyid });
|
|
910
|
+
}
|
|
911
|
+
const agentComponent = input.items.find((i) => i.value === "signature-agent");
|
|
912
|
+
if (!agentComponent) return fail("signature_agent_not_covered", { keyid });
|
|
913
|
+
const agentHeader = request.headers.get("signature-agent");
|
|
914
|
+
if (!agentHeader) return fail("signature_agent_missing", { keyid });
|
|
915
|
+
let member;
|
|
916
|
+
try {
|
|
917
|
+
const key = getParam(agentComponent.params, "key");
|
|
918
|
+
if (typeof key === "string") {
|
|
919
|
+
const m = parseDictionary(agentHeader).get(key);
|
|
920
|
+
if (!m || isInnerList(m)) return fail("signature_agent_member_missing", { keyid });
|
|
921
|
+
member = m;
|
|
922
|
+
} else {
|
|
923
|
+
member = parseItem(agentHeader);
|
|
924
|
+
}
|
|
925
|
+
} catch (e) {
|
|
926
|
+
return fail(`malformed_signature_agent: ${e.message}`, { keyid });
|
|
927
|
+
}
|
|
928
|
+
let signer, directoryUrl;
|
|
929
|
+
try {
|
|
930
|
+
;
|
|
931
|
+
({ signer, directoryUrl } = directoryUrlFor(member));
|
|
932
|
+
} catch (e) {
|
|
933
|
+
return fail(e.code ?? "invalid_signature_agent", { keyid });
|
|
934
|
+
}
|
|
935
|
+
if (!isAllowed(signer, opts.allowedDirectories)) return fail("signer_not_allowed", { agentUrl: signer, keyid });
|
|
936
|
+
const replayKey = nonce ? `${signer}|${keyid}|${nonce}` : void 0;
|
|
937
|
+
if (replayKey && await opts.replayCache?.has(replayKey)) return fail("replayed_nonce", { agentUrl: signer, keyid });
|
|
938
|
+
let dir;
|
|
939
|
+
try {
|
|
940
|
+
dir = await loadDirectory(directoryUrl, opts);
|
|
941
|
+
} catch (e) {
|
|
942
|
+
return fail(e.code ?? "discovery_failed", { agentUrl: signer, keyid });
|
|
943
|
+
}
|
|
944
|
+
const idx = dir.thumbprints.indexOf(keyid);
|
|
945
|
+
if (idx < 0) return fail("unknown_keyid", { agentUrl: signer, keyid });
|
|
946
|
+
const jwk = dir.keys[idx];
|
|
947
|
+
try {
|
|
948
|
+
const url = new URL(request.url);
|
|
949
|
+
const alg = resolveAlgorithm(jwk, getParam(input.params, "alg"));
|
|
950
|
+
const base = signatureBase(input, {
|
|
951
|
+
request,
|
|
952
|
+
url,
|
|
953
|
+
...opts.authority ? { authority: opts.authority(request, url) } : {}
|
|
954
|
+
});
|
|
955
|
+
const ok = await verifySignature(alg, jwk, base, signature);
|
|
956
|
+
if (!ok) return fail("bad_signature", { agentUrl: signer, keyid });
|
|
957
|
+
if (replayKey) await opts.replayCache?.add(replayKey, Math.max(expires - now, 1) + skew);
|
|
958
|
+
return { status: "verified", agentUrl: signer, keyid, label, alg, created, expires };
|
|
959
|
+
} catch (e) {
|
|
960
|
+
return fail(e instanceof SignatureError ? e.code : "verification_error", { agentUrl: signer, keyid });
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
// src/server/agentAuth.ts
|
|
965
|
+
var KNOWN_SIGNERS = {
|
|
966
|
+
"https://chatgpt.com": { name: "ChatGPT agent", vendor: "OpenAI" }
|
|
967
|
+
};
|
|
968
|
+
var GENERIC_AGENT_UA = /\b(bot|crawler|spider|agent|headless|scraper)\b/i;
|
|
969
|
+
var JWT_SHAPE = /^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]*$/;
|
|
970
|
+
function credential(request) {
|
|
971
|
+
const h = request.headers.get("authorization");
|
|
972
|
+
const m = h?.match(/^(bearer|dpop)\s+(.+)$/i);
|
|
973
|
+
return m ? { token: m[2].trim(), scheme: m[1].toLowerCase() } : null;
|
|
974
|
+
}
|
|
975
|
+
function createAgentAuth(options = {}) {
|
|
976
|
+
const replayCache = options.replayCache ?? memoryReplayCache();
|
|
977
|
+
const wba = options.webBotAuth === false ? null : { replayCache, ...typeof options.webBotAuth === "object" ? options.webBotAuth : {} };
|
|
978
|
+
const issuers = options.oauth2 ? Array.isArray(options.oauth2) ? options.oauth2 : [options.oauth2] : [];
|
|
979
|
+
const crawlers = options.crawlers === false ? null : typeof options.crawlers === "object" ? options.crawlers : {};
|
|
980
|
+
const keyPrefix = options.apiKey?.prefix ?? "agk_";
|
|
981
|
+
async function authenticate(request) {
|
|
982
|
+
const attempted = [];
|
|
983
|
+
const reasons = [];
|
|
984
|
+
let claimedName;
|
|
985
|
+
if (wba) {
|
|
986
|
+
const r = await verifyWebBotAuth(request, wba);
|
|
987
|
+
if (r.status === "verified") {
|
|
988
|
+
const known = KNOWN_SIGNERS[r.agentUrl];
|
|
989
|
+
const host = new URL(r.agentUrl).hostname;
|
|
990
|
+
return {
|
|
991
|
+
status: "verified",
|
|
992
|
+
agent: {
|
|
993
|
+
id: r.agentUrl,
|
|
994
|
+
name: known?.name ?? host,
|
|
995
|
+
vendor: known?.vendor ?? null,
|
|
996
|
+
method: "web-bot-auth",
|
|
997
|
+
claims: { keyid: r.keyid, alg: r.alg }
|
|
998
|
+
}
|
|
999
|
+
};
|
|
1000
|
+
}
|
|
1001
|
+
if (r.status === "invalid") {
|
|
1002
|
+
attempted.push("web-bot-auth");
|
|
1003
|
+
reasons.push(`web-bot-auth:${r.reason}`);
|
|
1004
|
+
claimedName ??= r.agentUrl ? new URL(r.agentUrl).hostname : void 0;
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
const cred = credential(request);
|
|
1008
|
+
const token = cred?.token ?? request.headers.get("x-agent-key");
|
|
1009
|
+
if (token && options.apiKey && token.startsWith(keyPrefix)) {
|
|
1010
|
+
attempted.push("api-key");
|
|
1011
|
+
const id = await options.apiKey.verify(token);
|
|
1012
|
+
if (id) {
|
|
1013
|
+
return {
|
|
1014
|
+
status: "verified",
|
|
1015
|
+
agent: {
|
|
1016
|
+
id: `key:${id.agentId}`,
|
|
1017
|
+
name: id.name ?? id.agentId,
|
|
1018
|
+
vendor: id.vendor ?? null,
|
|
1019
|
+
method: "api-key",
|
|
1020
|
+
...id.scopes ? { claims: { scopes: id.scopes } } : {}
|
|
1021
|
+
}
|
|
1022
|
+
};
|
|
1023
|
+
}
|
|
1024
|
+
reasons.push("api-key:unknown_or_revoked");
|
|
1025
|
+
} else if (cred && issuers.length && JWT_SHAPE.test(cred.token)) {
|
|
1026
|
+
const iss = unverifiedIssuer(cred.token);
|
|
1027
|
+
const config = issuers.find((i) => i.issuer === iss);
|
|
1028
|
+
if (config) {
|
|
1029
|
+
attempted.push("oauth2");
|
|
1030
|
+
const r = await verifyOAuthToken(cred.token, config, { request, scheme: cred.scheme, replayCache });
|
|
1031
|
+
if (!("error" in r)) {
|
|
1032
|
+
const agent = {
|
|
1033
|
+
id: r.id,
|
|
1034
|
+
name: r.name,
|
|
1035
|
+
vendor: r.vendor,
|
|
1036
|
+
method: "oauth2",
|
|
1037
|
+
claims: { ...r.claims, dpop: r.dpop }
|
|
1038
|
+
};
|
|
1039
|
+
if (r.onBehalfOf) agent.onBehalfOf = r.onBehalfOf;
|
|
1040
|
+
return { status: "verified", agent };
|
|
1041
|
+
}
|
|
1042
|
+
reasons.push(`oauth2:${r.error}`);
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
const ua = request.headers.get("user-agent");
|
|
1046
|
+
const claim = classifyUserAgent(ua);
|
|
1047
|
+
if (claim) {
|
|
1048
|
+
claimedName ??= claim.name;
|
|
1049
|
+
if (crawlers) {
|
|
1050
|
+
attempted.push("verified-crawler");
|
|
1051
|
+
const v = await verifyCrawler(claim, clientIp(request, crawlers.clientIp), crawlers.resolver, crawlers);
|
|
1052
|
+
if (v.status === "verified") {
|
|
1053
|
+
return {
|
|
1054
|
+
status: "verified",
|
|
1055
|
+
agent: { id: `crawler:${claim.name}`, name: claim.name, vendor: null, method: "verified-crawler", claims: { host: v.host, via: v.via } }
|
|
1056
|
+
};
|
|
1057
|
+
}
|
|
1058
|
+
reasons.push(`crawler:${v.reason}`);
|
|
1059
|
+
} else reasons.push("crawler:verification_disabled");
|
|
1060
|
+
}
|
|
1061
|
+
if (attempted.length || claim || ua && GENERIC_AGENT_UA.test(ua) || request.headers.has("signature-agent")) {
|
|
1062
|
+
return {
|
|
1063
|
+
status: "unverified",
|
|
1064
|
+
reason: reasons[0] ?? "agent_claim_without_credentials",
|
|
1065
|
+
...claimedName ? { claimedName } : {},
|
|
1066
|
+
attempted
|
|
1067
|
+
};
|
|
1068
|
+
}
|
|
1069
|
+
return { status: "none" };
|
|
1070
|
+
}
|
|
1071
|
+
return { authenticate };
|
|
1072
|
+
}
|
|
1073
|
+
var AGENT_HEADERS = {
|
|
1074
|
+
status: "x-agentronics-agent-status",
|
|
1075
|
+
id: "x-agentronics-agent-id",
|
|
1076
|
+
name: "x-agentronics-agent-name",
|
|
1077
|
+
vendor: "x-agentronics-agent-vendor",
|
|
1078
|
+
method: "x-agentronics-auth-method"
|
|
1079
|
+
};
|
|
1080
|
+
function withAgentHeaders(request, result) {
|
|
1081
|
+
const h = new Headers(request.headers);
|
|
1082
|
+
for (const k of [...h.keys()]) if (k.toLowerCase().startsWith("x-agentronics-")) h.delete(k);
|
|
1083
|
+
h.set(AGENT_HEADERS.status, result.status);
|
|
1084
|
+
if (result.status === "verified") {
|
|
1085
|
+
h.set(AGENT_HEADERS.id, result.agent.id);
|
|
1086
|
+
h.set(AGENT_HEADERS.name, result.agent.name);
|
|
1087
|
+
h.set(AGENT_HEADERS.method, result.agent.method);
|
|
1088
|
+
if (result.agent.vendor) h.set(AGENT_HEADERS.vendor, result.agent.vendor);
|
|
1089
|
+
}
|
|
1090
|
+
return h;
|
|
1091
|
+
}
|
|
1092
|
+
function readAgentHeaders(headers) {
|
|
1093
|
+
const status = headers.get(AGENT_HEADERS.status);
|
|
1094
|
+
return {
|
|
1095
|
+
status: status ?? "none",
|
|
1096
|
+
id: headers.get(AGENT_HEADERS.id),
|
|
1097
|
+
name: headers.get(AGENT_HEADERS.name),
|
|
1098
|
+
vendor: headers.get(AGENT_HEADERS.vendor),
|
|
1099
|
+
method: headers.get(AGENT_HEADERS.method)
|
|
1100
|
+
};
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
// src/server/middleware.ts
|
|
1104
|
+
function createAgentAuthHandler(options = {}) {
|
|
1105
|
+
const auth = createAgentAuth(options);
|
|
1106
|
+
return async function handle(request) {
|
|
1107
|
+
if (options.matcher && !options.matcher(new URL(request.url))) {
|
|
1108
|
+
const result2 = { status: "none" };
|
|
1109
|
+
return { result: result2, headers: withAgentHeaders(request, result2) };
|
|
1110
|
+
}
|
|
1111
|
+
const result = await auth.authenticate(request);
|
|
1112
|
+
if (result.status !== "none" && options.onResult) {
|
|
1113
|
+
void Promise.resolve().then(() => options.onResult({ result, request })).catch(() => void 0);
|
|
1114
|
+
}
|
|
1115
|
+
return { result, headers: withAgentHeaders(request, result) };
|
|
1116
|
+
};
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
// src/server/next.ts
|
|
1120
|
+
function agentronicsMiddleware(options = {}) {
|
|
1121
|
+
const handle = createAgentAuthHandler(options);
|
|
1122
|
+
return async function middleware(request) {
|
|
1123
|
+
const { NextResponse } = await import('next/server');
|
|
1124
|
+
try {
|
|
1125
|
+
const out = await handle(request);
|
|
1126
|
+
return NextResponse.next({ request: { headers: out.headers } });
|
|
1127
|
+
} catch {
|
|
1128
|
+
return NextResponse.next();
|
|
1129
|
+
}
|
|
1130
|
+
};
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
exports.agentronicsMiddleware = agentronicsMiddleware;
|
|
1134
|
+
exports.readAgentHeaders = readAgentHeaders;
|
|
1135
|
+
exports.withAgentHeaders = withAgentHeaders;
|
|
1136
|
+
//# sourceMappingURL=next.cjs.map
|
|
1137
|
+
//# sourceMappingURL=next.cjs.map
|