@juspay/neurolink 13.8.14 → 13.8.15
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/CHANGELOG.md +2 -2
- package/dist/browser/neurolink.min.js +398 -398
- package/dist/cli/utils/serverUtils.d.ts +4 -0
- package/dist/cli/utils/serverUtils.js +5 -1
- package/dist/server/abstract/baseServerAdapter.d.ts +30 -1
- package/dist/server/abstract/baseServerAdapter.js +71 -0
- package/dist/server/adapters/expressAdapter.js +5 -0
- package/dist/server/adapters/fastifyAdapter.d.ts +1 -0
- package/dist/server/adapters/fastifyAdapter.js +16 -3
- package/dist/server/adapters/honoAdapter.d.ts +6 -0
- package/dist/server/adapters/honoAdapter.js +67 -16
- package/dist/server/adapters/koaAdapter.js +5 -0
- package/dist/server/middleware/auth.js +3 -0
- package/dist/server/routes/agentRoutes.d.ts +42 -1
- package/dist/server/routes/agentRoutes.js +140 -31
- package/dist/server/routes/index.d.ts +9 -0
- package/dist/server/routes/index.js +15 -1
- package/dist/server/utils/carriedContextMark.d.ts +28 -0
- package/dist/server/utils/carriedContextMark.js +32 -0
- package/dist/types/server.d.ts +140 -2
- package/docs-site/static/search-index.json +6 -4
- package/package.json +2 -1
|
@@ -7,6 +7,7 @@ import { ProviderFactory } from "../../factories/providerFactory.js";
|
|
|
7
7
|
import { withSpan } from "../../telemetry/withSpan.js";
|
|
8
8
|
import { tracers } from "../../telemetry/tracers.js";
|
|
9
9
|
import { logger } from "../../utils/logger.js";
|
|
10
|
+
import { isContextCarried } from "../utils/carriedContextMark.js";
|
|
10
11
|
/**
|
|
11
12
|
* Resolve `request.tools` (an array of tool NAMES shared by the execute and
|
|
12
13
|
* stream endpoints) into a `toolFilter` whitelist. The field was previously
|
|
@@ -78,14 +79,144 @@ async function resolveRequestInput(ctx, requestInput, options) {
|
|
|
78
79
|
/**
|
|
79
80
|
* Create agent routes
|
|
80
81
|
*
|
|
82
|
+
* `options.perUserAuth` decides where a request's identity comes from; see
|
|
83
|
+
* `ServerAgentRouteOptions`. Authentication middleware that sets `ctx.user`
|
|
84
|
+
* does not change that on its own: one shared API key sets the same user for
|
|
85
|
+
* every caller, so trusting it as a person would give every client the same
|
|
86
|
+
* userId and no conversation memory. The converse is the operator's to act on:
|
|
87
|
+
* a server whose authentication identifies individual people must set
|
|
88
|
+
* `perUserAuth`, or any authenticated caller can name another userId and
|
|
89
|
+
* sessionId in the body.
|
|
90
|
+
*
|
|
91
|
+
* Unset, identity resolves as it did before the adapters carried `ctx.user` to
|
|
92
|
+
* the handler: the body's `userId` and `sessionId`, with one exception. On
|
|
93
|
+
* Hono, a request whose client sent its own `X-Request-ID` header reached the
|
|
94
|
+
* handler with the middleware's context, so an authenticated `ctx.user` (and
|
|
95
|
+
* `ctx.session`) won over the body there, and still does. A context the
|
|
96
|
+
* built-in adapters did not hand the carry to also keeps that rule: a custom
|
|
97
|
+
* adapter that sets `ctx.user` itself, or a handler called directly with a
|
|
98
|
+
* context the caller built. Only `perUserAuth` (with the server carrying
|
|
99
|
+
* middleware state) makes these routes take the user from `ctx.user`, and
|
|
100
|
+
* ignore the body's `userId`, on every built-in adapter and for every request.
|
|
101
|
+
*
|
|
102
|
+
* `perUserAuth` needs the user in the handler, which an adapter hands over on
|
|
103
|
+
* every request only when the server carries middleware state.
|
|
104
|
+
* `registerAllRoutes()` turns that on for the server when it is given
|
|
105
|
+
* `perUserAuth`. A caller that registers these routes itself with `perUserAuth`
|
|
106
|
+
* must create the server with `carryMiddlewareState: true`. Without it, a
|
|
107
|
+
* request has a user only on Hono when the client sent its own `X-Request-ID`
|
|
108
|
+
* (Hono then hands the handler the middleware's context regardless); every
|
|
109
|
+
* other request (Hono without that header, Express, Fastify, Koa) runs as
|
|
110
|
+
* anonymous, with no `userId` and no session.
|
|
111
|
+
*
|
|
112
|
+
* Without `perUserAuth`, and when the server carries middleware state, the
|
|
113
|
+
* routes log one warning per instance the first time a request whose body names
|
|
114
|
+
* a different `userId` than its `ctx.user` is served from the body, unless that
|
|
115
|
+
* user is marked `shared: true` (an identity for every holder of one
|
|
116
|
+
* credential, which the CLI's API-key middleware sets). Such a request is
|
|
117
|
+
* served from the body; the warning only tells the operator that per-person
|
|
118
|
+
* authentication is running without the flag that makes it hold. With the
|
|
119
|
+
* carry off the handler has no `ctx.user` on those requests, so there is
|
|
120
|
+
* nothing to compare and no warning. A request where `ctx.user` already won has
|
|
121
|
+
* nothing to warn about.
|
|
122
|
+
*
|
|
81
123
|
* @param options - Limits for `input.images` / `input.files`:
|
|
82
124
|
* `allowedInputRoots` (default: none, every local-path attachment is
|
|
83
125
|
* refused), `maxInputBytes` per attachment (default 50 MiB),
|
|
84
126
|
* `maxInputAttachments` per request (default 20, images and files together)
|
|
85
127
|
* and `maxInputTotalBytes` per request (default 100 MiB, one budget shared by
|
|
86
|
-
* images and files).
|
|
128
|
+
* images and files); and `perUserAuth` (described above).
|
|
87
129
|
*/
|
|
88
130
|
export function createAgentRoutes(basePath = "/api", options) {
|
|
131
|
+
// Truthiness, not `=== true`: a JavaScript caller passing "true" or 1 must get
|
|
132
|
+
// the strict mode, not silently the permissive one.
|
|
133
|
+
const perUser = Boolean(options?.perUserAuth);
|
|
134
|
+
// Without perUserAuth the body's userId wins (except where ctx.user already
|
|
135
|
+
// did), so an authenticated person who names someone else is acting as them.
|
|
136
|
+
// That is expected under one shared key (marked `shared`), where the body is
|
|
137
|
+
// how callers keep separate memory, and a misconfiguration under per-person
|
|
138
|
+
// authentication: say so, once, with no request values in the message.
|
|
139
|
+
// alwaysStderr, not warn: logger.warn is dropped unless debug logging is on,
|
|
140
|
+
// which would hide a security notice from the operator it is for.
|
|
141
|
+
let warnedNamingAnotherUser = false;
|
|
142
|
+
const warnIfNamingAnotherUser = (ctx, request) => {
|
|
143
|
+
if (warnedNamingAnotherUser ||
|
|
144
|
+
!ctx.user ||
|
|
145
|
+
ctx.user.shared === true ||
|
|
146
|
+
!isContextCarried(ctx) ||
|
|
147
|
+
typeof request.userId !== "string" ||
|
|
148
|
+
request.userId === ctx.user.id) {
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
warnedNamingAnotherUser = true;
|
|
152
|
+
logger.alwaysStderr("[agentRoutes] An authenticated user named a different userId in the request body, and the body's userId was used. " +
|
|
153
|
+
"Per-person authentication without perUserAuth lets a caller name another user. " +
|
|
154
|
+
"Set perUserAuth: true, or mark an identity that stands for a shared credential with shared: true. " +
|
|
155
|
+
"This warning is logged once per server.");
|
|
156
|
+
};
|
|
157
|
+
// Who a skill mutation is attributed to. With perUserAuth the authenticated
|
|
158
|
+
// user wins and a caller-supplied name is never trusted. Otherwise the body's
|
|
159
|
+
// `requestedBy` is used, since a shared key's `ctx.user` names no person, for
|
|
160
|
+
// a context that received the adapters' carry; a context that did not (Hono
|
|
161
|
+
// with the caller's X-Request-ID, a custom adapter, a direct call) lets the
|
|
162
|
+
// authenticated user win, as it did before.
|
|
163
|
+
const skillRequester = (ctx, bodyRequestedBy) => {
|
|
164
|
+
if (perUser) {
|
|
165
|
+
return ctx.user?.id;
|
|
166
|
+
}
|
|
167
|
+
return isContextCarried(ctx)
|
|
168
|
+
? bodyRequestedBy
|
|
169
|
+
: (ctx.user?.id ?? bodyRequestedBy);
|
|
170
|
+
};
|
|
171
|
+
// The identity generate() and stream() run under.
|
|
172
|
+
//
|
|
173
|
+
// perUserAuth: the middleware identifies individual people, so userId, email,
|
|
174
|
+
// roles and the session come only from ctx.user / ctx.session and the body's
|
|
175
|
+
// are ignored (no caller-supplied impersonation). The session is the
|
|
176
|
+
// authenticated user's: a request with no ctx.user gets none, even if a
|
|
177
|
+
// session middleware named one, because with optional authentication every
|
|
178
|
+
// anonymous caller that sends the same session id would otherwise share one
|
|
179
|
+
// conversation.
|
|
180
|
+
//
|
|
181
|
+
// Otherwise the body keeps choosing userId and sessionId, which is what every
|
|
182
|
+
// adapter did before it carried ctx.user to the handler: a context that
|
|
183
|
+
// received the adapters' carry is treated as the fresh one it used to be. The
|
|
184
|
+
// exceptions are the contexts that did not get the carry, where an
|
|
185
|
+
// authenticated ctx.user wins over the body, as it did, and so does the
|
|
186
|
+
// session: Hono with the caller's own X-Request-ID, a custom adapter that sets
|
|
187
|
+
// ctx.user itself, a handler called directly. Per-person authentication
|
|
188
|
+
// without perUserAuth therefore does not stop a caller naming someone else on
|
|
189
|
+
// the other requests.
|
|
190
|
+
const callerContext = (ctx, request) => {
|
|
191
|
+
if (perUser) {
|
|
192
|
+
return {
|
|
193
|
+
sessionId: ctx.user ? ctx.session?.id : undefined,
|
|
194
|
+
userId: ctx.user?.id,
|
|
195
|
+
userEmail: ctx.user?.email,
|
|
196
|
+
userRoles: ctx.user?.roles,
|
|
197
|
+
requestId: ctx.requestId,
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
if (!isContextCarried(ctx)) {
|
|
201
|
+
return {
|
|
202
|
+
sessionId: ctx.user
|
|
203
|
+
? ctx.session?.id
|
|
204
|
+
: (ctx.session?.id ?? request.sessionId),
|
|
205
|
+
userId: ctx.user ? ctx.user.id : request.userId,
|
|
206
|
+
userEmail: ctx.user?.email,
|
|
207
|
+
userRoles: ctx.user?.roles,
|
|
208
|
+
requestId: ctx.requestId,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
warnIfNamingAnotherUser(ctx, request);
|
|
212
|
+
return {
|
|
213
|
+
sessionId: request.sessionId,
|
|
214
|
+
userId: request.userId,
|
|
215
|
+
userEmail: undefined,
|
|
216
|
+
userRoles: undefined,
|
|
217
|
+
requestId: ctx.requestId,
|
|
218
|
+
};
|
|
219
|
+
};
|
|
89
220
|
return {
|
|
90
221
|
prefix: `${basePath}/agent`,
|
|
91
222
|
routes: [
|
|
@@ -121,17 +252,7 @@ export function createAgentRoutes(basePath = "/api", options) {
|
|
|
121
252
|
temperature: request.temperature,
|
|
122
253
|
maxTokens: request.maxTokens,
|
|
123
254
|
...(requestToolFilter ? { toolFilter: requestToolFilter } : {}),
|
|
124
|
-
context:
|
|
125
|
-
// When an authenticated user context exists (set by auth middleware),
|
|
126
|
-
// always use its IDs to prevent caller-supplied impersonation.
|
|
127
|
-
sessionId: ctx.user
|
|
128
|
-
? ctx.session?.id
|
|
129
|
-
: (ctx.session?.id ?? request.sessionId),
|
|
130
|
-
userId: ctx.user ? ctx.user.id : request.userId,
|
|
131
|
-
userEmail: ctx.user?.email,
|
|
132
|
-
userRoles: ctx.user?.roles,
|
|
133
|
-
requestId: ctx.requestId,
|
|
134
|
-
},
|
|
255
|
+
context: callerContext(ctx, request),
|
|
135
256
|
});
|
|
136
257
|
// Map tool calls from SDK format to API format
|
|
137
258
|
const toolCalls = result.toolCalls?.map((tc) => ({
|
|
@@ -174,17 +295,7 @@ export function createAgentRoutes(basePath = "/api", options) {
|
|
|
174
295
|
temperature: request.temperature,
|
|
175
296
|
maxTokens: request.maxTokens,
|
|
176
297
|
...(streamToolFilter ? { toolFilter: streamToolFilter } : {}),
|
|
177
|
-
context:
|
|
178
|
-
// When an authenticated user context exists (set by auth middleware),
|
|
179
|
-
// always use its IDs to prevent caller-supplied impersonation.
|
|
180
|
-
sessionId: ctx.user
|
|
181
|
-
? ctx.session?.id
|
|
182
|
-
: (ctx.session?.id ?? request.sessionId),
|
|
183
|
-
userId: ctx.user ? ctx.user.id : request.userId,
|
|
184
|
-
userEmail: ctx.user?.email,
|
|
185
|
-
userRoles: ctx.user?.roles,
|
|
186
|
-
requestId: ctx.requestId,
|
|
187
|
-
},
|
|
298
|
+
context: callerContext(ctx, request),
|
|
188
299
|
});
|
|
189
300
|
// Create redactor (no-op if redaction is not enabled)
|
|
190
301
|
const redactor = createStreamRedactor(ctx.redaction);
|
|
@@ -369,14 +480,12 @@ export function createAgentRoutes(basePath = "/api", options) {
|
|
|
369
480
|
return validation.error;
|
|
370
481
|
}
|
|
371
482
|
const { requestedBy, ...skill } = validation.data;
|
|
483
|
+
const requester = skillRequester(ctx, requestedBy);
|
|
372
484
|
try {
|
|
373
485
|
const result = await manager.requestMutation({
|
|
374
486
|
type: "create",
|
|
375
487
|
skill,
|
|
376
|
-
|
|
377
|
-
...(ctx.user?.id || requestedBy
|
|
378
|
-
? { requestedBy: ctx.user?.id ?? requestedBy }
|
|
379
|
-
: {}),
|
|
488
|
+
...(requester ? { requestedBy: requester } : {}),
|
|
380
489
|
});
|
|
381
490
|
return result;
|
|
382
491
|
}
|
|
@@ -400,14 +509,13 @@ export function createAgentRoutes(basePath = "/api", options) {
|
|
|
400
509
|
return validation.error;
|
|
401
510
|
}
|
|
402
511
|
const { requestedBy, ...patch } = validation.data;
|
|
512
|
+
const requester = skillRequester(ctx, requestedBy);
|
|
403
513
|
try {
|
|
404
514
|
const result = await manager.requestMutation({
|
|
405
515
|
type: "update",
|
|
406
516
|
skillId: ctx.params.id,
|
|
407
517
|
patch,
|
|
408
|
-
...(
|
|
409
|
-
? { requestedBy: ctx.user?.id ?? requestedBy }
|
|
410
|
-
: {}),
|
|
518
|
+
...(requester ? { requestedBy: requester } : {}),
|
|
411
519
|
});
|
|
412
520
|
return result;
|
|
413
521
|
}
|
|
@@ -427,11 +535,12 @@ export function createAgentRoutes(basePath = "/api", options) {
|
|
|
427
535
|
if ("error" in manager) {
|
|
428
536
|
return manager;
|
|
429
537
|
}
|
|
538
|
+
const requester = skillRequester(ctx, undefined);
|
|
430
539
|
try {
|
|
431
540
|
const result = await manager.requestMutation({
|
|
432
541
|
type: "delete",
|
|
433
542
|
skillId: ctx.params.id,
|
|
434
|
-
...(
|
|
543
|
+
...(requester ? { requestedBy: requester } : {}),
|
|
435
544
|
});
|
|
436
545
|
return result;
|
|
437
546
|
}
|
|
@@ -29,8 +29,17 @@ export { createTranscribeRoutes } from "./transcribeRoutes.js";
|
|
|
29
29
|
export declare function createAllRoutes(basePath?: string, options?: CreateRoutesOptions): RouteGroup[];
|
|
30
30
|
/**
|
|
31
31
|
* Register all routes with a server adapter
|
|
32
|
+
*
|
|
33
|
+
* With `options.perUserAuth` this also turns on the adapter's
|
|
34
|
+
* `carryMiddlewareState` (`enableMiddlewareStateCarry()`): the agent routes can
|
|
35
|
+
* only take identity from `ctx.user` if the adapter hands the handler what
|
|
36
|
+
* authentication middleware set. An adapter without that method must carry
|
|
37
|
+
* state itself; otherwise the routes see a user only where the adapter happens
|
|
38
|
+
* to share its context (Hono, when the client sent its own `X-Request-ID`), and
|
|
39
|
+
* every other request runs as anonymous.
|
|
32
40
|
*/
|
|
33
41
|
export declare function registerAllRoutes(adapter: {
|
|
34
42
|
registerRouteGroup: (group: RouteGroup) => void;
|
|
35
43
|
listRoutes?: () => RouteDefinition[];
|
|
44
|
+
enableMiddlewareStateCarry?: () => void;
|
|
36
45
|
}, basePath?: string, options?: CreateRoutesOptions): void;
|
|
@@ -41,7 +41,10 @@ export { createTranscribeRoutes } from "./transcribeRoutes.js";
|
|
|
41
41
|
*/
|
|
42
42
|
export function createAllRoutes(basePath = "/api", options) {
|
|
43
43
|
const routes = [
|
|
44
|
-
createAgentRoutes(basePath,
|
|
44
|
+
createAgentRoutes(basePath, {
|
|
45
|
+
...options?.agent,
|
|
46
|
+
perUserAuth: options?.perUserAuth,
|
|
47
|
+
}),
|
|
45
48
|
createToolRoutes(basePath),
|
|
46
49
|
createMCPRoutes(basePath),
|
|
47
50
|
createMemoryRoutes(basePath),
|
|
@@ -76,8 +79,19 @@ export function createAllRoutes(basePath = "/api", options) {
|
|
|
76
79
|
}
|
|
77
80
|
/**
|
|
78
81
|
* Register all routes with a server adapter
|
|
82
|
+
*
|
|
83
|
+
* With `options.perUserAuth` this also turns on the adapter's
|
|
84
|
+
* `carryMiddlewareState` (`enableMiddlewareStateCarry()`): the agent routes can
|
|
85
|
+
* only take identity from `ctx.user` if the adapter hands the handler what
|
|
86
|
+
* authentication middleware set. An adapter without that method must carry
|
|
87
|
+
* state itself; otherwise the routes see a user only where the adapter happens
|
|
88
|
+
* to share its context (Hono, when the client sent its own `X-Request-ID`), and
|
|
89
|
+
* every other request runs as anonymous.
|
|
79
90
|
*/
|
|
80
91
|
export function registerAllRoutes(adapter, basePath = "/api", options) {
|
|
92
|
+
if (options?.perUserAuth) {
|
|
93
|
+
adapter.enableMiddlewareStateCarry?.();
|
|
94
|
+
}
|
|
81
95
|
// If adapter has listRoutes and getRoutes not provided, use adapter's listRoutes
|
|
82
96
|
const routeOptions = {
|
|
83
97
|
...options,
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which route-handler contexts received the state the adapters now carry from
|
|
3
|
+
* middleware to the handler.
|
|
4
|
+
*
|
|
5
|
+
* Before the adapters carried that state, a handler normally got a fresh
|
|
6
|
+
* context with no `ctx.user` and no `ctx.session`. The one exception was Hono
|
|
7
|
+
* when the client sent its own `X-Request-ID`: Hono kept the middleware's
|
|
8
|
+
* context in a store keyed by that header, so the handler found it again, user
|
|
9
|
+
* included. The agent routes' default (`perUserAuth` unset) must not start
|
|
10
|
+
* trusting `ctx.user` on the requests that never had it, so the built-in
|
|
11
|
+
* adapters mark the contexts that received the carry, and the agent routes
|
|
12
|
+
* treat a marked context as the fresh one it used to be: identity from the body.
|
|
13
|
+
*
|
|
14
|
+
* Everything else keeps the earlier rule, an authenticated `ctx.user` wins: a
|
|
15
|
+
* Hono request with a caller-supplied `X-Request-ID` (never marked), a custom
|
|
16
|
+
* adapter that carries `ctx.user` itself, and a route handler called directly
|
|
17
|
+
* with a context the caller built.
|
|
18
|
+
*
|
|
19
|
+
* It is an internal fact about how the context was built, not part of
|
|
20
|
+
* `ServerContext`, and it is not exported from the package.
|
|
21
|
+
*
|
|
22
|
+
* @module server/utils/carriedContextMark
|
|
23
|
+
*/
|
|
24
|
+
import type { ServerContext } from "../../types/index.js";
|
|
25
|
+
/** Record that this context received state carried from middleware. */
|
|
26
|
+
export declare function markContextCarried(ctx: ServerContext): void;
|
|
27
|
+
/** `true` when `markContextCarried` was called for this context. */
|
|
28
|
+
export declare function isContextCarried(ctx: ServerContext): boolean;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which route-handler contexts received the state the adapters now carry from
|
|
3
|
+
* middleware to the handler.
|
|
4
|
+
*
|
|
5
|
+
* Before the adapters carried that state, a handler normally got a fresh
|
|
6
|
+
* context with no `ctx.user` and no `ctx.session`. The one exception was Hono
|
|
7
|
+
* when the client sent its own `X-Request-ID`: Hono kept the middleware's
|
|
8
|
+
* context in a store keyed by that header, so the handler found it again, user
|
|
9
|
+
* included. The agent routes' default (`perUserAuth` unset) must not start
|
|
10
|
+
* trusting `ctx.user` on the requests that never had it, so the built-in
|
|
11
|
+
* adapters mark the contexts that received the carry, and the agent routes
|
|
12
|
+
* treat a marked context as the fresh one it used to be: identity from the body.
|
|
13
|
+
*
|
|
14
|
+
* Everything else keeps the earlier rule, an authenticated `ctx.user` wins: a
|
|
15
|
+
* Hono request with a caller-supplied `X-Request-ID` (never marked), a custom
|
|
16
|
+
* adapter that carries `ctx.user` itself, and a route handler called directly
|
|
17
|
+
* with a context the caller built.
|
|
18
|
+
*
|
|
19
|
+
* It is an internal fact about how the context was built, not part of
|
|
20
|
+
* `ServerContext`, and it is not exported from the package.
|
|
21
|
+
*
|
|
22
|
+
* @module server/utils/carriedContextMark
|
|
23
|
+
*/
|
|
24
|
+
const receivedCarry = new WeakSet();
|
|
25
|
+
/** Record that this context received state carried from middleware. */
|
|
26
|
+
export function markContextCarried(ctx) {
|
|
27
|
+
receivedCarry.add(ctx);
|
|
28
|
+
}
|
|
29
|
+
/** `true` when `markContextCarried` was called for this context. */
|
|
30
|
+
export function isContextCarried(ctx) {
|
|
31
|
+
return receivedCarry.has(ctx);
|
|
32
|
+
}
|
package/dist/types/server.d.ts
CHANGED
|
@@ -40,6 +40,41 @@ export type ServerAdapterConfig = {
|
|
|
40
40
|
redaction?: RedactionConfig;
|
|
41
41
|
/** Shutdown configuration for graceful shutdown behavior */
|
|
42
42
|
shutdown?: ShutdownConfig;
|
|
43
|
+
/**
|
|
44
|
+
* Carry what middleware sets on its context (`ctx.user`, `ctx.session`,
|
|
45
|
+
* `ctx.metadata`, `ctx.abortSignal` and `ctx.abortController`) to later
|
|
46
|
+
* middleware and to the route handler, on every built-in adapter (default:
|
|
47
|
+
* `false`). It does not apply to a context you build yourself: a custom
|
|
48
|
+
* adapter, or a route handler called directly, can supply a context that
|
|
49
|
+
* already has `ctx.user` set.
|
|
50
|
+
*
|
|
51
|
+
* Off, each adapter propagates context as it always did. Express, Fastify
|
|
52
|
+
* and Koa build a fresh context for every middleware step and for the
|
|
53
|
+
* handler and carry only `responseHeaders`; Hono shares one context between
|
|
54
|
+
* the steps and the handler only when the caller sent its own
|
|
55
|
+
* `X-Request-ID`. So a role guard, a permission guard or a rate-limit
|
|
56
|
+
* `keyGenerator` registered after authentication sees `ctx.user` only on
|
|
57
|
+
* that Hono request shape: on Express, Fastify and Koa, and on Hono without
|
|
58
|
+
* a caller `X-Request-ID`, the user never reaches it and the guard refuses
|
|
59
|
+
* everyone.
|
|
60
|
+
*
|
|
61
|
+
* On, the user and the rest reach later middleware and the handler on every
|
|
62
|
+
* built-in adapter. Set it when guards or key generators depend on `ctx.user`.
|
|
63
|
+
* `registerAllRoutes()` turns it on for the server by itself when called with
|
|
64
|
+
* `perUserAuth`, which needs the user in the handler. A caller that builds
|
|
65
|
+
* the agent routes with `createAgentRoutes(basePath, { perUserAuth: true })`
|
|
66
|
+
* and registers them itself must set this on the server: with it off, such a
|
|
67
|
+
* request has a user only on Hono when the client sent its own
|
|
68
|
+
* `X-Request-ID`, and every other request runs as anonymous (no `userId`, no
|
|
69
|
+
* session).
|
|
70
|
+
*
|
|
71
|
+
* The agent routes' warning about a body naming another user is raised only
|
|
72
|
+
* with this on. With it off, a handler that does receive `ctx.user` (Hono
|
|
73
|
+
* when the caller sent its own `X-Request-ID`) takes the user from it and
|
|
74
|
+
* ignores the body's `userId`, so nothing is named over it; every other
|
|
75
|
+
* request reaches the handler without a `ctx.user` to compare against.
|
|
76
|
+
*/
|
|
77
|
+
carryMiddlewareState?: boolean;
|
|
43
78
|
};
|
|
44
79
|
/**
|
|
45
80
|
* Required server adapter configuration (with defaults applied)
|
|
@@ -58,6 +93,7 @@ export type RequiredServerAdapterConfig = {
|
|
|
58
93
|
disableBuiltInHealth: boolean;
|
|
59
94
|
redaction?: RequiredRedactionConfig;
|
|
60
95
|
shutdown: RequiredShutdownConfig;
|
|
96
|
+
carryMiddlewareState: boolean;
|
|
61
97
|
};
|
|
62
98
|
/**
|
|
63
99
|
* CORS configuration
|
|
@@ -256,6 +292,12 @@ export type ServerContext = {
|
|
|
256
292
|
/** Redaction configuration (for stream redaction support) */
|
|
257
293
|
redaction?: RedactionConfig;
|
|
258
294
|
};
|
|
295
|
+
/**
|
|
296
|
+
* What middleware sets on its context that later middleware and the route
|
|
297
|
+
* handler must see. Adapters build a fresh context for every step, so this is
|
|
298
|
+
* what they carry between steps in the framework's per-request storage.
|
|
299
|
+
*/
|
|
300
|
+
export type ServerCarriedContext = Pick<ServerContext, "user" | "session" | "metadata" | "abortSignal" | "abortController">;
|
|
259
301
|
/**
|
|
260
302
|
* Server response object
|
|
261
303
|
*/
|
|
@@ -471,6 +513,50 @@ export type ServerAgentRouteOptions = {
|
|
|
471
513
|
* file or download that would overflow it stops mid-transfer.
|
|
472
514
|
*/
|
|
473
515
|
maxInputTotalBytes?: number;
|
|
516
|
+
/**
|
|
517
|
+
* For a caller of `createAgentRoutes()` itself; with `registerAllRoutes()` /
|
|
518
|
+
* `createAllRoutes()` use `CreateRoutesOptions.perUserAuth` instead. The
|
|
519
|
+
* routes can only take identity from `ctx.user` if the server hands it to
|
|
520
|
+
* the handler, so a caller that registers them itself with this option must
|
|
521
|
+
* create the server with `carryMiddlewareState: true`; `registerAllRoutes()`
|
|
522
|
+
* does that for the server by itself. With `carryMiddlewareState` off, such a request has
|
|
523
|
+
* a user only on Hono when the client sent its own `X-Request-ID` (Hono then
|
|
524
|
+
* hands the handler the middleware's context regardless); every other request
|
|
525
|
+
* runs as anonymous, with no `userId` and no session.
|
|
526
|
+
*
|
|
527
|
+
* Set to `true` when the authentication middleware identifies individual
|
|
528
|
+
* people (one credential per person). The agent routes then take `userId`
|
|
529
|
+
* and the session only from the authenticated user (`ctx.user`,
|
|
530
|
+
* `ctx.session`) and ignore the request body's, on every adapter and for
|
|
531
|
+
* every request, so a caller cannot act as someone else. A request with no
|
|
532
|
+
* authenticated user gets no `userId` and no session, so with optional
|
|
533
|
+
* authentication anonymous callers never share a conversation. Any truthy
|
|
534
|
+
* value turns it on.
|
|
535
|
+
*
|
|
536
|
+
* **A server with per-person authentication must set this.** Left at the
|
|
537
|
+
* default (`false`) identity resolves as it always has: requests choose
|
|
538
|
+
* `userId` and `sessionId` in the body, so any authenticated caller can name
|
|
539
|
+
* another person there and act as them. The one exception is Hono, where a
|
|
540
|
+
* request whose client sent its own `X-Request-ID` header reaches the
|
|
541
|
+
* handler with the middleware's context, and an authenticated `ctx.user`
|
|
542
|
+
* (and `ctx.session`) wins over the body, as before, and so does it on a
|
|
543
|
+
* context the built-in adapters did not build (your own adapter, a handler
|
|
544
|
+
* called directly). Only this flag, with `carryMiddlewareState` on, makes the agent routes take
|
|
545
|
+
* the user from `ctx.user`, and ignore the body's `userId`, on every
|
|
546
|
+
* built-in adapter and every request. When the server carries middleware state
|
|
547
|
+
* the routes log one warning per instance the first time a request served
|
|
548
|
+
* from the body names a different `userId` than a `ctx.user` that is not
|
|
549
|
+
* marked `shared: true`; the body's `userId` is still used. With `carryMiddlewareState`
|
|
550
|
+
* off the handler has no `ctx.user` on such a request, so there is nothing to
|
|
551
|
+
* compare and no warning.
|
|
552
|
+
*
|
|
553
|
+
* Leave it unset for no authentication or one shared API key, such as
|
|
554
|
+
* `neurolink serve` with `NEUROLINK_SERVER_API_KEY`: a shared key names no
|
|
555
|
+
* person, so requests choose `userId` and `sessionId` in the body. Middleware
|
|
556
|
+
* for a shared key should set `shared: true` on the `AuthenticatedUser` it
|
|
557
|
+
* returns, which keeps the warning quiet (the CLI's key middleware does).
|
|
558
|
+
*/
|
|
559
|
+
perUserAuth?: boolean;
|
|
474
560
|
};
|
|
475
561
|
/**
|
|
476
562
|
* The route options plus the one thing that belongs to a single call:
|
|
@@ -762,6 +848,15 @@ export type WebSocketRequestId = string | number;
|
|
|
762
848
|
*/
|
|
763
849
|
export type AuthenticatedUser = {
|
|
764
850
|
id: string;
|
|
851
|
+
/**
|
|
852
|
+
* `true` when this identity stands for every holder of one credential, such
|
|
853
|
+
* as a shared API key, rather than for one person. The agent routes then do
|
|
854
|
+
* not warn when a request names a different `userId` in its body: with a
|
|
855
|
+
* shared key every caller is one trust domain and the body is how callers
|
|
856
|
+
* keep separate conversation memory. Middleware that sets one fixed user for
|
|
857
|
+
* everyone holding a key should set this; per-person middleware should not.
|
|
858
|
+
*/
|
|
859
|
+
shared?: boolean;
|
|
765
860
|
email?: string;
|
|
766
861
|
name?: string;
|
|
767
862
|
roles?: string[];
|
|
@@ -1073,6 +1168,12 @@ export type AuthResult = {
|
|
|
1073
1168
|
email?: string;
|
|
1074
1169
|
/** User roles (optional) */
|
|
1075
1170
|
roles?: string[];
|
|
1171
|
+
/**
|
|
1172
|
+
* `true` when this identity stands for every holder of one credential, such
|
|
1173
|
+
* as a shared API key, rather than one person. Carried to `ctx.user.shared`;
|
|
1174
|
+
* see `AuthenticatedUser.shared`.
|
|
1175
|
+
*/
|
|
1176
|
+
shared?: boolean;
|
|
1076
1177
|
/** Additional user data */
|
|
1077
1178
|
metadata?: Record<string, unknown>;
|
|
1078
1179
|
};
|
|
@@ -1174,9 +1275,46 @@ export type CreateRoutesOptions = {
|
|
|
1174
1275
|
/**
|
|
1175
1276
|
* Options for the agent routes (`POST <basePath>/agent/execute` and
|
|
1176
1277
|
* `/agent/stream`): where local-path attachments may be read from, and how
|
|
1177
|
-
* large an attachment may be.
|
|
1278
|
+
* large an attachment may be. Whose identity the routes trust is decided by
|
|
1279
|
+
* the top-level `perUserAuth`, which is why it is not accepted here.
|
|
1280
|
+
*/
|
|
1281
|
+
agent?: Omit<ServerAgentRouteOptions, "perUserAuth">;
|
|
1282
|
+
/**
|
|
1283
|
+
* Set to `true` when the authentication middleware identifies individual
|
|
1284
|
+
* people (one credential per person). The agent routes then take `userId`
|
|
1285
|
+
* and the session only from the authenticated user and ignore the request
|
|
1286
|
+
* body's, on every adapter and for every request; a request with no
|
|
1287
|
+
* authenticated user gets neither. Any truthy value turns it on.
|
|
1288
|
+
*
|
|
1289
|
+
* **A server with per-person authentication must set this.** Left at the
|
|
1290
|
+
* default (`false`) requests choose `userId` and `sessionId` in the body, so
|
|
1291
|
+
* any authenticated caller can name another person there and act as them.
|
|
1292
|
+
* The one exception is Hono, where a request whose client sent its own
|
|
1293
|
+
* `X-Request-ID` header reaches the handler with the middleware's context and
|
|
1294
|
+
* an authenticated `ctx.user` wins over the body, as before, and so on a
|
|
1295
|
+
* context the built-in adapters did not build (your own adapter, a handler
|
|
1296
|
+
* called directly); only this flag, with `carryMiddlewareState` on, makes the agent routes take
|
|
1297
|
+
* the user from `ctx.user` everywhere. When the server carries middleware state, the first time a
|
|
1298
|
+
* request served from the body names a different `userId` than its
|
|
1299
|
+
* `ctx.user`, the routes log a warning (once per instance); with `carryMiddlewareState`
|
|
1300
|
+
* off the handler has no `ctx.user` to compare, and there is no warning.
|
|
1301
|
+
* Leave it unset for no authentication or one shared
|
|
1302
|
+
* API key, such as `neurolink serve` with `NEUROLINK_SERVER_API_KEY`:
|
|
1303
|
+
* requests choose `userId` and `sessionId` in the body. Middleware for a
|
|
1304
|
+
* shared key should set `shared: true` on the `AuthenticatedUser` it
|
|
1305
|
+
* returns, which keeps the warning quiet.
|
|
1306
|
+
*
|
|
1307
|
+
* This is the one switch for a server built with `registerAllRoutes` /
|
|
1308
|
+
* `createAllRoutes`: every route group that scopes by identity reads it from
|
|
1309
|
+
* here, so it cannot be set for the agent routes alone. `registerAllRoutes`
|
|
1310
|
+
* also turns on the server's `carryMiddlewareState` when this is set, since
|
|
1311
|
+
* the routes need the user in the handler; `createAllRoutes` returns route
|
|
1312
|
+
* groups only, so a caller that registers them itself must create the server
|
|
1313
|
+
* with `carryMiddlewareState: true`; with `carryMiddlewareState` off, a `perUserAuth`
|
|
1314
|
+
* request has a user only on Hono when the client sent its own
|
|
1315
|
+
* `X-Request-ID`, and every other request runs as anonymous.
|
|
1178
1316
|
*/
|
|
1179
|
-
|
|
1317
|
+
perUserAuth?: boolean;
|
|
1180
1318
|
};
|
|
1181
1319
|
/** Options for `createTranscribeRoutes()`. */
|
|
1182
1320
|
/**
|