@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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AstraSyncGateway } from '../gateway/gateway.mjs';
|
|
2
|
-
import { A as AgentAction, I as InterceptResult, P as PDLSSContext, V as VerificationDecision } from '../types-
|
|
3
|
-
import '../types-
|
|
2
|
+
import { A as AgentAction, I as InterceptResult, P as PDLSSContext, V as VerificationDecision } from '../types-CgDCUfo8.mjs';
|
|
3
|
+
import '../types-B3USs-Kx.mjs';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* PlatformAdapter Interface
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AstraSyncGateway } from '../gateway/gateway.js';
|
|
2
|
-
import { A as AgentAction, I as InterceptResult, P as PDLSSContext, V as VerificationDecision } from '../types-
|
|
3
|
-
import '../types-
|
|
2
|
+
import { A as AgentAction, I as InterceptResult, P as PDLSSContext, V as VerificationDecision } from '../types-R5N4ET6x.js';
|
|
3
|
+
import '../types-B3USs-Kx.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* PlatformAdapter Interface
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import 'express';
|
|
2
|
-
import '../types-
|
|
3
|
-
export { c as createMiddleware, a as extractAstraSyncCredentials } from '../express-
|
|
2
|
+
import '../types-B3USs-Kx.mjs';
|
|
3
|
+
export { c as createMiddleware, a as extractAstraSyncCredentials } from '../express-ienhAXps.mjs';
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import 'express';
|
|
2
|
-
import '../types-
|
|
3
|
-
export { c as createMiddleware, a as extractAstraSyncCredentials } from '../express-
|
|
2
|
+
import '../types-B3USs-Kx.js';
|
|
3
|
+
export { c as createMiddleware, a as extractAstraSyncCredentials } from '../express-CrfwoNAR.js';
|
package/dist/adapters/express.js
CHANGED
|
@@ -45,7 +45,66 @@ function hasMinimumAccess(actual, required) {
|
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
// src/version.ts
|
|
48
|
-
var SDK_VERSION = "2.4.
|
|
48
|
+
var SDK_VERSION = "2.4.13";
|
|
49
|
+
|
|
50
|
+
// src/well-known.ts
|
|
51
|
+
var CACHE_TTL_MS = 60 * 60 * 1e3;
|
|
52
|
+
var cache = /* @__PURE__ */ new Map();
|
|
53
|
+
var inflight = /* @__PURE__ */ new Map();
|
|
54
|
+
function wellKnownUrl(apiBaseUrl) {
|
|
55
|
+
const base = apiBaseUrl.replace(/\/api\/?$/, "");
|
|
56
|
+
return `${base}/.well-known/agentic-commerce`;
|
|
57
|
+
}
|
|
58
|
+
async function fetchWellKnown(apiBaseUrl) {
|
|
59
|
+
const url = wellKnownUrl(apiBaseUrl);
|
|
60
|
+
const response = await fetch(url, {
|
|
61
|
+
method: "GET",
|
|
62
|
+
headers: { Accept: "application/json" },
|
|
63
|
+
signal: AbortSignal.timeout(5e3)
|
|
64
|
+
});
|
|
65
|
+
if (!response.ok) {
|
|
66
|
+
throw new Error(
|
|
67
|
+
`AstraSync platform must expose /.well-known/agentic-commerce; got ${response.status} from ${url}. SDK cannot initialise without it.`
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
const data = await response.json();
|
|
71
|
+
if (!data.registrationUrl || !data.documentationUrl || !data.verifyAccessUrl) {
|
|
72
|
+
throw new Error(
|
|
73
|
+
`/.well-known/agentic-commerce response missing required fields (registrationUrl, documentationUrl, verifyAccessUrl).`
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
return data;
|
|
77
|
+
}
|
|
78
|
+
function prefetchWellKnown(apiBaseUrl) {
|
|
79
|
+
const existing = inflight.get(apiBaseUrl);
|
|
80
|
+
if (existing) return existing;
|
|
81
|
+
const promise = fetchWellKnown(apiBaseUrl).then((data) => {
|
|
82
|
+
cache.set(apiBaseUrl, { data, fetchedAt: Date.now() });
|
|
83
|
+
inflight.delete(apiBaseUrl);
|
|
84
|
+
return data;
|
|
85
|
+
}).catch((err) => {
|
|
86
|
+
inflight.delete(apiBaseUrl);
|
|
87
|
+
throw err;
|
|
88
|
+
});
|
|
89
|
+
inflight.set(apiBaseUrl, promise);
|
|
90
|
+
return promise;
|
|
91
|
+
}
|
|
92
|
+
async function getWellKnownUrls(apiBaseUrl) {
|
|
93
|
+
const entry = cache.get(apiBaseUrl);
|
|
94
|
+
if (entry) {
|
|
95
|
+
if (Date.now() - entry.fetchedAt > CACHE_TTL_MS) {
|
|
96
|
+
prefetchWellKnown(apiBaseUrl).catch(() => {
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
return entry.data;
|
|
100
|
+
}
|
|
101
|
+
const pending = inflight.get(apiBaseUrl);
|
|
102
|
+
if (pending) return pending;
|
|
103
|
+
return prefetchWellKnown(apiBaseUrl);
|
|
104
|
+
}
|
|
105
|
+
function getCachedWellKnownUrls(apiBaseUrl) {
|
|
106
|
+
return cache.get(apiBaseUrl)?.data;
|
|
107
|
+
}
|
|
49
108
|
|
|
50
109
|
// src/verify.ts
|
|
51
110
|
var DEFAULT_CONFIG = {
|
|
@@ -64,22 +123,27 @@ var DEFAULT_CONFIG = {
|
|
|
64
123
|
};
|
|
65
124
|
var initCheckPerformed = false;
|
|
66
125
|
var deprecationWarningShown = false;
|
|
67
|
-
async function performInitCheck(apiBaseUrl, debug) {
|
|
126
|
+
async function performInitCheck(apiBaseUrl, debug, strictInit) {
|
|
68
127
|
initCheckPerformed = true;
|
|
69
128
|
try {
|
|
70
129
|
const probeUrl = `${apiBaseUrl}/agents/verify-access`;
|
|
71
130
|
const response = await fetch(probeUrl, { method: "HEAD" });
|
|
72
131
|
const contentType = response.headers.get("content-type") ?? "";
|
|
73
132
|
if (contentType.startsWith("text/html")) {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
133
|
+
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).`;
|
|
134
|
+
if (strictInit) {
|
|
135
|
+
throw new Error(`${message} (strictInit=true)`);
|
|
136
|
+
}
|
|
137
|
+
console.warn(`${message} Set disableInitChecks: true on GatewayConfig to silence.`);
|
|
77
138
|
} else if (debug) {
|
|
78
139
|
console.log(
|
|
79
140
|
`[VerificationGateway] init check passed for ${apiBaseUrl} (content-type: ${contentType})`
|
|
80
141
|
);
|
|
81
142
|
}
|
|
82
143
|
} catch (err) {
|
|
144
|
+
if (strictInit) {
|
|
145
|
+
throw err;
|
|
146
|
+
}
|
|
83
147
|
if (debug) {
|
|
84
148
|
console.log(`[VerificationGateway] init check failed (non-blocking): ${String(err)}`);
|
|
85
149
|
}
|
|
@@ -103,7 +167,23 @@ function getCacheKey(request) {
|
|
|
103
167
|
request.counterpartyType || "",
|
|
104
168
|
request.isSubAgentRequest ? "1" : "0",
|
|
105
169
|
request.parentAgentId || "",
|
|
106
|
-
request.subAgentDepth ?? ""
|
|
170
|
+
request.subAgentDepth ?? "",
|
|
171
|
+
// Audit F-A1-07: previously-missing dimensions that DO affect the
|
|
172
|
+
// backend verdict. Without these, two requests with different
|
|
173
|
+
// durations (e.g. 60s vs 86400s) collided on the same cache key and
|
|
174
|
+
// the shorter-duration allow served the longer-duration request.
|
|
175
|
+
request.durationRequired ?? "",
|
|
176
|
+
request.invocationProtocol || "",
|
|
177
|
+
request.enableRuntimeChallenge ? "1" : "0",
|
|
178
|
+
// callerMetadata fields contribute to risk model; include the ones
|
|
179
|
+
// backend reads. sourceIp/userAgent/forwardedFor change per-request
|
|
180
|
+
// so their inclusion effectively forces a re-check for any varying
|
|
181
|
+
// client (the right behavior — IP-driven anomaly scoring shouldn't
|
|
182
|
+
// be cached across IPs).
|
|
183
|
+
request.callerMetadata?.sourceIp || "",
|
|
184
|
+
request.callerMetadata?.userAgent || "",
|
|
185
|
+
request.callerMetadata?.forwardedFor || "",
|
|
186
|
+
request.callerMetadata?.agentCardUrl || ""
|
|
107
187
|
].join("|");
|
|
108
188
|
}
|
|
109
189
|
function getCachedResult(request) {
|
|
@@ -129,9 +209,13 @@ function cacheResult(request, result, configuredTtl) {
|
|
|
129
209
|
}
|
|
130
210
|
function extractCredentials(headers, query) {
|
|
131
211
|
const credentials = {};
|
|
212
|
+
const ASTRA_ID_PATTERN = /^ASTRAE?-[A-Za-z0-9_-]{1,64}$/;
|
|
132
213
|
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"];
|
|
133
214
|
if (astraIdHeader) {
|
|
134
|
-
|
|
215
|
+
const raw = Array.isArray(astraIdHeader) ? astraIdHeader[0] : typeof astraIdHeader === "string" ? astraIdHeader : void 0;
|
|
216
|
+
if (typeof raw === "string" && ASTRA_ID_PATTERN.test(raw)) {
|
|
217
|
+
credentials.astraId = raw;
|
|
218
|
+
}
|
|
135
219
|
}
|
|
136
220
|
const apiKeyHeader = headers["x-api-key"] || headers["X-Api-Key"] || headers["X-API-KEY"];
|
|
137
221
|
if (apiKeyHeader) {
|
|
@@ -140,9 +224,11 @@ function extractCredentials(headers, query) {
|
|
|
140
224
|
const authHeader = headers["authorization"] || headers["Authorization"];
|
|
141
225
|
if (authHeader) {
|
|
142
226
|
const authValue = Array.isArray(authHeader) ? authHeader[0] : authHeader;
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
227
|
+
if (typeof authValue === "string") {
|
|
228
|
+
credentials.authorizationHeader = authValue;
|
|
229
|
+
if (authValue.startsWith("Bearer ")) {
|
|
230
|
+
credentials.jwt = authValue.slice(7);
|
|
231
|
+
}
|
|
146
232
|
}
|
|
147
233
|
}
|
|
148
234
|
if (query) {
|
|
@@ -155,21 +241,22 @@ function extractCredentials(headers, query) {
|
|
|
155
241
|
}
|
|
156
242
|
return credentials;
|
|
157
243
|
}
|
|
158
|
-
function createGuidanceResponse(
|
|
244
|
+
function createGuidanceResponse(_config, reason, options = {}) {
|
|
159
245
|
const source = options.source ?? "no_credentials";
|
|
160
246
|
const isApiError = source === "api_error";
|
|
247
|
+
const urls = options.urls;
|
|
161
248
|
const guidance = isApiError ? {
|
|
162
249
|
message: "Verification is temporarily unavailable. Retry with exponential backoff; if the issue persists, contact support with the correlationId.",
|
|
163
|
-
registrationUrl:
|
|
164
|
-
documentationUrl:
|
|
250
|
+
registrationUrl: urls?.registrationUrl ?? "",
|
|
251
|
+
documentationUrl: urls?.documentationUrl ?? "",
|
|
165
252
|
steps: [
|
|
166
253
|
"Retry the request with exponential backoff",
|
|
167
254
|
"If failures persist, share the correlationId with support"
|
|
168
255
|
]
|
|
169
256
|
} : {
|
|
170
257
|
message: "This service verifies AI agents before granting access. Please register your agent with AstraSync.",
|
|
171
|
-
registrationUrl:
|
|
172
|
-
documentationUrl:
|
|
258
|
+
registrationUrl: urls?.registrationUrl ?? "",
|
|
259
|
+
documentationUrl: urls?.documentationUrl ?? "",
|
|
173
260
|
steps: [
|
|
174
261
|
"Register for an AstraSync account",
|
|
175
262
|
"Create and register your agent",
|
|
@@ -211,7 +298,7 @@ async function callVerifyAccessAPI(config, request) {
|
|
|
211
298
|
const { credentials, ...requestData } = request;
|
|
212
299
|
const body = {
|
|
213
300
|
...credentials.astraId && { agentId: credentials.astraId },
|
|
214
|
-
purpose: requestData.purpose
|
|
301
|
+
...requestData.purpose && { purpose: requestData.purpose }
|
|
215
302
|
};
|
|
216
303
|
if (requestData.action) body.action = requestData.action;
|
|
217
304
|
if (requestData.resourceType) body.resourceType = requestData.resourceType;
|
|
@@ -245,12 +332,8 @@ async function callVerifyAccessAPI(config, request) {
|
|
|
245
332
|
"Content-Type": "application/json",
|
|
246
333
|
...config.customHeaders
|
|
247
334
|
};
|
|
248
|
-
if (credentials.authorizationHeader) {
|
|
249
|
-
headers["Authorization"] = credentials.authorizationHeader;
|
|
250
|
-
} else if (config.apiKey) {
|
|
251
|
-
headers["Authorization"] = `Bearer ${config.apiKey}`;
|
|
252
|
-
}
|
|
253
335
|
if (config.apiKey) {
|
|
336
|
+
headers["Authorization"] = `Bearer ${config.apiKey}`;
|
|
254
337
|
headers["X-API-Key"] = config.apiKey;
|
|
255
338
|
}
|
|
256
339
|
try {
|
|
@@ -295,8 +378,13 @@ async function callVerifyAccessAPI(config, request) {
|
|
|
295
378
|
}
|
|
296
379
|
async function verify(config, request) {
|
|
297
380
|
const mergedConfig = { ...DEFAULT_CONFIG, ...config };
|
|
381
|
+
const urls = mergedConfig.apiBaseUrl ? getCachedWellKnownUrls(mergedConfig.apiBaseUrl) : void 0;
|
|
298
382
|
if (!initCheckPerformed && !mergedConfig.disableInitChecks && mergedConfig.apiBaseUrl) {
|
|
299
|
-
|
|
383
|
+
if (mergedConfig.strictInit) {
|
|
384
|
+
await performInitCheck(mergedConfig.apiBaseUrl, mergedConfig.debug, true);
|
|
385
|
+
} else {
|
|
386
|
+
void performInitCheck(mergedConfig.apiBaseUrl, mergedConfig.debug, false);
|
|
387
|
+
}
|
|
300
388
|
}
|
|
301
389
|
if (!deprecationWarningShown && (config.minTrustScore !== void 0 || config.minTrustScoreForFull !== void 0)) {
|
|
302
390
|
deprecationWarningShown = true;
|
|
@@ -327,7 +415,8 @@ async function verify(config, request) {
|
|
|
327
415
|
if (!apiResponse.success) {
|
|
328
416
|
return createGuidanceResponse(mergedConfig, apiResponse.error, {
|
|
329
417
|
source: "api_error",
|
|
330
|
-
correlationId: apiResponse.correlationId
|
|
418
|
+
correlationId: apiResponse.correlationId,
|
|
419
|
+
urls
|
|
331
420
|
});
|
|
332
421
|
}
|
|
333
422
|
if (!apiResponse.access?.allowed) {
|
|
@@ -350,8 +439,8 @@ async function verify(config, request) {
|
|
|
350
439
|
requiresApproval: apiResponse.access?.requiresApproval,
|
|
351
440
|
guidance: {
|
|
352
441
|
message: apiResponse.access?.reason || "Access denied by PDLSS policy",
|
|
353
|
-
registrationUrl:
|
|
354
|
-
documentationUrl:
|
|
442
|
+
registrationUrl: urls?.registrationUrl ?? "",
|
|
443
|
+
documentationUrl: urls?.documentationUrl ?? ""
|
|
355
444
|
},
|
|
356
445
|
verifiedAt: /* @__PURE__ */ new Date(),
|
|
357
446
|
// Extract sessionId so decisions can be recorded for denials too
|
|
@@ -420,13 +509,15 @@ async function verify(config, request) {
|
|
|
420
509
|
result.denialReasons = result.recommendationReasons || [
|
|
421
510
|
"Access denied by AstraSync recommendation"
|
|
422
511
|
];
|
|
423
|
-
|
|
424
|
-
result.
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
512
|
+
result.guidance = result.runtimeChallenge ? {
|
|
513
|
+
message: `Verification failed: ${result.runtimeChallenge.reason || "runtime challenge failed"}`,
|
|
514
|
+
registrationUrl: urls?.registrationUrl ?? "",
|
|
515
|
+
documentationUrl: urls?.documentationUrl ?? ""
|
|
516
|
+
} : {
|
|
517
|
+
message: result.recommendationReasons?.[0] || "Access denied by AstraSync recommendation",
|
|
518
|
+
registrationUrl: urls?.registrationUrl ?? "",
|
|
519
|
+
documentationUrl: urls?.documentationUrl ?? ""
|
|
520
|
+
};
|
|
430
521
|
} else if (result.recommendation === "step_up_required") {
|
|
431
522
|
result.requiresStepUp = true;
|
|
432
523
|
if (ACCESS_LEVEL_HIERARCHY[result.accessLevel] > ACCESS_LEVEL_HIERARCHY["read-only"]) {
|
|
@@ -615,18 +706,43 @@ function defaultExtractPurpose(req) {
|
|
|
615
706
|
return "general";
|
|
616
707
|
}
|
|
617
708
|
}
|
|
618
|
-
function matchRoute(pattern, path) {
|
|
709
|
+
function matchRoute(pattern, path, opts) {
|
|
619
710
|
const regexPattern = pattern.replace(/\*/g, ".*").replace(/\//g, "\\/");
|
|
620
|
-
const
|
|
621
|
-
|
|
711
|
+
const caseSensitiveRegex = new RegExp(`^${regexPattern}$`);
|
|
712
|
+
const caseSensitiveResult = caseSensitiveRegex.test(path);
|
|
713
|
+
if (!opts?.caseInsensitive && !opts?.logShadowDivergence) {
|
|
714
|
+
return caseSensitiveResult;
|
|
715
|
+
}
|
|
716
|
+
const caseInsensitiveRegex = new RegExp(`^${regexPattern}$`, "i");
|
|
717
|
+
const caseInsensitiveResult = caseInsensitiveRegex.test(path);
|
|
718
|
+
if (opts?.logShadowDivergence && caseSensitiveResult !== caseInsensitiveResult) {
|
|
719
|
+
console.warn(
|
|
720
|
+
`[SHADOW] matchRoute case-insensitive would change result: pattern=${pattern} path=${path} caseSensitive=${caseSensitiveResult} caseInsensitive=${caseInsensitiveResult} correlationId=${opts.correlationId ?? "unknown"}`
|
|
721
|
+
);
|
|
722
|
+
}
|
|
723
|
+
return opts?.caseInsensitive ? caseInsensitiveResult : caseSensitiveResult;
|
|
622
724
|
}
|
|
623
|
-
function findRouteConfig(routes, path, method) {
|
|
725
|
+
function findRouteConfig(routes, path, method, opts) {
|
|
624
726
|
return routes.find((route) => {
|
|
625
727
|
const methodMatches = route.method === "*" || route.method.toUpperCase() === method.toUpperCase();
|
|
626
|
-
const pathMatches = matchRoute(route.pattern, path);
|
|
728
|
+
const pathMatches = matchRoute(route.pattern, path, opts);
|
|
627
729
|
return methodMatches && pathMatches;
|
|
628
730
|
});
|
|
629
731
|
}
|
|
732
|
+
function dedupeFailures(result) {
|
|
733
|
+
if (result.failures && result.failures.length > 1) {
|
|
734
|
+
const seen = /* @__PURE__ */ new Set();
|
|
735
|
+
result.failures = result.failures.filter((f) => {
|
|
736
|
+
const key = `${f.dimension}|${f.message}|${f.guidance ?? ""}`;
|
|
737
|
+
if (seen.has(key)) return false;
|
|
738
|
+
seen.add(key);
|
|
739
|
+
return true;
|
|
740
|
+
});
|
|
741
|
+
}
|
|
742
|
+
if (result.denialReasons && result.denialReasons.length > 1) {
|
|
743
|
+
result.denialReasons = [...new Set(result.denialReasons)];
|
|
744
|
+
}
|
|
745
|
+
}
|
|
630
746
|
function defaultOnDenied(result, _req, res) {
|
|
631
747
|
const statusCode = !result.identityVerified ? 401 : 403;
|
|
632
748
|
res.setHeader("X-Astra-Gateway-Mode", "enforced");
|
|
@@ -653,8 +769,14 @@ function createMiddleware(options) {
|
|
|
653
769
|
recordDecisions,
|
|
654
770
|
enableRuntimeChallenge = true,
|
|
655
771
|
routesRefreshMs = DEFAULT_ROUTES_REFRESH_MS,
|
|
772
|
+
failOnError = "open",
|
|
773
|
+
caseInsensitiveRouteMatch = false,
|
|
656
774
|
...config
|
|
657
775
|
} = options;
|
|
776
|
+
if (config.apiBaseUrl) {
|
|
777
|
+
prefetchWellKnown(config.apiBaseUrl).catch(() => {
|
|
778
|
+
});
|
|
779
|
+
}
|
|
658
780
|
let cachedRoutes = [];
|
|
659
781
|
let lastFetchAt = 0;
|
|
660
782
|
let refreshing = null;
|
|
@@ -675,7 +797,7 @@ function createMiddleware(options) {
|
|
|
675
797
|
cachedRoutes = fetched;
|
|
676
798
|
lastFetchAt = Date.now();
|
|
677
799
|
if (cachedRoutes.length === 0 && !warnedEmptyRoutes) {
|
|
678
|
-
const dashboard = config.dashboardUrl ?? "https://
|
|
800
|
+
const dashboard = config.dashboardUrl ?? "https://astrasync.ai/dashboard";
|
|
679
801
|
console.warn(
|
|
680
802
|
`[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`
|
|
681
803
|
);
|
|
@@ -701,7 +823,12 @@ function createMiddleware(options) {
|
|
|
701
823
|
refreshing = null;
|
|
702
824
|
});
|
|
703
825
|
}
|
|
704
|
-
const
|
|
826
|
+
const correlationId = req.headers["x-request-id"] || req.headers["x-correlation-id"];
|
|
827
|
+
const routeConfig = findRouteConfig(cachedRoutes, req.path, req.method, {
|
|
828
|
+
caseInsensitive: caseInsensitiveRouteMatch,
|
|
829
|
+
logShadowDivergence: true,
|
|
830
|
+
correlationId
|
|
831
|
+
});
|
|
705
832
|
if (!routeConfig) {
|
|
706
833
|
if (config.setPassThroughHeader) {
|
|
707
834
|
res.setHeader("X-Astra-Gateway-Mode", "unenforced");
|
|
@@ -712,6 +839,7 @@ function createMiddleware(options) {
|
|
|
712
839
|
}
|
|
713
840
|
return next();
|
|
714
841
|
}
|
|
842
|
+
const wellKnownUrls = config.apiBaseUrl ? await getWellKnownUrls(config.apiBaseUrl).catch(() => void 0) : void 0;
|
|
715
843
|
const credentials = customExtractCredentials ? customExtractCredentials(req) : defaultExtractCredentials(req);
|
|
716
844
|
const shouldEnforce = routeConfig.minAccessLevel !== "none";
|
|
717
845
|
if (routeConfig.minAccessLevel === "none" && (!config.evaluateAlwaysIfCredentialed || !credentials.astraId)) {
|
|
@@ -733,8 +861,8 @@ function createMiddleware(options) {
|
|
|
733
861
|
denialReasons: preCheckFailures.map((f) => f.message),
|
|
734
862
|
guidance: {
|
|
735
863
|
message: "Request exceeds counterparty-defined PDLSS limits.",
|
|
736
|
-
registrationUrl:
|
|
737
|
-
documentationUrl:
|
|
864
|
+
registrationUrl: wellKnownUrls?.registrationUrl ?? "",
|
|
865
|
+
documentationUrl: wellKnownUrls?.documentationUrl ?? ""
|
|
738
866
|
},
|
|
739
867
|
verifiedAt: /* @__PURE__ */ new Date()
|
|
740
868
|
};
|
|
@@ -748,13 +876,19 @@ function createMiddleware(options) {
|
|
|
748
876
|
requestMethod: req.method
|
|
749
877
|
}).catch(() => {
|
|
750
878
|
});
|
|
879
|
+
dedupeFailures(result2);
|
|
751
880
|
onDenied(result2, req, res);
|
|
752
881
|
return;
|
|
753
882
|
}
|
|
754
883
|
const shouldRecordDecisions = recordDecisions !== false;
|
|
755
884
|
const forwardedFor = req.headers["x-forwarded-for"];
|
|
756
885
|
const forwardedForStr = Array.isArray(forwardedFor) ? forwardedFor.join(", ") : forwardedFor;
|
|
757
|
-
const originalClientIp = forwardedForStr ? forwardedForStr.split(",")[0].trim() :
|
|
886
|
+
const originalClientIp = req.ip ?? (forwardedForStr ? forwardedForStr.split(",")[0].trim() : void 0);
|
|
887
|
+
if (!req.ip && forwardedForStr) {
|
|
888
|
+
console.warn(
|
|
889
|
+
"[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."
|
|
890
|
+
);
|
|
891
|
+
}
|
|
758
892
|
const agentCardUrl = typeof req.headers["x-astrasync-agent-card"] === "string" ? req.headers["x-astrasync-agent-card"] : void 0;
|
|
759
893
|
const result = await verify(config, {
|
|
760
894
|
credentials,
|
|
@@ -785,6 +919,7 @@ function createMiddleware(options) {
|
|
|
785
919
|
recordDecision(config, sessionId, "denied", result.denialReasons?.[0]).catch(() => {
|
|
786
920
|
});
|
|
787
921
|
}
|
|
922
|
+
dedupeFailures(result);
|
|
788
923
|
onDenied(result, req, res);
|
|
789
924
|
return;
|
|
790
925
|
}
|
|
@@ -807,10 +942,18 @@ function createMiddleware(options) {
|
|
|
807
942
|
};
|
|
808
943
|
result.failures = [...result.failures ?? [], insufficientFailure];
|
|
809
944
|
result.denialReasons = [...result.denialReasons ?? [], insufficientFailure.message];
|
|
945
|
+
if (!result.guidance && wellKnownUrls) {
|
|
946
|
+
result.guidance = {
|
|
947
|
+
message: insufficientFailure.message,
|
|
948
|
+
registrationUrl: wellKnownUrls.registrationUrl,
|
|
949
|
+
documentationUrl: wellKnownUrls.documentationUrl
|
|
950
|
+
};
|
|
951
|
+
}
|
|
810
952
|
if (shouldRecordDecisions && sessionId) {
|
|
811
953
|
recordDecision(config, sessionId, "denied", insufficientFailure.message).catch(() => {
|
|
812
954
|
});
|
|
813
955
|
}
|
|
956
|
+
dedupeFailures(result);
|
|
814
957
|
onDenied(result, req, res);
|
|
815
958
|
return;
|
|
816
959
|
}
|
|
@@ -823,10 +966,18 @@ function createMiddleware(options) {
|
|
|
823
966
|
};
|
|
824
967
|
result.failures = [...result.failures ?? [], trustFailure];
|
|
825
968
|
result.denialReasons = [trustFailure.message];
|
|
969
|
+
if (!result.guidance && wellKnownUrls) {
|
|
970
|
+
result.guidance = {
|
|
971
|
+
message: trustFailure.message,
|
|
972
|
+
registrationUrl: wellKnownUrls.registrationUrl,
|
|
973
|
+
documentationUrl: wellKnownUrls.documentationUrl
|
|
974
|
+
};
|
|
975
|
+
}
|
|
826
976
|
if (shouldRecordDecisions && sessionId) {
|
|
827
977
|
recordDecision(config, sessionId, "denied", trustFailure.message).catch(() => {
|
|
828
978
|
});
|
|
829
979
|
}
|
|
980
|
+
dedupeFailures(result);
|
|
830
981
|
onDenied(result, req, res);
|
|
831
982
|
return;
|
|
832
983
|
}
|
|
@@ -841,7 +992,38 @@ function createMiddleware(options) {
|
|
|
841
992
|
}
|
|
842
993
|
next();
|
|
843
994
|
} catch (error) {
|
|
995
|
+
const errorClass = error instanceof Error ? error.constructor.name : typeof error;
|
|
996
|
+
const correlationId = req.headers["x-request-id"] || req.headers["x-correlation-id"] || `gen-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
844
997
|
console.error("[VerificationGateway] Middleware error:", error);
|
|
998
|
+
console.warn(
|
|
999
|
+
`[SHADOW] would-have-denied: errorClass=${errorClass} route=${req.method}:${req.path} merchantId=${config.counterpartyId ?? "unknown"} correlationId=${correlationId}`
|
|
1000
|
+
);
|
|
1001
|
+
if (failOnError === "closed") {
|
|
1002
|
+
const result = {
|
|
1003
|
+
identityVerified: false,
|
|
1004
|
+
policyAllowed: false,
|
|
1005
|
+
accessLevel: "none",
|
|
1006
|
+
denialReasons: [`Verification middleware internal error: ${errorClass}`],
|
|
1007
|
+
failures: [
|
|
1008
|
+
{
|
|
1009
|
+
dimension: "middleware.internal_error",
|
|
1010
|
+
message: `Middleware threw ${errorClass} \u2014 failing closed`
|
|
1011
|
+
}
|
|
1012
|
+
],
|
|
1013
|
+
verifiedAt: /* @__PURE__ */ new Date(),
|
|
1014
|
+
correlationId
|
|
1015
|
+
};
|
|
1016
|
+
const catchUrls = config.apiBaseUrl ? await getWellKnownUrls(config.apiBaseUrl).catch(() => void 0) : void 0;
|
|
1017
|
+
if (catchUrls) {
|
|
1018
|
+
result.guidance = {
|
|
1019
|
+
message: `Middleware threw ${errorClass} \u2014 failing closed`,
|
|
1020
|
+
registrationUrl: catchUrls.registrationUrl,
|
|
1021
|
+
documentationUrl: catchUrls.documentationUrl
|
|
1022
|
+
};
|
|
1023
|
+
}
|
|
1024
|
+
dedupeFailures(result);
|
|
1025
|
+
return onDenied(result, req, res);
|
|
1026
|
+
}
|
|
845
1027
|
next();
|
|
846
1028
|
}
|
|
847
1029
|
};
|