@astrasyncai/verification-gateway 5.11.0 → 5.12.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 +4 -0
- package/dist/adapters/express.d.mts +1 -1
- package/dist/adapters/express.d.ts +1 -1
- package/dist/adapters/express.js +359 -30
- package/dist/adapters/express.js.map +1 -1
- package/dist/adapters/express.mjs +359 -30
- package/dist/adapters/express.mjs.map +1 -1
- package/dist/adapters/mcp.d.mts +5 -1
- package/dist/adapters/mcp.d.ts +5 -1
- package/dist/adapters/mcp.js +168 -41
- package/dist/adapters/mcp.js.map +1 -1
- package/dist/adapters/mcp.mjs +168 -41
- package/dist/adapters/mcp.mjs.map +1 -1
- package/dist/adapters/nextjs.d.mts +1 -1
- package/dist/adapters/nextjs.d.ts +1 -1
- package/dist/adapters/nextjs.js +352 -13
- package/dist/adapters/nextjs.js.map +1 -1
- package/dist/adapters/nextjs.mjs +352 -13
- package/dist/adapters/nextjs.mjs.map +1 -1
- package/dist/adapters/sdk.d.mts +9 -1
- package/dist/adapters/sdk.d.ts +9 -1
- package/dist/adapters/sdk.js +100 -8
- package/dist/adapters/sdk.js.map +1 -1
- package/dist/adapters/sdk.mjs +100 -8
- package/dist/adapters/sdk.mjs.map +1 -1
- package/dist/agent/index.js +1 -1
- package/dist/agent/index.js.map +1 -1
- package/dist/agent/index.mjs +1 -1
- package/dist/agent/index.mjs.map +1 -1
- package/dist/bin/astrasync-claude-hook.js +284 -7
- package/dist/bin/astrasync-codex-hook.js +284 -7
- package/dist/bin/astrasync-guard.js +284 -7
- package/dist/bin/astrasync.js +297 -9
- package/dist/browser/background.js +284 -7
- package/dist/browser/background.js.map +1 -1
- package/dist/browser/background.mjs +284 -7
- package/dist/browser/background.mjs.map +1 -1
- package/dist/cli/index.js +1 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/index.mjs +1 -1
- package/dist/cli/index.mjs.map +1 -1
- package/dist/codex/index.js +284 -7
- package/dist/codex/index.js.map +1 -1
- package/dist/codex/index.mjs +284 -7
- package/dist/codex/index.mjs.map +1 -1
- package/dist/cursor/extension.js +284 -7
- package/dist/cursor/extension.js.map +1 -1
- package/dist/cursor/extension.mjs +284 -7
- package/dist/cursor/extension.mjs.map +1 -1
- package/dist/edge-config.d.mts +1 -1
- package/dist/edge-config.d.ts +1 -1
- package/dist/edge-config.js +1 -1
- package/dist/edge-config.js.map +1 -1
- package/dist/edge-config.mjs +1 -1
- package/dist/edge-config.mjs.map +1 -1
- package/dist/edge-core/index.d.mts +128 -13
- package/dist/edge-core/index.d.ts +128 -13
- package/dist/edge-core/index.js +337 -55
- package/dist/edge-core/index.js.map +1 -1
- package/dist/edge-core/index.mjs +334 -55
- package/dist/edge-core/index.mjs.map +1 -1
- package/dist/gateway/gateway.js +284 -7
- package/dist/gateway/gateway.js.map +1 -1
- package/dist/gateway/gateway.mjs +284 -7
- package/dist/gateway/gateway.mjs.map +1 -1
- package/dist/git-trigger/git-hooks.d.mts +1 -1
- package/dist/git-trigger/git-hooks.d.ts +1 -1
- package/dist/index.d.mts +118 -5
- package/dist/index.d.ts +118 -5
- package/dist/index.js +486 -63
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +480 -63
- package/dist/index.mjs.map +1 -1
- package/dist/platform-signatures.js +4 -1
- package/dist/platform-signatures.js.map +1 -1
- package/dist/platform-signatures.mjs +4 -1
- package/dist/platform-signatures.mjs.map +1 -1
- package/dist/registration/index.js +1 -1
- package/dist/registration/index.js.map +1 -1
- package/dist/registration/index.mjs +1 -1
- package/dist/registration/index.mjs.map +1 -1
- package/dist/transport/index.js +1 -1
- package/dist/transport/index.js.map +1 -1
- package/dist/transport/index.mjs +1 -1
- package/dist/transport/index.mjs.map +1 -1
- package/dist/{types-CTmUpNpS.d.mts → types-BCkwBA84.d.mts} +12 -0
- package/dist/{types-CTmUpNpS.d.ts → types-BCkwBA84.d.ts} +12 -0
- package/dist/{types-CtFXmDzm.d.ts → types-C_b4QTyO.d.ts} +18 -0
- package/dist/{types-DQrn_kei.d.mts → types-DlshIIR-.d.mts} +18 -0
- package/dist/ui/index.d.mts +1 -1
- package/dist/ui/index.d.ts +1 -1
- package/dist/verify.d.mts +35 -4
- package/dist/verify.d.ts +35 -4
- package/dist/verify.js +130 -16
- package/dist/verify.js.map +1 -1
- package/dist/verify.mjs +129 -16
- package/dist/verify.mjs.map +1 -1
- package/package.json +1 -1
package/dist/adapters/nextjs.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/version.ts
|
|
2
|
-
var SDK_VERSION = "5.
|
|
2
|
+
var SDK_VERSION = "5.12.0";
|
|
3
3
|
|
|
4
4
|
// src/http.ts
|
|
5
5
|
var SDK_USER_AGENT = `astrasync-sdk/${SDK_VERSION}`;
|
|
@@ -92,6 +92,59 @@ function getCachedWellKnownUrls(apiBaseUrl) {
|
|
|
92
92
|
return cache.get(apiBaseUrl)?.data;
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
+
// src/failure-posture.ts
|
|
96
|
+
var VERIFICATION_STATUS_HEADER = "X-Astra-Verification";
|
|
97
|
+
var DEFAULT_UNAVAILABLE_RETRY_AFTER_S = 30;
|
|
98
|
+
var REJECTED_IDENTITY_DIMENSIONS = /* @__PURE__ */ new Set([
|
|
99
|
+
"identity.invalid_agent_id",
|
|
100
|
+
"identity.unknown_agent",
|
|
101
|
+
"agent.lookup"
|
|
102
|
+
]);
|
|
103
|
+
var UNKNOWN_AGENT_GUIDANCE = "Register your agent with AstraSync (or, inside an MCP host, add the AstraSync MCP connector for keyless, human-approved registration), then present the canonical ASTRA-* id issued after owner approval.";
|
|
104
|
+
var UNAVAILABLE_DIMENSIONS = /* @__PURE__ */ new Set([
|
|
105
|
+
"verify_access.api_error",
|
|
106
|
+
"middleware.internal_error"
|
|
107
|
+
]);
|
|
108
|
+
function isVerificationUnavailable(result) {
|
|
109
|
+
if (result.misconfigured) return false;
|
|
110
|
+
return (result.failures ?? []).some((f) => UNAVAILABLE_DIMENSIONS.has(f.dimension));
|
|
111
|
+
}
|
|
112
|
+
function unavailableRetryAfterSeconds(result) {
|
|
113
|
+
if (typeof result.retryAfterMs === "number" && Number.isFinite(result.retryAfterMs)) {
|
|
114
|
+
return Math.max(1, Math.ceil(result.retryAfterMs / 1e3));
|
|
115
|
+
}
|
|
116
|
+
return DEFAULT_UNAVAILABLE_RETRY_AFTER_S;
|
|
117
|
+
}
|
|
118
|
+
function isAnonymousRead(method, credentials) {
|
|
119
|
+
const m = (method ?? "").toUpperCase();
|
|
120
|
+
if (m !== "GET" && m !== "HEAD") return false;
|
|
121
|
+
return !credentials.astraId && !credentials.jwt;
|
|
122
|
+
}
|
|
123
|
+
function buildVerificationUnavailableBody(result) {
|
|
124
|
+
return {
|
|
125
|
+
success: false,
|
|
126
|
+
error: {
|
|
127
|
+
code: "VERIFICATION_UNAVAILABLE",
|
|
128
|
+
message: "Agent verification is temporarily unavailable and this path enforces verification. Your identity was not evaluated and no denial was recorded \u2014 retry after the Retry-After interval.",
|
|
129
|
+
retryable: true,
|
|
130
|
+
retryAfterSeconds: unavailableRetryAfterSeconds(result),
|
|
131
|
+
...result.failures && { failures: result.failures },
|
|
132
|
+
...result.correlationId && { correlationId: result.correlationId },
|
|
133
|
+
guidance: {
|
|
134
|
+
message: "Retry the request after the Retry-After interval, with exponential backoff; the condition is transient."
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
function isRejectedIdentityClaim(result) {
|
|
140
|
+
return (result.failures ?? []).some((f) => REJECTED_IDENTITY_DIMENSIONS.has(f.dimension));
|
|
141
|
+
}
|
|
142
|
+
function denyStatusFor(result, policyDeniedCode) {
|
|
143
|
+
if (isRejectedIdentityClaim(result)) return { status: 403, code: "AGENT_IDENTITY_REJECTED" };
|
|
144
|
+
if (result.identityVerified) return { status: 403, code: policyDeniedCode };
|
|
145
|
+
return { status: 401, code: "UNAUTHORIZED" };
|
|
146
|
+
}
|
|
147
|
+
|
|
95
148
|
// src/verify.ts
|
|
96
149
|
var DEFAULT_CONFIG = {
|
|
97
150
|
apiBaseUrl: "https://astrasync.ai/api",
|
|
@@ -337,11 +390,44 @@ function createGuidanceResponse(_config, reason, options = {}) {
|
|
|
337
390
|
// coercing the absent recommendation to a policy 'deny'. Misconfig is
|
|
338
391
|
// deterministic and intentionally NOT marked retryable.
|
|
339
392
|
...isApiError && { retryable: true },
|
|
393
|
+
...options.httpStatus !== void 0 && { httpStatus: options.httpStatus },
|
|
394
|
+
...options.retryAfterMs !== void 0 && { retryAfterMs: options.retryAfterMs },
|
|
340
395
|
correlationId: options.correlationId,
|
|
341
396
|
attemptId: options.attemptId,
|
|
342
397
|
verifiedAt: /* @__PURE__ */ new Date()
|
|
343
398
|
};
|
|
344
399
|
}
|
|
400
|
+
async function readJsonSafe(response) {
|
|
401
|
+
try {
|
|
402
|
+
const parsed = await response.json();
|
|
403
|
+
return parsed && typeof parsed === "object" ? parsed : {};
|
|
404
|
+
} catch {
|
|
405
|
+
return {};
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
function rejectedIdentityGuidance(failures) {
|
|
409
|
+
const f = failures?.find((x) => REJECTED_IDENTITY_DIMENSIONS.has(x.dimension));
|
|
410
|
+
if (!f) return void 0;
|
|
411
|
+
if (f.dimension === "agent.lookup") {
|
|
412
|
+
return `The presented agent identifier is not registered with AstraSync. ${UNKNOWN_AGENT_GUIDANCE}`;
|
|
413
|
+
}
|
|
414
|
+
return f.guidance ? `${f.message} ${f.guidance}` : f.message;
|
|
415
|
+
}
|
|
416
|
+
var MAX_RETRY_AFTER_MS = 15 * 60 * 1e3;
|
|
417
|
+
function parseRetryAfterMs(response) {
|
|
418
|
+
const raw = response.headers?.get?.("retry-after");
|
|
419
|
+
if (!raw) return void 0;
|
|
420
|
+
const trimmed = raw.trim();
|
|
421
|
+
let ms;
|
|
422
|
+
if (/^\d+(\.\d+)?$/.test(trimmed)) {
|
|
423
|
+
ms = Number(trimmed) * 1e3;
|
|
424
|
+
} else {
|
|
425
|
+
const at = Date.parse(trimmed);
|
|
426
|
+
if (Number.isNaN(at)) return void 0;
|
|
427
|
+
ms = at - Date.now();
|
|
428
|
+
}
|
|
429
|
+
return Math.min(MAX_RETRY_AFTER_MS, Math.max(0, Math.round(ms)));
|
|
430
|
+
}
|
|
345
431
|
async function callVerifyAccessAPI(config, request) {
|
|
346
432
|
const { credentials, ...requestData } = request;
|
|
347
433
|
const body = {
|
|
@@ -398,7 +484,8 @@ async function callVerifyAccessAPI(config, request) {
|
|
|
398
484
|
body: JSON.stringify(body),
|
|
399
485
|
signal: AbortSignal.timeout(config.verifyTimeoutMs ?? 1e4)
|
|
400
486
|
});
|
|
401
|
-
const data = await response
|
|
487
|
+
const data = await readJsonSafe(response);
|
|
488
|
+
const retryAfterMs = parseRetryAfterMs(response);
|
|
402
489
|
if (response.status === 410) {
|
|
403
490
|
return {
|
|
404
491
|
success: true,
|
|
@@ -436,6 +523,23 @@ async function callVerifyAccessAPI(config, request) {
|
|
|
436
523
|
}
|
|
437
524
|
};
|
|
438
525
|
}
|
|
526
|
+
if (response.status === 404 && credentials.astraId && (data?.error === "AGENT_NOT_FOUND" || typeof data?.message === "string" && /agent/i.test(data.message))) {
|
|
527
|
+
return {
|
|
528
|
+
success: true,
|
|
529
|
+
attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0,
|
|
530
|
+
access: {
|
|
531
|
+
allowed: false,
|
|
532
|
+
reason: "unknown_agent",
|
|
533
|
+
failures: [
|
|
534
|
+
{
|
|
535
|
+
dimension: "identity.unknown_agent",
|
|
536
|
+
message: "The presented agent identifier is not registered with AstraSync.",
|
|
537
|
+
guidance: UNKNOWN_AGENT_GUIDANCE
|
|
538
|
+
}
|
|
539
|
+
]
|
|
540
|
+
}
|
|
541
|
+
};
|
|
542
|
+
}
|
|
439
543
|
if (!response.ok) {
|
|
440
544
|
if (response.status === 401) {
|
|
441
545
|
return {
|
|
@@ -450,9 +554,11 @@ async function callVerifyAccessAPI(config, request) {
|
|
|
450
554
|
}
|
|
451
555
|
return {
|
|
452
556
|
success: false,
|
|
453
|
-
error: data.message || data.error || `API returned ${response.status}`,
|
|
557
|
+
error: typeof data?.message === "string" && data.message || typeof data?.error === "string" && data.error || `API returned ${response.status}`,
|
|
454
558
|
correlationId: typeof data?.correlationId === "string" ? data.correlationId : void 0,
|
|
455
|
-
attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0
|
|
559
|
+
attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0,
|
|
560
|
+
httpStatus: response.status,
|
|
561
|
+
...retryAfterMs !== void 0 && { retryAfterMs }
|
|
456
562
|
};
|
|
457
563
|
}
|
|
458
564
|
return data;
|
|
@@ -524,12 +630,14 @@ async function verify(config, request, options) {
|
|
|
524
630
|
correlationId: apiResponse.correlationId,
|
|
525
631
|
attemptId: apiResponse.attemptId,
|
|
526
632
|
remediation: apiResponse.remediation,
|
|
633
|
+
httpStatus: apiResponse.httpStatus,
|
|
634
|
+
retryAfterMs: apiResponse.retryAfterMs,
|
|
527
635
|
urls
|
|
528
636
|
});
|
|
529
637
|
}
|
|
530
638
|
if (!apiResponse.access?.allowed) {
|
|
531
639
|
const aggregatedFailures = apiResponse.access?.failures;
|
|
532
|
-
const idVerifiedFromBackend = apiResponse.verificationContext?.idVerified === true;
|
|
640
|
+
const idVerifiedFromBackend = apiResponse.verificationContext?.idVerified === true && !aggregatedFailures?.some((f) => REJECTED_IDENTITY_DIMENSIONS.has(f.dimension));
|
|
533
641
|
const result2 = {
|
|
534
642
|
identityVerified: idVerifiedFromBackend,
|
|
535
643
|
policyAllowed: false,
|
|
@@ -541,7 +649,10 @@ async function verify(config, request, options) {
|
|
|
541
649
|
requiresStepUp: apiResponse.access?.requiresStepUp,
|
|
542
650
|
requiresApproval: apiResponse.access?.requiresApproval,
|
|
543
651
|
guidance: {
|
|
544
|
-
|
|
652
|
+
// 5.12.0: a presented-but-rejected id (malformed / unknown) carries
|
|
653
|
+
// actionable registration guidance on its failure — surface it
|
|
654
|
+
// instead of the bare machine reason ('unknown_agent').
|
|
655
|
+
message: rejectedIdentityGuidance(aggregatedFailures) ?? (apiResponse.access?.reason || "Access denied by PDLSS policy"),
|
|
545
656
|
registrationUrl: urls?.registrationUrl ?? "",
|
|
546
657
|
documentationUrl: urls?.documentationUrl ?? "",
|
|
547
658
|
// 5.1.0: anonymous callers land on this branch — steer MCP-hosted
|
|
@@ -641,7 +752,7 @@ async function verify(config, request, options) {
|
|
|
641
752
|
registrationUrl: urls?.registrationUrl ?? "",
|
|
642
753
|
documentationUrl: urls?.documentationUrl ?? ""
|
|
643
754
|
};
|
|
644
|
-
} else if (result.recommendation === "step_up_required") {
|
|
755
|
+
} else if (result.recommendation === "step_up_required" && result.identityVerified) {
|
|
645
756
|
result.requiresStepUp = true;
|
|
646
757
|
result.denialReasons = serverDenialReasons ?? result.recommendationReasons ?? ["Step-up verification required"];
|
|
647
758
|
}
|
|
@@ -680,7 +791,10 @@ async function reportUnregisteredAttempt(config, data) {
|
|
|
680
791
|
};
|
|
681
792
|
await sdkFetch(`${apiBaseUrl}/verification-activity/unregistered-attempt`, {
|
|
682
793
|
method: "POST",
|
|
683
|
-
headers: {
|
|
794
|
+
headers: {
|
|
795
|
+
"Content-Type": "application/json",
|
|
796
|
+
...config.apiKey && { Authorization: `Bearer ${config.apiKey}` }
|
|
797
|
+
},
|
|
684
798
|
body: JSON.stringify(body)
|
|
685
799
|
}).catch(() => {
|
|
686
800
|
});
|
|
@@ -1153,7 +1267,10 @@ var PLATFORM_AGENT_SIGNATURES = [
|
|
|
1153
1267
|
{
|
|
1154
1268
|
vendor: "chatgpt",
|
|
1155
1269
|
displayName: "OpenAI ChatGPT",
|
|
1156
|
-
|
|
1270
|
+
// GPTBot (training crawler) and OAI-SearchBot (ChatGPT search) are
|
|
1271
|
+
// OpenAI's declared crawler UAs — `gpt-?\d` never matched them, so they
|
|
1272
|
+
// classified as unattributed crawlers (round-27.4 W5).
|
|
1273
|
+
uaPatterns: [/chatgpt/i, /openai/i, /gpt-?\d/i, /\bGPTBot\b/i, /\bOAI-SearchBot\b/i],
|
|
1157
1274
|
agentCardPatterns: [/openai\.com/i, /chatgpt\.com/i],
|
|
1158
1275
|
apiKeyFormatPatterns: [/^sk-proj/i, /^sk-svcacct/i],
|
|
1159
1276
|
headerPatterns: [/^openai-organization/i, /^openai-project/i, /^openai-version/i]
|
|
@@ -1288,6 +1405,215 @@ var BROWSER_UA_PATTERN = /mozilla\/\d/i;
|
|
|
1288
1405
|
var _patterns = { STATIC_EXTENSIONS, BOT_UA_PATTERN, BROWSER_UA_PATTERN, SELF_UA_PATTERN };
|
|
1289
1406
|
|
|
1290
1407
|
// src/edge-core/events.ts
|
|
1408
|
+
async function withBudget(promise, ms) {
|
|
1409
|
+
let timer;
|
|
1410
|
+
try {
|
|
1411
|
+
return await Promise.race([
|
|
1412
|
+
promise,
|
|
1413
|
+
new Promise((resolve) => {
|
|
1414
|
+
timer = setTimeout(() => resolve(void 0), ms);
|
|
1415
|
+
})
|
|
1416
|
+
]);
|
|
1417
|
+
} catch {
|
|
1418
|
+
return void 0;
|
|
1419
|
+
} finally {
|
|
1420
|
+
if (timer) clearTimeout(timer);
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
var destKeyOf = (d) => `${d.apiBaseUrl}\0${d.apiKey ?? ""}\0${d.counterparty}\0${d.adapter ?? ""}`;
|
|
1424
|
+
var BeaconBuffer = class {
|
|
1425
|
+
constructor(options = {}) {
|
|
1426
|
+
this.queue = [];
|
|
1427
|
+
this.droppedSinceLast = 0;
|
|
1428
|
+
this.droppedTotal = 0;
|
|
1429
|
+
this.deliveredTotal = 0;
|
|
1430
|
+
this.backoffUntil = 0;
|
|
1431
|
+
this.collapse = /* @__PURE__ */ new Map();
|
|
1432
|
+
/** Keys the backend answered 401 for — later batches go unauthenticated. */
|
|
1433
|
+
this.rejectedKeys = /* @__PURE__ */ new Set();
|
|
1434
|
+
this.opts = {
|
|
1435
|
+
maxItems: options.maxItems ?? 1e3,
|
|
1436
|
+
maxBatch: options.maxBatch ?? 100,
|
|
1437
|
+
maxAttempts: options.maxAttempts ?? 3,
|
|
1438
|
+
collapseWindowMs: options.collapseWindowMs ?? 6e4,
|
|
1439
|
+
collapseMaxKeys: options.collapseMaxKeys ?? 1e3,
|
|
1440
|
+
requestTimeoutMs: options.requestTimeoutMs ?? 1e4,
|
|
1441
|
+
retryBackoffMs: options.retryBackoffMs ?? 1e3,
|
|
1442
|
+
now: options.now ?? Date.now
|
|
1443
|
+
};
|
|
1444
|
+
}
|
|
1445
|
+
/**
|
|
1446
|
+
* Queue one beacon. With a `collapseKey`, a repeat inside the window is
|
|
1447
|
+
* merged into a held aggregate (sent when the window closes) instead of
|
|
1448
|
+
* becoming its own item.
|
|
1449
|
+
*/
|
|
1450
|
+
enqueue(dest, item, collapseKey) {
|
|
1451
|
+
const now = this.opts.now();
|
|
1452
|
+
if (collapseKey) {
|
|
1453
|
+
const entry = this.collapse.get(collapseKey);
|
|
1454
|
+
if (entry && now - entry.firstSeenAt < this.opts.collapseWindowMs) {
|
|
1455
|
+
for (const target of [entry.first, entry.held]) {
|
|
1456
|
+
if (target && !target.inFlight && this.queue.includes(target)) {
|
|
1457
|
+
target.item.repeatCount = (target.item.repeatCount ?? 1) + 1;
|
|
1458
|
+
return;
|
|
1459
|
+
}
|
|
1460
|
+
}
|
|
1461
|
+
const held = this.push(
|
|
1462
|
+
dest,
|
|
1463
|
+
{ ...item, repeatCount: 1 },
|
|
1464
|
+
entry.firstSeenAt + this.opts.collapseWindowMs
|
|
1465
|
+
);
|
|
1466
|
+
entry.held = held;
|
|
1467
|
+
return;
|
|
1468
|
+
}
|
|
1469
|
+
this.collapse.delete(collapseKey);
|
|
1470
|
+
if (this.collapse.size >= this.opts.collapseMaxKeys) {
|
|
1471
|
+
const oldest = this.collapse.keys().next().value;
|
|
1472
|
+
if (oldest !== void 0) this.collapse.delete(oldest);
|
|
1473
|
+
}
|
|
1474
|
+
const fresh = { firstSeenAt: now };
|
|
1475
|
+
this.collapse.set(collapseKey, fresh);
|
|
1476
|
+
fresh.first = this.push(dest, item, now);
|
|
1477
|
+
return;
|
|
1478
|
+
}
|
|
1479
|
+
this.push(dest, item, now);
|
|
1480
|
+
}
|
|
1481
|
+
/**
|
|
1482
|
+
* Deliver every eligible queued item. Concurrent callers share ONE
|
|
1483
|
+
* in-flight drain (it survives the caller's budget and a Lambda freeze),
|
|
1484
|
+
* so nothing in flight is ever sent twice; items queued while it runs are
|
|
1485
|
+
* picked up by the same drain.
|
|
1486
|
+
*/
|
|
1487
|
+
flush() {
|
|
1488
|
+
if (!this.inFlight) {
|
|
1489
|
+
this.inFlight = this.drain().finally(() => {
|
|
1490
|
+
this.inFlight = void 0;
|
|
1491
|
+
});
|
|
1492
|
+
}
|
|
1493
|
+
return this.inFlight;
|
|
1494
|
+
}
|
|
1495
|
+
/** Counters for tests and diagnostics. */
|
|
1496
|
+
stats() {
|
|
1497
|
+
return {
|
|
1498
|
+
queueDepth: this.queue.length,
|
|
1499
|
+
droppedSinceLast: this.droppedSinceLast,
|
|
1500
|
+
droppedTotal: this.droppedTotal,
|
|
1501
|
+
deliveredTotal: this.deliveredTotal,
|
|
1502
|
+
flushing: this.inFlight !== void 0
|
|
1503
|
+
};
|
|
1504
|
+
}
|
|
1505
|
+
push(dest, item, eligibleAt) {
|
|
1506
|
+
while (this.queue.length >= this.opts.maxItems) this.dropOldest();
|
|
1507
|
+
const entry = {
|
|
1508
|
+
dest,
|
|
1509
|
+
destKey: destKeyOf(dest),
|
|
1510
|
+
item,
|
|
1511
|
+
attempts: 0,
|
|
1512
|
+
eligibleAt,
|
|
1513
|
+
inFlight: false
|
|
1514
|
+
};
|
|
1515
|
+
this.queue.push(entry);
|
|
1516
|
+
return entry;
|
|
1517
|
+
}
|
|
1518
|
+
dropOldest() {
|
|
1519
|
+
if (this.queue.shift()) this.countDropped(1);
|
|
1520
|
+
}
|
|
1521
|
+
countDropped(n) {
|
|
1522
|
+
this.droppedSinceLast += n;
|
|
1523
|
+
this.droppedTotal += n;
|
|
1524
|
+
}
|
|
1525
|
+
async drain() {
|
|
1526
|
+
for (; ; ) {
|
|
1527
|
+
const now = this.opts.now();
|
|
1528
|
+
if (now < this.backoffUntil) return;
|
|
1529
|
+
const batch = this.takeBatch(now);
|
|
1530
|
+
if (batch.length === 0) return;
|
|
1531
|
+
const ok = await this.send(batch);
|
|
1532
|
+
if (!ok) return;
|
|
1533
|
+
}
|
|
1534
|
+
}
|
|
1535
|
+
/** Oldest eligible item + up to maxBatch-1 more for the same destination. */
|
|
1536
|
+
takeBatch(now) {
|
|
1537
|
+
const first = this.queue.find((q) => q.eligibleAt <= now);
|
|
1538
|
+
if (!first) return [];
|
|
1539
|
+
const batch = [];
|
|
1540
|
+
const rest = [];
|
|
1541
|
+
for (const q of this.queue) {
|
|
1542
|
+
if (batch.length < this.opts.maxBatch && q.destKey === first.destKey && q.eligibleAt <= now) {
|
|
1543
|
+
q.inFlight = true;
|
|
1544
|
+
batch.push(q);
|
|
1545
|
+
} else {
|
|
1546
|
+
rest.push(q);
|
|
1547
|
+
}
|
|
1548
|
+
}
|
|
1549
|
+
this.queue = rest;
|
|
1550
|
+
return batch;
|
|
1551
|
+
}
|
|
1552
|
+
/** POST one batch. Returns false when the drain should pause (backoff). */
|
|
1553
|
+
async send(batch) {
|
|
1554
|
+
const dest = batch[0].dest;
|
|
1555
|
+
const useKey = dest.apiKey && !this.rejectedKeys.has(dest.apiKey) ? dest.apiKey : void 0;
|
|
1556
|
+
const reportedDropped = this.droppedSinceLast;
|
|
1557
|
+
const meta = {
|
|
1558
|
+
droppedSinceLast: reportedDropped,
|
|
1559
|
+
queueDepth: this.queue.length,
|
|
1560
|
+
adapter: dest.adapter ?? "edge-core",
|
|
1561
|
+
version: SDK_VERSION
|
|
1562
|
+
};
|
|
1563
|
+
const controller = typeof AbortController !== "undefined" ? new AbortController() : void 0;
|
|
1564
|
+
const response = await withBudget(
|
|
1565
|
+
sdkFetch(`${dest.apiBaseUrl}/verification-activity/edge-events`, {
|
|
1566
|
+
method: "POST",
|
|
1567
|
+
headers: {
|
|
1568
|
+
"Content-Type": "application/json",
|
|
1569
|
+
...useKey && { Authorization: `Bearer ${useKey}` }
|
|
1570
|
+
},
|
|
1571
|
+
body: JSON.stringify({ events: batch.map((q) => q.item), meta }),
|
|
1572
|
+
...controller && { signal: controller.signal }
|
|
1573
|
+
}),
|
|
1574
|
+
this.opts.requestTimeoutMs
|
|
1575
|
+
);
|
|
1576
|
+
if (!response) controller?.abort();
|
|
1577
|
+
const status = response?.status;
|
|
1578
|
+
for (const q of batch) q.inFlight = false;
|
|
1579
|
+
if (status !== void 0 && status >= 200 && status < 300) {
|
|
1580
|
+
this.droppedSinceLast = Math.max(0, this.droppedSinceLast - reportedDropped);
|
|
1581
|
+
this.deliveredTotal += batch.length;
|
|
1582
|
+
return true;
|
|
1583
|
+
}
|
|
1584
|
+
if (status === 401 && useKey) {
|
|
1585
|
+
this.rejectedKeys.add(useKey);
|
|
1586
|
+
this.requeue(batch, false);
|
|
1587
|
+
return true;
|
|
1588
|
+
}
|
|
1589
|
+
if (status === 429) {
|
|
1590
|
+
const retryAfter = Number(response?.headers.get("retry-after"));
|
|
1591
|
+
const waitMs = Number.isFinite(retryAfter) && retryAfter > 0 ? Math.min(retryAfter, 120) * 1e3 : 3e4;
|
|
1592
|
+
this.backoffUntil = this.opts.now() + waitMs;
|
|
1593
|
+
this.requeue(batch, false);
|
|
1594
|
+
return false;
|
|
1595
|
+
}
|
|
1596
|
+
if (status !== void 0 && status >= 400 && status < 500) {
|
|
1597
|
+
this.countDropped(batch.length);
|
|
1598
|
+
return true;
|
|
1599
|
+
}
|
|
1600
|
+
this.backoffUntil = this.opts.now() + this.opts.retryBackoffMs;
|
|
1601
|
+
this.requeue(batch, true);
|
|
1602
|
+
return false;
|
|
1603
|
+
}
|
|
1604
|
+
/** Put a failed batch back at the head of the queue (order preserved). */
|
|
1605
|
+
requeue(batch, countAttempt) {
|
|
1606
|
+
const keep = [];
|
|
1607
|
+
for (const q of batch) {
|
|
1608
|
+
if (countAttempt) q.attempts += 1;
|
|
1609
|
+
if (q.attempts >= this.opts.maxAttempts) this.countDropped(1);
|
|
1610
|
+
else keep.push(q);
|
|
1611
|
+
}
|
|
1612
|
+
this.queue = [...keep, ...this.queue];
|
|
1613
|
+
while (this.queue.length > this.opts.maxItems) this.dropOldest();
|
|
1614
|
+
}
|
|
1615
|
+
};
|
|
1616
|
+
var beaconBuffer = new BeaconBuffer();
|
|
1291
1617
|
function sampled(rate, random = Math.random) {
|
|
1292
1618
|
if (rate <= 0) return false;
|
|
1293
1619
|
if (rate >= 1) return true;
|
|
@@ -1905,6 +2231,20 @@ function createMiddleware(options) {
|
|
|
1905
2231
|
}
|
|
1906
2232
|
const approvalRequired = result.policyAllowed && requiresHumanApproval(result);
|
|
1907
2233
|
if (approvalRequired) annotateApprovalRequired(result);
|
|
2234
|
+
if (!result.policyAllowed && isVerificationUnavailable(result)) {
|
|
2235
|
+
if (isAnonymousRead(request.method, credentials)) {
|
|
2236
|
+
const passThrough = NextResponse.next();
|
|
2237
|
+
passThrough.headers.set(VERIFICATION_STATUS_HEADER, "unavailable");
|
|
2238
|
+
return passThrough;
|
|
2239
|
+
}
|
|
2240
|
+
return NextResponse.json(buildVerificationUnavailableBody(result), {
|
|
2241
|
+
status: 503,
|
|
2242
|
+
headers: {
|
|
2243
|
+
"Retry-After": String(unavailableRetryAfterSeconds(result)),
|
|
2244
|
+
[VERIFICATION_STATUS_HEADER]: "unavailable"
|
|
2245
|
+
}
|
|
2246
|
+
});
|
|
2247
|
+
}
|
|
1908
2248
|
if (!result.policyAllowed || approvalRequired) {
|
|
1909
2249
|
if (result.misconfigured && pathname.startsWith("/api/")) {
|
|
1910
2250
|
return NextResponse.json(
|
|
@@ -1922,13 +2262,12 @@ function createMiddleware(options) {
|
|
|
1922
2262
|
);
|
|
1923
2263
|
}
|
|
1924
2264
|
if (pathname.startsWith("/api/")) {
|
|
2265
|
+
const denyStatus = denyStatusFor(result, "POLICY_DENIED");
|
|
1925
2266
|
return NextResponse.json(
|
|
1926
2267
|
{
|
|
1927
2268
|
success: false,
|
|
1928
2269
|
error: {
|
|
1929
|
-
|
|
1930
|
-
// OK, policy denied (update PDLSS / step up).
|
|
1931
|
-
code: !result.identityVerified ? "UNAUTHORIZED" : "POLICY_DENIED",
|
|
2270
|
+
code: denyStatus.code,
|
|
1932
2271
|
message: result.denialReasons?.[0] || "Access denied",
|
|
1933
2272
|
guidance: result.guidance,
|
|
1934
2273
|
failures: result.failures,
|
|
@@ -1938,7 +2277,7 @@ function createMiddleware(options) {
|
|
|
1938
2277
|
}
|
|
1939
2278
|
},
|
|
1940
2279
|
{
|
|
1941
|
-
status:
|
|
2280
|
+
status: denyStatus.status,
|
|
1942
2281
|
...stepUpHoldOutcome && {
|
|
1943
2282
|
headers: { "X-Astra-Step-Up": stepUpHoldOutcome }
|
|
1944
2283
|
}
|