@astrasyncai/verification-gateway 2.4.0 → 2.4.2
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 +137 -22
- 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 +37 -7
- package/dist/adapters/express.js.map +1 -1
- package/dist/adapters/express.mjs +37 -7
- package/dist/adapters/express.mjs.map +1 -1
- package/dist/adapters/mcp.d.mts +1 -1
- package/dist/adapters/mcp.d.ts +1 -1
- package/dist/adapters/mcp.js +37 -7
- package/dist/adapters/mcp.js.map +1 -1
- package/dist/adapters/mcp.mjs +37 -7
- 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 +30 -4
- package/dist/adapters/nextjs.js.map +1 -1
- package/dist/adapters/nextjs.mjs +30 -4
- 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 +30 -4
- package/dist/adapters/sdk.js.map +1 -1
- package/dist/adapters/sdk.mjs +30 -4
- 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.js +163 -4
- package/dist/browser/background.js +30 -4
- package/dist/browser/background.js.map +1 -1
- package/dist/browser/background.mjs +30 -4
- 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 +30 -4
- package/dist/cursor/extension.js.map +1 -1
- package/dist/cursor/extension.mjs +30 -4
- package/dist/cursor/extension.mjs.map +1 -1
- package/dist/{express-4Vau6x6X.d.mts → express-DneHiMhu.d.mts} +1 -1
- package/dist/{express-Nq-wWICa.d.ts → express-DsiaQRFt.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 +30 -4
- package/dist/gateway/gateway.js.map +1 -1
- package/dist/gateway/gateway.mjs +30 -4
- 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-DkyPV14Y.d.mts → index-C9yWlQ2Y.d.mts} +1 -1
- package/dist/{index-DiToN8gh.d.mts → index-DAGm-Sgf.d.mts} +1 -1
- package/dist/{index-B-EovXnY.d.ts → index-Dd4alF0l.d.ts} +1 -1
- package/dist/{index-CxwCN7AC.d.ts → index-NZiKvrtE.d.ts} +1 -1
- package/dist/index.d.mts +7 -7
- package/dist/index.d.ts +7 -7
- package/dist/index.js +37 -7
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +37 -7
- 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-DO_4crcp.d.ts → nextjs-B4WmoiVm.d.ts} +1 -1
- package/dist/{nextjs-BTR7Oix-.d.mts → nextjs-vUuVCaBP.d.mts} +1 -1
- package/dist/registration/index.d.mts +174 -7
- package/dist/registration/index.d.ts +174 -7
- package/dist/registration/index.js +171 -6
- package/dist/registration/index.js.map +1 -1
- package/dist/registration/index.mjs +167 -5
- package/dist/registration/index.mjs.map +1 -1
- package/dist/{sdk-TnHXD-Oh.d.ts → sdk-BvWp4q2q.d.ts} +1 -1
- package/dist/{sdk-DSLCyXIX.d.mts → sdk-Cixo6pTV.d.mts} +1 -1
- package/dist/transport/index.d.mts +2 -2
- package/dist/transport/index.d.ts +2 -2
- package/dist/{types-pU2O0BFq.d.mts → types-C_e1IZdU.d.mts} +1 -1
- package/dist/{types-BVp22KkN.d.mts → types-DLai3jly.d.mts} +16 -13
- package/dist/{types-BVp22KkN.d.ts → types-DLai3jly.d.ts} +16 -13
- package/dist/{types-DVCWReEN.d.ts → types-IUzu-A4u.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
package/README.md
CHANGED
|
@@ -105,35 +105,109 @@ if (result.verified && result.accessLevel !== 'none') {
|
|
|
105
105
|
|
|
106
106
|
### Agent Registration
|
|
107
107
|
|
|
108
|
-
|
|
108
|
+
**Use the SDK for all agent registration**, whether you're a developer firing off a one-shot CLI call or an autonomous agent self-registering on first run. The SDK handles auth-mode routing for you: signature-authenticated callers get synchronous registration; API-key-only callers get an owner-approval handshake (server-side rule, not negotiable).
|
|
109
|
+
|
|
110
|
+
#### `apiEndpoint` — runtime-challenge URL
|
|
111
|
+
|
|
112
|
+
`apiEndpoint` is the URL where your agent's verification-gateway SDK is mounted to receive runtime challenges from counterparties. Optional but recommended — if you omit it, your agent declares no runtime-challenge support, which is included in the verification payload and may cause some counterparties to decline access requests.
|
|
113
|
+
|
|
114
|
+
#### Two registration response modes
|
|
115
|
+
|
|
116
|
+
The same `register()` call returns one of two shapes depending on auth context:
|
|
117
|
+
|
|
118
|
+
- **201 active** — synchronous: returned when the request is signed with a crypto keypair (`privateKey` configured) or when authenticated via email+password. Result: `{ status: 'active', agent }`.
|
|
119
|
+
- **202 pending_approval** — API-key only: the SDK was authenticated with an API key but the request was not signed. The backend emails the account owner a "Sign In to Accept" link, fires a dashboard alert, and returns a tracking token. Result: `{ status: 'pending_approval', requestId, pollUrl, expiresAt }`. The agent becomes active only after the owner approves.
|
|
120
|
+
|
|
121
|
+
This split enforces the platform rule that **API-key registrations always require owner step-up** — registering an agent autonomously with just an API key is not a sufficient authority signal on its own.
|
|
122
|
+
|
|
123
|
+
#### Pattern A — developer / long-running agent (block until approved)
|
|
124
|
+
|
|
125
|
+
Best for CLI tools, dev-machine first-run, and long-running services:
|
|
109
126
|
|
|
110
127
|
```typescript
|
|
111
|
-
import {
|
|
128
|
+
import {
|
|
129
|
+
AstraSync,
|
|
130
|
+
RegistrationDeniedError,
|
|
131
|
+
RegistrationTimeoutError,
|
|
132
|
+
} from '@astrasyncai/verification-gateway/registration';
|
|
112
133
|
|
|
113
|
-
const
|
|
114
|
-
apiKey: process.env.ASTRASYNC_API_KEY,
|
|
115
|
-
//
|
|
116
|
-
privateKey: process.env.ASTRASYNC_PRIVATE_KEY,
|
|
134
|
+
const sdk = new AstraSync({
|
|
135
|
+
apiKey: process.env.ASTRASYNC_API_KEY,
|
|
136
|
+
privateKey: process.env.ASTRASYNC_PRIVATE_KEY, // optional — when set, 201 sync path
|
|
117
137
|
});
|
|
118
138
|
|
|
119
|
-
|
|
139
|
+
try {
|
|
140
|
+
const agent = await sdk.register({
|
|
141
|
+
name: 'invoice-bot',
|
|
142
|
+
description: 'Reconciles AP/AR across accounting systems.',
|
|
143
|
+
agentType: 'autonomous',
|
|
144
|
+
apiEndpoint: 'https://invoice-bot.example.com', // runtime-challenge URL
|
|
145
|
+
model: { modelProvider: 'anthropic', modelName: 'claude-sonnet-4-5' },
|
|
146
|
+
framework: { frameworkName: 'langchain', frameworkVersion: '0.3.0' },
|
|
147
|
+
protocols: ['a2a', 'mcp'],
|
|
148
|
+
pdlss: {
|
|
149
|
+
purpose: {
|
|
150
|
+
categories: ['accounting'],
|
|
151
|
+
allowedActions: ['accounting.read', 'accounting.write'],
|
|
152
|
+
},
|
|
153
|
+
limits: { stepUpThreshold: 1_000, approvalThreshold: 10_000, currency: 'USD' },
|
|
154
|
+
scope: { resources: ['xero.invoices', 'quickbooks.bills'] },
|
|
155
|
+
selfInstantiation: { allowed: false },
|
|
156
|
+
},
|
|
157
|
+
// Blocking mode: poll until the request resolves.
|
|
158
|
+
waitForApproval: true,
|
|
159
|
+
timeoutMs: 10 * 60 * 1000, // 10 minutes default
|
|
160
|
+
onPending: ({ ageMs }) =>
|
|
161
|
+
console.log(`Awaiting owner approval (${(ageMs / 1000).toFixed(0)}s)…`),
|
|
162
|
+
});
|
|
163
|
+
console.log(`Agent registered: ${agent.astrasyncIdLevel1}`);
|
|
164
|
+
} catch (err) {
|
|
165
|
+
if (err instanceof RegistrationDeniedError) {
|
|
166
|
+
console.error('Owner denied:', err.reason);
|
|
167
|
+
} else if (err instanceof RegistrationTimeoutError) {
|
|
168
|
+
console.error(
|
|
169
|
+
'Timed out — request is still active server-side; poll later via sdk.waitForApproval(requestId)'
|
|
170
|
+
);
|
|
171
|
+
} else {
|
|
172
|
+
throw err;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
#### Pattern B — serverless / scheduled agent (non-blocking, exit and resume)
|
|
178
|
+
|
|
179
|
+
Best for Lambda / Cloud Functions / cron-driven self-registration where you can't hold the runtime open for minutes:
|
|
180
|
+
|
|
181
|
+
```typescript
|
|
182
|
+
const sdk = new AstraSync({ apiKey: process.env.ASTRASYNC_API_KEY });
|
|
183
|
+
|
|
184
|
+
const result = await sdk.register({
|
|
120
185
|
name: 'invoice-bot',
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
model: { modelProvider: 'anthropic', modelName: 'claude-sonnet-4-5' },
|
|
124
|
-
framework: { frameworkName: 'langchain', frameworkVersion: '0.3.0' },
|
|
125
|
-
protocols: ['a2a', 'mcp'],
|
|
126
|
-
pdlss: {
|
|
127
|
-
purpose: { allowed: ['accounting.read', 'accounting.write'] },
|
|
128
|
-
duration: { requested: 'P30D' },
|
|
129
|
-
limits: { transactionValue: 5000, currency: 'USD' },
|
|
130
|
-
scope: { resources: ['xero.invoices', 'quickbooks.bills'] },
|
|
131
|
-
selfInstantiation: { allowed: false },
|
|
132
|
-
},
|
|
186
|
+
apiEndpoint: 'https://invoice-bot.example.com',
|
|
187
|
+
pdlss: { purpose: { categories: ['accounting'], allowedActions: ['accounting.read'] } },
|
|
133
188
|
});
|
|
134
189
|
|
|
135
|
-
|
|
136
|
-
|
|
190
|
+
if (result.status === 'pending_approval') {
|
|
191
|
+
// Store the requestId in your durable storage (DynamoDB, KV, etc.)
|
|
192
|
+
await store.set('astrasync.pendingRequestId', result.requestId);
|
|
193
|
+
console.log(`Awaiting owner approval at ${result.pollUrl}; will resume on next scheduled run.`);
|
|
194
|
+
return; // function exits — owner has time to approve
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
console.log(`Agent registered as ${result.agent.astrasyncIdLevel1}`);
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
On the next scheduled run, resume by polling:
|
|
201
|
+
|
|
202
|
+
```typescript
|
|
203
|
+
const requestId = await store.get('astrasync.pendingRequestId');
|
|
204
|
+
const status = await sdk.pollRegistration(requestId);
|
|
205
|
+
if (status.state === 'approved') {
|
|
206
|
+
await store.set('astrasync.agentId', status.agent.kyaAgentId);
|
|
207
|
+
await store.delete('astrasync.pendingRequestId');
|
|
208
|
+
} else if (status.state === 'denied' || status.state === 'expired') {
|
|
209
|
+
console.error(`Registration terminated: ${status.state}`);
|
|
210
|
+
}
|
|
137
211
|
```
|
|
138
212
|
|
|
139
213
|
CLI equivalent — same surface from a shell, also via `npx`:
|
|
@@ -143,9 +217,12 @@ npx astrasync register --name invoice-bot --agent-type autonomous \
|
|
|
143
217
|
--model-provider anthropic --model-name claude-sonnet-4-5 \
|
|
144
218
|
--framework-name langchain --framework-version 0.3.0 \
|
|
145
219
|
--protocols a2a,mcp \
|
|
146
|
-
--
|
|
220
|
+
--api-endpoint https://invoice-bot.example.com \
|
|
221
|
+
--pdlss '{"purpose":{"categories":["accounting"],"allowedActions":["accounting.read"]}}'
|
|
147
222
|
```
|
|
148
223
|
|
|
224
|
+
The CLI uses the same response logic — it will print a "Sign in to approve" message and a poll URL when the response is 202 pending, and exit non-zero on deny/expire.
|
|
225
|
+
|
|
149
226
|
> `/registration` is for **one-shot onboarding** — register an agent, look up its public profile, check API health. For **per-request credential injection** (attaching ASTRA-id, sessionId, PDLSS to outgoing HTTP / A2A / MCP calls from an already-registered agent), use `/agent` (`AgentClient`). The two roles are deliberately separate concerns: registration is identity, `/agent` is runtime.
|
|
150
227
|
|
|
151
228
|
> A `PDLSSConfig` type exists in both `/registration` and `/agent`, with **different shapes**. `/registration`'s `PDLSSConfig` is the boundary declaration submitted at register time; `/agent`'s `PDLSSConfig` is the per-request runtime request shape. Disambiguate via the import path — the root export deliberately does not re-export either.
|
|
@@ -380,6 +457,44 @@ When an unverified agent visits a protected page, the Commerce Shield overlay di
|
|
|
380
457
|
|
|
381
458
|
This creates a smooth experience for agents while maintaining security.
|
|
382
459
|
|
|
460
|
+
## Two `baseUrl` conventions — same package, different clients
|
|
461
|
+
|
|
462
|
+
This package ships two clients. They use slightly different URL conventions; this is intentional but worth pinning down:
|
|
463
|
+
|
|
464
|
+
| Client | Field | Expected value | Example |
|
|
465
|
+
| ---------------------------------------------------------------------------- | -------------------------- | ----------------------- | -------------------------- |
|
|
466
|
+
| Verification gateway (`createMiddleware`, `createMcpMiddleware`, `verify()`) | `GatewayConfig.apiBaseUrl` | Origin + `/api` | `https://astrasync.ai/api` |
|
|
467
|
+
| Registration SDK (`new AstraSync(...)`) | `AstraSyncConfig.baseUrl` | Bare origin (no `/api`) | `https://astrasync.ai` |
|
|
468
|
+
|
|
469
|
+
Why the split: the gateway derives multiple endpoints from `apiBaseUrl` (verify-access, fetchRoutes, recordDecision, etc.), all under `/api/*`. The registration SDK prepends `/api/agents/...` per call and would double the path if `baseUrl` already ended with `/api`.
|
|
470
|
+
|
|
471
|
+
As of **v2.4.2**, the registration SDK tolerates a trailing `/api` on `baseUrl` — it strips the suffix and emits a one-time console warning so you can fix the source. Pass `silent: true` in `AstraSyncConfig` to suppress the warning (e.g. in tests).
|
|
472
|
+
|
|
473
|
+
## Header semantics — `X-Astra-Gateway-Mode`
|
|
474
|
+
|
|
475
|
+
The SDK sets `X-Astra-Gateway-Mode` on every response that's been through the gateway middleware. The value describes the GATE state — not whether the request succeeded end-to-end.
|
|
476
|
+
|
|
477
|
+
| Value | Meaning |
|
|
478
|
+
| ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
479
|
+
| `enforced` | The gateway evaluated policy on this request. Set on denial responses (`defaultOnDenied`, `defaultMcpDenied`). |
|
|
480
|
+
| `unenforced` | The gateway skipped policy evaluation for this request. The `X-Astra-Gateway-Reason` header explains why (`mcp-tier-none`, `non-jsonrpc-body`, `no-policy`, `no-match`, `route-none`). The downstream handler may still return any status. |
|
|
481
|
+
|
|
482
|
+
Pre-v2.4.2 used the value `pass-through` — renamed in v2.4.2 to disambiguate "gate skipped" from "request allowed".
|
|
483
|
+
|
|
484
|
+
## Changelog
|
|
485
|
+
|
|
486
|
+
### v2.4.2 — Round-10 partner re-test alignment
|
|
487
|
+
|
|
488
|
+
- **baseUrl normalization** (`AstraSyncConfig`): trailing `/api` is stripped + warned. Removes a class of NOT_FOUND bugs when partners pass the verify-gateway-style URL to the registration client.
|
|
489
|
+
- **Denial responses surface `failures[]` + `correlationId`** (`defaultOnDenied`, `defaultMcpDenied`): partners can now render per-dimension UX and tie a denial back to a server log line. Previously only the first denialReason was surfaced.
|
|
490
|
+
- **API-error fallback** (`createGuidanceResponse`): synthesised stubs for transient 5xx / network failures now carry `verify_access.api_error` in `failures[]` instead of the misleading "register your agent" template.
|
|
491
|
+
- **`X-Astra-Gateway-Mode` rename**: `pass-through` → `unenforced`. The new `enforced` value lands on denial paths. See the header semantics table above.
|
|
492
|
+
- **`silent` config flag** (`AstraSyncConfig`): suppresses one-time SDK warnings (baseUrl strip, deprecation notices).
|
|
493
|
+
|
|
494
|
+
### v2.4.1 — Round-9 docs/SDK alignment
|
|
495
|
+
|
|
496
|
+
See git log for details.
|
|
497
|
+
|
|
383
498
|
## License
|
|
384
499
|
|
|
385
500
|
MIT
|
|
@@ -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-C_e1IZdU.mjs';
|
|
3
|
+
import '../types-DLai3jly.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-IUzu-A4u.js';
|
|
3
|
+
import '../types-DLai3jly.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-DLai3jly.mjs';
|
|
3
|
+
export { c as createMiddleware, a as extractAstraSyncCredentials } from '../express-DneHiMhu.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-DLai3jly.js';
|
|
3
|
+
export { c as createMiddleware, a as extractAstraSyncCredentials } from '../express-DsiaQRFt.js';
|
package/dist/adapters/express.js
CHANGED
|
@@ -130,8 +130,18 @@ function extractCredentials(headers, query) {
|
|
|
130
130
|
}
|
|
131
131
|
return credentials;
|
|
132
132
|
}
|
|
133
|
-
function createGuidanceResponse(config, reason) {
|
|
134
|
-
const
|
|
133
|
+
function createGuidanceResponse(config, reason, options = {}) {
|
|
134
|
+
const source = options.source ?? "no_credentials";
|
|
135
|
+
const isApiError = source === "api_error";
|
|
136
|
+
const guidance = isApiError ? {
|
|
137
|
+
message: "Verification is temporarily unavailable. Retry with exponential backoff; if the issue persists, contact support with the correlationId.",
|
|
138
|
+
registrationUrl: `${config.apiBaseUrl.replace("/api", "")}/register`,
|
|
139
|
+
documentationUrl: `${config.apiBaseUrl.replace("/api", "")}/docs/agent-access`,
|
|
140
|
+
steps: [
|
|
141
|
+
"Retry the request with exponential backoff",
|
|
142
|
+
"If failures persist, share the correlationId with support"
|
|
143
|
+
]
|
|
144
|
+
} : {
|
|
135
145
|
message: "This service verifies AI agents before granting access. Please register your agent with AstraSync.",
|
|
136
146
|
registrationUrl: `${config.apiBaseUrl.replace("/api", "")}/register`,
|
|
137
147
|
documentationUrl: `${config.apiBaseUrl.replace("/api", "")}/docs/agent-access`,
|
|
@@ -152,6 +162,18 @@ function createGuidanceResponse(config, reason) {
|
|
|
152
162
|
accessLevel: "none",
|
|
153
163
|
guidance,
|
|
154
164
|
denialReasons: reason ? [reason] : ["No valid agent credentials provided"],
|
|
165
|
+
// Round-10 (#47, O5): on API-error fallback, surface a typed failure so
|
|
166
|
+
// partners (and their custom onDenied handlers) can branch on
|
|
167
|
+
// dimension. Without this, the synthesised stub was indistinguishable
|
|
168
|
+
// from a real policy deny.
|
|
169
|
+
failures: isApiError ? [
|
|
170
|
+
{
|
|
171
|
+
dimension: "verify_access.api_error",
|
|
172
|
+
message: reason ?? "Verification temporarily unavailable",
|
|
173
|
+
guidance: guidance.message
|
|
174
|
+
}
|
|
175
|
+
] : void 0,
|
|
176
|
+
correlationId: options.correlationId,
|
|
155
177
|
verifiedAt: /* @__PURE__ */ new Date()
|
|
156
178
|
};
|
|
157
179
|
}
|
|
@@ -226,7 +248,8 @@ async function callVerifyAccessAPI(config, request) {
|
|
|
226
248
|
if (!response.ok) {
|
|
227
249
|
return {
|
|
228
250
|
success: false,
|
|
229
|
-
error: data.message || data.error || `API returned ${response.status}
|
|
251
|
+
error: data.message || data.error || `API returned ${response.status}`,
|
|
252
|
+
correlationId: typeof data?.correlationId === "string" ? data.correlationId : void 0
|
|
230
253
|
};
|
|
231
254
|
}
|
|
232
255
|
return data;
|
|
@@ -270,7 +293,10 @@ async function verify(config, request) {
|
|
|
270
293
|
}
|
|
271
294
|
const apiResponse = await callVerifyAccessAPI(mergedConfig, enrichedRequest);
|
|
272
295
|
if (!apiResponse.success) {
|
|
273
|
-
return createGuidanceResponse(mergedConfig, apiResponse.error
|
|
296
|
+
return createGuidanceResponse(mergedConfig, apiResponse.error, {
|
|
297
|
+
source: "api_error",
|
|
298
|
+
correlationId: apiResponse.correlationId
|
|
299
|
+
});
|
|
274
300
|
}
|
|
275
301
|
if (!apiResponse.access?.allowed) {
|
|
276
302
|
const aggregatedFailures = apiResponse.access?.failures;
|
|
@@ -563,13 +589,17 @@ function findRouteConfig(routes, path, method) {
|
|
|
563
589
|
}
|
|
564
590
|
function defaultOnDenied(result, _req, res) {
|
|
565
591
|
const statusCode = result.verified ? 403 : 401;
|
|
592
|
+
res.setHeader("X-Astra-Gateway-Mode", "enforced");
|
|
566
593
|
res.status(statusCode).json({
|
|
567
594
|
success: false,
|
|
568
595
|
error: {
|
|
569
596
|
code: result.verified ? "INSUFFICIENT_ACCESS" : "UNAUTHORIZED",
|
|
570
597
|
message: result.denialReasons?.[0] || "Access denied",
|
|
571
598
|
accessLevel: result.accessLevel,
|
|
572
|
-
guidance: result.guidance
|
|
599
|
+
guidance: result.guidance,
|
|
600
|
+
// Round-10: aggregated per-dimension detail + correlation handle.
|
|
601
|
+
failures: result.failures,
|
|
602
|
+
correlationId: result.correlationId
|
|
573
603
|
}
|
|
574
604
|
});
|
|
575
605
|
}
|
|
@@ -634,7 +664,7 @@ function createMiddleware(options) {
|
|
|
634
664
|
const routeConfig = findRouteConfig(cachedRoutes, req.path, req.method);
|
|
635
665
|
if (!routeConfig) {
|
|
636
666
|
if (config.setPassThroughHeader) {
|
|
637
|
-
res.setHeader("X-Astra-Gateway-Mode", "
|
|
667
|
+
res.setHeader("X-Astra-Gateway-Mode", "unenforced");
|
|
638
668
|
res.setHeader(
|
|
639
669
|
"X-Astra-Gateway-Reason",
|
|
640
670
|
cachedRoutes.length === 0 ? "no-policy" : "no-match"
|
|
@@ -644,7 +674,7 @@ function createMiddleware(options) {
|
|
|
644
674
|
}
|
|
645
675
|
if (routeConfig.minAccessLevel === "none") {
|
|
646
676
|
if (config.setPassThroughHeader) {
|
|
647
|
-
res.setHeader("X-Astra-Gateway-Mode", "
|
|
677
|
+
res.setHeader("X-Astra-Gateway-Mode", "unenforced");
|
|
648
678
|
res.setHeader("X-Astra-Gateway-Reason", "route-none");
|
|
649
679
|
}
|
|
650
680
|
return next();
|