@astrasyncai/verification-gateway 2.4.12 → 2.5.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/dist/adapter-interface/interface.d.mts +2 -2
- package/dist/adapter-interface/interface.d.ts +2 -2
- package/dist/adapters/express.d.mts +2 -2
- package/dist/adapters/express.d.ts +2 -2
- package/dist/adapters/express.js +224 -42
- package/dist/adapters/express.js.map +1 -1
- package/dist/adapters/express.mjs +224 -42
- package/dist/adapters/express.mjs.map +1 -1
- package/dist/adapters/mcp.d.mts +101 -57
- package/dist/adapters/mcp.d.ts +101 -57
- package/dist/adapters/mcp.js +215 -44
- package/dist/adapters/mcp.js.map +1 -1
- package/dist/adapters/mcp.mjs +215 -44
- package/dist/adapters/mcp.mjs.map +1 -1
- package/dist/adapters/nextjs.d.mts +2 -2
- package/dist/adapters/nextjs.d.ts +2 -2
- package/dist/adapters/nextjs.js +87 -34
- package/dist/adapters/nextjs.js.map +1 -1
- package/dist/adapters/nextjs.mjs +87 -34
- package/dist/adapters/nextjs.mjs.map +1 -1
- package/dist/adapters/sdk.d.mts +2 -2
- package/dist/adapters/sdk.d.ts +2 -2
- package/dist/adapters/sdk.js +61 -28
- package/dist/adapters/sdk.js.map +1 -1
- package/dist/adapters/sdk.mjs +61 -28
- package/dist/adapters/sdk.mjs.map +1 -1
- package/dist/agent/index.d.mts +2 -2
- package/dist/agent/index.d.ts +2 -2
- package/dist/agent/index.js +29 -0
- package/dist/agent/index.js.map +1 -1
- package/dist/agent/index.mjs +29 -0
- package/dist/agent/index.mjs.map +1 -1
- package/dist/browser/background.js +102 -30
- package/dist/browser/background.js.map +1 -1
- package/dist/browser/background.mjs +102 -30
- package/dist/browser/background.mjs.map +1 -1
- package/dist/browser/browser-adapter.d.mts +2 -2
- package/dist/browser/browser-adapter.d.ts +2 -2
- package/dist/cli/index.d.mts +2 -2
- package/dist/cli/index.d.ts +2 -2
- package/dist/cursor/cursor-adapter.d.mts +2 -2
- package/dist/cursor/cursor-adapter.d.ts +2 -2
- package/dist/cursor/extension.d.mts +2 -2
- package/dist/cursor/extension.d.ts +2 -2
- package/dist/cursor/extension.js +102 -30
- package/dist/cursor/extension.js.map +1 -1
- package/dist/cursor/extension.mjs +102 -30
- package/dist/cursor/extension.mjs.map +1 -1
- package/dist/{express-C1ePFB7n.d.ts → express-CrfwoNAR.d.ts} +1 -1
- package/dist/{express-4WStX3PV.d.mts → express-ienhAXps.d.mts} +1 -1
- package/dist/gateway/gateway.d.mts +2 -2
- package/dist/gateway/gateway.d.ts +2 -2
- package/dist/gateway/gateway.js +102 -30
- package/dist/gateway/gateway.js.map +1 -1
- package/dist/gateway/gateway.mjs +102 -30
- package/dist/gateway/gateway.mjs.map +1 -1
- package/dist/git-trigger/git-hooks.d.mts +2 -2
- package/dist/git-trigger/git-hooks.d.ts +2 -2
- package/dist/{index-ChPX4WHl.d.mts → index-B5e2IDWU.d.mts} +1 -1
- package/dist/{index-CzJMCgEy.d.ts → index-CCdZxvAr.d.ts} +71 -6
- package/dist/{index-D8IEntil.d.mts → index-CEg_WG6y.d.mts} +71 -6
- package/dist/{index-Cjm-zBeZ.d.ts → index-DC5f8eoQ.d.ts} +1 -1
- package/dist/index.d.mts +39 -9
- package/dist/index.d.ts +39 -9
- package/dist/index.js +500 -94
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +497 -94
- package/dist/index.mjs.map +1 -1
- package/dist/local-evaluator/evaluator.d.mts +2 -2
- package/dist/local-evaluator/evaluator.d.ts +2 -2
- package/dist/local-evaluator/evaluator.js +12 -2
- package/dist/local-evaluator/evaluator.js.map +1 -1
- package/dist/local-evaluator/evaluator.mjs +12 -2
- package/dist/local-evaluator/evaluator.mjs.map +1 -1
- package/dist/{nextjs-BIORS__0.d.ts → nextjs-66R1KW8e.d.ts} +1 -1
- package/dist/{nextjs-CjzHdaXA.d.mts → nextjs-DSpisQst.d.mts} +1 -1
- package/dist/{sdk-Chhz-FcT.d.mts → sdk-5U_CBRpr.d.mts} +1 -1
- package/dist/{sdk-CqTEQAc6.d.ts → sdk-Bm8np66n.d.ts} +1 -1
- package/dist/transport/index.d.mts +2 -2
- package/dist/transport/index.d.ts +2 -2
- package/dist/transport/index.js +146 -28
- package/dist/transport/index.js.map +1 -1
- package/dist/transport/index.mjs +146 -28
- package/dist/transport/index.mjs.map +1 -1
- package/dist/{types-L15pYd2c.d.mts → types-B3USs-Kx.d.mts} +42 -1
- package/dist/{types-L15pYd2c.d.ts → types-B3USs-Kx.d.ts} +42 -1
- package/dist/{types-DNK2BgIf.d.mts → types-CgDCUfo8.d.mts} +1 -1
- package/dist/{types-DoWIuzfj.d.ts → types-R5N4ET6x.d.ts} +1 -1
- package/dist/ui/index.d.mts +1 -1
- package/dist/ui/index.d.ts +1 -1
- package/package.json +1 -1
|
@@ -18,7 +18,66 @@ function hasMinimumAccess(actual, required) {
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
// src/version.ts
|
|
21
|
-
var SDK_VERSION = "2.4.
|
|
21
|
+
var SDK_VERSION = "2.4.13";
|
|
22
|
+
|
|
23
|
+
// src/well-known.ts
|
|
24
|
+
var CACHE_TTL_MS = 60 * 60 * 1e3;
|
|
25
|
+
var cache = /* @__PURE__ */ new Map();
|
|
26
|
+
var inflight = /* @__PURE__ */ new Map();
|
|
27
|
+
function wellKnownUrl(apiBaseUrl) {
|
|
28
|
+
const base = apiBaseUrl.replace(/\/api\/?$/, "");
|
|
29
|
+
return `${base}/.well-known/agentic-commerce`;
|
|
30
|
+
}
|
|
31
|
+
async function fetchWellKnown(apiBaseUrl) {
|
|
32
|
+
const url = wellKnownUrl(apiBaseUrl);
|
|
33
|
+
const response = await fetch(url, {
|
|
34
|
+
method: "GET",
|
|
35
|
+
headers: { Accept: "application/json" },
|
|
36
|
+
signal: AbortSignal.timeout(5e3)
|
|
37
|
+
});
|
|
38
|
+
if (!response.ok) {
|
|
39
|
+
throw new Error(
|
|
40
|
+
`AstraSync platform must expose /.well-known/agentic-commerce; got ${response.status} from ${url}. SDK cannot initialise without it.`
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
const data = await response.json();
|
|
44
|
+
if (!data.registrationUrl || !data.documentationUrl || !data.verifyAccessUrl) {
|
|
45
|
+
throw new Error(
|
|
46
|
+
`/.well-known/agentic-commerce response missing required fields (registrationUrl, documentationUrl, verifyAccessUrl).`
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
return data;
|
|
50
|
+
}
|
|
51
|
+
function prefetchWellKnown(apiBaseUrl) {
|
|
52
|
+
const existing = inflight.get(apiBaseUrl);
|
|
53
|
+
if (existing) return existing;
|
|
54
|
+
const promise = fetchWellKnown(apiBaseUrl).then((data) => {
|
|
55
|
+
cache.set(apiBaseUrl, { data, fetchedAt: Date.now() });
|
|
56
|
+
inflight.delete(apiBaseUrl);
|
|
57
|
+
return data;
|
|
58
|
+
}).catch((err) => {
|
|
59
|
+
inflight.delete(apiBaseUrl);
|
|
60
|
+
throw err;
|
|
61
|
+
});
|
|
62
|
+
inflight.set(apiBaseUrl, promise);
|
|
63
|
+
return promise;
|
|
64
|
+
}
|
|
65
|
+
async function getWellKnownUrls(apiBaseUrl) {
|
|
66
|
+
const entry = cache.get(apiBaseUrl);
|
|
67
|
+
if (entry) {
|
|
68
|
+
if (Date.now() - entry.fetchedAt > CACHE_TTL_MS) {
|
|
69
|
+
prefetchWellKnown(apiBaseUrl).catch(() => {
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
return entry.data;
|
|
73
|
+
}
|
|
74
|
+
const pending = inflight.get(apiBaseUrl);
|
|
75
|
+
if (pending) return pending;
|
|
76
|
+
return prefetchWellKnown(apiBaseUrl);
|
|
77
|
+
}
|
|
78
|
+
function getCachedWellKnownUrls(apiBaseUrl) {
|
|
79
|
+
return cache.get(apiBaseUrl)?.data;
|
|
80
|
+
}
|
|
22
81
|
|
|
23
82
|
// src/verify.ts
|
|
24
83
|
var DEFAULT_CONFIG = {
|
|
@@ -37,22 +96,27 @@ var DEFAULT_CONFIG = {
|
|
|
37
96
|
};
|
|
38
97
|
var initCheckPerformed = false;
|
|
39
98
|
var deprecationWarningShown = false;
|
|
40
|
-
async function performInitCheck(apiBaseUrl, debug) {
|
|
99
|
+
async function performInitCheck(apiBaseUrl, debug, strictInit) {
|
|
41
100
|
initCheckPerformed = true;
|
|
42
101
|
try {
|
|
43
102
|
const probeUrl = `${apiBaseUrl}/agents/verify-access`;
|
|
44
103
|
const response = await fetch(probeUrl, { method: "HEAD" });
|
|
45
104
|
const contentType = response.headers.get("content-type") ?? "";
|
|
46
105
|
if (contentType.startsWith("text/html")) {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
106
|
+
const message = `[VerificationGateway] apiBaseUrl '${apiBaseUrl}' returned HTML (content-type: ${contentType}). This usually means apiBaseUrl is pointing at a marketing site instead of the API. Expected: 'https://astrasync.ai/api' (prod) or 'https://staging.astrasync.ai/api' (staging).`;
|
|
107
|
+
if (strictInit) {
|
|
108
|
+
throw new Error(`${message} (strictInit=true)`);
|
|
109
|
+
}
|
|
110
|
+
console.warn(`${message} Set disableInitChecks: true on GatewayConfig to silence.`);
|
|
50
111
|
} else if (debug) {
|
|
51
112
|
console.log(
|
|
52
113
|
`[VerificationGateway] init check passed for ${apiBaseUrl} (content-type: ${contentType})`
|
|
53
114
|
);
|
|
54
115
|
}
|
|
55
116
|
} catch (err) {
|
|
117
|
+
if (strictInit) {
|
|
118
|
+
throw err;
|
|
119
|
+
}
|
|
56
120
|
if (debug) {
|
|
57
121
|
console.log(`[VerificationGateway] init check failed (non-blocking): ${String(err)}`);
|
|
58
122
|
}
|
|
@@ -76,7 +140,23 @@ function getCacheKey(request) {
|
|
|
76
140
|
request.counterpartyType || "",
|
|
77
141
|
request.isSubAgentRequest ? "1" : "0",
|
|
78
142
|
request.parentAgentId || "",
|
|
79
|
-
request.subAgentDepth ?? ""
|
|
143
|
+
request.subAgentDepth ?? "",
|
|
144
|
+
// Audit F-A1-07: previously-missing dimensions that DO affect the
|
|
145
|
+
// backend verdict. Without these, two requests with different
|
|
146
|
+
// durations (e.g. 60s vs 86400s) collided on the same cache key and
|
|
147
|
+
// the shorter-duration allow served the longer-duration request.
|
|
148
|
+
request.durationRequired ?? "",
|
|
149
|
+
request.invocationProtocol || "",
|
|
150
|
+
request.enableRuntimeChallenge ? "1" : "0",
|
|
151
|
+
// callerMetadata fields contribute to risk model; include the ones
|
|
152
|
+
// backend reads. sourceIp/userAgent/forwardedFor change per-request
|
|
153
|
+
// so their inclusion effectively forces a re-check for any varying
|
|
154
|
+
// client (the right behavior — IP-driven anomaly scoring shouldn't
|
|
155
|
+
// be cached across IPs).
|
|
156
|
+
request.callerMetadata?.sourceIp || "",
|
|
157
|
+
request.callerMetadata?.userAgent || "",
|
|
158
|
+
request.callerMetadata?.forwardedFor || "",
|
|
159
|
+
request.callerMetadata?.agentCardUrl || ""
|
|
80
160
|
].join("|");
|
|
81
161
|
}
|
|
82
162
|
function getCachedResult(request) {
|
|
@@ -102,9 +182,13 @@ function cacheResult(request, result, configuredTtl) {
|
|
|
102
182
|
}
|
|
103
183
|
function extractCredentials(headers, query) {
|
|
104
184
|
const credentials = {};
|
|
185
|
+
const ASTRA_ID_PATTERN = /^ASTRAE?-[A-Za-z0-9_-]{1,64}$/;
|
|
105
186
|
const astraIdHeader = headers["x-astra-id"] || headers["X-Astra-Id"] || headers["X-ASTRA-ID"] || headers["x-astra-agentid"] || headers["X-Astra-AgentId"] || headers["x-astra-agent-id"] || headers["X-Astra-Agent-Id"] || headers["X-ASTRA-AGENT-ID"];
|
|
106
187
|
if (astraIdHeader) {
|
|
107
|
-
|
|
188
|
+
const raw = Array.isArray(astraIdHeader) ? astraIdHeader[0] : typeof astraIdHeader === "string" ? astraIdHeader : void 0;
|
|
189
|
+
if (typeof raw === "string" && ASTRA_ID_PATTERN.test(raw)) {
|
|
190
|
+
credentials.astraId = raw;
|
|
191
|
+
}
|
|
108
192
|
}
|
|
109
193
|
const apiKeyHeader = headers["x-api-key"] || headers["X-Api-Key"] || headers["X-API-KEY"];
|
|
110
194
|
if (apiKeyHeader) {
|
|
@@ -113,9 +197,11 @@ function extractCredentials(headers, query) {
|
|
|
113
197
|
const authHeader = headers["authorization"] || headers["Authorization"];
|
|
114
198
|
if (authHeader) {
|
|
115
199
|
const authValue = Array.isArray(authHeader) ? authHeader[0] : authHeader;
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
200
|
+
if (typeof authValue === "string") {
|
|
201
|
+
credentials.authorizationHeader = authValue;
|
|
202
|
+
if (authValue.startsWith("Bearer ")) {
|
|
203
|
+
credentials.jwt = authValue.slice(7);
|
|
204
|
+
}
|
|
119
205
|
}
|
|
120
206
|
}
|
|
121
207
|
if (query) {
|
|
@@ -128,21 +214,22 @@ function extractCredentials(headers, query) {
|
|
|
128
214
|
}
|
|
129
215
|
return credentials;
|
|
130
216
|
}
|
|
131
|
-
function createGuidanceResponse(
|
|
217
|
+
function createGuidanceResponse(_config, reason, options = {}) {
|
|
132
218
|
const source = options.source ?? "no_credentials";
|
|
133
219
|
const isApiError = source === "api_error";
|
|
220
|
+
const urls = options.urls;
|
|
134
221
|
const guidance = isApiError ? {
|
|
135
222
|
message: "Verification is temporarily unavailable. Retry with exponential backoff; if the issue persists, contact support with the correlationId.",
|
|
136
|
-
registrationUrl:
|
|
137
|
-
documentationUrl:
|
|
223
|
+
registrationUrl: urls?.registrationUrl ?? "",
|
|
224
|
+
documentationUrl: urls?.documentationUrl ?? "",
|
|
138
225
|
steps: [
|
|
139
226
|
"Retry the request with exponential backoff",
|
|
140
227
|
"If failures persist, share the correlationId with support"
|
|
141
228
|
]
|
|
142
229
|
} : {
|
|
143
230
|
message: "This service verifies AI agents before granting access. Please register your agent with AstraSync.",
|
|
144
|
-
registrationUrl:
|
|
145
|
-
documentationUrl:
|
|
231
|
+
registrationUrl: urls?.registrationUrl ?? "",
|
|
232
|
+
documentationUrl: urls?.documentationUrl ?? "",
|
|
146
233
|
steps: [
|
|
147
234
|
"Register for an AstraSync account",
|
|
148
235
|
"Create and register your agent",
|
|
@@ -184,7 +271,7 @@ async function callVerifyAccessAPI(config, request) {
|
|
|
184
271
|
const { credentials, ...requestData } = request;
|
|
185
272
|
const body = {
|
|
186
273
|
...credentials.astraId && { agentId: credentials.astraId },
|
|
187
|
-
purpose: requestData.purpose
|
|
274
|
+
...requestData.purpose && { purpose: requestData.purpose }
|
|
188
275
|
};
|
|
189
276
|
if (requestData.action) body.action = requestData.action;
|
|
190
277
|
if (requestData.resourceType) body.resourceType = requestData.resourceType;
|
|
@@ -218,12 +305,8 @@ async function callVerifyAccessAPI(config, request) {
|
|
|
218
305
|
"Content-Type": "application/json",
|
|
219
306
|
...config.customHeaders
|
|
220
307
|
};
|
|
221
|
-
if (credentials.authorizationHeader) {
|
|
222
|
-
headers["Authorization"] = credentials.authorizationHeader;
|
|
223
|
-
} else if (config.apiKey) {
|
|
224
|
-
headers["Authorization"] = `Bearer ${config.apiKey}`;
|
|
225
|
-
}
|
|
226
308
|
if (config.apiKey) {
|
|
309
|
+
headers["Authorization"] = `Bearer ${config.apiKey}`;
|
|
227
310
|
headers["X-API-Key"] = config.apiKey;
|
|
228
311
|
}
|
|
229
312
|
try {
|
|
@@ -268,8 +351,13 @@ async function callVerifyAccessAPI(config, request) {
|
|
|
268
351
|
}
|
|
269
352
|
async function verify(config, request) {
|
|
270
353
|
const mergedConfig = { ...DEFAULT_CONFIG, ...config };
|
|
354
|
+
const urls = mergedConfig.apiBaseUrl ? getCachedWellKnownUrls(mergedConfig.apiBaseUrl) : void 0;
|
|
271
355
|
if (!initCheckPerformed && !mergedConfig.disableInitChecks && mergedConfig.apiBaseUrl) {
|
|
272
|
-
|
|
356
|
+
if (mergedConfig.strictInit) {
|
|
357
|
+
await performInitCheck(mergedConfig.apiBaseUrl, mergedConfig.debug, true);
|
|
358
|
+
} else {
|
|
359
|
+
void performInitCheck(mergedConfig.apiBaseUrl, mergedConfig.debug, false);
|
|
360
|
+
}
|
|
273
361
|
}
|
|
274
362
|
if (!deprecationWarningShown && (config.minTrustScore !== void 0 || config.minTrustScoreForFull !== void 0)) {
|
|
275
363
|
deprecationWarningShown = true;
|
|
@@ -300,7 +388,8 @@ async function verify(config, request) {
|
|
|
300
388
|
if (!apiResponse.success) {
|
|
301
389
|
return createGuidanceResponse(mergedConfig, apiResponse.error, {
|
|
302
390
|
source: "api_error",
|
|
303
|
-
correlationId: apiResponse.correlationId
|
|
391
|
+
correlationId: apiResponse.correlationId,
|
|
392
|
+
urls
|
|
304
393
|
});
|
|
305
394
|
}
|
|
306
395
|
if (!apiResponse.access?.allowed) {
|
|
@@ -323,8 +412,8 @@ async function verify(config, request) {
|
|
|
323
412
|
requiresApproval: apiResponse.access?.requiresApproval,
|
|
324
413
|
guidance: {
|
|
325
414
|
message: apiResponse.access?.reason || "Access denied by PDLSS policy",
|
|
326
|
-
registrationUrl:
|
|
327
|
-
documentationUrl:
|
|
415
|
+
registrationUrl: urls?.registrationUrl ?? "",
|
|
416
|
+
documentationUrl: urls?.documentationUrl ?? ""
|
|
328
417
|
},
|
|
329
418
|
verifiedAt: /* @__PURE__ */ new Date(),
|
|
330
419
|
// Extract sessionId so decisions can be recorded for denials too
|
|
@@ -393,13 +482,15 @@ async function verify(config, request) {
|
|
|
393
482
|
result.denialReasons = result.recommendationReasons || [
|
|
394
483
|
"Access denied by AstraSync recommendation"
|
|
395
484
|
];
|
|
396
|
-
|
|
397
|
-
result.
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
485
|
+
result.guidance = result.runtimeChallenge ? {
|
|
486
|
+
message: `Verification failed: ${result.runtimeChallenge.reason || "runtime challenge failed"}`,
|
|
487
|
+
registrationUrl: urls?.registrationUrl ?? "",
|
|
488
|
+
documentationUrl: urls?.documentationUrl ?? ""
|
|
489
|
+
} : {
|
|
490
|
+
message: result.recommendationReasons?.[0] || "Access denied by AstraSync recommendation",
|
|
491
|
+
registrationUrl: urls?.registrationUrl ?? "",
|
|
492
|
+
documentationUrl: urls?.documentationUrl ?? ""
|
|
493
|
+
};
|
|
403
494
|
} else if (result.recommendation === "step_up_required") {
|
|
404
495
|
result.requiresStepUp = true;
|
|
405
496
|
if (ACCESS_LEVEL_HIERARCHY[result.accessLevel] > ACCESS_LEVEL_HIERARCHY["read-only"]) {
|
|
@@ -588,18 +679,43 @@ function defaultExtractPurpose(req) {
|
|
|
588
679
|
return "general";
|
|
589
680
|
}
|
|
590
681
|
}
|
|
591
|
-
function matchRoute(pattern, path) {
|
|
682
|
+
function matchRoute(pattern, path, opts) {
|
|
592
683
|
const regexPattern = pattern.replace(/\*/g, ".*").replace(/\//g, "\\/");
|
|
593
|
-
const
|
|
594
|
-
|
|
684
|
+
const caseSensitiveRegex = new RegExp(`^${regexPattern}$`);
|
|
685
|
+
const caseSensitiveResult = caseSensitiveRegex.test(path);
|
|
686
|
+
if (!opts?.caseInsensitive && !opts?.logShadowDivergence) {
|
|
687
|
+
return caseSensitiveResult;
|
|
688
|
+
}
|
|
689
|
+
const caseInsensitiveRegex = new RegExp(`^${regexPattern}$`, "i");
|
|
690
|
+
const caseInsensitiveResult = caseInsensitiveRegex.test(path);
|
|
691
|
+
if (opts?.logShadowDivergence && caseSensitiveResult !== caseInsensitiveResult) {
|
|
692
|
+
console.warn(
|
|
693
|
+
`[SHADOW] matchRoute case-insensitive would change result: pattern=${pattern} path=${path} caseSensitive=${caseSensitiveResult} caseInsensitive=${caseInsensitiveResult} correlationId=${opts.correlationId ?? "unknown"}`
|
|
694
|
+
);
|
|
695
|
+
}
|
|
696
|
+
return opts?.caseInsensitive ? caseInsensitiveResult : caseSensitiveResult;
|
|
595
697
|
}
|
|
596
|
-
function findRouteConfig(routes, path, method) {
|
|
698
|
+
function findRouteConfig(routes, path, method, opts) {
|
|
597
699
|
return routes.find((route) => {
|
|
598
700
|
const methodMatches = route.method === "*" || route.method.toUpperCase() === method.toUpperCase();
|
|
599
|
-
const pathMatches = matchRoute(route.pattern, path);
|
|
701
|
+
const pathMatches = matchRoute(route.pattern, path, opts);
|
|
600
702
|
return methodMatches && pathMatches;
|
|
601
703
|
});
|
|
602
704
|
}
|
|
705
|
+
function dedupeFailures(result) {
|
|
706
|
+
if (result.failures && result.failures.length > 1) {
|
|
707
|
+
const seen = /* @__PURE__ */ new Set();
|
|
708
|
+
result.failures = result.failures.filter((f) => {
|
|
709
|
+
const key = `${f.dimension}|${f.message}|${f.guidance ?? ""}`;
|
|
710
|
+
if (seen.has(key)) return false;
|
|
711
|
+
seen.add(key);
|
|
712
|
+
return true;
|
|
713
|
+
});
|
|
714
|
+
}
|
|
715
|
+
if (result.denialReasons && result.denialReasons.length > 1) {
|
|
716
|
+
result.denialReasons = [...new Set(result.denialReasons)];
|
|
717
|
+
}
|
|
718
|
+
}
|
|
603
719
|
function defaultOnDenied(result, _req, res) {
|
|
604
720
|
const statusCode = !result.identityVerified ? 401 : 403;
|
|
605
721
|
res.setHeader("X-Astra-Gateway-Mode", "enforced");
|
|
@@ -626,8 +742,14 @@ function createMiddleware(options) {
|
|
|
626
742
|
recordDecisions,
|
|
627
743
|
enableRuntimeChallenge = true,
|
|
628
744
|
routesRefreshMs = DEFAULT_ROUTES_REFRESH_MS,
|
|
745
|
+
failOnError = "open",
|
|
746
|
+
caseInsensitiveRouteMatch = false,
|
|
629
747
|
...config
|
|
630
748
|
} = options;
|
|
749
|
+
if (config.apiBaseUrl) {
|
|
750
|
+
prefetchWellKnown(config.apiBaseUrl).catch(() => {
|
|
751
|
+
});
|
|
752
|
+
}
|
|
631
753
|
let cachedRoutes = [];
|
|
632
754
|
let lastFetchAt = 0;
|
|
633
755
|
let refreshing = null;
|
|
@@ -648,7 +770,7 @@ function createMiddleware(options) {
|
|
|
648
770
|
cachedRoutes = fetched;
|
|
649
771
|
lastFetchAt = Date.now();
|
|
650
772
|
if (cachedRoutes.length === 0 && !warnedEmptyRoutes) {
|
|
651
|
-
const dashboard = config.dashboardUrl ?? "https://
|
|
773
|
+
const dashboard = config.dashboardUrl ?? "https://astrasync.ai/dashboard";
|
|
652
774
|
console.warn(
|
|
653
775
|
`[VerificationGateway] No route policy configured for ${config.counterpartyId}. Gateway is in pass-through mode for ALL traffic until you add at least one route. Configure at ${dashboard}/dashboard/endpoints/${config.counterpartyId}/routes`
|
|
654
776
|
);
|
|
@@ -674,7 +796,12 @@ function createMiddleware(options) {
|
|
|
674
796
|
refreshing = null;
|
|
675
797
|
});
|
|
676
798
|
}
|
|
677
|
-
const
|
|
799
|
+
const correlationId = req.headers["x-request-id"] || req.headers["x-correlation-id"];
|
|
800
|
+
const routeConfig = findRouteConfig(cachedRoutes, req.path, req.method, {
|
|
801
|
+
caseInsensitive: caseInsensitiveRouteMatch,
|
|
802
|
+
logShadowDivergence: true,
|
|
803
|
+
correlationId
|
|
804
|
+
});
|
|
678
805
|
if (!routeConfig) {
|
|
679
806
|
if (config.setPassThroughHeader) {
|
|
680
807
|
res.setHeader("X-Astra-Gateway-Mode", "unenforced");
|
|
@@ -685,6 +812,7 @@ function createMiddleware(options) {
|
|
|
685
812
|
}
|
|
686
813
|
return next();
|
|
687
814
|
}
|
|
815
|
+
const wellKnownUrls = config.apiBaseUrl ? await getWellKnownUrls(config.apiBaseUrl).catch(() => void 0) : void 0;
|
|
688
816
|
const credentials = customExtractCredentials ? customExtractCredentials(req) : defaultExtractCredentials(req);
|
|
689
817
|
const shouldEnforce = routeConfig.minAccessLevel !== "none";
|
|
690
818
|
if (routeConfig.minAccessLevel === "none" && (!config.evaluateAlwaysIfCredentialed || !credentials.astraId)) {
|
|
@@ -706,8 +834,8 @@ function createMiddleware(options) {
|
|
|
706
834
|
denialReasons: preCheckFailures.map((f) => f.message),
|
|
707
835
|
guidance: {
|
|
708
836
|
message: "Request exceeds counterparty-defined PDLSS limits.",
|
|
709
|
-
registrationUrl:
|
|
710
|
-
documentationUrl:
|
|
837
|
+
registrationUrl: wellKnownUrls?.registrationUrl ?? "",
|
|
838
|
+
documentationUrl: wellKnownUrls?.documentationUrl ?? ""
|
|
711
839
|
},
|
|
712
840
|
verifiedAt: /* @__PURE__ */ new Date()
|
|
713
841
|
};
|
|
@@ -721,13 +849,19 @@ function createMiddleware(options) {
|
|
|
721
849
|
requestMethod: req.method
|
|
722
850
|
}).catch(() => {
|
|
723
851
|
});
|
|
852
|
+
dedupeFailures(result2);
|
|
724
853
|
onDenied(result2, req, res);
|
|
725
854
|
return;
|
|
726
855
|
}
|
|
727
856
|
const shouldRecordDecisions = recordDecisions !== false;
|
|
728
857
|
const forwardedFor = req.headers["x-forwarded-for"];
|
|
729
858
|
const forwardedForStr = Array.isArray(forwardedFor) ? forwardedFor.join(", ") : forwardedFor;
|
|
730
|
-
const originalClientIp = forwardedForStr ? forwardedForStr.split(",")[0].trim() :
|
|
859
|
+
const originalClientIp = req.ip ?? (forwardedForStr ? forwardedForStr.split(",")[0].trim() : void 0);
|
|
860
|
+
if (!req.ip && forwardedForStr) {
|
|
861
|
+
console.warn(
|
|
862
|
+
"[VerificationGateway] req.ip unset \u2014 falling back to leftmost X-Forwarded-For. Configure Express trust proxy correctly to avoid spoofable client IPs in audit logs."
|
|
863
|
+
);
|
|
864
|
+
}
|
|
731
865
|
const agentCardUrl = typeof req.headers["x-astrasync-agent-card"] === "string" ? req.headers["x-astrasync-agent-card"] : void 0;
|
|
732
866
|
const result = await verify(config, {
|
|
733
867
|
credentials,
|
|
@@ -758,6 +892,7 @@ function createMiddleware(options) {
|
|
|
758
892
|
recordDecision(config, sessionId, "denied", result.denialReasons?.[0]).catch(() => {
|
|
759
893
|
});
|
|
760
894
|
}
|
|
895
|
+
dedupeFailures(result);
|
|
761
896
|
onDenied(result, req, res);
|
|
762
897
|
return;
|
|
763
898
|
}
|
|
@@ -780,10 +915,18 @@ function createMiddleware(options) {
|
|
|
780
915
|
};
|
|
781
916
|
result.failures = [...result.failures ?? [], insufficientFailure];
|
|
782
917
|
result.denialReasons = [...result.denialReasons ?? [], insufficientFailure.message];
|
|
918
|
+
if (!result.guidance && wellKnownUrls) {
|
|
919
|
+
result.guidance = {
|
|
920
|
+
message: insufficientFailure.message,
|
|
921
|
+
registrationUrl: wellKnownUrls.registrationUrl,
|
|
922
|
+
documentationUrl: wellKnownUrls.documentationUrl
|
|
923
|
+
};
|
|
924
|
+
}
|
|
783
925
|
if (shouldRecordDecisions && sessionId) {
|
|
784
926
|
recordDecision(config, sessionId, "denied", insufficientFailure.message).catch(() => {
|
|
785
927
|
});
|
|
786
928
|
}
|
|
929
|
+
dedupeFailures(result);
|
|
787
930
|
onDenied(result, req, res);
|
|
788
931
|
return;
|
|
789
932
|
}
|
|
@@ -796,10 +939,18 @@ function createMiddleware(options) {
|
|
|
796
939
|
};
|
|
797
940
|
result.failures = [...result.failures ?? [], trustFailure];
|
|
798
941
|
result.denialReasons = [trustFailure.message];
|
|
942
|
+
if (!result.guidance && wellKnownUrls) {
|
|
943
|
+
result.guidance = {
|
|
944
|
+
message: trustFailure.message,
|
|
945
|
+
registrationUrl: wellKnownUrls.registrationUrl,
|
|
946
|
+
documentationUrl: wellKnownUrls.documentationUrl
|
|
947
|
+
};
|
|
948
|
+
}
|
|
799
949
|
if (shouldRecordDecisions && sessionId) {
|
|
800
950
|
recordDecision(config, sessionId, "denied", trustFailure.message).catch(() => {
|
|
801
951
|
});
|
|
802
952
|
}
|
|
953
|
+
dedupeFailures(result);
|
|
803
954
|
onDenied(result, req, res);
|
|
804
955
|
return;
|
|
805
956
|
}
|
|
@@ -814,7 +965,38 @@ function createMiddleware(options) {
|
|
|
814
965
|
}
|
|
815
966
|
next();
|
|
816
967
|
} catch (error) {
|
|
968
|
+
const errorClass = error instanceof Error ? error.constructor.name : typeof error;
|
|
969
|
+
const correlationId = req.headers["x-request-id"] || req.headers["x-correlation-id"] || `gen-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
817
970
|
console.error("[VerificationGateway] Middleware error:", error);
|
|
971
|
+
console.warn(
|
|
972
|
+
`[SHADOW] would-have-denied: errorClass=${errorClass} route=${req.method}:${req.path} merchantId=${config.counterpartyId ?? "unknown"} correlationId=${correlationId}`
|
|
973
|
+
);
|
|
974
|
+
if (failOnError === "closed") {
|
|
975
|
+
const result = {
|
|
976
|
+
identityVerified: false,
|
|
977
|
+
policyAllowed: false,
|
|
978
|
+
accessLevel: "none",
|
|
979
|
+
denialReasons: [`Verification middleware internal error: ${errorClass}`],
|
|
980
|
+
failures: [
|
|
981
|
+
{
|
|
982
|
+
dimension: "middleware.internal_error",
|
|
983
|
+
message: `Middleware threw ${errorClass} \u2014 failing closed`
|
|
984
|
+
}
|
|
985
|
+
],
|
|
986
|
+
verifiedAt: /* @__PURE__ */ new Date(),
|
|
987
|
+
correlationId
|
|
988
|
+
};
|
|
989
|
+
const catchUrls = config.apiBaseUrl ? await getWellKnownUrls(config.apiBaseUrl).catch(() => void 0) : void 0;
|
|
990
|
+
if (catchUrls) {
|
|
991
|
+
result.guidance = {
|
|
992
|
+
message: `Middleware threw ${errorClass} \u2014 failing closed`,
|
|
993
|
+
registrationUrl: catchUrls.registrationUrl,
|
|
994
|
+
documentationUrl: catchUrls.documentationUrl
|
|
995
|
+
};
|
|
996
|
+
}
|
|
997
|
+
dedupeFailures(result);
|
|
998
|
+
return onDenied(result, req, res);
|
|
999
|
+
}
|
|
818
1000
|
next();
|
|
819
1001
|
}
|
|
820
1002
|
};
|