@a2a-exposed/control-core 0.8.8 → 0.8.10
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/dist/index.js +179 -96
- package/dist/types/control/src/core/index.d.ts +2 -1
- package/dist/types/control/src/provision/inbox.d.ts +8 -0
- package/dist/types/control/src/tenants/create.d.ts +7 -0
- package/dist/types/control/src/tenants/events.d.ts +19 -0
- package/dist/types/control/src/tenants/wake-secrets.d.ts +7 -2
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -59,6 +59,65 @@ async function newApprovalClient(input) {
|
|
|
59
59
|
return { clientId, clientSecret, secretHash: await sha256(clientSecret), redirectUris, approval };
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
+
// src/tenants/events.ts
|
|
63
|
+
var EVENT_REPLAY_WINDOW_S = 300;
|
|
64
|
+
var URL_MAX = 2e3;
|
|
65
|
+
var SECRET_MAX = 512;
|
|
66
|
+
function httpsEventUrl(value) {
|
|
67
|
+
const raw = (value || "").trim();
|
|
68
|
+
if (!raw || raw.length > URL_MAX) return "";
|
|
69
|
+
try {
|
|
70
|
+
const url = new URL(raw);
|
|
71
|
+
if (url.protocol !== "https:" || url.username || url.password) return "";
|
|
72
|
+
return raw;
|
|
73
|
+
} catch {
|
|
74
|
+
return "";
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function eventSecretText(value) {
|
|
78
|
+
const text = (value || "").trim();
|
|
79
|
+
if (!text || text.length > SECRET_MAX || /[\u0000-\u001f]/.test(text)) return "";
|
|
80
|
+
return text;
|
|
81
|
+
}
|
|
82
|
+
function pairingEventConfig(eventUrl, eventSecret) {
|
|
83
|
+
const url = httpsEventUrl(eventUrl);
|
|
84
|
+
const secret = eventSecretText(eventSecret);
|
|
85
|
+
if (!url || !secret) return null;
|
|
86
|
+
return { EVENT_URL: url, EVENT_SECRET: secret };
|
|
87
|
+
}
|
|
88
|
+
function headerValue(headers, name) {
|
|
89
|
+
if (typeof headers.get === "function") return headers.get(name) || "";
|
|
90
|
+
const record = headers;
|
|
91
|
+
const direct = record[name];
|
|
92
|
+
if (typeof direct === "string") return direct;
|
|
93
|
+
const found = Object.keys(record).find((key) => key.toLowerCase() === name.toLowerCase());
|
|
94
|
+
const value = found ? record[found] : "";
|
|
95
|
+
return typeof value === "string" ? value : "";
|
|
96
|
+
}
|
|
97
|
+
async function hmacHex(secret, message) {
|
|
98
|
+
const key = await crypto.subtle.importKey("raw", new TextEncoder().encode(secret), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
|
|
99
|
+
const mac = await crypto.subtle.sign("HMAC", key, new TextEncoder().encode(message));
|
|
100
|
+
return [...new Uint8Array(mac)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
101
|
+
}
|
|
102
|
+
function timingSafeEqual(a, b) {
|
|
103
|
+
const left = new TextEncoder().encode(a);
|
|
104
|
+
const right = new TextEncoder().encode(b);
|
|
105
|
+
if (left.length !== right.length) return false;
|
|
106
|
+
let diff = 0;
|
|
107
|
+
for (let i = 0; i < left.length; i++) diff |= left[i] ^ right[i];
|
|
108
|
+
return diff === 0;
|
|
109
|
+
}
|
|
110
|
+
async function verifyEventSignature(body, headers, secret, now = Date.now()) {
|
|
111
|
+
const key = eventSecretText(secret);
|
|
112
|
+
const timestamp = headerValue(headers, "x-a2a-timestamp").trim();
|
|
113
|
+
const presented = headerValue(headers, "x-a2a-signature").trim();
|
|
114
|
+
if (!key || !/^\d{1,12}$/.test(timestamp) || !presented.startsWith("sha256=")) return false;
|
|
115
|
+
const skew = Math.abs(Math.floor(now / 1e3) - Number(timestamp));
|
|
116
|
+
if (skew > EVENT_REPLAY_WINDOW_S) return false;
|
|
117
|
+
const expected = `sha256=${await hmacHex(key, `${timestamp}.${body}`)}`;
|
|
118
|
+
return timingSafeEqual(expected, presented);
|
|
119
|
+
}
|
|
120
|
+
|
|
62
121
|
// src/tenants/push.ts
|
|
63
122
|
function namespaceFor(ns, region) {
|
|
64
123
|
if ((region === "eu" || region === "fedramp") && typeof ns.jurisdiction === "function") return ns.jurisdiction(region);
|
|
@@ -226,6 +285,97 @@ function tenantNameProblem(name) {
|
|
|
226
285
|
return "";
|
|
227
286
|
}
|
|
228
287
|
|
|
288
|
+
// src/tenants/wake-secrets.ts
|
|
289
|
+
var INFO = "tenant-wake-secrets v1";
|
|
290
|
+
var SALT = "a2a-exposed";
|
|
291
|
+
var WAKE_SECRET_KEYS = [
|
|
292
|
+
"WAKE_WEBHOOK_KEY",
|
|
293
|
+
"WAKE_HMAC_SECRET",
|
|
294
|
+
"WAKE_ACCESS_CLIENT_ID",
|
|
295
|
+
"WAKE_ACCESS_CLIENT_SECRET",
|
|
296
|
+
"EVENT_SECRET"
|
|
297
|
+
];
|
|
298
|
+
var MAX_WAKE_VALUE = 4096;
|
|
299
|
+
var MAX_WAKE_PLAIN = (() => {
|
|
300
|
+
let n = 2;
|
|
301
|
+
for (let i = 0; i < WAKE_SECRET_KEYS.length; i++) {
|
|
302
|
+
if (i) n += 1;
|
|
303
|
+
n += WAKE_SECRET_KEYS[i].length + 5 + MAX_WAKE_VALUE * 6;
|
|
304
|
+
}
|
|
305
|
+
return n;
|
|
306
|
+
})();
|
|
307
|
+
var MAX_WAKE_SEALED = 3 + 16 + 1 + Math.ceil((MAX_WAKE_PLAIN * 3 + 16) * 4 / 3);
|
|
308
|
+
function b64(bytes2) {
|
|
309
|
+
let raw = "";
|
|
310
|
+
for (let i = 0; i < bytes2.length; i += 32768) raw += String.fromCharCode(...bytes2.subarray(i, i + 32768));
|
|
311
|
+
return btoa(raw);
|
|
312
|
+
}
|
|
313
|
+
var unb64 = (s) => Uint8Array.from(atob(s), (c) => c.charCodeAt(0));
|
|
314
|
+
async function derive(ikm, tenantId) {
|
|
315
|
+
if (!ikm || !tenantId) return null;
|
|
316
|
+
const base = await crypto.subtle.importKey("raw", new TextEncoder().encode(ikm), "HKDF", false, ["deriveKey"]);
|
|
317
|
+
return crypto.subtle.deriveKey(
|
|
318
|
+
{ name: "HKDF", hash: "SHA-256", salt: new TextEncoder().encode(SALT), info: new TextEncoder().encode(`${INFO}\0${tenantId}`) },
|
|
319
|
+
base,
|
|
320
|
+
{ name: "AES-GCM", length: 256 },
|
|
321
|
+
false,
|
|
322
|
+
["encrypt", "decrypt"]
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
function payloadOf(secrets) {
|
|
326
|
+
const payload = {};
|
|
327
|
+
for (const key of WAKE_SECRET_KEYS) {
|
|
328
|
+
const value = secrets[key];
|
|
329
|
+
if (value == null || value === "") continue;
|
|
330
|
+
if (typeof value !== "string" || value.length > MAX_WAKE_VALUE) return null;
|
|
331
|
+
payload[key] = value;
|
|
332
|
+
}
|
|
333
|
+
return payload;
|
|
334
|
+
}
|
|
335
|
+
async function sealWakeSecrets(ikm, tenantId, secrets) {
|
|
336
|
+
const key = await derive(ikm, tenantId);
|
|
337
|
+
const payload = payloadOf(secrets);
|
|
338
|
+
if (!key || !payload) return null;
|
|
339
|
+
const json = JSON.stringify(payload);
|
|
340
|
+
const encoded = new TextEncoder().encode(json);
|
|
341
|
+
if (json.length > MAX_WAKE_PLAIN || encoded.length > MAX_WAKE_PLAIN * 3) return null;
|
|
342
|
+
const iv = crypto.getRandomValues(new Uint8Array(12));
|
|
343
|
+
const ct = new Uint8Array(await crypto.subtle.encrypt(
|
|
344
|
+
{ name: "AES-GCM", iv, additionalData: new TextEncoder().encode(tenantId) },
|
|
345
|
+
key,
|
|
346
|
+
encoded
|
|
347
|
+
));
|
|
348
|
+
const sealed = `v1.${b64(iv)}.${b64(ct)}`;
|
|
349
|
+
return sealed.length <= MAX_WAKE_SEALED ? sealed : null;
|
|
350
|
+
}
|
|
351
|
+
async function openWakeSecrets(ikm, tenantId, sealed) {
|
|
352
|
+
if (sealed == null || sealed === "") return {};
|
|
353
|
+
if (sealed.length > MAX_WAKE_SEALED) return null;
|
|
354
|
+
const key = await derive(ikm, tenantId);
|
|
355
|
+
if (!key) return null;
|
|
356
|
+
const [version, iv, ct, extra] = sealed.split(".");
|
|
357
|
+
if (version !== "v1" || !iv || !ct || extra !== void 0) return null;
|
|
358
|
+
try {
|
|
359
|
+
const plain = new TextDecoder().decode(await crypto.subtle.decrypt(
|
|
360
|
+
{ name: "AES-GCM", iv: unb64(iv), additionalData: new TextEncoder().encode(tenantId) },
|
|
361
|
+
key,
|
|
362
|
+
unb64(ct)
|
|
363
|
+
));
|
|
364
|
+
if (plain.length > MAX_WAKE_PLAIN) return null;
|
|
365
|
+
const parsed = JSON.parse(plain);
|
|
366
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
|
|
367
|
+
const out = {};
|
|
368
|
+
for (const name of WAKE_SECRET_KEYS) {
|
|
369
|
+
const value = parsed[name];
|
|
370
|
+
if (typeof value !== "string" || !value || value.length > MAX_WAKE_VALUE) continue;
|
|
371
|
+
out[name] = value;
|
|
372
|
+
}
|
|
373
|
+
return out;
|
|
374
|
+
} catch {
|
|
375
|
+
return null;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
229
379
|
// src/tenants/create.ts
|
|
230
380
|
function configuredFreeAgentLimit(value) {
|
|
231
381
|
const trimmed = (value ?? "").trim();
|
|
@@ -265,7 +415,11 @@ async function createTenant(db, plane, input) {
|
|
|
265
415
|
const hash = await sha2562(token);
|
|
266
416
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
267
417
|
const region = regionOf(input.dataRegion);
|
|
418
|
+
const events = input.eventUrl && input.eventSecret ? pairingEventConfig(input.eventUrl, input.eventSecret) : null;
|
|
419
|
+
const sealedEvents = events ? await sealWakeSecrets((input.secretsKey || "").trim(), id, { EVENT_SECRET: events.EVENT_SECRET }) : null;
|
|
268
420
|
const config = { AGENT_NAME: name };
|
|
421
|
+
if (events && sealedEvents) config.EVENT_URL = events.EVENT_URL;
|
|
422
|
+
const secretsEnc = sealedEvents;
|
|
269
423
|
const version = 1;
|
|
270
424
|
const domain = (input.domain || "").trim().toLowerCase().replace(/^\.+|\.+$/g, "");
|
|
271
425
|
const publicUrl = domain ? `https://${name}.${domain}` : null;
|
|
@@ -281,8 +435,8 @@ async function createTenant(db, plane, input) {
|
|
|
281
435
|
`INSERT INTO tenants (
|
|
282
436
|
id, name, owner_account_id, status, region, config_json, limits_json, approval_json,
|
|
283
437
|
owner_token_hash, secrets_enc, config_version, created_at, updated_at
|
|
284
|
-
) VALUES (?, ?, ?, 'active', ?, ?, '{}', ?, ?,
|
|
285
|
-
).bind(id, name, input.accountId, region, JSON.stringify(config), JSON.stringify(approval), hash, version, now, now),
|
|
438
|
+
) VALUES (?, ?, ?, 'active', ?, ?, '{}', ?, ?, ?, ?, ?, ?)`
|
|
439
|
+
).bind(id, name, input.accountId, region, JSON.stringify(config), JSON.stringify(approval), hash, secretsEnc, version, now, now),
|
|
286
440
|
...client ? [db.prepare(
|
|
287
441
|
"INSERT INTO oauth_client (id, secret_hash, owner_account_id, redirect_uris, created_at) VALUES (?, ?, ?, ?, ?)"
|
|
288
442
|
).bind(client.clientId, client.secretHash, input.accountId, JSON.stringify(client.redirectUris), now)] : [],
|
|
@@ -303,7 +457,7 @@ async function createTenant(db, plane, input) {
|
|
|
303
457
|
limits_json: "{}",
|
|
304
458
|
approval_json: JSON.stringify(approval),
|
|
305
459
|
owner_token_hash: hash,
|
|
306
|
-
secrets_enc:
|
|
460
|
+
secrets_enc: secretsEnc,
|
|
307
461
|
version,
|
|
308
462
|
created_at: now
|
|
309
463
|
};
|
|
@@ -343,96 +497,6 @@ async function listTenants(db, accountId, domain) {
|
|
|
343
497
|
}));
|
|
344
498
|
}
|
|
345
499
|
|
|
346
|
-
// src/tenants/wake-secrets.ts
|
|
347
|
-
var INFO = "tenant-wake-secrets v1";
|
|
348
|
-
var SALT = "a2a-exposed";
|
|
349
|
-
var WAKE_SECRET_KEYS = [
|
|
350
|
-
"WAKE_WEBHOOK_KEY",
|
|
351
|
-
"WAKE_HMAC_SECRET",
|
|
352
|
-
"WAKE_ACCESS_CLIENT_ID",
|
|
353
|
-
"WAKE_ACCESS_CLIENT_SECRET"
|
|
354
|
-
];
|
|
355
|
-
var MAX_WAKE_VALUE = 4096;
|
|
356
|
-
var MAX_WAKE_PLAIN = (() => {
|
|
357
|
-
let n = 2;
|
|
358
|
-
for (let i = 0; i < WAKE_SECRET_KEYS.length; i++) {
|
|
359
|
-
if (i) n += 1;
|
|
360
|
-
n += WAKE_SECRET_KEYS[i].length + 5 + MAX_WAKE_VALUE * 6;
|
|
361
|
-
}
|
|
362
|
-
return n;
|
|
363
|
-
})();
|
|
364
|
-
var MAX_WAKE_SEALED = 3 + 16 + 1 + Math.ceil((MAX_WAKE_PLAIN * 3 + 16) * 4 / 3);
|
|
365
|
-
function b64(bytes2) {
|
|
366
|
-
let raw = "";
|
|
367
|
-
for (let i = 0; i < bytes2.length; i += 32768) raw += String.fromCharCode(...bytes2.subarray(i, i + 32768));
|
|
368
|
-
return btoa(raw);
|
|
369
|
-
}
|
|
370
|
-
var unb64 = (s) => Uint8Array.from(atob(s), (c) => c.charCodeAt(0));
|
|
371
|
-
async function derive(ikm, tenantId) {
|
|
372
|
-
if (!ikm || !tenantId) return null;
|
|
373
|
-
const base = await crypto.subtle.importKey("raw", new TextEncoder().encode(ikm), "HKDF", false, ["deriveKey"]);
|
|
374
|
-
return crypto.subtle.deriveKey(
|
|
375
|
-
{ name: "HKDF", hash: "SHA-256", salt: new TextEncoder().encode(SALT), info: new TextEncoder().encode(`${INFO}\0${tenantId}`) },
|
|
376
|
-
base,
|
|
377
|
-
{ name: "AES-GCM", length: 256 },
|
|
378
|
-
false,
|
|
379
|
-
["encrypt", "decrypt"]
|
|
380
|
-
);
|
|
381
|
-
}
|
|
382
|
-
function payloadOf(secrets) {
|
|
383
|
-
const payload = {};
|
|
384
|
-
for (const key of WAKE_SECRET_KEYS) {
|
|
385
|
-
const value = secrets[key];
|
|
386
|
-
if (value == null || value === "") continue;
|
|
387
|
-
if (typeof value !== "string" || value.length > MAX_WAKE_VALUE) return null;
|
|
388
|
-
payload[key] = value;
|
|
389
|
-
}
|
|
390
|
-
return payload;
|
|
391
|
-
}
|
|
392
|
-
async function sealWakeSecrets(ikm, tenantId, secrets) {
|
|
393
|
-
const key = await derive(ikm, tenantId);
|
|
394
|
-
const payload = payloadOf(secrets);
|
|
395
|
-
if (!key || !payload) return null;
|
|
396
|
-
const json = JSON.stringify(payload);
|
|
397
|
-
const encoded = new TextEncoder().encode(json);
|
|
398
|
-
if (json.length > MAX_WAKE_PLAIN || encoded.length > MAX_WAKE_PLAIN * 3) return null;
|
|
399
|
-
const iv = crypto.getRandomValues(new Uint8Array(12));
|
|
400
|
-
const ct = new Uint8Array(await crypto.subtle.encrypt(
|
|
401
|
-
{ name: "AES-GCM", iv, additionalData: new TextEncoder().encode(tenantId) },
|
|
402
|
-
key,
|
|
403
|
-
encoded
|
|
404
|
-
));
|
|
405
|
-
const sealed = `v1.${b64(iv)}.${b64(ct)}`;
|
|
406
|
-
return sealed.length <= MAX_WAKE_SEALED ? sealed : null;
|
|
407
|
-
}
|
|
408
|
-
async function openWakeSecrets(ikm, tenantId, sealed) {
|
|
409
|
-
if (sealed == null || sealed === "") return {};
|
|
410
|
-
if (sealed.length > MAX_WAKE_SEALED) return null;
|
|
411
|
-
const key = await derive(ikm, tenantId);
|
|
412
|
-
if (!key) return null;
|
|
413
|
-
const [version, iv, ct, extra] = sealed.split(".");
|
|
414
|
-
if (version !== "v1" || !iv || !ct || extra !== void 0) return null;
|
|
415
|
-
try {
|
|
416
|
-
const plain = new TextDecoder().decode(await crypto.subtle.decrypt(
|
|
417
|
-
{ name: "AES-GCM", iv: unb64(iv), additionalData: new TextEncoder().encode(tenantId) },
|
|
418
|
-
key,
|
|
419
|
-
unb64(ct)
|
|
420
|
-
));
|
|
421
|
-
if (plain.length > MAX_WAKE_PLAIN) return null;
|
|
422
|
-
const parsed = JSON.parse(plain);
|
|
423
|
-
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
|
|
424
|
-
const out = {};
|
|
425
|
-
for (const name of WAKE_SECRET_KEYS) {
|
|
426
|
-
const value = parsed[name];
|
|
427
|
-
if (typeof value !== "string" || !value || value.length > MAX_WAKE_VALUE) continue;
|
|
428
|
-
out[name] = value;
|
|
429
|
-
}
|
|
430
|
-
return out;
|
|
431
|
-
} catch {
|
|
432
|
-
return null;
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
|
|
436
500
|
// src/tenants/wake.ts
|
|
437
501
|
var WAKE_PRESETS = ["grok-bot", "claude-code", "openclaw-wake", "openclaw-agent", "hermes", "generic"];
|
|
438
502
|
var PLAIN_HEADERS = /* @__PURE__ */ new Set([
|
|
@@ -2134,8 +2198,18 @@ function inboxApprovalBindings(input) {
|
|
|
2134
2198
|
{ type: "plain_text", name: "APPROVAL_METHODS", text: "oidc" }
|
|
2135
2199
|
];
|
|
2136
2200
|
}
|
|
2201
|
+
function inboxEventBindings(eventUrl, eventSecret) {
|
|
2202
|
+
const url = httpsEventUrl(eventUrl);
|
|
2203
|
+
const secret = eventSecretText(eventSecret);
|
|
2204
|
+
if (!url || !secret) return null;
|
|
2205
|
+
return [
|
|
2206
|
+
{ type: "plain_text", name: "EVENT_URL", text: url },
|
|
2207
|
+
{ type: "secret_text", name: "EVENT_SECRET", text: secret }
|
|
2208
|
+
];
|
|
2209
|
+
}
|
|
2137
2210
|
function scriptMetadata(input) {
|
|
2138
2211
|
const approval = inboxApprovalBindings(input.approvalOidc);
|
|
2212
|
+
const events = inboxEventBindings(input.eventUrl, input.eventSecret);
|
|
2139
2213
|
return {
|
|
2140
2214
|
main_module: "index.js",
|
|
2141
2215
|
compatibility_date: INBOX_COMPATIBILITY_DATE,
|
|
@@ -2147,7 +2221,8 @@ function scriptMetadata(input) {
|
|
|
2147
2221
|
{ type: "plain_text", name: "WAKE_PRESET", text: "generic" },
|
|
2148
2222
|
{ type: "plain_text", name: PROVISION_TAG_NAME, text: input.scriptTag },
|
|
2149
2223
|
{ type: "secret_text", name: "OWNER_TOKEN", text: input.ownerToken },
|
|
2150
|
-
...approval ?? []
|
|
2224
|
+
...approval ?? [],
|
|
2225
|
+
...events ?? []
|
|
2151
2226
|
]
|
|
2152
2227
|
};
|
|
2153
2228
|
}
|
|
@@ -2567,7 +2642,9 @@ async function runInboxStep(input) {
|
|
|
2567
2642
|
migrations: input.migrations,
|
|
2568
2643
|
grant,
|
|
2569
2644
|
permissionDelaysMs: input.permissionDelaysMs ?? PERMISSION_DELAYS_MS,
|
|
2570
|
-
approvalOidc: input.approvalOidc
|
|
2645
|
+
approvalOidc: input.approvalOidc,
|
|
2646
|
+
eventUrl: input.eventUrl,
|
|
2647
|
+
eventSecret: input.eventSecret
|
|
2571
2648
|
};
|
|
2572
2649
|
try {
|
|
2573
2650
|
if (next !== "verify") {
|
|
@@ -2853,7 +2930,7 @@ async function runUpload(ctx) {
|
|
|
2853
2930
|
accountId,
|
|
2854
2931
|
name: workerName,
|
|
2855
2932
|
script: ctx.script,
|
|
2856
|
-
metadata: scriptMetadata({ d1Id: databaseId, publicUrl, workerName, ownerToken, scriptTag, approvalOidc: ctx.approvalOidc })
|
|
2933
|
+
metadata: scriptMetadata({ d1Id: databaseId, publicUrl, workerName, ownerToken, scriptTag, approvalOidc: ctx.approvalOidc, eventUrl: ctx.eventUrl, eventSecret: ctx.eventSecret })
|
|
2857
2934
|
});
|
|
2858
2935
|
if (uploaded2 !== true) {
|
|
2859
2936
|
return { wizard: await failOrWait(ctx, "upload", uploaded2, "Cloudflare stops while uploading the inbox.", "upload") };
|
|
@@ -3056,6 +3133,7 @@ export {
|
|
|
3056
3133
|
CONTROL_MIGRATIONS_DIR,
|
|
3057
3134
|
D1_WRITE,
|
|
3058
3135
|
DNS_WRITE,
|
|
3136
|
+
EVENT_REPLAY_WINDOW_S,
|
|
3059
3137
|
EVERY_PLAN,
|
|
3060
3138
|
HOST_RE,
|
|
3061
3139
|
INBOX_COMPATIBILITY_DATE,
|
|
@@ -3110,6 +3188,7 @@ export {
|
|
|
3110
3188
|
deliverOutbox,
|
|
3111
3189
|
describeCfFailure,
|
|
3112
3190
|
describeSetupGrant,
|
|
3191
|
+
eventSecretText,
|
|
3113
3192
|
fieldsFromForm,
|
|
3114
3193
|
findD1,
|
|
3115
3194
|
flushOutbox,
|
|
@@ -3118,9 +3197,11 @@ export {
|
|
|
3118
3197
|
grantedScopeSet,
|
|
3119
3198
|
hashOwnerToken,
|
|
3120
3199
|
headerSafeToken,
|
|
3200
|
+
httpsEventUrl,
|
|
3121
3201
|
httpsOrigin,
|
|
3122
3202
|
inboxApprovalBindings,
|
|
3123
3203
|
inboxCanProvision,
|
|
3204
|
+
inboxEventBindings,
|
|
3124
3205
|
listAccounts,
|
|
3125
3206
|
listCloudflareInboxes,
|
|
3126
3207
|
listTenants,
|
|
@@ -3132,6 +3213,7 @@ export {
|
|
|
3132
3213
|
normalizeHostname,
|
|
3133
3214
|
openCloudflareRefresh,
|
|
3134
3215
|
openWakeSecrets,
|
|
3216
|
+
pairingEventConfig,
|
|
3135
3217
|
parseSetupChoice,
|
|
3136
3218
|
parseSetupHost,
|
|
3137
3219
|
permissionNotReady,
|
|
@@ -3167,6 +3249,7 @@ export {
|
|
|
3167
3249
|
tenantForAccount,
|
|
3168
3250
|
tenantNameProblem,
|
|
3169
3251
|
uploadWorker,
|
|
3252
|
+
verifyEventSignature,
|
|
3170
3253
|
viewInboxWizard,
|
|
3171
3254
|
wakeHeaderProblem,
|
|
3172
3255
|
wakeSettingsOf,
|
|
@@ -26,7 +26,8 @@ export * from "../provision/statements.js";
|
|
|
26
26
|
export * from "../provision/permission-wait.js";
|
|
27
27
|
export * from "../provision/inbox-row.js";
|
|
28
28
|
export * from "../provision/remove.js";
|
|
29
|
-
export { INBOX_COMPATIBILITY_DATE, PERMISSION_DELAYS_MS, PERMISSION_TRIES, WIZARD_STEPS, createCloudflareAgent, dashboardLinks, fieldsFromForm, permissionRetryDue, inboxApprovalBindings, resumeFields, runInboxStep, viewInboxWizard, } from "../provision/inbox.js";
|
|
29
|
+
export { INBOX_COMPATIBILITY_DATE, PERMISSION_DELAYS_MS, PERMISSION_TRIES, WIZARD_STEPS, createCloudflareAgent, dashboardLinks, fieldsFromForm, permissionRetryDue, inboxApprovalBindings, inboxEventBindings, resumeFields, runInboxStep, viewInboxWizard, } from "../provision/inbox.js";
|
|
30
|
+
export { EVENT_REPLAY_WINDOW_S, eventSecretText, httpsEventUrl, pairingEventConfig, verifyEventSignature, } from "../tenants/events.js";
|
|
30
31
|
export type { ApprovalOidcInput, InboxMigration, InboxWizard, ProvisionFields, WizardControl, WizardState, WizardStepId, WizardStepView, } from "../provision/inbox.js";
|
|
31
32
|
export { INBOX_MIGRATIONS } from "../provision/inbox-migrations.js";
|
|
32
33
|
export { CONTROL_MIGRATIONS, CONTROL_MIGRATIONS_DIR } from "./migrations.js";
|
|
@@ -114,6 +114,11 @@ type ScriptBinding = {
|
|
|
114
114
|
* has a value. Otherwise returns null and the upload keeps the approval password.
|
|
115
115
|
*/
|
|
116
116
|
export declare function inboxApprovalBindings(input: ApprovalOidcInput | undefined): ScriptBinding[] | null;
|
|
117
|
+
/**
|
|
118
|
+
* Bindings for a signed pairing event. Null unless the URL is https and the secret has a value,
|
|
119
|
+
* so a partial pair leaves the upload unchanged.
|
|
120
|
+
*/
|
|
121
|
+
export declare function inboxEventBindings(eventUrl?: string, eventSecret?: string): ScriptBinding[] | null;
|
|
117
122
|
export declare function fieldsFromForm(form: FormData): ProvisionFields;
|
|
118
123
|
export declare function resumeFields(value: {
|
|
119
124
|
name?: unknown;
|
|
@@ -167,6 +172,9 @@ export declare function runInboxStep(input: {
|
|
|
167
172
|
* Omit it and the agent stays on the approval password.
|
|
168
173
|
*/
|
|
169
174
|
approvalOidc?: ApprovalOidcInput;
|
|
175
|
+
/** https URL and secret for pairing events. Both are required; a partial pair writes no EVENT_* binding. */
|
|
176
|
+
eventUrl?: string;
|
|
177
|
+
eventSecret?: string;
|
|
170
178
|
}): Promise<{
|
|
171
179
|
wizard: InboxWizard;
|
|
172
180
|
ownerToken?: string;
|
|
@@ -31,6 +31,13 @@ export declare function createTenant(db: Sql, plane: DataPlane, input: {
|
|
|
31
31
|
issuer?: string;
|
|
32
32
|
label?: string;
|
|
33
33
|
freeAgentLimit?: number | null;
|
|
34
|
+
/**
|
|
35
|
+
* https URL and secret for pairing events, plus the key that seals the secret.
|
|
36
|
+
* A partial pair, or a secret that cannot be sealed, is left out of the tenant.
|
|
37
|
+
*/
|
|
38
|
+
eventUrl?: string;
|
|
39
|
+
eventSecret?: string;
|
|
40
|
+
secretsKey?: string;
|
|
34
41
|
}): Promise<CreateResult>;
|
|
35
42
|
export declare function listTenants(db: Sql, accountId: string, domain?: string): Promise<{
|
|
36
43
|
id: string;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/** How far `x-a2a-timestamp` may sit from the check, in seconds. */
|
|
2
|
+
export declare const EVENT_REPLAY_WINDOW_S = 300;
|
|
3
|
+
/** https URL with no userinfo, at most 2000 characters. Any other value returns "". */
|
|
4
|
+
export declare function httpsEventUrl(value: string | undefined): string;
|
|
5
|
+
/** Shared secret for the HMAC. Empty when missing, too long, or it contains a control character. */
|
|
6
|
+
export declare function eventSecretText(value: string | undefined): string;
|
|
7
|
+
/**
|
|
8
|
+
* Tenant config fields for pairing events. Null unless the URL is https and the secret has a value,
|
|
9
|
+
* so a partial pair is left out of the push.
|
|
10
|
+
*/
|
|
11
|
+
export declare function pairingEventConfig(eventUrl: string, eventSecret: string): {
|
|
12
|
+
EVENT_URL: string;
|
|
13
|
+
EVENT_SECRET: string;
|
|
14
|
+
} | null;
|
|
15
|
+
/**
|
|
16
|
+
* True when `x-a2a-signature` is the HMAC-SHA256 of the timestamp, a period, and the raw body,
|
|
17
|
+
* and `x-a2a-timestamp` is within 5 minutes of `now`. The header value is `sha256=<hex>`.
|
|
18
|
+
*/
|
|
19
|
+
export declare function verifyEventSignature(body: string, headers: Headers | Record<string, string | null | undefined>, secret: string, now?: number): Promise<boolean>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
/**
|
|
2
|
-
export declare const WAKE_SECRET_KEYS: readonly ["WAKE_WEBHOOK_KEY", "WAKE_HMAC_SECRET", "WAKE_ACCESS_CLIENT_ID", "WAKE_ACCESS_CLIENT_SECRET"];
|
|
1
|
+
/** Sealed secret fields. Anything else in the blob is ignored. */
|
|
2
|
+
export declare const WAKE_SECRET_KEYS: readonly ["WAKE_WEBHOOK_KEY", "WAKE_HMAC_SECRET", "WAKE_ACCESS_CLIENT_ID", "WAKE_ACCESS_CLIENT_SECRET", "EVENT_SECRET"];
|
|
3
3
|
export type WakeSecretKey = (typeof WAKE_SECRET_KEYS)[number];
|
|
4
4
|
export type WakeSecrets = Partial<Record<WakeSecretKey, string>>;
|
|
5
5
|
/** One secret. The form uses the same cap. */
|
|
@@ -11,6 +11,11 @@ export declare const MAX_WAKE_VALUE = 4096;
|
|
|
11
11
|
export declare const MAX_WAKE_PLAIN: number;
|
|
12
12
|
/** `v1.` + base64(12-byte iv) + `.` + base64(utf-8 plaintext + 16-byte tag). */
|
|
13
13
|
export declare const MAX_WAKE_SEALED: number;
|
|
14
|
+
/**
|
|
15
|
+
* Config written next to a sealed blob. The sealed fields stay in `secrets_enc`.
|
|
16
|
+
* With no ciphertext, the config is returned unchanged.
|
|
17
|
+
*/
|
|
18
|
+
export declare function omitSealedSecrets<T extends Record<string, unknown>>(config: T, secretsEnc: string | null | undefined): T;
|
|
14
19
|
/** `v1.<iv>.<ciphertext>`, or null when the key or a value is unusable. */
|
|
15
20
|
export declare function sealWakeSecrets(ikm: string, tenantId: string, secrets: WakeSecrets): Promise<string | null>;
|
|
16
21
|
/**
|