@astrasyncai/verification-gateway 3.7.1 → 3.9.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 +99 -11
- 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 +2 -1
- package/dist/adapters/express.js.map +1 -1
- package/dist/adapters/express.mjs +2 -1
- package/dist/adapters/express.mjs.map +1 -1
- package/dist/adapters/http-pdlss.d.mts +81 -0
- package/dist/adapters/http-pdlss.d.ts +81 -0
- package/dist/adapters/http-pdlss.js +117 -0
- package/dist/adapters/http-pdlss.js.map +1 -0
- package/dist/adapters/http-pdlss.mjs +87 -0
- package/dist/adapters/http-pdlss.mjs.map +1 -0
- package/dist/adapters/mcp.d.mts +1 -1
- package/dist/adapters/mcp.d.ts +1 -1
- package/dist/adapters/mcp.js +2 -1
- package/dist/adapters/mcp.js.map +1 -1
- package/dist/adapters/mcp.mjs +2 -1
- 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 +2 -1
- package/dist/adapters/nextjs.js.map +1 -1
- package/dist/adapters/nextjs.mjs +2 -1
- 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 +2 -1
- package/dist/adapters/sdk.js.map +1 -1
- package/dist/adapters/sdk.mjs +2 -1
- 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/bin/astrasync-claude-hook.js +14 -1
- package/dist/bin/astrasync-guard.js +14 -1
- package/dist/bin/astrasync.js +31 -1
- package/dist/browser/background.js +14 -1
- package/dist/browser/background.js.map +1 -1
- package/dist/browser/background.mjs +14 -1
- 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/claude-code/claude-code-adapter.d.mts +2 -2
- package/dist/claude-code/claude-code-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 +14 -1
- package/dist/cursor/extension.js.map +1 -1
- package/dist/cursor/extension.mjs +14 -1
- package/dist/cursor/extension.mjs.map +1 -1
- package/dist/edge-config.d.mts +130 -0
- package/dist/edge-config.d.ts +130 -0
- package/dist/edge-config.js +162 -0
- package/dist/edge-config.js.map +1 -0
- package/dist/edge-config.mjs +131 -0
- package/dist/edge-config.mjs.map +1 -0
- package/dist/{express-BnMO-H3y.d.mts → express-DXUyvv7t.d.mts} +1 -1
- package/dist/{express-DQhDzNAt.d.ts → express-sGXhDdd0.d.ts} +1 -1
- package/dist/gateway/gateway.d.mts +2 -2
- package/dist/gateway/gateway.d.ts +2 -2
- package/dist/gateway/gateway.js +14 -1
- package/dist/gateway/gateway.js.map +1 -1
- package/dist/gateway/gateway.mjs +14 -1
- 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-D8-dvUFA.d.ts → index-C61VL_OW.d.ts} +1 -1
- package/dist/{index-rftma0ln.d.ts → index-CMg-vjEK.d.ts} +1 -1
- package/dist/{index-WHKq2v17.d.mts → index-Dalih3EB.d.mts} +1 -1
- package/dist/{index-BT_aRn-I.d.mts → index-DmmdehNu.d.mts} +1 -1
- package/dist/index.d.mts +26 -82
- package/dist/index.d.ts +26 -82
- package/dist/index.js +226 -10
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +215 -10
- 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/{nextjs-DPj7ZNlq.d.ts → nextjs-DwUW-Nm1.d.ts} +1 -1
- package/dist/{nextjs-DFKYhxe6.d.mts → nextjs-F_2pbPIA.d.mts} +1 -1
- package/dist/platform-signatures.d.mts +47 -0
- package/dist/platform-signatures.d.ts +47 -0
- package/dist/platform-signatures.js +89 -0
- package/dist/platform-signatures.js.map +1 -0
- package/dist/platform-signatures.mjs +63 -0
- package/dist/platform-signatures.mjs.map +1 -0
- package/dist/{sdk-CAcHgB5x.d.ts → sdk-CfHANs4A.d.ts} +1 -1
- package/dist/{sdk-Cy3wAmDt.d.mts → sdk-Clob2-0A.d.mts} +1 -1
- package/dist/transport/index.d.mts +2 -2
- package/dist/transport/index.d.ts +2 -2
- package/dist/{types-CKe8O0lR.d.mts → types-ACMsV07B.d.mts} +112 -9
- package/dist/{types-CKe8O0lR.d.ts → types-ACMsV07B.d.ts} +112 -9
- package/dist/{types-B-ubjHER.d.mts → types-BtqUvK8v.d.mts} +1 -1
- package/dist/{types-Bgn5gxHQ.d.ts → types-DmSDgPpG.d.ts} +1 -1
- package/dist/ui/index.d.mts +1 -1
- package/dist/ui/index.d.ts +1 -1
- package/dist/verify.d.mts +144 -0
- package/dist/verify.d.ts +144 -0
- package/dist/verify.js +593 -0
- package/dist/verify.js.map +1 -0
- package/dist/verify.mjs +557 -0
- package/dist/verify.mjs.map +1 -0
- package/package.json +21 -1
package/dist/verify.js
ADDED
|
@@ -0,0 +1,593 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/verify.ts
|
|
21
|
+
var verify_exports = {};
|
|
22
|
+
__export(verify_exports, {
|
|
23
|
+
clearCache: () => clearCache,
|
|
24
|
+
extractCredentials: () => extractCredentials,
|
|
25
|
+
fetchRoutes: () => fetchRoutes,
|
|
26
|
+
hasCredentials: () => hasCredentials,
|
|
27
|
+
quickVerify: () => quickVerify,
|
|
28
|
+
recordDecision: () => recordDecision,
|
|
29
|
+
reportCounterpartyPreCheckFailure: () => reportCounterpartyPreCheckFailure,
|
|
30
|
+
reportUnregisteredAttempt: () => reportUnregisteredAttempt,
|
|
31
|
+
verify: () => verify,
|
|
32
|
+
verifyAndRecord: () => verifyAndRecord
|
|
33
|
+
});
|
|
34
|
+
module.exports = __toCommonJS(verify_exports);
|
|
35
|
+
|
|
36
|
+
// src/access-levels.ts
|
|
37
|
+
function getTrustLevel(score) {
|
|
38
|
+
if (score >= 80) return "PLATINUM";
|
|
39
|
+
if (score >= 60) return "GOLD";
|
|
40
|
+
if (score >= 40) return "SILVER";
|
|
41
|
+
return "BRONZE";
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// src/version.ts
|
|
45
|
+
var SDK_VERSION = "3.9.0";
|
|
46
|
+
|
|
47
|
+
// src/well-known.ts
|
|
48
|
+
var CACHE_TTL_MS = 60 * 60 * 1e3;
|
|
49
|
+
var cache = /* @__PURE__ */ new Map();
|
|
50
|
+
function getCachedWellKnownUrls(apiBaseUrl) {
|
|
51
|
+
return cache.get(apiBaseUrl)?.data;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// src/verify.ts
|
|
55
|
+
var DEFAULT_CONFIG = {
|
|
56
|
+
apiBaseUrl: "https://astrasync.ai/api",
|
|
57
|
+
// v2.3.9 (defect #30): default for unconfigured callers is `'none'` (no
|
|
58
|
+
// access). Pre-rename this defaulted to `'guidance'`, which combined with
|
|
59
|
+
// a route gated at `'guidance'` to silently let unverified traffic
|
|
60
|
+
// through (`hasMinimumAccess('guidance', 'guidance') === true`).
|
|
61
|
+
defaultAccessLevel: "none",
|
|
62
|
+
// minTrustScore + minTrustScoreForFull deprecated in v2.3.0 — server decides.
|
|
63
|
+
// cacheTtl deliberately unset. When undefined, cacheResult applies the 60s
|
|
64
|
+
// default; the caller's explicit value is honoured for cacheable results.
|
|
65
|
+
// Denials and step-up escalations are NEVER cached (3.6.0) regardless of
|
|
66
|
+
// cacheTtl. Set cacheTtl: 0 to disable caching entirely.
|
|
67
|
+
debug: false
|
|
68
|
+
};
|
|
69
|
+
var initCheckPerformed = false;
|
|
70
|
+
var deprecationWarningShown = false;
|
|
71
|
+
async function performInitCheck(apiBaseUrl, debug, strictInit) {
|
|
72
|
+
initCheckPerformed = true;
|
|
73
|
+
try {
|
|
74
|
+
const probeUrl = `${apiBaseUrl}/agents/verify-access`;
|
|
75
|
+
const response = await fetch(probeUrl, { method: "HEAD" });
|
|
76
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
77
|
+
if (contentType.startsWith("text/html")) {
|
|
78
|
+
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).`;
|
|
79
|
+
if (strictInit) {
|
|
80
|
+
throw new Error(`${message} (strictInit=true)`);
|
|
81
|
+
}
|
|
82
|
+
console.warn(`${message} Set disableInitChecks: true on GatewayConfig to silence.`);
|
|
83
|
+
} else if (debug) {
|
|
84
|
+
console.log(
|
|
85
|
+
`[VerificationGateway] init check passed for ${apiBaseUrl} (content-type: ${contentType})`
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
} catch (err) {
|
|
89
|
+
if (strictInit) {
|
|
90
|
+
throw err;
|
|
91
|
+
}
|
|
92
|
+
if (debug) {
|
|
93
|
+
console.log(`[VerificationGateway] init check failed (non-blocking): ${String(err)}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
var verificationCache = /* @__PURE__ */ new Map();
|
|
98
|
+
function getCacheKey(request, counterpartyId) {
|
|
99
|
+
const c = request.credentials;
|
|
100
|
+
return [
|
|
101
|
+
c.astraId || "",
|
|
102
|
+
c.apiKey || "",
|
|
103
|
+
c.jwt || "",
|
|
104
|
+
request.purpose || "",
|
|
105
|
+
request.action || "",
|
|
106
|
+
request.resourceType || "",
|
|
107
|
+
request.resource || "",
|
|
108
|
+
request.jurisdiction || "",
|
|
109
|
+
request.transactionValue ?? "",
|
|
110
|
+
request.currency || "",
|
|
111
|
+
// SECURITY (cross-merchant cache leak): the merchant identity is sent via
|
|
112
|
+
// `config.counterpartyId`, NOT on the request, so it was previously absent
|
|
113
|
+
// from the key — two verifies for the SAME agent/purpose/action/value but
|
|
114
|
+
// DIFFERENT merchants collided, and a grant at a permissive merchant (low
|
|
115
|
+
// trust floor) was served for a stricter one. Same bug class as the
|
|
116
|
+
// duration omission (F-A1-07). counterpartyId affects the backend verdict
|
|
117
|
+
// (trust floor / per-route policy), so it MUST key the cache.
|
|
118
|
+
counterpartyId || "",
|
|
119
|
+
request.counterpartyUrl || "",
|
|
120
|
+
request.counterpartyType || "",
|
|
121
|
+
request.isSubAgentRequest ? "1" : "0",
|
|
122
|
+
request.parentAgentId || "",
|
|
123
|
+
request.subAgentDepth ?? "",
|
|
124
|
+
// Audit F-A1-07: previously-missing dimensions that DO affect the
|
|
125
|
+
// backend verdict. Without these, two requests with different
|
|
126
|
+
// durations (e.g. 60s vs 86400s) collided on the same cache key and
|
|
127
|
+
// the shorter-duration allow served the longer-duration request.
|
|
128
|
+
request.durationRequired ?? "",
|
|
129
|
+
request.invocationProtocol || "",
|
|
130
|
+
request.enableRuntimeChallenge ? "1" : "0",
|
|
131
|
+
// callerMetadata fields contribute to risk model; include the ones
|
|
132
|
+
// backend reads. sourceIp/userAgent/forwardedFor change per-request
|
|
133
|
+
// so their inclusion effectively forces a re-check for any varying
|
|
134
|
+
// client (the right behavior — IP-driven anomaly scoring shouldn't
|
|
135
|
+
// be cached across IPs).
|
|
136
|
+
request.callerMetadata?.sourceIp || "",
|
|
137
|
+
request.callerMetadata?.userAgent || "",
|
|
138
|
+
request.callerMetadata?.forwardedFor || "",
|
|
139
|
+
request.callerMetadata?.agentCardUrl || ""
|
|
140
|
+
].join("|");
|
|
141
|
+
}
|
|
142
|
+
function getCachedResult(request, counterpartyId) {
|
|
143
|
+
const key = getCacheKey(request, counterpartyId);
|
|
144
|
+
const cached = verificationCache.get(key);
|
|
145
|
+
if (cached && cached.expiresAt > Date.now()) {
|
|
146
|
+
return cached.result;
|
|
147
|
+
}
|
|
148
|
+
if (cached) {
|
|
149
|
+
verificationCache.delete(key);
|
|
150
|
+
}
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
var DEFAULT_CACHE_TTL_SECONDS = 60;
|
|
154
|
+
function cacheResult(request, result, configuredTtl, counterpartyId) {
|
|
155
|
+
const ttlSeconds = configuredTtl && configuredTtl > 0 ? configuredTtl : DEFAULT_CACHE_TTL_SECONDS;
|
|
156
|
+
const key = getCacheKey(request, counterpartyId);
|
|
157
|
+
verificationCache.set(key, {
|
|
158
|
+
result,
|
|
159
|
+
expiresAt: Date.now() + ttlSeconds * 1e3
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
function clearCache() {
|
|
163
|
+
verificationCache.clear();
|
|
164
|
+
}
|
|
165
|
+
function extractCredentials(headers, query) {
|
|
166
|
+
const credentials = {};
|
|
167
|
+
const ASTRA_ID_PATTERN = /^ASTRAE?-[A-Za-z0-9_-]{1,64}$/;
|
|
168
|
+
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"];
|
|
169
|
+
if (astraIdHeader) {
|
|
170
|
+
const raw = Array.isArray(astraIdHeader) ? astraIdHeader[0] : typeof astraIdHeader === "string" ? astraIdHeader : void 0;
|
|
171
|
+
if (typeof raw === "string" && ASTRA_ID_PATTERN.test(raw)) {
|
|
172
|
+
credentials.astraId = raw;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
const apiKeyHeader = headers["x-api-key"] || headers["X-Api-Key"] || headers["X-API-KEY"];
|
|
176
|
+
if (apiKeyHeader) {
|
|
177
|
+
credentials.apiKey = Array.isArray(apiKeyHeader) ? apiKeyHeader[0] : apiKeyHeader;
|
|
178
|
+
}
|
|
179
|
+
const authHeader = headers["authorization"] || headers["Authorization"];
|
|
180
|
+
if (authHeader) {
|
|
181
|
+
const authValue = Array.isArray(authHeader) ? authHeader[0] : authHeader;
|
|
182
|
+
if (typeof authValue === "string") {
|
|
183
|
+
credentials.authorizationHeader = authValue;
|
|
184
|
+
if (authValue.startsWith("Bearer ")) {
|
|
185
|
+
credentials.jwt = authValue.slice(7);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
if (query) {
|
|
190
|
+
if (query.astraId && !credentials.astraId) {
|
|
191
|
+
credentials.astraId = query.astraId;
|
|
192
|
+
}
|
|
193
|
+
if (query.apiKey && !credentials.apiKey) {
|
|
194
|
+
credentials.apiKey = query.apiKey;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return credentials;
|
|
198
|
+
}
|
|
199
|
+
function hasCredentials(credentials) {
|
|
200
|
+
return !!(credentials.astraId || credentials.apiKey || credentials.jwt);
|
|
201
|
+
}
|
|
202
|
+
function createGuidanceResponse(_config, reason, options = {}) {
|
|
203
|
+
const source = options.source ?? "no_credentials";
|
|
204
|
+
const isApiError = source === "api_error";
|
|
205
|
+
const urls = options.urls;
|
|
206
|
+
const guidance = isApiError ? {
|
|
207
|
+
message: "Verification is temporarily unavailable. Retry with exponential backoff; if the issue persists, contact support with the correlationId.",
|
|
208
|
+
registrationUrl: urls?.registrationUrl ?? "",
|
|
209
|
+
documentationUrl: urls?.documentationUrl ?? "",
|
|
210
|
+
steps: [
|
|
211
|
+
"Retry the request with exponential backoff",
|
|
212
|
+
"If failures persist, share the correlationId with support"
|
|
213
|
+
]
|
|
214
|
+
} : {
|
|
215
|
+
message: "This service verifies AI agents before granting access. Please register your agent with AstraSync.",
|
|
216
|
+
registrationUrl: urls?.registrationUrl ?? "",
|
|
217
|
+
documentationUrl: urls?.documentationUrl ?? "",
|
|
218
|
+
steps: [
|
|
219
|
+
"Register for an AstraSync account",
|
|
220
|
+
"Create and register your agent",
|
|
221
|
+
"Add your ASTRA-ID to request headers",
|
|
222
|
+
"Retry your request"
|
|
223
|
+
]
|
|
224
|
+
};
|
|
225
|
+
return {
|
|
226
|
+
// Round-18 G4: createGuidanceResponse fires for unverified-agent path or
|
|
227
|
+
// API-error fallback. Identity is not verified (no agent resolved);
|
|
228
|
+
// policy is not evaluated (we never reached the gate).
|
|
229
|
+
identityVerified: false,
|
|
230
|
+
policyAllowed: false,
|
|
231
|
+
// v2.3.9 (defect #30): denials grant `'none'`, NEVER a positive band.
|
|
232
|
+
// Adapters additionally short-circuit on `!identityVerified ||
|
|
233
|
+
// !policyAllowed` before the gate check, but the access level still has
|
|
234
|
+
// to be honest at the data layer so downstream consumers (SDK adapters
|
|
235
|
+
// in other languages, custom integrations) inherit the correct
|
|
236
|
+
// semantics.
|
|
237
|
+
accessLevel: "none",
|
|
238
|
+
guidance,
|
|
239
|
+
denialReasons: reason ? [reason] : ["No valid agent credentials provided"],
|
|
240
|
+
// Round-10 (#47, O5): on API-error fallback, surface a typed failure so
|
|
241
|
+
// partners (and their custom onDenied handlers) can branch on
|
|
242
|
+
// dimension. Without this, the synthesised stub was indistinguishable
|
|
243
|
+
// from a real policy deny.
|
|
244
|
+
failures: isApiError ? [
|
|
245
|
+
{
|
|
246
|
+
dimension: "verify_access.api_error",
|
|
247
|
+
message: reason ?? "Verification temporarily unavailable",
|
|
248
|
+
guidance: guidance.message
|
|
249
|
+
}
|
|
250
|
+
] : void 0,
|
|
251
|
+
correlationId: options.correlationId,
|
|
252
|
+
verifiedAt: /* @__PURE__ */ new Date()
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
async function callVerifyAccessAPI(config, request) {
|
|
256
|
+
const { credentials, ...requestData } = request;
|
|
257
|
+
const body = {
|
|
258
|
+
...credentials.astraId && { agentId: credentials.astraId },
|
|
259
|
+
...requestData.purpose && { purpose: requestData.purpose }
|
|
260
|
+
};
|
|
261
|
+
if (requestData.action) body.action = requestData.action;
|
|
262
|
+
if (requestData.resourceType) body.resourceType = requestData.resourceType;
|
|
263
|
+
if (requestData.resource) body.resource = requestData.resource;
|
|
264
|
+
if (requestData.jurisdiction) body.jurisdiction = requestData.jurisdiction;
|
|
265
|
+
if (requestData.transactionValue) body.transactionValue = requestData.transactionValue;
|
|
266
|
+
if (requestData.currency) body.currency = requestData.currency;
|
|
267
|
+
if (requestData.isSubAgentRequest) body.isSubAgentRequest = requestData.isSubAgentRequest;
|
|
268
|
+
if (requestData.parentAgentId) body.parentAgentId = requestData.parentAgentId;
|
|
269
|
+
if (requestData.subAgentDepth !== void 0) body.subAgentDepth = requestData.subAgentDepth;
|
|
270
|
+
if (requestData.enableRuntimeChallenge)
|
|
271
|
+
body.enableRuntimeChallenge = requestData.enableRuntimeChallenge;
|
|
272
|
+
if (requestData.createSession) body.createSession = requestData.createSession;
|
|
273
|
+
if (requestData.durationRequired) body.durationRequired = requestData.durationRequired;
|
|
274
|
+
if (requestData.counterpartyType) body.counterpartyType = requestData.counterpartyType;
|
|
275
|
+
if (requestData.counterpartyUrl) body.counterpartyUrl = requestData.counterpartyUrl;
|
|
276
|
+
if (config.counterpartyId) body.counterpartyId = config.counterpartyId;
|
|
277
|
+
if (requestData.runtimeChallengeOptions)
|
|
278
|
+
body.runtimeChallengeOptions = requestData.runtimeChallengeOptions;
|
|
279
|
+
if (requestData.invocationProtocol) body.invocationProtocol = requestData.invocationProtocol;
|
|
280
|
+
if (requestData.commerceArtifacts) body.commerceArtifacts = requestData.commerceArtifacts;
|
|
281
|
+
body.sdkVersion = SDK_VERSION;
|
|
282
|
+
if (requestData.callerMetadata || requestData.clientIp || requestData.userAgent) {
|
|
283
|
+
const meta = {
|
|
284
|
+
...requestData.clientIp && { sourceIp: requestData.clientIp },
|
|
285
|
+
...requestData.userAgent && { userAgent: requestData.userAgent },
|
|
286
|
+
...requestData.callerMetadata
|
|
287
|
+
};
|
|
288
|
+
if (Object.keys(meta).length > 0) body.callerMetadata = meta;
|
|
289
|
+
}
|
|
290
|
+
const headers = {
|
|
291
|
+
"Content-Type": "application/json",
|
|
292
|
+
...config.customHeaders
|
|
293
|
+
};
|
|
294
|
+
if (config.apiKey) {
|
|
295
|
+
headers["Authorization"] = `Bearer ${config.apiKey}`;
|
|
296
|
+
headers["X-API-Key"] = config.apiKey;
|
|
297
|
+
}
|
|
298
|
+
try {
|
|
299
|
+
const response = await fetch(`${config.apiBaseUrl}/agents/verify-access`, {
|
|
300
|
+
method: "POST",
|
|
301
|
+
headers,
|
|
302
|
+
body: JSON.stringify(body)
|
|
303
|
+
});
|
|
304
|
+
const data = await response.json();
|
|
305
|
+
if (response.status === 410) {
|
|
306
|
+
return {
|
|
307
|
+
success: true,
|
|
308
|
+
access: {
|
|
309
|
+
allowed: false,
|
|
310
|
+
accessLevel: "none",
|
|
311
|
+
reason: "endpoint_deactivated",
|
|
312
|
+
failures: [
|
|
313
|
+
{
|
|
314
|
+
dimension: "endpoint.deactivated",
|
|
315
|
+
message: typeof data?.message === "string" ? data.message : "Endpoint has been deactivated",
|
|
316
|
+
guidance: typeof data?.guidance === "string" ? data.guidance : "Reactivate via POST /api/endpoints/{id}/reactivate, or update the URL on the calling agent."
|
|
317
|
+
}
|
|
318
|
+
]
|
|
319
|
+
}
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
if (!response.ok) {
|
|
323
|
+
return {
|
|
324
|
+
success: false,
|
|
325
|
+
error: data.message || data.error || `API returned ${response.status}`,
|
|
326
|
+
correlationId: typeof data?.correlationId === "string" ? data.correlationId : void 0
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
return data;
|
|
330
|
+
} catch (error) {
|
|
331
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
332
|
+
return {
|
|
333
|
+
success: false,
|
|
334
|
+
error: `Failed to call verify-access API: ${message}`
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
async function verify(config, request, options) {
|
|
339
|
+
const mergedConfig = { ...DEFAULT_CONFIG, ...config };
|
|
340
|
+
const urls = mergedConfig.apiBaseUrl ? getCachedWellKnownUrls(mergedConfig.apiBaseUrl) : void 0;
|
|
341
|
+
if (!initCheckPerformed && !mergedConfig.disableInitChecks && mergedConfig.apiBaseUrl) {
|
|
342
|
+
if (mergedConfig.strictInit) {
|
|
343
|
+
await performInitCheck(mergedConfig.apiBaseUrl, mergedConfig.debug, true);
|
|
344
|
+
} else {
|
|
345
|
+
void performInitCheck(mergedConfig.apiBaseUrl, mergedConfig.debug, false);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
if (!deprecationWarningShown && (config.minTrustScore !== void 0 || config.minTrustScoreForFull !== void 0)) {
|
|
349
|
+
deprecationWarningShown = true;
|
|
350
|
+
console.warn(
|
|
351
|
+
"[VerificationGateway] minTrustScore / minTrustScoreForFull are deprecated in v2.3.0 and have no effect. Server is now the single source of truth for access-level decisions (the SDK reads access.accessLevel from the verify-access response). To gate access to an endpoint, configure the endpoint's trust_score_requirement server-side."
|
|
352
|
+
);
|
|
353
|
+
}
|
|
354
|
+
if (mergedConfig.cacheTtl !== 0 && !options?.bypassCache) {
|
|
355
|
+
const cached = getCachedResult(request, mergedConfig.counterpartyId);
|
|
356
|
+
if (cached) {
|
|
357
|
+
if (mergedConfig.debug) {
|
|
358
|
+
console.log("[VerificationGateway] Returning cached result");
|
|
359
|
+
}
|
|
360
|
+
return cached;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
const enrichedRequest = { ...request };
|
|
364
|
+
if (!enrichedRequest.counterpartyUrl && mergedConfig.counterpartyUrl) {
|
|
365
|
+
enrichedRequest.counterpartyUrl = mergedConfig.counterpartyUrl;
|
|
366
|
+
}
|
|
367
|
+
if (!enrichedRequest.counterpartyType && mergedConfig.counterpartyType) {
|
|
368
|
+
enrichedRequest.counterpartyType = mergedConfig.counterpartyType;
|
|
369
|
+
}
|
|
370
|
+
if (mergedConfig.debug) {
|
|
371
|
+
console.log("[VerificationGateway] Calling verify-access API");
|
|
372
|
+
}
|
|
373
|
+
const apiResponse = await callVerifyAccessAPI(mergedConfig, enrichedRequest);
|
|
374
|
+
const rawServerDenialReasons = apiResponse.denialReasons;
|
|
375
|
+
const serverDenialReasons = Array.isArray(rawServerDenialReasons) && rawServerDenialReasons.length > 0 ? rawServerDenialReasons : void 0;
|
|
376
|
+
if (!apiResponse.success) {
|
|
377
|
+
return createGuidanceResponse(mergedConfig, apiResponse.error, {
|
|
378
|
+
source: "api_error",
|
|
379
|
+
correlationId: apiResponse.correlationId,
|
|
380
|
+
urls
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
if (!apiResponse.access?.allowed) {
|
|
384
|
+
const aggregatedFailures = apiResponse.access?.failures;
|
|
385
|
+
const idVerifiedFromBackend = apiResponse.verificationContext?.idVerified === true;
|
|
386
|
+
const result2 = {
|
|
387
|
+
identityVerified: idVerifiedFromBackend,
|
|
388
|
+
policyAllowed: false,
|
|
389
|
+
// v2.3.9 (defect #30): denials grant `'none'`, NEVER a positive band.
|
|
390
|
+
// Pre-rename this hardcoded `'guidance'`, which conflated with the
|
|
391
|
+
// colocated `guidance: {...}` help-payload object below and let
|
|
392
|
+
// denied requests pass any route gated at `'guidance'` because
|
|
393
|
+
// `hasMinimumAccess('guidance', 'guidance') === true`. Adapters now
|
|
394
|
+
// ALSO short-circuit on `!identityVerified || !policyAllowed` before
|
|
395
|
+
// the gate check — belt-and-braces.
|
|
396
|
+
accessLevel: "none",
|
|
397
|
+
// 3.6.0: the server now emits top-level denialReasons on every deny
|
|
398
|
+
// path — prefer it verbatim; the failure-message reconstruction remains
|
|
399
|
+
// as the fallback for older backends.
|
|
400
|
+
denialReasons: serverDenialReasons ?? (aggregatedFailures && aggregatedFailures.length > 0 ? aggregatedFailures.map((f) => f.message) : apiResponse.access?.reason ? [apiResponse.access.reason] : ["Access denied"]),
|
|
401
|
+
failures: aggregatedFailures,
|
|
402
|
+
requiresStepUp: apiResponse.access?.requiresStepUp,
|
|
403
|
+
requiresApproval: apiResponse.access?.requiresApproval,
|
|
404
|
+
guidance: {
|
|
405
|
+
message: apiResponse.access?.reason || "Access denied by PDLSS policy",
|
|
406
|
+
registrationUrl: urls?.registrationUrl ?? "",
|
|
407
|
+
documentationUrl: urls?.documentationUrl ?? ""
|
|
408
|
+
},
|
|
409
|
+
verifiedAt: /* @__PURE__ */ new Date(),
|
|
410
|
+
// Extract sessionId so decisions can be recorded for denials too
|
|
411
|
+
sessionId: apiResponse.sessionId,
|
|
412
|
+
// Anonymous traffic has no session → correlationId is the per-attempt
|
|
413
|
+
// linking key (the sessionId-equivalent for anonymous callers).
|
|
414
|
+
correlationId: apiResponse.correlationId,
|
|
415
|
+
recommendation: apiResponse.recommendation,
|
|
416
|
+
recommendationReasons: apiResponse.recommendationReasons,
|
|
417
|
+
stepUpApproval: apiResponse.stepUpApproval,
|
|
418
|
+
settlement: apiResponse.settlement
|
|
419
|
+
};
|
|
420
|
+
return result2;
|
|
421
|
+
}
|
|
422
|
+
const agent = apiResponse.agent ? {
|
|
423
|
+
astraId: apiResponse.agent.astraId,
|
|
424
|
+
name: apiResponse.agent.name,
|
|
425
|
+
trustScore: apiResponse.agent.trustScore,
|
|
426
|
+
trustLevel: getTrustLevel(apiResponse.agent.trustScore),
|
|
427
|
+
blockchainVerified: apiResponse.agent.blockchainStatus === "verified",
|
|
428
|
+
status: apiResponse.agent.agentStatus
|
|
429
|
+
} : void 0;
|
|
430
|
+
const developer = apiResponse.developer ? {
|
|
431
|
+
astradId: apiResponse.developer.kyaOwnerId,
|
|
432
|
+
name: apiResponse.developer.fullName,
|
|
433
|
+
trustScore: apiResponse.developer.trustScore || 0,
|
|
434
|
+
verified: apiResponse.developer.identityVerified
|
|
435
|
+
} : void 0;
|
|
436
|
+
const organization = apiResponse.organization ? {
|
|
437
|
+
name: apiResponse.organization.name,
|
|
438
|
+
verified: apiResponse.organization.verified,
|
|
439
|
+
trustScore: apiResponse.organization.trustScore
|
|
440
|
+
} : void 0;
|
|
441
|
+
const verificationContext = apiResponse.verificationContext;
|
|
442
|
+
const accessLevel = apiResponse.access?.accessLevel ?? "standard";
|
|
443
|
+
const result = {
|
|
444
|
+
// Round-18 G4: backend allowed access. Identity is verified (we resolved
|
|
445
|
+
// the caller to an agent) and policy passed all gates. Read idVerified
|
|
446
|
+
// from verificationContext for symmetry with the deny branch; default true
|
|
447
|
+
// on success path since `access.allowed === true` implies identity was
|
|
448
|
+
// resolvable (anonymous-allow paths flow through createGuidanceResponse).
|
|
449
|
+
identityVerified: apiResponse.verificationContext?.idVerified !== false,
|
|
450
|
+
policyAllowed: true,
|
|
451
|
+
accessLevel,
|
|
452
|
+
agent,
|
|
453
|
+
developer,
|
|
454
|
+
organization,
|
|
455
|
+
appliedPolicy: apiResponse.access?.appliedPolicy,
|
|
456
|
+
verificationContext,
|
|
457
|
+
requiresStepUp: apiResponse.access?.requiresStepUp,
|
|
458
|
+
requiresApproval: apiResponse.access?.requiresApproval,
|
|
459
|
+
verifiedAt: /* @__PURE__ */ new Date(),
|
|
460
|
+
cacheTtl: mergedConfig.cacheTtl,
|
|
461
|
+
// Handshake Protocol v10 enhanced fields (present when backend returns them)
|
|
462
|
+
sessionId: apiResponse.sessionId,
|
|
463
|
+
// v2.3.10 (defect #34, round-4): anonymous responses surface correlationId
|
|
464
|
+
// (no session row exists for unverified callers).
|
|
465
|
+
correlationId: apiResponse.correlationId,
|
|
466
|
+
runtimeChallenge: apiResponse.runtimeChallenge,
|
|
467
|
+
tokenGuidance: apiResponse.tokenGuidance,
|
|
468
|
+
recommendation: apiResponse.recommendation,
|
|
469
|
+
recommendationReasons: apiResponse.recommendationReasons,
|
|
470
|
+
warningHeader: apiResponse.warningHeader,
|
|
471
|
+
stepUpApproval: apiResponse.stepUpApproval,
|
|
472
|
+
settlement: apiResponse.settlement
|
|
473
|
+
};
|
|
474
|
+
if (result.recommendation === "deny") {
|
|
475
|
+
result.policyAllowed = false;
|
|
476
|
+
result.accessLevel = "none";
|
|
477
|
+
result.denialReasons = serverDenialReasons ?? result.recommendationReasons ?? ["Access denied by AstraSync recommendation"];
|
|
478
|
+
result.guidance = result.runtimeChallenge ? {
|
|
479
|
+
message: `Verification failed: ${result.runtimeChallenge.reason || "runtime challenge failed"}`,
|
|
480
|
+
registrationUrl: urls?.registrationUrl ?? "",
|
|
481
|
+
documentationUrl: urls?.documentationUrl ?? ""
|
|
482
|
+
} : {
|
|
483
|
+
message: result.recommendationReasons?.[0] || "Access denied by AstraSync recommendation",
|
|
484
|
+
registrationUrl: urls?.registrationUrl ?? "",
|
|
485
|
+
documentationUrl: urls?.documentationUrl ?? ""
|
|
486
|
+
};
|
|
487
|
+
} else if (result.recommendation === "step_up_required") {
|
|
488
|
+
result.requiresStepUp = true;
|
|
489
|
+
result.denialReasons = serverDenialReasons ?? result.recommendationReasons ?? ["Step-up verification required"];
|
|
490
|
+
}
|
|
491
|
+
const escalates = result.requiresStepUp === true || result.requiresApproval === true || result.recommendation === "step_up_required";
|
|
492
|
+
if (mergedConfig.cacheTtl !== 0 && result.recommendation !== "deny" && !escalates) {
|
|
493
|
+
cacheResult(request, result, mergedConfig.cacheTtl, mergedConfig.counterpartyId);
|
|
494
|
+
}
|
|
495
|
+
return result;
|
|
496
|
+
}
|
|
497
|
+
async function recordDecision(config, sessionId, decision, reason) {
|
|
498
|
+
const headers = { "Content-Type": "application/json" };
|
|
499
|
+
if (config.apiKey) {
|
|
500
|
+
headers["Authorization"] = `Bearer ${config.apiKey}`;
|
|
501
|
+
headers["X-API-Key"] = config.apiKey;
|
|
502
|
+
}
|
|
503
|
+
await fetch(`${config.apiBaseUrl}/agents/verify-access/${sessionId}/decision`, {
|
|
504
|
+
method: "POST",
|
|
505
|
+
headers,
|
|
506
|
+
body: JSON.stringify({ decision, reason })
|
|
507
|
+
}).catch(() => {
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
async function fetchRoutes(config, counterpartyId) {
|
|
511
|
+
if (!counterpartyId) return null;
|
|
512
|
+
const headers = { "Content-Type": "application/json" };
|
|
513
|
+
if (config.apiKey) {
|
|
514
|
+
headers["Authorization"] = `Bearer ${config.apiKey}`;
|
|
515
|
+
headers["X-API-Key"] = config.apiKey;
|
|
516
|
+
}
|
|
517
|
+
try {
|
|
518
|
+
const response = await fetch(
|
|
519
|
+
`${config.apiBaseUrl}/endpoints/${encodeURIComponent(counterpartyId)}/routes`,
|
|
520
|
+
{ method: "GET", headers }
|
|
521
|
+
);
|
|
522
|
+
if (!response.ok) return null;
|
|
523
|
+
const body = await response.json();
|
|
524
|
+
return body.data?.routes ?? [];
|
|
525
|
+
} catch {
|
|
526
|
+
return null;
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
async function verifyAndRecord(config, request, options) {
|
|
530
|
+
const mergedConfig = { ...DEFAULT_CONFIG, ...config };
|
|
531
|
+
const result = await verify(mergedConfig, { ...request, createSession: true }, options);
|
|
532
|
+
const sessionId = result.sessionId;
|
|
533
|
+
if (sessionId) {
|
|
534
|
+
if (result.identityVerified && result.policyAllowed) {
|
|
535
|
+
recordDecision(mergedConfig, sessionId, "granted").catch(() => {
|
|
536
|
+
});
|
|
537
|
+
} else {
|
|
538
|
+
recordDecision(mergedConfig, sessionId, "denied", result.denialReasons?.[0]).catch(() => {
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
return result;
|
|
543
|
+
}
|
|
544
|
+
async function reportUnregisteredAttempt(config, data) {
|
|
545
|
+
const apiBaseUrl = config.apiBaseUrl || DEFAULT_CONFIG.apiBaseUrl;
|
|
546
|
+
const { counterpartyUrl, ...rest } = data;
|
|
547
|
+
const body = {
|
|
548
|
+
...rest,
|
|
549
|
+
...counterpartyUrl && { counterpartyUrl },
|
|
550
|
+
...config.counterpartyId && { counterpartyId: config.counterpartyId }
|
|
551
|
+
};
|
|
552
|
+
await fetch(`${apiBaseUrl}/verification-activity/unregistered-attempt`, {
|
|
553
|
+
method: "POST",
|
|
554
|
+
headers: { "Content-Type": "application/json" },
|
|
555
|
+
body: JSON.stringify(body)
|
|
556
|
+
}).catch(() => {
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
async function reportCounterpartyPreCheckFailure(config, data) {
|
|
560
|
+
const apiBaseUrl = config.apiBaseUrl || DEFAULT_CONFIG.apiBaseUrl;
|
|
561
|
+
await fetch(`${apiBaseUrl}/verification-activity/counterparty-pre-check-failure`, {
|
|
562
|
+
method: "POST",
|
|
563
|
+
headers: { "Content-Type": "application/json" },
|
|
564
|
+
body: JSON.stringify(data)
|
|
565
|
+
}).catch(() => {
|
|
566
|
+
});
|
|
567
|
+
}
|
|
568
|
+
async function quickVerify(config, credentials) {
|
|
569
|
+
const result = await verify(config, {
|
|
570
|
+
credentials,
|
|
571
|
+
purpose: "verification"
|
|
572
|
+
});
|
|
573
|
+
return {
|
|
574
|
+
identityVerified: result.identityVerified,
|
|
575
|
+
policyAllowed: result.policyAllowed,
|
|
576
|
+
accessLevel: result.accessLevel,
|
|
577
|
+
reason: result.denialReasons?.[0]
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
581
|
+
0 && (module.exports = {
|
|
582
|
+
clearCache,
|
|
583
|
+
extractCredentials,
|
|
584
|
+
fetchRoutes,
|
|
585
|
+
hasCredentials,
|
|
586
|
+
quickVerify,
|
|
587
|
+
recordDecision,
|
|
588
|
+
reportCounterpartyPreCheckFailure,
|
|
589
|
+
reportUnregisteredAttempt,
|
|
590
|
+
verify,
|
|
591
|
+
verifyAndRecord
|
|
592
|
+
});
|
|
593
|
+
//# sourceMappingURL=verify.js.map
|