@astrasyncai/verification-gateway 5.10.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 +805 -16
- package/dist/bin/astrasync-codex-hook.js +805 -16
- package/dist/bin/astrasync-guard.js +894 -71
- package/dist/bin/astrasync.js +951 -74
- 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.d.mts +8 -8
- package/dist/cli/index.d.ts +8 -8
- package/dist/cli/index.js +22 -64
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/index.mjs +22 -64
- package/dist/cli/index.mjs.map +1 -1
- package/dist/codex/index.d.mts +5 -0
- package/dist/codex/index.d.ts +5 -0
- package/dist/codex/index.js +821 -24
- package/dist/codex/index.js.map +1 -1
- package/dist/codex/index.mjs +823 -26
- package/dist/codex/index.mjs.map +1 -1
- package/dist/cursor/extension.d.mts +9 -2
- package/dist/cursor/extension.d.ts +9 -2
- package/dist/cursor/extension.js +787 -11
- package/dist/cursor/extension.js.map +1 -1
- package/dist/cursor/extension.mjs +789 -11
- 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/enrolment-store-8DyaIOLe.d.mts +26 -0
- package/dist/enrolment-store-8DyaIOLe.d.ts +26 -0
- 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 +215 -5
- package/dist/index.d.ts +215 -5
- package/dist/index.js +3756 -80
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +3748 -80
- 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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as next_server from 'next/server';
|
|
2
2
|
import { NextRequest } from 'next/server';
|
|
3
|
-
import { N as NextJsMiddlewareOptions } from '../types-
|
|
3
|
+
import { N as NextJsMiddlewareOptions } from '../types-BCkwBA84.mjs';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Create Next.js middleware for agent verification.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as next_server from 'next/server';
|
|
2
2
|
import { NextRequest } from 'next/server';
|
|
3
|
-
import { N as NextJsMiddlewareOptions } from '../types-
|
|
3
|
+
import { N as NextJsMiddlewareOptions } from '../types-BCkwBA84.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Create Next.js middleware for agent verification.
|
package/dist/adapters/nextjs.js
CHANGED
|
@@ -36,7 +36,7 @@ __export(nextjs_exports, {
|
|
|
36
36
|
module.exports = __toCommonJS(nextjs_exports);
|
|
37
37
|
|
|
38
38
|
// src/version.ts
|
|
39
|
-
var SDK_VERSION = "5.
|
|
39
|
+
var SDK_VERSION = "5.12.0";
|
|
40
40
|
|
|
41
41
|
// src/http.ts
|
|
42
42
|
var SDK_USER_AGENT = `astrasync-sdk/${SDK_VERSION}`;
|
|
@@ -129,6 +129,59 @@ function getCachedWellKnownUrls(apiBaseUrl) {
|
|
|
129
129
|
return cache.get(apiBaseUrl)?.data;
|
|
130
130
|
}
|
|
131
131
|
|
|
132
|
+
// src/failure-posture.ts
|
|
133
|
+
var VERIFICATION_STATUS_HEADER = "X-Astra-Verification";
|
|
134
|
+
var DEFAULT_UNAVAILABLE_RETRY_AFTER_S = 30;
|
|
135
|
+
var REJECTED_IDENTITY_DIMENSIONS = /* @__PURE__ */ new Set([
|
|
136
|
+
"identity.invalid_agent_id",
|
|
137
|
+
"identity.unknown_agent",
|
|
138
|
+
"agent.lookup"
|
|
139
|
+
]);
|
|
140
|
+
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.";
|
|
141
|
+
var UNAVAILABLE_DIMENSIONS = /* @__PURE__ */ new Set([
|
|
142
|
+
"verify_access.api_error",
|
|
143
|
+
"middleware.internal_error"
|
|
144
|
+
]);
|
|
145
|
+
function isVerificationUnavailable(result) {
|
|
146
|
+
if (result.misconfigured) return false;
|
|
147
|
+
return (result.failures ?? []).some((f) => UNAVAILABLE_DIMENSIONS.has(f.dimension));
|
|
148
|
+
}
|
|
149
|
+
function unavailableRetryAfterSeconds(result) {
|
|
150
|
+
if (typeof result.retryAfterMs === "number" && Number.isFinite(result.retryAfterMs)) {
|
|
151
|
+
return Math.max(1, Math.ceil(result.retryAfterMs / 1e3));
|
|
152
|
+
}
|
|
153
|
+
return DEFAULT_UNAVAILABLE_RETRY_AFTER_S;
|
|
154
|
+
}
|
|
155
|
+
function isAnonymousRead(method, credentials) {
|
|
156
|
+
const m = (method ?? "").toUpperCase();
|
|
157
|
+
if (m !== "GET" && m !== "HEAD") return false;
|
|
158
|
+
return !credentials.astraId && !credentials.jwt;
|
|
159
|
+
}
|
|
160
|
+
function buildVerificationUnavailableBody(result) {
|
|
161
|
+
return {
|
|
162
|
+
success: false,
|
|
163
|
+
error: {
|
|
164
|
+
code: "VERIFICATION_UNAVAILABLE",
|
|
165
|
+
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.",
|
|
166
|
+
retryable: true,
|
|
167
|
+
retryAfterSeconds: unavailableRetryAfterSeconds(result),
|
|
168
|
+
...result.failures && { failures: result.failures },
|
|
169
|
+
...result.correlationId && { correlationId: result.correlationId },
|
|
170
|
+
guidance: {
|
|
171
|
+
message: "Retry the request after the Retry-After interval, with exponential backoff; the condition is transient."
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
function isRejectedIdentityClaim(result) {
|
|
177
|
+
return (result.failures ?? []).some((f) => REJECTED_IDENTITY_DIMENSIONS.has(f.dimension));
|
|
178
|
+
}
|
|
179
|
+
function denyStatusFor(result, policyDeniedCode) {
|
|
180
|
+
if (isRejectedIdentityClaim(result)) return { status: 403, code: "AGENT_IDENTITY_REJECTED" };
|
|
181
|
+
if (result.identityVerified) return { status: 403, code: policyDeniedCode };
|
|
182
|
+
return { status: 401, code: "UNAUTHORIZED" };
|
|
183
|
+
}
|
|
184
|
+
|
|
132
185
|
// src/verify.ts
|
|
133
186
|
var DEFAULT_CONFIG = {
|
|
134
187
|
apiBaseUrl: "https://astrasync.ai/api",
|
|
@@ -374,11 +427,44 @@ function createGuidanceResponse(_config, reason, options = {}) {
|
|
|
374
427
|
// coercing the absent recommendation to a policy 'deny'. Misconfig is
|
|
375
428
|
// deterministic and intentionally NOT marked retryable.
|
|
376
429
|
...isApiError && { retryable: true },
|
|
430
|
+
...options.httpStatus !== void 0 && { httpStatus: options.httpStatus },
|
|
431
|
+
...options.retryAfterMs !== void 0 && { retryAfterMs: options.retryAfterMs },
|
|
377
432
|
correlationId: options.correlationId,
|
|
378
433
|
attemptId: options.attemptId,
|
|
379
434
|
verifiedAt: /* @__PURE__ */ new Date()
|
|
380
435
|
};
|
|
381
436
|
}
|
|
437
|
+
async function readJsonSafe(response) {
|
|
438
|
+
try {
|
|
439
|
+
const parsed = await response.json();
|
|
440
|
+
return parsed && typeof parsed === "object" ? parsed : {};
|
|
441
|
+
} catch {
|
|
442
|
+
return {};
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
function rejectedIdentityGuidance(failures) {
|
|
446
|
+
const f = failures?.find((x) => REJECTED_IDENTITY_DIMENSIONS.has(x.dimension));
|
|
447
|
+
if (!f) return void 0;
|
|
448
|
+
if (f.dimension === "agent.lookup") {
|
|
449
|
+
return `The presented agent identifier is not registered with AstraSync. ${UNKNOWN_AGENT_GUIDANCE}`;
|
|
450
|
+
}
|
|
451
|
+
return f.guidance ? `${f.message} ${f.guidance}` : f.message;
|
|
452
|
+
}
|
|
453
|
+
var MAX_RETRY_AFTER_MS = 15 * 60 * 1e3;
|
|
454
|
+
function parseRetryAfterMs(response) {
|
|
455
|
+
const raw = response.headers?.get?.("retry-after");
|
|
456
|
+
if (!raw) return void 0;
|
|
457
|
+
const trimmed = raw.trim();
|
|
458
|
+
let ms;
|
|
459
|
+
if (/^\d+(\.\d+)?$/.test(trimmed)) {
|
|
460
|
+
ms = Number(trimmed) * 1e3;
|
|
461
|
+
} else {
|
|
462
|
+
const at = Date.parse(trimmed);
|
|
463
|
+
if (Number.isNaN(at)) return void 0;
|
|
464
|
+
ms = at - Date.now();
|
|
465
|
+
}
|
|
466
|
+
return Math.min(MAX_RETRY_AFTER_MS, Math.max(0, Math.round(ms)));
|
|
467
|
+
}
|
|
382
468
|
async function callVerifyAccessAPI(config, request) {
|
|
383
469
|
const { credentials, ...requestData } = request;
|
|
384
470
|
const body = {
|
|
@@ -435,7 +521,8 @@ async function callVerifyAccessAPI(config, request) {
|
|
|
435
521
|
body: JSON.stringify(body),
|
|
436
522
|
signal: AbortSignal.timeout(config.verifyTimeoutMs ?? 1e4)
|
|
437
523
|
});
|
|
438
|
-
const data = await response
|
|
524
|
+
const data = await readJsonSafe(response);
|
|
525
|
+
const retryAfterMs = parseRetryAfterMs(response);
|
|
439
526
|
if (response.status === 410) {
|
|
440
527
|
return {
|
|
441
528
|
success: true,
|
|
@@ -473,6 +560,23 @@ async function callVerifyAccessAPI(config, request) {
|
|
|
473
560
|
}
|
|
474
561
|
};
|
|
475
562
|
}
|
|
563
|
+
if (response.status === 404 && credentials.astraId && (data?.error === "AGENT_NOT_FOUND" || typeof data?.message === "string" && /agent/i.test(data.message))) {
|
|
564
|
+
return {
|
|
565
|
+
success: true,
|
|
566
|
+
attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0,
|
|
567
|
+
access: {
|
|
568
|
+
allowed: false,
|
|
569
|
+
reason: "unknown_agent",
|
|
570
|
+
failures: [
|
|
571
|
+
{
|
|
572
|
+
dimension: "identity.unknown_agent",
|
|
573
|
+
message: "The presented agent identifier is not registered with AstraSync.",
|
|
574
|
+
guidance: UNKNOWN_AGENT_GUIDANCE
|
|
575
|
+
}
|
|
576
|
+
]
|
|
577
|
+
}
|
|
578
|
+
};
|
|
579
|
+
}
|
|
476
580
|
if (!response.ok) {
|
|
477
581
|
if (response.status === 401) {
|
|
478
582
|
return {
|
|
@@ -487,9 +591,11 @@ async function callVerifyAccessAPI(config, request) {
|
|
|
487
591
|
}
|
|
488
592
|
return {
|
|
489
593
|
success: false,
|
|
490
|
-
error: data.message || data.error || `API returned ${response.status}`,
|
|
594
|
+
error: typeof data?.message === "string" && data.message || typeof data?.error === "string" && data.error || `API returned ${response.status}`,
|
|
491
595
|
correlationId: typeof data?.correlationId === "string" ? data.correlationId : void 0,
|
|
492
|
-
attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0
|
|
596
|
+
attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0,
|
|
597
|
+
httpStatus: response.status,
|
|
598
|
+
...retryAfterMs !== void 0 && { retryAfterMs }
|
|
493
599
|
};
|
|
494
600
|
}
|
|
495
601
|
return data;
|
|
@@ -561,12 +667,14 @@ async function verify(config, request, options) {
|
|
|
561
667
|
correlationId: apiResponse.correlationId,
|
|
562
668
|
attemptId: apiResponse.attemptId,
|
|
563
669
|
remediation: apiResponse.remediation,
|
|
670
|
+
httpStatus: apiResponse.httpStatus,
|
|
671
|
+
retryAfterMs: apiResponse.retryAfterMs,
|
|
564
672
|
urls
|
|
565
673
|
});
|
|
566
674
|
}
|
|
567
675
|
if (!apiResponse.access?.allowed) {
|
|
568
676
|
const aggregatedFailures = apiResponse.access?.failures;
|
|
569
|
-
const idVerifiedFromBackend = apiResponse.verificationContext?.idVerified === true;
|
|
677
|
+
const idVerifiedFromBackend = apiResponse.verificationContext?.idVerified === true && !aggregatedFailures?.some((f) => REJECTED_IDENTITY_DIMENSIONS.has(f.dimension));
|
|
570
678
|
const result2 = {
|
|
571
679
|
identityVerified: idVerifiedFromBackend,
|
|
572
680
|
policyAllowed: false,
|
|
@@ -578,7 +686,10 @@ async function verify(config, request, options) {
|
|
|
578
686
|
requiresStepUp: apiResponse.access?.requiresStepUp,
|
|
579
687
|
requiresApproval: apiResponse.access?.requiresApproval,
|
|
580
688
|
guidance: {
|
|
581
|
-
|
|
689
|
+
// 5.12.0: a presented-but-rejected id (malformed / unknown) carries
|
|
690
|
+
// actionable registration guidance on its failure — surface it
|
|
691
|
+
// instead of the bare machine reason ('unknown_agent').
|
|
692
|
+
message: rejectedIdentityGuidance(aggregatedFailures) ?? (apiResponse.access?.reason || "Access denied by PDLSS policy"),
|
|
582
693
|
registrationUrl: urls?.registrationUrl ?? "",
|
|
583
694
|
documentationUrl: urls?.documentationUrl ?? "",
|
|
584
695
|
// 5.1.0: anonymous callers land on this branch — steer MCP-hosted
|
|
@@ -678,7 +789,7 @@ async function verify(config, request, options) {
|
|
|
678
789
|
registrationUrl: urls?.registrationUrl ?? "",
|
|
679
790
|
documentationUrl: urls?.documentationUrl ?? ""
|
|
680
791
|
};
|
|
681
|
-
} else if (result.recommendation === "step_up_required") {
|
|
792
|
+
} else if (result.recommendation === "step_up_required" && result.identityVerified) {
|
|
682
793
|
result.requiresStepUp = true;
|
|
683
794
|
result.denialReasons = serverDenialReasons ?? result.recommendationReasons ?? ["Step-up verification required"];
|
|
684
795
|
}
|
|
@@ -717,7 +828,10 @@ async function reportUnregisteredAttempt(config, data) {
|
|
|
717
828
|
};
|
|
718
829
|
await sdkFetch(`${apiBaseUrl}/verification-activity/unregistered-attempt`, {
|
|
719
830
|
method: "POST",
|
|
720
|
-
headers: {
|
|
831
|
+
headers: {
|
|
832
|
+
"Content-Type": "application/json",
|
|
833
|
+
...config.apiKey && { Authorization: `Bearer ${config.apiKey}` }
|
|
834
|
+
},
|
|
721
835
|
body: JSON.stringify(body)
|
|
722
836
|
}).catch(() => {
|
|
723
837
|
});
|
|
@@ -1190,7 +1304,10 @@ var PLATFORM_AGENT_SIGNATURES = [
|
|
|
1190
1304
|
{
|
|
1191
1305
|
vendor: "chatgpt",
|
|
1192
1306
|
displayName: "OpenAI ChatGPT",
|
|
1193
|
-
|
|
1307
|
+
// GPTBot (training crawler) and OAI-SearchBot (ChatGPT search) are
|
|
1308
|
+
// OpenAI's declared crawler UAs — `gpt-?\d` never matched them, so they
|
|
1309
|
+
// classified as unattributed crawlers (round-27.4 W5).
|
|
1310
|
+
uaPatterns: [/chatgpt/i, /openai/i, /gpt-?\d/i, /\bGPTBot\b/i, /\bOAI-SearchBot\b/i],
|
|
1194
1311
|
agentCardPatterns: [/openai\.com/i, /chatgpt\.com/i],
|
|
1195
1312
|
apiKeyFormatPatterns: [/^sk-proj/i, /^sk-svcacct/i],
|
|
1196
1313
|
headerPatterns: [/^openai-organization/i, /^openai-project/i, /^openai-version/i]
|
|
@@ -1325,6 +1442,215 @@ var BROWSER_UA_PATTERN = /mozilla\/\d/i;
|
|
|
1325
1442
|
var _patterns = { STATIC_EXTENSIONS, BOT_UA_PATTERN, BROWSER_UA_PATTERN, SELF_UA_PATTERN };
|
|
1326
1443
|
|
|
1327
1444
|
// src/edge-core/events.ts
|
|
1445
|
+
async function withBudget(promise, ms) {
|
|
1446
|
+
let timer;
|
|
1447
|
+
try {
|
|
1448
|
+
return await Promise.race([
|
|
1449
|
+
promise,
|
|
1450
|
+
new Promise((resolve) => {
|
|
1451
|
+
timer = setTimeout(() => resolve(void 0), ms);
|
|
1452
|
+
})
|
|
1453
|
+
]);
|
|
1454
|
+
} catch {
|
|
1455
|
+
return void 0;
|
|
1456
|
+
} finally {
|
|
1457
|
+
if (timer) clearTimeout(timer);
|
|
1458
|
+
}
|
|
1459
|
+
}
|
|
1460
|
+
var destKeyOf = (d) => `${d.apiBaseUrl}\0${d.apiKey ?? ""}\0${d.counterparty}\0${d.adapter ?? ""}`;
|
|
1461
|
+
var BeaconBuffer = class {
|
|
1462
|
+
constructor(options = {}) {
|
|
1463
|
+
this.queue = [];
|
|
1464
|
+
this.droppedSinceLast = 0;
|
|
1465
|
+
this.droppedTotal = 0;
|
|
1466
|
+
this.deliveredTotal = 0;
|
|
1467
|
+
this.backoffUntil = 0;
|
|
1468
|
+
this.collapse = /* @__PURE__ */ new Map();
|
|
1469
|
+
/** Keys the backend answered 401 for — later batches go unauthenticated. */
|
|
1470
|
+
this.rejectedKeys = /* @__PURE__ */ new Set();
|
|
1471
|
+
this.opts = {
|
|
1472
|
+
maxItems: options.maxItems ?? 1e3,
|
|
1473
|
+
maxBatch: options.maxBatch ?? 100,
|
|
1474
|
+
maxAttempts: options.maxAttempts ?? 3,
|
|
1475
|
+
collapseWindowMs: options.collapseWindowMs ?? 6e4,
|
|
1476
|
+
collapseMaxKeys: options.collapseMaxKeys ?? 1e3,
|
|
1477
|
+
requestTimeoutMs: options.requestTimeoutMs ?? 1e4,
|
|
1478
|
+
retryBackoffMs: options.retryBackoffMs ?? 1e3,
|
|
1479
|
+
now: options.now ?? Date.now
|
|
1480
|
+
};
|
|
1481
|
+
}
|
|
1482
|
+
/**
|
|
1483
|
+
* Queue one beacon. With a `collapseKey`, a repeat inside the window is
|
|
1484
|
+
* merged into a held aggregate (sent when the window closes) instead of
|
|
1485
|
+
* becoming its own item.
|
|
1486
|
+
*/
|
|
1487
|
+
enqueue(dest, item, collapseKey) {
|
|
1488
|
+
const now = this.opts.now();
|
|
1489
|
+
if (collapseKey) {
|
|
1490
|
+
const entry = this.collapse.get(collapseKey);
|
|
1491
|
+
if (entry && now - entry.firstSeenAt < this.opts.collapseWindowMs) {
|
|
1492
|
+
for (const target of [entry.first, entry.held]) {
|
|
1493
|
+
if (target && !target.inFlight && this.queue.includes(target)) {
|
|
1494
|
+
target.item.repeatCount = (target.item.repeatCount ?? 1) + 1;
|
|
1495
|
+
return;
|
|
1496
|
+
}
|
|
1497
|
+
}
|
|
1498
|
+
const held = this.push(
|
|
1499
|
+
dest,
|
|
1500
|
+
{ ...item, repeatCount: 1 },
|
|
1501
|
+
entry.firstSeenAt + this.opts.collapseWindowMs
|
|
1502
|
+
);
|
|
1503
|
+
entry.held = held;
|
|
1504
|
+
return;
|
|
1505
|
+
}
|
|
1506
|
+
this.collapse.delete(collapseKey);
|
|
1507
|
+
if (this.collapse.size >= this.opts.collapseMaxKeys) {
|
|
1508
|
+
const oldest = this.collapse.keys().next().value;
|
|
1509
|
+
if (oldest !== void 0) this.collapse.delete(oldest);
|
|
1510
|
+
}
|
|
1511
|
+
const fresh = { firstSeenAt: now };
|
|
1512
|
+
this.collapse.set(collapseKey, fresh);
|
|
1513
|
+
fresh.first = this.push(dest, item, now);
|
|
1514
|
+
return;
|
|
1515
|
+
}
|
|
1516
|
+
this.push(dest, item, now);
|
|
1517
|
+
}
|
|
1518
|
+
/**
|
|
1519
|
+
* Deliver every eligible queued item. Concurrent callers share ONE
|
|
1520
|
+
* in-flight drain (it survives the caller's budget and a Lambda freeze),
|
|
1521
|
+
* so nothing in flight is ever sent twice; items queued while it runs are
|
|
1522
|
+
* picked up by the same drain.
|
|
1523
|
+
*/
|
|
1524
|
+
flush() {
|
|
1525
|
+
if (!this.inFlight) {
|
|
1526
|
+
this.inFlight = this.drain().finally(() => {
|
|
1527
|
+
this.inFlight = void 0;
|
|
1528
|
+
});
|
|
1529
|
+
}
|
|
1530
|
+
return this.inFlight;
|
|
1531
|
+
}
|
|
1532
|
+
/** Counters for tests and diagnostics. */
|
|
1533
|
+
stats() {
|
|
1534
|
+
return {
|
|
1535
|
+
queueDepth: this.queue.length,
|
|
1536
|
+
droppedSinceLast: this.droppedSinceLast,
|
|
1537
|
+
droppedTotal: this.droppedTotal,
|
|
1538
|
+
deliveredTotal: this.deliveredTotal,
|
|
1539
|
+
flushing: this.inFlight !== void 0
|
|
1540
|
+
};
|
|
1541
|
+
}
|
|
1542
|
+
push(dest, item, eligibleAt) {
|
|
1543
|
+
while (this.queue.length >= this.opts.maxItems) this.dropOldest();
|
|
1544
|
+
const entry = {
|
|
1545
|
+
dest,
|
|
1546
|
+
destKey: destKeyOf(dest),
|
|
1547
|
+
item,
|
|
1548
|
+
attempts: 0,
|
|
1549
|
+
eligibleAt,
|
|
1550
|
+
inFlight: false
|
|
1551
|
+
};
|
|
1552
|
+
this.queue.push(entry);
|
|
1553
|
+
return entry;
|
|
1554
|
+
}
|
|
1555
|
+
dropOldest() {
|
|
1556
|
+
if (this.queue.shift()) this.countDropped(1);
|
|
1557
|
+
}
|
|
1558
|
+
countDropped(n) {
|
|
1559
|
+
this.droppedSinceLast += n;
|
|
1560
|
+
this.droppedTotal += n;
|
|
1561
|
+
}
|
|
1562
|
+
async drain() {
|
|
1563
|
+
for (; ; ) {
|
|
1564
|
+
const now = this.opts.now();
|
|
1565
|
+
if (now < this.backoffUntil) return;
|
|
1566
|
+
const batch = this.takeBatch(now);
|
|
1567
|
+
if (batch.length === 0) return;
|
|
1568
|
+
const ok = await this.send(batch);
|
|
1569
|
+
if (!ok) return;
|
|
1570
|
+
}
|
|
1571
|
+
}
|
|
1572
|
+
/** Oldest eligible item + up to maxBatch-1 more for the same destination. */
|
|
1573
|
+
takeBatch(now) {
|
|
1574
|
+
const first = this.queue.find((q) => q.eligibleAt <= now);
|
|
1575
|
+
if (!first) return [];
|
|
1576
|
+
const batch = [];
|
|
1577
|
+
const rest = [];
|
|
1578
|
+
for (const q of this.queue) {
|
|
1579
|
+
if (batch.length < this.opts.maxBatch && q.destKey === first.destKey && q.eligibleAt <= now) {
|
|
1580
|
+
q.inFlight = true;
|
|
1581
|
+
batch.push(q);
|
|
1582
|
+
} else {
|
|
1583
|
+
rest.push(q);
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
this.queue = rest;
|
|
1587
|
+
return batch;
|
|
1588
|
+
}
|
|
1589
|
+
/** POST one batch. Returns false when the drain should pause (backoff). */
|
|
1590
|
+
async send(batch) {
|
|
1591
|
+
const dest = batch[0].dest;
|
|
1592
|
+
const useKey = dest.apiKey && !this.rejectedKeys.has(dest.apiKey) ? dest.apiKey : void 0;
|
|
1593
|
+
const reportedDropped = this.droppedSinceLast;
|
|
1594
|
+
const meta = {
|
|
1595
|
+
droppedSinceLast: reportedDropped,
|
|
1596
|
+
queueDepth: this.queue.length,
|
|
1597
|
+
adapter: dest.adapter ?? "edge-core",
|
|
1598
|
+
version: SDK_VERSION
|
|
1599
|
+
};
|
|
1600
|
+
const controller = typeof AbortController !== "undefined" ? new AbortController() : void 0;
|
|
1601
|
+
const response = await withBudget(
|
|
1602
|
+
sdkFetch(`${dest.apiBaseUrl}/verification-activity/edge-events`, {
|
|
1603
|
+
method: "POST",
|
|
1604
|
+
headers: {
|
|
1605
|
+
"Content-Type": "application/json",
|
|
1606
|
+
...useKey && { Authorization: `Bearer ${useKey}` }
|
|
1607
|
+
},
|
|
1608
|
+
body: JSON.stringify({ events: batch.map((q) => q.item), meta }),
|
|
1609
|
+
...controller && { signal: controller.signal }
|
|
1610
|
+
}),
|
|
1611
|
+
this.opts.requestTimeoutMs
|
|
1612
|
+
);
|
|
1613
|
+
if (!response) controller?.abort();
|
|
1614
|
+
const status = response?.status;
|
|
1615
|
+
for (const q of batch) q.inFlight = false;
|
|
1616
|
+
if (status !== void 0 && status >= 200 && status < 300) {
|
|
1617
|
+
this.droppedSinceLast = Math.max(0, this.droppedSinceLast - reportedDropped);
|
|
1618
|
+
this.deliveredTotal += batch.length;
|
|
1619
|
+
return true;
|
|
1620
|
+
}
|
|
1621
|
+
if (status === 401 && useKey) {
|
|
1622
|
+
this.rejectedKeys.add(useKey);
|
|
1623
|
+
this.requeue(batch, false);
|
|
1624
|
+
return true;
|
|
1625
|
+
}
|
|
1626
|
+
if (status === 429) {
|
|
1627
|
+
const retryAfter = Number(response?.headers.get("retry-after"));
|
|
1628
|
+
const waitMs = Number.isFinite(retryAfter) && retryAfter > 0 ? Math.min(retryAfter, 120) * 1e3 : 3e4;
|
|
1629
|
+
this.backoffUntil = this.opts.now() + waitMs;
|
|
1630
|
+
this.requeue(batch, false);
|
|
1631
|
+
return false;
|
|
1632
|
+
}
|
|
1633
|
+
if (status !== void 0 && status >= 400 && status < 500) {
|
|
1634
|
+
this.countDropped(batch.length);
|
|
1635
|
+
return true;
|
|
1636
|
+
}
|
|
1637
|
+
this.backoffUntil = this.opts.now() + this.opts.retryBackoffMs;
|
|
1638
|
+
this.requeue(batch, true);
|
|
1639
|
+
return false;
|
|
1640
|
+
}
|
|
1641
|
+
/** Put a failed batch back at the head of the queue (order preserved). */
|
|
1642
|
+
requeue(batch, countAttempt) {
|
|
1643
|
+
const keep = [];
|
|
1644
|
+
for (const q of batch) {
|
|
1645
|
+
if (countAttempt) q.attempts += 1;
|
|
1646
|
+
if (q.attempts >= this.opts.maxAttempts) this.countDropped(1);
|
|
1647
|
+
else keep.push(q);
|
|
1648
|
+
}
|
|
1649
|
+
this.queue = [...keep, ...this.queue];
|
|
1650
|
+
while (this.queue.length > this.opts.maxItems) this.dropOldest();
|
|
1651
|
+
}
|
|
1652
|
+
};
|
|
1653
|
+
var beaconBuffer = new BeaconBuffer();
|
|
1328
1654
|
function sampled(rate, random = Math.random) {
|
|
1329
1655
|
if (rate <= 0) return false;
|
|
1330
1656
|
if (rate >= 1) return true;
|
|
@@ -1942,6 +2268,20 @@ function createMiddleware(options) {
|
|
|
1942
2268
|
}
|
|
1943
2269
|
const approvalRequired = result.policyAllowed && requiresHumanApproval(result);
|
|
1944
2270
|
if (approvalRequired) annotateApprovalRequired(result);
|
|
2271
|
+
if (!result.policyAllowed && isVerificationUnavailable(result)) {
|
|
2272
|
+
if (isAnonymousRead(request.method, credentials)) {
|
|
2273
|
+
const passThrough = NextResponse.next();
|
|
2274
|
+
passThrough.headers.set(VERIFICATION_STATUS_HEADER, "unavailable");
|
|
2275
|
+
return passThrough;
|
|
2276
|
+
}
|
|
2277
|
+
return NextResponse.json(buildVerificationUnavailableBody(result), {
|
|
2278
|
+
status: 503,
|
|
2279
|
+
headers: {
|
|
2280
|
+
"Retry-After": String(unavailableRetryAfterSeconds(result)),
|
|
2281
|
+
[VERIFICATION_STATUS_HEADER]: "unavailable"
|
|
2282
|
+
}
|
|
2283
|
+
});
|
|
2284
|
+
}
|
|
1945
2285
|
if (!result.policyAllowed || approvalRequired) {
|
|
1946
2286
|
if (result.misconfigured && pathname.startsWith("/api/")) {
|
|
1947
2287
|
return NextResponse.json(
|
|
@@ -1959,13 +2299,12 @@ function createMiddleware(options) {
|
|
|
1959
2299
|
);
|
|
1960
2300
|
}
|
|
1961
2301
|
if (pathname.startsWith("/api/")) {
|
|
2302
|
+
const denyStatus = denyStatusFor(result, "POLICY_DENIED");
|
|
1962
2303
|
return NextResponse.json(
|
|
1963
2304
|
{
|
|
1964
2305
|
success: false,
|
|
1965
2306
|
error: {
|
|
1966
|
-
|
|
1967
|
-
// OK, policy denied (update PDLSS / step up).
|
|
1968
|
-
code: !result.identityVerified ? "UNAUTHORIZED" : "POLICY_DENIED",
|
|
2307
|
+
code: denyStatus.code,
|
|
1969
2308
|
message: result.denialReasons?.[0] || "Access denied",
|
|
1970
2309
|
guidance: result.guidance,
|
|
1971
2310
|
failures: result.failures,
|
|
@@ -1975,7 +2314,7 @@ function createMiddleware(options) {
|
|
|
1975
2314
|
}
|
|
1976
2315
|
},
|
|
1977
2316
|
{
|
|
1978
|
-
status:
|
|
2317
|
+
status: denyStatus.status,
|
|
1979
2318
|
...stepUpHoldOutcome && {
|
|
1980
2319
|
headers: { "X-Astra-Step-Up": stepUpHoldOutcome }
|
|
1981
2320
|
}
|