@axonflow/sdk 9.1.0 → 9.3.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 +238 -22
- package/dist/cjs/adapters/langgraph.d.ts.map +1 -1
- package/dist/cjs/adapters/langgraph.js +20 -0
- package/dist/cjs/adapters/langgraph.js.map +1 -1
- package/dist/cjs/authzen.d.ts +353 -0
- package/dist/cjs/authzen.d.ts.map +1 -0
- package/dist/cjs/authzen.js +901 -0
- package/dist/cjs/authzen.js.map +1 -0
- package/dist/cjs/client.d.ts +135 -9
- package/dist/cjs/client.d.ts.map +1 -1
- package/dist/cjs/client.js +397 -30
- package/dist/cjs/client.js.map +1 -1
- package/dist/cjs/heartbeat.d.ts +55 -4
- package/dist/cjs/heartbeat.d.ts.map +1 -1
- package/dist/cjs/heartbeat.js +86 -7
- package/dist/cjs/heartbeat.js.map +1 -1
- package/dist/cjs/index.d.ts +6 -0
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +55 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/read-identity.d.ts +267 -0
- package/dist/cjs/read-identity.d.ts.map +1 -0
- package/dist/cjs/read-identity.js +329 -0
- package/dist/cjs/read-identity.js.map +1 -0
- package/dist/cjs/telemetry.d.ts +176 -13
- package/dist/cjs/telemetry.d.ts.map +1 -1
- package/dist/cjs/telemetry.js +403 -37
- package/dist/cjs/telemetry.js.map +1 -1
- package/dist/cjs/types/authzen.gen.d.ts +407 -0
- package/dist/cjs/types/authzen.gen.d.ts.map +1 -0
- package/dist/cjs/types/authzen.gen.js +804 -0
- package/dist/cjs/types/authzen.gen.js.map +1 -0
- package/dist/cjs/types/config.d.ts +43 -1
- package/dist/cjs/types/config.d.ts.map +1 -1
- package/dist/cjs/types/hitl.js +1 -1
- package/dist/cjs/types/hitl.js.map +1 -1
- package/dist/cjs/version.d.ts +1 -1
- package/dist/cjs/version.js +1 -1
- package/dist/esm/adapters/langgraph.d.ts.map +1 -1
- package/dist/esm/adapters/langgraph.js +20 -0
- package/dist/esm/adapters/langgraph.js.map +1 -1
- package/dist/esm/authzen.d.ts +353 -0
- package/dist/esm/authzen.d.ts.map +1 -0
- package/dist/esm/authzen.js +886 -0
- package/dist/esm/authzen.js.map +1 -0
- package/dist/esm/client.d.ts +135 -9
- package/dist/esm/client.d.ts.map +1 -1
- package/dist/esm/client.js +397 -30
- package/dist/esm/client.js.map +1 -1
- package/dist/esm/heartbeat.d.ts +55 -4
- package/dist/esm/heartbeat.d.ts.map +1 -1
- package/dist/esm/heartbeat.js +85 -7
- package/dist/esm/heartbeat.js.map +1 -1
- package/dist/esm/index.d.ts +6 -0
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +17 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/read-identity.d.ts +267 -0
- package/dist/esm/read-identity.d.ts.map +1 -0
- package/dist/esm/read-identity.js +320 -0
- package/dist/esm/read-identity.js.map +1 -0
- package/dist/esm/telemetry.d.ts +176 -13
- package/dist/esm/telemetry.d.ts.map +1 -1
- package/dist/esm/telemetry.js +397 -37
- package/dist/esm/telemetry.js.map +1 -1
- package/dist/esm/types/authzen.gen.d.ts +407 -0
- package/dist/esm/types/authzen.gen.d.ts.map +1 -0
- package/dist/esm/types/authzen.gen.js +779 -0
- package/dist/esm/types/authzen.gen.js.map +1 -0
- package/dist/esm/types/config.d.ts +43 -1
- package/dist/esm/types/config.d.ts.map +1 -1
- package/dist/esm/types/hitl.js +1 -1
- package/dist/esm/types/hitl.js.map +1 -1
- package/dist/esm/version.d.ts +1 -1
- package/dist/esm/version.js +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read-path per-user identity and the platform's read-scope contract.
|
|
3
|
+
*
|
|
4
|
+
* Since platform #2922 the role-scoped read routes (audit / decisions /
|
|
5
|
+
* overrides) answer from the identity the CALLER presents, not from the tenant
|
|
6
|
+
* credential alone. The tenant credential in `Authorization` says which
|
|
7
|
+
* organization is asking; it does not say WHO. A caller that presents no
|
|
8
|
+
* per-user identity to an enterprise stack is not "a caller who sees
|
|
9
|
+
* everything" and is not "a caller who sees nothing by coincidence" — it is a
|
|
10
|
+
* caller the platform cannot scope, and every scoped read it makes returns
|
|
11
|
+
* zero rows by construction.
|
|
12
|
+
*
|
|
13
|
+
* This module carries the whole surface:
|
|
14
|
+
*
|
|
15
|
+
* - the per-user identity itself (`userToken` on the config for a client-wide
|
|
16
|
+
* identity, the per-call `{ userToken }` option on a read, and
|
|
17
|
+
* `client.asUser(token)` for a process acting on behalf of several people),
|
|
18
|
+
* stamped as the `X-User-Token` header from exactly ONE site — the client's
|
|
19
|
+
* `_fetch` wrapper, which every request goes through. There is no per-method
|
|
20
|
+
* header plumbing, deliberately: the platform reads the header once in its
|
|
21
|
+
* own proxy middleware (`platform/agent/proxy.go` `proxyAuthMiddleware`),
|
|
22
|
+
* not per route, so a per-method sprinkle here would be a second, drifting
|
|
23
|
+
* copy of a decision the platform makes in one place.
|
|
24
|
+
*
|
|
25
|
+
* - the response side of the same contract: `X-Axonflow-Read-Scope`, which the
|
|
26
|
+
* platform stamps on every scoped read (`platform/orchestrator/read_scope.go`
|
|
27
|
+
* `applyReadScopeHeader`) to say which of the three scopes the answer was
|
|
28
|
+
* computed under. Without it, a 404 from explain and an empty list from
|
|
29
|
+
* `listDecisions` are indistinguishable from "the row is not there", which is
|
|
30
|
+
* how a governed read comes to report a confident, vacuous nothing.
|
|
31
|
+
*/
|
|
32
|
+
/**
|
|
33
|
+
* The request header carrying the per-user identity.
|
|
34
|
+
*
|
|
35
|
+
* This constant is the SDK's only spelling of it. The header is set in exactly
|
|
36
|
+
* one place (`applyReadIdentity`, called from the client's `_fetch`); if you
|
|
37
|
+
* find yourself setting it in a method, the method is the wrong altitude.
|
|
38
|
+
*/
|
|
39
|
+
export declare const HEADER_USER_TOKEN = "X-User-Token";
|
|
40
|
+
/** The response header the platform stamps on scoped reads. */
|
|
41
|
+
export declare const HEADER_READ_SCOPE = "X-Axonflow-Read-Scope";
|
|
42
|
+
/**
|
|
43
|
+
* The scope the platform computed a role-scoped read under, taken from the
|
|
44
|
+
* `X-Axonflow-Read-Scope` response header.
|
|
45
|
+
*
|
|
46
|
+
* A plain string union widened with `(string & {})` rather than a closed enum,
|
|
47
|
+
* for one deliberate reason: a scope value a newer platform names and this
|
|
48
|
+
* build does not recognise must round-trip verbatim instead of being narrowed
|
|
49
|
+
* away or folded into a neighbour.
|
|
50
|
+
*
|
|
51
|
+
* Three named values are the platform's closed set. Two states are NOT in it
|
|
52
|
+
* and are deliberately distinct from each other and from the three:
|
|
53
|
+
*
|
|
54
|
+
* - `''` (ReadScope.Absent) — the response carried no such header. That is
|
|
55
|
+
* what a pre-#2922 platform, a non-scoped route, or a proxy that dropped the
|
|
56
|
+
* header looks like. It means "not stated", never "none": treating an absent
|
|
57
|
+
* header as a scope of `none` would turn every older stack's perfectly good
|
|
58
|
+
* read into a refusal.
|
|
59
|
+
*
|
|
60
|
+
* - any other non-empty string — preserved verbatim so a caller can see what it
|
|
61
|
+
* was, and never a trigger for a refusal: this header is the platform's
|
|
62
|
+
* account of a decision it has ALREADY made and applied, so an unrecognised
|
|
63
|
+
* value is a reporting gap on our side, not a licence to invent an outcome.
|
|
64
|
+
*/
|
|
65
|
+
export type ReadScope = 'tenant' | 'own-rows' | 'none' | '' | (string & {});
|
|
66
|
+
export declare const ReadScope: {
|
|
67
|
+
/** No `X-Axonflow-Read-Scope` header at all. Distinct from `None`. */
|
|
68
|
+
readonly Absent: ReadScope;
|
|
69
|
+
/**
|
|
70
|
+
* Tenant-wide: a tenant-wide role (admin / owner / policy_admin), or a
|
|
71
|
+
* Community / Community-SaaS deployment where the whole tenant is the one
|
|
72
|
+
* operator.
|
|
73
|
+
*/
|
|
74
|
+
readonly Tenant: ReadScope;
|
|
75
|
+
/**
|
|
76
|
+
* Narrowed to the rows attributed to the identity presented. A miss under
|
|
77
|
+
* this scope means "not among yours", which is NOT the same statement as
|
|
78
|
+
* "not there" — see `ReadScopeError`.
|
|
79
|
+
*/
|
|
80
|
+
readonly OwnRows: ReadScope;
|
|
81
|
+
/**
|
|
82
|
+
* The platform RESOLVED no per-user identity and the caller holds no
|
|
83
|
+
* tenant-wide authority, so it returned zero rows by construction. Under this
|
|
84
|
+
* scope a read CANNOT have returned data, so its empty answer says nothing
|
|
85
|
+
* about what exists.
|
|
86
|
+
*
|
|
87
|
+
* "Resolved none" is wider than "presented none", and the difference is worth
|
|
88
|
+
* knowing before you go looking in the wrong place. A token that validates
|
|
89
|
+
* perfectly still resolves to no identity when its address is one the
|
|
90
|
+
* platform reserves for SHARED, non-personal identities — the whole of
|
|
91
|
+
* `@axonflow.local` and `@axonflow.internal`, plus the community and
|
|
92
|
+
* evaluator addresses. Those name a pool of callers rather than a person, and
|
|
93
|
+
* scoping a read to one would return the pool, so the platform deliberately
|
|
94
|
+
* censuses them to nothing. A per-user token minted with an address in one of
|
|
95
|
+
* those domains therefore reads exactly like no token at all. (Easy to hit:
|
|
96
|
+
* the platform's own `generate-jwt.sh` defaults to
|
|
97
|
+
* `demo-user@axonflow.local`.)
|
|
98
|
+
*/
|
|
99
|
+
readonly None: ReadScope;
|
|
100
|
+
};
|
|
101
|
+
/**
|
|
102
|
+
* The scope the platform reported on `response`.
|
|
103
|
+
*
|
|
104
|
+
* Trimmed and lower-cased, for the same reason the platform's own header
|
|
105
|
+
* helpers are: a proxy that normalises header casing or appends whitespace must
|
|
106
|
+
* not silently change the answer. The cost of getting that wrong is one-sided
|
|
107
|
+
* and quiet — a scope spelled `None` would fall to the unrecognised branch and
|
|
108
|
+
* the vacuous empty page it describes would come back as data again. An
|
|
109
|
+
* unrecognised value is otherwise unchanged, so it still round-trips.
|
|
110
|
+
*/
|
|
111
|
+
export declare function readScopeOf(response: Response | null | undefined): ReadScope;
|
|
112
|
+
/**
|
|
113
|
+
* A role-scoped read whose answer was decided by the caller's identity scope
|
|
114
|
+
* rather than by the data.
|
|
115
|
+
*
|
|
116
|
+
* It exists because "no rows" and "no identity" are the same bytes on the wire.
|
|
117
|
+
* The platform distinguishes them in the `X-Axonflow-Read-Scope` header; this
|
|
118
|
+
* error is that distinction made visible, so a read that could not have
|
|
119
|
+
* succeeded reports a cause instead of a confident nothing.
|
|
120
|
+
*
|
|
121
|
+
* Two shapes, told apart by `identityMissing`:
|
|
122
|
+
*
|
|
123
|
+
* - `ReadScope.None` — no identity was RESOLVED; the read returned zero rows by
|
|
124
|
+
* construction and says nothing about what exists. Remedy: present an
|
|
125
|
+
* identity whose address is a real person's — see `ReadScope.None` for why a
|
|
126
|
+
* valid token can still resolve to nothing.
|
|
127
|
+
* - `ReadScope.OwnRows` — an identity WAS resolved, and the row is not among
|
|
128
|
+
* the ones attributed to it. That does NOT mean the row exists and belongs to
|
|
129
|
+
* somebody else: the platform answers "not attributed to you" and "not there
|
|
130
|
+
* at all" with the identical 404, deliberately, so that a miss cannot be used
|
|
131
|
+
* to probe for another user's rows. This error therefore reports the scope,
|
|
132
|
+
* not a claim about what exists.
|
|
133
|
+
*
|
|
134
|
+
* The presented token is never included in the message: it is safe to log,
|
|
135
|
+
* which is the point of putting the diagnosis in a type rather than in a string
|
|
136
|
+
* the caller assembles from the credential.
|
|
137
|
+
*/
|
|
138
|
+
export declare class ReadScopeError extends Error {
|
|
139
|
+
readonly scope: ReadScope;
|
|
140
|
+
readonly statusCode: number;
|
|
141
|
+
readonly resource: string;
|
|
142
|
+
readonly identifier?: string;
|
|
143
|
+
constructor(args: {
|
|
144
|
+
scope: ReadScope;
|
|
145
|
+
statusCode: number;
|
|
146
|
+
resource?: string;
|
|
147
|
+
identifier?: string;
|
|
148
|
+
});
|
|
149
|
+
/**
|
|
150
|
+
* Whether the read failed because no per-user identity was resolved, as
|
|
151
|
+
* opposed to one being resolved and not matching.
|
|
152
|
+
*/
|
|
153
|
+
get identityMissing(): boolean;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* The typed refusal for a scoped read that came back with nothing, or
|
|
157
|
+
* `undefined` when the scope does not explain the result.
|
|
158
|
+
*
|
|
159
|
+
* `undefined` for `ReadScope.Tenant` (the caller could see the whole tenant and
|
|
160
|
+
* it still was not there — a genuine miss), for `ReadScope.Absent` (the
|
|
161
|
+
* platform did not state a scope; see `ReadScope` for why absent is not none),
|
|
162
|
+
* and for any scope value this build does not recognise (a newer platform's;
|
|
163
|
+
* reporting a cause we cannot actually read would be a confident wrong
|
|
164
|
+
* diagnosis).
|
|
165
|
+
*/
|
|
166
|
+
export declare function readScopeErrorFor(args: {
|
|
167
|
+
resource: string;
|
|
168
|
+
identifier?: string;
|
|
169
|
+
scope: ReadScope;
|
|
170
|
+
statusCode: number;
|
|
171
|
+
}): ReadScopeError | undefined;
|
|
172
|
+
/**
|
|
173
|
+
* The typed refusal for a scoped read that came back EMPTY under a scope that
|
|
174
|
+
* could not have returned a row; `undefined` in every other case.
|
|
175
|
+
*
|
|
176
|
+
* One helper rather than a check at each read, because "the page is empty and
|
|
177
|
+
* the scope is none" is one rule and the reads that need it decode their body
|
|
178
|
+
* on more than one path each. A rule copied per return site is a rule that ends
|
|
179
|
+
* up applied on some of them.
|
|
180
|
+
*
|
|
181
|
+
* The emptiness guard is as load-bearing as the scope guard: a non-empty page
|
|
182
|
+
* is never turned into an error, whatever the header says. And only
|
|
183
|
+
* `ReadScope.None` refuses — an own-rows or tenant-wide read that legitimately
|
|
184
|
+
* found nothing is a real answer, and replacing it with an error would swap one
|
|
185
|
+
* wrong report for another.
|
|
186
|
+
*/
|
|
187
|
+
export declare function refuseVacuousScopedPage(response: Response | null | undefined, resource: string, rows: number): ReadScopeError | undefined;
|
|
188
|
+
/** Options every read method accepts on top of its own. */
|
|
189
|
+
export interface ReadIdentityOptions {
|
|
190
|
+
/**
|
|
191
|
+
* Per-user identity for THIS call only, overriding the client-wide
|
|
192
|
+
* `userToken`.
|
|
193
|
+
*
|
|
194
|
+
* Use it when one process acts on behalf of several people. An empty string
|
|
195
|
+
* is not an identity: it makes this read explicitly unidentified rather than
|
|
196
|
+
* falling back to the client-wide one. That distinction has to exist, because
|
|
197
|
+
* "unidentified" is a state the platform treats as different from every other
|
|
198
|
+
* (see `ReadScope.None`).
|
|
199
|
+
*
|
|
200
|
+
* For a process acting for several people across MANY methods, prefer
|
|
201
|
+
* `client.asUser(token)`: this option is only accepted by the read methods,
|
|
202
|
+
* while a derived client reaches every method with no carve-out.
|
|
203
|
+
*/
|
|
204
|
+
userToken?: string;
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Drop every credential from `headers` because the request is leaving the
|
|
208
|
+
* origin they were issued for.
|
|
209
|
+
*
|
|
210
|
+
* Case-insensitive on the way in: a caller may have spelled a header
|
|
211
|
+
* differently, and two spellings of one header is one credential that survives.
|
|
212
|
+
*/
|
|
213
|
+
export declare function stripCredentialsOffOrigin(headers: Record<string, string>): void;
|
|
214
|
+
/**
|
|
215
|
+
* Stamp the per-user identity on `headers`, if there is one, for a request to
|
|
216
|
+
* `target` from a client configured for `endpoint`.
|
|
217
|
+
*
|
|
218
|
+
* Called from the client's `_fetch` — the one site every request goes through —
|
|
219
|
+
* so the identity travels on every request without any method knowing about it.
|
|
220
|
+
* That is on purpose and mirrors the platform: the agent reads `X-User-Token`
|
|
221
|
+
* once, in the middleware in front of every proxied route, and the routes
|
|
222
|
+
* themselves never look at it.
|
|
223
|
+
*
|
|
224
|
+
* **The header is NOT inert on the routes that are not reads.** It is validated
|
|
225
|
+
* on every route the agent proxies: `proxyAuthMiddleware` resolves it before
|
|
226
|
+
* dispatch and answers `401 invalid user token` for a present-but-INVALID one —
|
|
227
|
+
* on `/api/v1/plans`, `/api/v1/policies`, `/api/v1/connectors`,
|
|
228
|
+
* `/api/v1/process`, `/api/v1/budgets`, `/api/v1/cost`, `/api/v1/executions`
|
|
229
|
+
* and the rest. So a stale or rotated token does not degrade to "unscoped
|
|
230
|
+
* reads"; it turns `listConnectors`, `installConnector` and policy CRUD into
|
|
231
|
+
* 401s. Fail-closed is the right direction, but it puts the value in the same
|
|
232
|
+
* rotation story as `clientSecret`.
|
|
233
|
+
*
|
|
234
|
+
* Genuinely inert only on the routes the agent SERVES ITSELF — only `proxy.go`
|
|
235
|
+
* and `mcp_identity.go` read the header at all: `/api/request`,
|
|
236
|
+
* `/api/v1/decide` (whose identity comes from the request BODY's `user_token`,
|
|
237
|
+
* which is the whole reason the read path needed a surface of its own),
|
|
238
|
+
* `/api/v1/access/evaluation`, `/api/v1/static-policies/*`,
|
|
239
|
+
* `/api/v1/circuit-breaker/*`, `/api/v1/hitl/*`, `/api/v1/mcp/check-input`,
|
|
240
|
+
* `/api/v1/mcp/check-output`, `/api/v1/register`, `/api/policy/pre-check`,
|
|
241
|
+
* `/api/audit/llm-call` and `/health`.
|
|
242
|
+
*
|
|
243
|
+
* **It is never sent anywhere but the configured endpoint.** `target` is
|
|
244
|
+
* compared against `endpoint` and the header is removed when they differ. That
|
|
245
|
+
* guard exists because of redirects: the fetch spec strips `Authorization` on a
|
|
246
|
+
* cross-origin redirect, but its list is fixed and `X-User-Token` is not on it.
|
|
247
|
+
* Measured on Node 25: the redirect target received `authorization: undefined`
|
|
248
|
+
* and `x-user-token: SENTINEL`. The client follows redirects manually while an
|
|
249
|
+
* identity is attached, re-entering this function on each hop, so the identity
|
|
250
|
+
* is dropped the moment the origin changes.
|
|
251
|
+
*
|
|
252
|
+
* The token is a CREDENTIAL. It is written to the header and nowhere else: it
|
|
253
|
+
* is never logged, never carried in an error message, and never reaches
|
|
254
|
+
* telemetry — the heartbeat uses raw `fetch`, deliberately not the wrapper this
|
|
255
|
+
* is called from.
|
|
256
|
+
*/
|
|
257
|
+
/**
|
|
258
|
+
* @param headers the request's header bag, MUTATED in place.
|
|
259
|
+
*
|
|
260
|
+
* A plain record rather than a `Headers` instance, deliberately: this SDK
|
|
261
|
+
* builds its requests with plain objects, and converting them here would change
|
|
262
|
+
* the shape every existing caller and test observes on `fetch`. Widening the
|
|
263
|
+
* blast radius of an identity fix to "every request's header representation"
|
|
264
|
+
* is how a small change acquires a long tail of unrelated failures.
|
|
265
|
+
*/
|
|
266
|
+
export declare function applyReadIdentity(headers: Record<string, string>, target: URL, endpoint: string | undefined, token: string | undefined): void;
|
|
267
|
+
//# sourceMappingURL=read-identity.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"read-identity.d.ts","sourceRoot":"","sources":["../../src/read-identity.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,iBAAiB,CAAC;AAEhD,+DAA+D;AAC/D,eAAO,MAAM,iBAAiB,0BAA0B,CAAC;AAEzD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,MAAM,SAAS,GAAG,QAAQ,GAAG,UAAU,GAAG,MAAM,GAAG,EAAE,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC;AAE5E,eAAO,MAAM,SAAS;IACpB,sEAAsE;qBACxD,SAAS;IACvB;;;;OAIG;qBACiB,SAAS;IAC7B;;;;OAIG;sBACoB,SAAS;IAChC;;;;;;;;;;;;;;;;;OAiBG;mBACa,SAAS;CACjB,CAAC;AAEX;;;;;;;;;GASG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,QAAQ,GAAG,IAAI,GAAG,SAAS,GAAG,SAAS,CAQ5E;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,qBAAa,cAAe,SAAQ,KAAK;IACvC,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC;IAC1B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;gBAEjB,IAAI,EAAE;QAChB,KAAK,EAAE,SAAS,CAAC;QACjB,UAAU,EAAE,MAAM,CAAC;QACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,UAAU,CAAC,EAAE,MAAM,CAAC;KACrB;IA6BD;;;OAGG;IACH,IAAI,eAAe,IAAI,OAAO,CAE7B;CACF;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE;IACtC,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,SAAS,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;CACpB,GAAG,cAAc,GAAG,SAAS,CAK7B;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,uBAAuB,CACrC,QAAQ,EAAE,QAAQ,GAAG,IAAI,GAAG,SAAS,EACrC,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,GACX,cAAc,GAAG,SAAS,CAQ5B;AAED,2DAA2D;AAC3D,MAAM,WAAW,mBAAmB;IAClC;;;;;;;;;;;;;OAaG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AA4BD;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAM/E;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AACH;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC/B,MAAM,EAAE,GAAG,EACX,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,KAAK,EAAE,MAAM,GAAG,SAAS,GACxB,IAAI,CA0BN"}
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Read-path per-user identity and the platform's read-scope contract.
|
|
4
|
+
*
|
|
5
|
+
* Since platform #2922 the role-scoped read routes (audit / decisions /
|
|
6
|
+
* overrides) answer from the identity the CALLER presents, not from the tenant
|
|
7
|
+
* credential alone. The tenant credential in `Authorization` says which
|
|
8
|
+
* organization is asking; it does not say WHO. A caller that presents no
|
|
9
|
+
* per-user identity to an enterprise stack is not "a caller who sees
|
|
10
|
+
* everything" and is not "a caller who sees nothing by coincidence" — it is a
|
|
11
|
+
* caller the platform cannot scope, and every scoped read it makes returns
|
|
12
|
+
* zero rows by construction.
|
|
13
|
+
*
|
|
14
|
+
* This module carries the whole surface:
|
|
15
|
+
*
|
|
16
|
+
* - the per-user identity itself (`userToken` on the config for a client-wide
|
|
17
|
+
* identity, the per-call `{ userToken }` option on a read, and
|
|
18
|
+
* `client.asUser(token)` for a process acting on behalf of several people),
|
|
19
|
+
* stamped as the `X-User-Token` header from exactly ONE site — the client's
|
|
20
|
+
* `_fetch` wrapper, which every request goes through. There is no per-method
|
|
21
|
+
* header plumbing, deliberately: the platform reads the header once in its
|
|
22
|
+
* own proxy middleware (`platform/agent/proxy.go` `proxyAuthMiddleware`),
|
|
23
|
+
* not per route, so a per-method sprinkle here would be a second, drifting
|
|
24
|
+
* copy of a decision the platform makes in one place.
|
|
25
|
+
*
|
|
26
|
+
* - the response side of the same contract: `X-Axonflow-Read-Scope`, which the
|
|
27
|
+
* platform stamps on every scoped read (`platform/orchestrator/read_scope.go`
|
|
28
|
+
* `applyReadScopeHeader`) to say which of the three scopes the answer was
|
|
29
|
+
* computed under. Without it, a 404 from explain and an empty list from
|
|
30
|
+
* `listDecisions` are indistinguishable from "the row is not there", which is
|
|
31
|
+
* how a governed read comes to report a confident, vacuous nothing.
|
|
32
|
+
*/
|
|
33
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
34
|
+
exports.ReadScopeError = exports.ReadScope = exports.HEADER_READ_SCOPE = exports.HEADER_USER_TOKEN = void 0;
|
|
35
|
+
exports.readScopeOf = readScopeOf;
|
|
36
|
+
exports.readScopeErrorFor = readScopeErrorFor;
|
|
37
|
+
exports.refuseVacuousScopedPage = refuseVacuousScopedPage;
|
|
38
|
+
exports.stripCredentialsOffOrigin = stripCredentialsOffOrigin;
|
|
39
|
+
exports.applyReadIdentity = applyReadIdentity;
|
|
40
|
+
/**
|
|
41
|
+
* The request header carrying the per-user identity.
|
|
42
|
+
*
|
|
43
|
+
* This constant is the SDK's only spelling of it. The header is set in exactly
|
|
44
|
+
* one place (`applyReadIdentity`, called from the client's `_fetch`); if you
|
|
45
|
+
* find yourself setting it in a method, the method is the wrong altitude.
|
|
46
|
+
*/
|
|
47
|
+
exports.HEADER_USER_TOKEN = 'X-User-Token';
|
|
48
|
+
/** The response header the platform stamps on scoped reads. */
|
|
49
|
+
exports.HEADER_READ_SCOPE = 'X-Axonflow-Read-Scope';
|
|
50
|
+
exports.ReadScope = {
|
|
51
|
+
/** No `X-Axonflow-Read-Scope` header at all. Distinct from `None`. */
|
|
52
|
+
Absent: '',
|
|
53
|
+
/**
|
|
54
|
+
* Tenant-wide: a tenant-wide role (admin / owner / policy_admin), or a
|
|
55
|
+
* Community / Community-SaaS deployment where the whole tenant is the one
|
|
56
|
+
* operator.
|
|
57
|
+
*/
|
|
58
|
+
Tenant: 'tenant',
|
|
59
|
+
/**
|
|
60
|
+
* Narrowed to the rows attributed to the identity presented. A miss under
|
|
61
|
+
* this scope means "not among yours", which is NOT the same statement as
|
|
62
|
+
* "not there" — see `ReadScopeError`.
|
|
63
|
+
*/
|
|
64
|
+
OwnRows: 'own-rows',
|
|
65
|
+
/**
|
|
66
|
+
* The platform RESOLVED no per-user identity and the caller holds no
|
|
67
|
+
* tenant-wide authority, so it returned zero rows by construction. Under this
|
|
68
|
+
* scope a read CANNOT have returned data, so its empty answer says nothing
|
|
69
|
+
* about what exists.
|
|
70
|
+
*
|
|
71
|
+
* "Resolved none" is wider than "presented none", and the difference is worth
|
|
72
|
+
* knowing before you go looking in the wrong place. A token that validates
|
|
73
|
+
* perfectly still resolves to no identity when its address is one the
|
|
74
|
+
* platform reserves for SHARED, non-personal identities — the whole of
|
|
75
|
+
* `@axonflow.local` and `@axonflow.internal`, plus the community and
|
|
76
|
+
* evaluator addresses. Those name a pool of callers rather than a person, and
|
|
77
|
+
* scoping a read to one would return the pool, so the platform deliberately
|
|
78
|
+
* censuses them to nothing. A per-user token minted with an address in one of
|
|
79
|
+
* those domains therefore reads exactly like no token at all. (Easy to hit:
|
|
80
|
+
* the platform's own `generate-jwt.sh` defaults to
|
|
81
|
+
* `demo-user@axonflow.local`.)
|
|
82
|
+
*/
|
|
83
|
+
None: 'none',
|
|
84
|
+
};
|
|
85
|
+
/**
|
|
86
|
+
* The scope the platform reported on `response`.
|
|
87
|
+
*
|
|
88
|
+
* Trimmed and lower-cased, for the same reason the platform's own header
|
|
89
|
+
* helpers are: a proxy that normalises header casing or appends whitespace must
|
|
90
|
+
* not silently change the answer. The cost of getting that wrong is one-sided
|
|
91
|
+
* and quiet — a scope spelled `None` would fall to the unrecognised branch and
|
|
92
|
+
* the vacuous empty page it describes would come back as data again. An
|
|
93
|
+
* unrecognised value is otherwise unchanged, so it still round-trips.
|
|
94
|
+
*/
|
|
95
|
+
function readScopeOf(response) {
|
|
96
|
+
// A response with no readable header bag states no scope. That is the same
|
|
97
|
+
// answer as a pre-#2922 platform's, and it is the SAFE one: the alternative
|
|
98
|
+
// is throwing from inside a read, or — worse — inventing `none` and refusing
|
|
99
|
+
// a page that was perfectly good.
|
|
100
|
+
const headers = response?.headers;
|
|
101
|
+
if (!headers || typeof headers.get !== 'function')
|
|
102
|
+
return exports.ReadScope.Absent;
|
|
103
|
+
return (headers.get(exports.HEADER_READ_SCOPE) ?? '').trim().toLowerCase();
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* A role-scoped read whose answer was decided by the caller's identity scope
|
|
107
|
+
* rather than by the data.
|
|
108
|
+
*
|
|
109
|
+
* It exists because "no rows" and "no identity" are the same bytes on the wire.
|
|
110
|
+
* The platform distinguishes them in the `X-Axonflow-Read-Scope` header; this
|
|
111
|
+
* error is that distinction made visible, so a read that could not have
|
|
112
|
+
* succeeded reports a cause instead of a confident nothing.
|
|
113
|
+
*
|
|
114
|
+
* Two shapes, told apart by `identityMissing`:
|
|
115
|
+
*
|
|
116
|
+
* - `ReadScope.None` — no identity was RESOLVED; the read returned zero rows by
|
|
117
|
+
* construction and says nothing about what exists. Remedy: present an
|
|
118
|
+
* identity whose address is a real person's — see `ReadScope.None` for why a
|
|
119
|
+
* valid token can still resolve to nothing.
|
|
120
|
+
* - `ReadScope.OwnRows` — an identity WAS resolved, and the row is not among
|
|
121
|
+
* the ones attributed to it. That does NOT mean the row exists and belongs to
|
|
122
|
+
* somebody else: the platform answers "not attributed to you" and "not there
|
|
123
|
+
* at all" with the identical 404, deliberately, so that a miss cannot be used
|
|
124
|
+
* to probe for another user's rows. This error therefore reports the scope,
|
|
125
|
+
* not a claim about what exists.
|
|
126
|
+
*
|
|
127
|
+
* The presented token is never included in the message: it is safe to log,
|
|
128
|
+
* which is the point of putting the diagnosis in a type rather than in a string
|
|
129
|
+
* the caller assembles from the credential.
|
|
130
|
+
*/
|
|
131
|
+
class ReadScopeError extends Error {
|
|
132
|
+
constructor(args) {
|
|
133
|
+
const resource = args.resource ?? 'read';
|
|
134
|
+
const subject = args.identifier ? `${resource} "${args.identifier}"` : resource;
|
|
135
|
+
const identityMissing = args.scope === exports.ReadScope.None;
|
|
136
|
+
super(identityMissing
|
|
137
|
+
? `HTTP ${args.statusCode}: ${subject}: the platform resolved no per-user identity for ` +
|
|
138
|
+
`this read (${exports.HEADER_READ_SCOPE}: ${args.scope}), so it returned zero rows by ` +
|
|
139
|
+
`construction and the empty answer says nothing about what exists. Either no ` +
|
|
140
|
+
`identity was presented — set userToken on the client, pass it to this call, or use ` +
|
|
141
|
+
`client.asUser(...) — or the one presented carries an address the platform reserves ` +
|
|
142
|
+
`for shared identities (@axonflow.local, @axonflow.internal), which resolves to ` +
|
|
143
|
+
`nobody. (platform #2922)`
|
|
144
|
+
: `HTTP ${args.statusCode}: ${subject} was not found among the rows this identity can ` +
|
|
145
|
+
`see: the platform reports ${exports.HEADER_READ_SCOPE}: ${args.scope}, so the read was ` +
|
|
146
|
+
`narrowed to the identity's own rows. It is either not attributed to this identity ` +
|
|
147
|
+
`or not there at all — the platform answers both the same way ON PURPOSE, so that a ` +
|
|
148
|
+
`miss cannot be used to probe for the existence of another user's rows, and this SDK ` +
|
|
149
|
+
`cannot tell them apart either. A tenant-wide role (admin, owner or policy_admin) ` +
|
|
150
|
+
`reads the whole tenant. (platform #2922)`);
|
|
151
|
+
this.name = 'ReadScopeError';
|
|
152
|
+
this.scope = args.scope;
|
|
153
|
+
this.statusCode = args.statusCode;
|
|
154
|
+
this.resource = resource;
|
|
155
|
+
this.identifier = args.identifier;
|
|
156
|
+
Object.setPrototypeOf(this, ReadScopeError.prototype);
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Whether the read failed because no per-user identity was resolved, as
|
|
160
|
+
* opposed to one being resolved and not matching.
|
|
161
|
+
*/
|
|
162
|
+
get identityMissing() {
|
|
163
|
+
return this.scope === exports.ReadScope.None;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
exports.ReadScopeError = ReadScopeError;
|
|
167
|
+
/**
|
|
168
|
+
* The typed refusal for a scoped read that came back with nothing, or
|
|
169
|
+
* `undefined` when the scope does not explain the result.
|
|
170
|
+
*
|
|
171
|
+
* `undefined` for `ReadScope.Tenant` (the caller could see the whole tenant and
|
|
172
|
+
* it still was not there — a genuine miss), for `ReadScope.Absent` (the
|
|
173
|
+
* platform did not state a scope; see `ReadScope` for why absent is not none),
|
|
174
|
+
* and for any scope value this build does not recognise (a newer platform's;
|
|
175
|
+
* reporting a cause we cannot actually read would be a confident wrong
|
|
176
|
+
* diagnosis).
|
|
177
|
+
*/
|
|
178
|
+
function readScopeErrorFor(args) {
|
|
179
|
+
if (args.scope === exports.ReadScope.None || args.scope === exports.ReadScope.OwnRows) {
|
|
180
|
+
return new ReadScopeError(args);
|
|
181
|
+
}
|
|
182
|
+
return undefined;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* The typed refusal for a scoped read that came back EMPTY under a scope that
|
|
186
|
+
* could not have returned a row; `undefined` in every other case.
|
|
187
|
+
*
|
|
188
|
+
* One helper rather than a check at each read, because "the page is empty and
|
|
189
|
+
* the scope is none" is one rule and the reads that need it decode their body
|
|
190
|
+
* on more than one path each. A rule copied per return site is a rule that ends
|
|
191
|
+
* up applied on some of them.
|
|
192
|
+
*
|
|
193
|
+
* The emptiness guard is as load-bearing as the scope guard: a non-empty page
|
|
194
|
+
* is never turned into an error, whatever the header says. And only
|
|
195
|
+
* `ReadScope.None` refuses — an own-rows or tenant-wide read that legitimately
|
|
196
|
+
* found nothing is a real answer, and replacing it with an error would swap one
|
|
197
|
+
* wrong report for another.
|
|
198
|
+
*/
|
|
199
|
+
function refuseVacuousScopedPage(response, resource, rows) {
|
|
200
|
+
if (rows > 0)
|
|
201
|
+
return undefined;
|
|
202
|
+
if (readScopeOf(response) !== exports.ReadScope.None)
|
|
203
|
+
return undefined;
|
|
204
|
+
return new ReadScopeError({
|
|
205
|
+
scope: exports.ReadScope.None,
|
|
206
|
+
statusCode: response?.status ?? 0,
|
|
207
|
+
resource,
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
/** Whether two URLs are the same origin: scheme, host AND port. */
|
|
211
|
+
function sameOrigin(a, b) {
|
|
212
|
+
return a.origin === b.origin;
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Every credential this SDK sends, so an off-origin hop can drop ALL of them.
|
|
216
|
+
*
|
|
217
|
+
* Not just the new one. `fetch`'s own redirect follower strips `Authorization`
|
|
218
|
+
* on a cross-origin hop; the moment this SDK follows redirects by hand — which
|
|
219
|
+
* it does whenever an identity is attached — that stripping stops happening and
|
|
220
|
+
* becomes this code's job. Getting it wrong makes setting `userToken` LEAK
|
|
221
|
+
* `clientSecret` to a host the caller never named, on a client that did not
|
|
222
|
+
* leak it before: a fix for one credential that exports another.
|
|
223
|
+
*
|
|
224
|
+
* `X-Client-ID` and `X-Axonflow-Client` are not secrets, but they name the
|
|
225
|
+
* caller to whoever receives them and there is no reason for a host the caller
|
|
226
|
+
* never chose to learn it.
|
|
227
|
+
*/
|
|
228
|
+
const CREDENTIAL_HEADERS = [
|
|
229
|
+
'authorization',
|
|
230
|
+
exports.HEADER_USER_TOKEN.toLowerCase(),
|
|
231
|
+
'x-client-id',
|
|
232
|
+
'x-axonflow-client',
|
|
233
|
+
];
|
|
234
|
+
/**
|
|
235
|
+
* Drop every credential from `headers` because the request is leaving the
|
|
236
|
+
* origin they were issued for.
|
|
237
|
+
*
|
|
238
|
+
* Case-insensitive on the way in: a caller may have spelled a header
|
|
239
|
+
* differently, and two spellings of one header is one credential that survives.
|
|
240
|
+
*/
|
|
241
|
+
function stripCredentialsOffOrigin(headers) {
|
|
242
|
+
for (const key of Object.keys(headers)) {
|
|
243
|
+
if (CREDENTIAL_HEADERS.includes(key.toLowerCase())) {
|
|
244
|
+
delete headers[key];
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Stamp the per-user identity on `headers`, if there is one, for a request to
|
|
250
|
+
* `target` from a client configured for `endpoint`.
|
|
251
|
+
*
|
|
252
|
+
* Called from the client's `_fetch` — the one site every request goes through —
|
|
253
|
+
* so the identity travels on every request without any method knowing about it.
|
|
254
|
+
* That is on purpose and mirrors the platform: the agent reads `X-User-Token`
|
|
255
|
+
* once, in the middleware in front of every proxied route, and the routes
|
|
256
|
+
* themselves never look at it.
|
|
257
|
+
*
|
|
258
|
+
* **The header is NOT inert on the routes that are not reads.** It is validated
|
|
259
|
+
* on every route the agent proxies: `proxyAuthMiddleware` resolves it before
|
|
260
|
+
* dispatch and answers `401 invalid user token` for a present-but-INVALID one —
|
|
261
|
+
* on `/api/v1/plans`, `/api/v1/policies`, `/api/v1/connectors`,
|
|
262
|
+
* `/api/v1/process`, `/api/v1/budgets`, `/api/v1/cost`, `/api/v1/executions`
|
|
263
|
+
* and the rest. So a stale or rotated token does not degrade to "unscoped
|
|
264
|
+
* reads"; it turns `listConnectors`, `installConnector` and policy CRUD into
|
|
265
|
+
* 401s. Fail-closed is the right direction, but it puts the value in the same
|
|
266
|
+
* rotation story as `clientSecret`.
|
|
267
|
+
*
|
|
268
|
+
* Genuinely inert only on the routes the agent SERVES ITSELF — only `proxy.go`
|
|
269
|
+
* and `mcp_identity.go` read the header at all: `/api/request`,
|
|
270
|
+
* `/api/v1/decide` (whose identity comes from the request BODY's `user_token`,
|
|
271
|
+
* which is the whole reason the read path needed a surface of its own),
|
|
272
|
+
* `/api/v1/access/evaluation`, `/api/v1/static-policies/*`,
|
|
273
|
+
* `/api/v1/circuit-breaker/*`, `/api/v1/hitl/*`, `/api/v1/mcp/check-input`,
|
|
274
|
+
* `/api/v1/mcp/check-output`, `/api/v1/register`, `/api/policy/pre-check`,
|
|
275
|
+
* `/api/audit/llm-call` and `/health`.
|
|
276
|
+
*
|
|
277
|
+
* **It is never sent anywhere but the configured endpoint.** `target` is
|
|
278
|
+
* compared against `endpoint` and the header is removed when they differ. That
|
|
279
|
+
* guard exists because of redirects: the fetch spec strips `Authorization` on a
|
|
280
|
+
* cross-origin redirect, but its list is fixed and `X-User-Token` is not on it.
|
|
281
|
+
* Measured on Node 25: the redirect target received `authorization: undefined`
|
|
282
|
+
* and `x-user-token: SENTINEL`. The client follows redirects manually while an
|
|
283
|
+
* identity is attached, re-entering this function on each hop, so the identity
|
|
284
|
+
* is dropped the moment the origin changes.
|
|
285
|
+
*
|
|
286
|
+
* The token is a CREDENTIAL. It is written to the header and nowhere else: it
|
|
287
|
+
* is never logged, never carried in an error message, and never reaches
|
|
288
|
+
* telemetry — the heartbeat uses raw `fetch`, deliberately not the wrapper this
|
|
289
|
+
* is called from.
|
|
290
|
+
*/
|
|
291
|
+
/**
|
|
292
|
+
* @param headers the request's header bag, MUTATED in place.
|
|
293
|
+
*
|
|
294
|
+
* A plain record rather than a `Headers` instance, deliberately: this SDK
|
|
295
|
+
* builds its requests with plain objects, and converting them here would change
|
|
296
|
+
* the shape every existing caller and test observes on `fetch`. Widening the
|
|
297
|
+
* blast radius of an identity fix to "every request's header representation"
|
|
298
|
+
* is how a small change acquires a long tail of unrelated failures.
|
|
299
|
+
*/
|
|
300
|
+
function applyReadIdentity(headers, target, endpoint, token) {
|
|
301
|
+
// Case-insensitively, because a caller may have spelled it differently and
|
|
302
|
+
// two spellings of one header is two identities.
|
|
303
|
+
for (const key of Object.keys(headers)) {
|
|
304
|
+
if (key.toLowerCase() === exports.HEADER_USER_TOKEN.toLowerCase())
|
|
305
|
+
delete headers[key];
|
|
306
|
+
}
|
|
307
|
+
const trimmed = (token ?? '').trim();
|
|
308
|
+
if (!trimmed) {
|
|
309
|
+
// Never send an empty header. To the platform a present-but-empty
|
|
310
|
+
// X-User-Token is still an absent one, but sending it advertises an
|
|
311
|
+
// identity mechanism the caller is not using, and it is one refactor away
|
|
312
|
+
// from a present-but-invalid token, which is a hard 401. The delete above
|
|
313
|
+
// also makes an explicit per-call clearing actually clear.
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
if (endpoint) {
|
|
317
|
+
try {
|
|
318
|
+
if (!sameOrigin(target, new URL(endpoint)))
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
catch {
|
|
322
|
+
// An unparseable configured endpoint is not a licence to send the
|
|
323
|
+
// credential anyway.
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
headers[exports.HEADER_USER_TOKEN] = trimmed;
|
|
328
|
+
}
|
|
329
|
+
//# sourceMappingURL=read-identity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"read-identity.js","sourceRoot":"","sources":["../../src/read-identity.ts"],"names":[],"mappings":"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"}
|