@procedures/client 0.27.2 → 0.27.4
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 +33 -0
- package/README.md +1 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/adapters/http/server/handler.js +3 -2
- package/dist/adapters/http/server/handler.js.map +1 -1
- package/dist/adapters/websocket/server/transport.d.ts.map +1 -1
- package/dist/adapters/websocket/server/transport.js +3 -2
- package/dist/adapters/websocket/server/transport.js.map +1 -1
- package/dist/browser.d.ts +1 -1
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js.map +1 -1
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js.map +1 -1
- package/dist/server/procedure-server.d.ts +6 -6
- package/dist/server/request-auth.d.ts +5 -1
- package/dist/server/request-auth.d.ts.map +1 -1
- package/dist/server/request-auth.js +26 -0
- package/dist/server/request-auth.js.map +1 -1
- package/dist/server/types.d.ts +40 -3
- package/dist/server/types.d.ts.map +1 -1
- package/dist/server/types.js.map +1 -1
- package/package.json +1 -1
- package/src/adapters/http/server/handler.ts +3 -2
- package/src/adapters/websocket/server/transport.ts +3 -2
- package/src/browser.ts +1 -0
- package/src/server/index.ts +1 -0
- package/src/server/procedure-server.ts +6 -6
- package/src/server/request-auth.ts +26 -2
- package/src/server/types.ts +41 -3
|
@@ -396,8 +396,9 @@ export function createProcedureHandler(server: RequestHandler, options: Procedur
|
|
|
396
396
|
method,
|
|
397
397
|
payload,
|
|
398
398
|
metadata,
|
|
399
|
-
// A network request: ProcedureServer examines the token and the request hook (R18, CLI-2a)
|
|
400
|
-
credentials
|
|
399
|
+
// A network request: ProcedureServer examines the token and the request hook (R18, CLI-2a).
|
|
400
|
+
// The credentials have the connection of the socket of the request (CLI-71)
|
|
401
|
+
credentials: requestCredentials({ headers: req.headers as Record<string, unknown>, url: req.url, socket: req.socket }),
|
|
401
402
|
};
|
|
402
403
|
};
|
|
403
404
|
|
|
@@ -387,8 +387,9 @@ export class WebSocketServerTransport implements ServerTransport {
|
|
|
387
387
|
};
|
|
388
388
|
this.trackedConnections.set(connectionId, trackedConnection);
|
|
389
389
|
// The credentials of the upgrade, for each call of the connection: the bearer token of its
|
|
390
|
-
// Authorization header or of its token query, its cookies, its headers and its query (CLI-2a)
|
|
391
|
-
|
|
390
|
+
// Authorization header or of its token query, its cookies, its headers and its query (CLI-2a),
|
|
391
|
+
// and the connection of its socket (CLI-71)
|
|
392
|
+
const credentials = requestCredentials({ headers: req.headers as Record<string, unknown>, url: req.url, socket: req.socket }, { tokenQuery: "token" });
|
|
392
393
|
|
|
393
394
|
// The calls of this connection in the two directions (roadmap R24)
|
|
394
395
|
const isOpen = (): boolean => ws.readyState === WebSocket.OPEN;
|
package/src/browser.ts
CHANGED
package/src/server/index.ts
CHANGED
|
@@ -162,12 +162,12 @@ export interface ProcedureServerOptions extends ServerOptions {
|
|
|
162
162
|
|
|
163
163
|
/**
|
|
164
164
|
* The request hook of the server: it reads the credentials of each network request (a cookie, a
|
|
165
|
-
* header, the query) and gives a bearer token, a principal and a
|
|
166
|
-
* token check: its bearer token replaces the bearer token of the
|
|
167
|
-
* replaces the principal of the token check. `authenticate` runs
|
|
168
|
-
* `request.principal`. Undefined or an error refuses the request with
|
|
169
|
-
* request (`LOCAL_CREDENTIALS`) does not go through the hook. A server
|
|
170
|
-
* request without credentials, and the hook does not run for it (CLI-1).
|
|
165
|
+
* header, the query, the connection of the socket) and gives a bearer token, a principal and a
|
|
166
|
+
* narrowing. It runs before the token check: its bearer token replaces the bearer token of the
|
|
167
|
+
* credentials. Its principal replaces the principal of the token check. `authenticate` runs
|
|
168
|
+
* after it and can read `request.principal`. Undefined or an error refuses the request with
|
|
169
|
+
* `UNAUTHORIZED`. A local request (`LOCAL_CREDENTIALS`) does not go through the hook. A server
|
|
170
|
+
* with a hook refuses a request without credentials, and the hook does not run for it (CLI-1).
|
|
171
171
|
*
|
|
172
172
|
* The hook can run more than one time for one request or one task: `admits()` and `identify()`
|
|
173
173
|
* run it too (CLI-69).
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* This module imports no Node code: a server in a browser or a worker can use it too.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import { LOCAL_MARKER, type Credentials } from "./types.js";
|
|
9
|
+
import { LOCAL_MARKER, type Credentials, type RequestConnection } from "./types.js";
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* The credentials of a local face (CLI-1): a call from this application, not from the network.
|
|
@@ -166,13 +166,36 @@ export interface RequestCredentialsOptions {
|
|
|
166
166
|
readonly tokenQuery?: string | false | undefined;
|
|
167
167
|
}
|
|
168
168
|
|
|
169
|
+
/** True for a port number of a socket: an integer from 0 to 65535. */
|
|
170
|
+
function isPort(value: unknown): value is number {
|
|
171
|
+
return typeof value === "number" && Number.isInteger(value) && value >= 0 && value <= 65535;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* The connection of a network request (CLI-71): a frozen copy of the addresses and the ports of
|
|
176
|
+
* its socket. The copy has only the fields that the socket has. A Unix socket or a named pipe
|
|
177
|
+
* has no ports. No header goes into the copy.
|
|
178
|
+
*/
|
|
179
|
+
function connectionOf(socket: RequestConnection): RequestConnection {
|
|
180
|
+
const connection: { remoteAddress?: string; remotePort?: number; localAddress?: string; localPort?: number } = {};
|
|
181
|
+
const { remoteAddress, remotePort, localAddress, localPort } = socket;
|
|
182
|
+
if (typeof remoteAddress === "string" && remoteAddress !== "") connection.remoteAddress = remoteAddress;
|
|
183
|
+
if (isPort(remotePort)) connection.remotePort = remotePort;
|
|
184
|
+
if (typeof localAddress === "string" && localAddress !== "") connection.localAddress = localAddress;
|
|
185
|
+
if (isPort(localPort)) connection.localPort = localPort;
|
|
186
|
+
return Object.freeze(connection);
|
|
187
|
+
}
|
|
188
|
+
|
|
169
189
|
/**
|
|
170
190
|
* The credentials of a network request (roadmap R18): the bearer token, the cookies, each header
|
|
171
191
|
* (the secret ones too) and the first value of each query parameter. The request hook of a server
|
|
172
192
|
* (`principalOf`) reads them. They are not in the metadata, so a handler does not see them.
|
|
193
|
+
*
|
|
194
|
+
* With `request.socket` (the socket of a request of `node:http`), the credentials also have the
|
|
195
|
+
* connection of the request (CLI-71): the addresses and the ports of that socket, as frozen data.
|
|
173
196
|
*/
|
|
174
197
|
export function requestCredentials(
|
|
175
|
-
request: { headers: Record<string, unknown>; url?: string | undefined },
|
|
198
|
+
request: { headers: Record<string, unknown>; url?: string | undefined; socket?: RequestConnection | undefined },
|
|
176
199
|
options: RequestCredentialsOptions = {}
|
|
177
200
|
): Credentials {
|
|
178
201
|
const headers = new Map<string, string>();
|
|
@@ -189,5 +212,6 @@ export function requestCredentials(
|
|
|
189
212
|
cookies: cookiesOf(headers.get("cookie")),
|
|
190
213
|
headers: Object.fromEntries(headers),
|
|
191
214
|
query: queryRecord(query),
|
|
215
|
+
...(request.socket ? { connection: connectionOf(request.socket) } : {}),
|
|
192
216
|
};
|
|
193
217
|
}
|
package/src/server/types.ts
CHANGED
|
@@ -97,6 +97,42 @@ export interface Credentials {
|
|
|
97
97
|
readonly headers?: Readonly<Record<string, string>> | undefined;
|
|
98
98
|
/** The first value of each query parameter of the request URL, by name. */
|
|
99
99
|
readonly query?: Readonly<Record<string, string>> | undefined;
|
|
100
|
+
/**
|
|
101
|
+
* The connection of a network request (CLI-71): the addresses and the ports of its socket. A
|
|
102
|
+
* request hook can find the caller from its TCP peer with it, for example from the process that
|
|
103
|
+
* owns the remote port. Each network face sets it: the HTTP face, the WebSocket face (the upgrade
|
|
104
|
+
* and each call) and the Streamable HTTP endpoint of the MCP face.
|
|
105
|
+
*
|
|
106
|
+
* - A local request (a call in this process, MCP over stdio, a message port) has no connection.
|
|
107
|
+
* - An SDK transport of the application (`sdkTransport`, for example the legacy SSE transport of
|
|
108
|
+
* the MCP SDK) gives no connection. The SDK gives only the headers and the URL of each request.
|
|
109
|
+
* - The data comes only from the socket. It does not come from a header, for example
|
|
110
|
+
* `X-Forwarded-For` or `Forwarded`. Behind a proxy, the peer is the proxy. Thus a request
|
|
111
|
+
* hook that trusts the connection there sees the proxy, not the client.
|
|
112
|
+
* - The key of the idempotency cache has the principal (audit s21). When the principal comes
|
|
113
|
+
* from the connection, two callers behind one address can have one principal. Then a call
|
|
114
|
+
* of one caller can get the cached result of a call of the other caller with the same
|
|
115
|
+
* idempotency key.
|
|
116
|
+
*/
|
|
117
|
+
readonly connection?: RequestConnection | undefined;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* The connection of a network request (CLI-71): the addresses and the ports of its socket, as
|
|
122
|
+
* frozen plain data. The face copies them from the socket when the request comes in.
|
|
123
|
+
*
|
|
124
|
+
* Each field is optional, because a socket does not always have it. A Unix socket and a named
|
|
125
|
+
* pipe have no IP addresses and no ports. A socket that closed before the copy has no addresses.
|
|
126
|
+
*/
|
|
127
|
+
export interface RequestConnection {
|
|
128
|
+
/** The IP address of the other end of the socket: the client, or a proxy. */
|
|
129
|
+
readonly remoteAddress?: string | undefined;
|
|
130
|
+
/** The port of the other end of the socket. It is the local port of the socket of the client. */
|
|
131
|
+
readonly remotePort?: number | undefined;
|
|
132
|
+
/** The IP address of this end of the socket: the address on which the listener took the request. */
|
|
133
|
+
readonly localAddress?: string | undefined;
|
|
134
|
+
/** The port of this end of the socket: the port of the listener. */
|
|
135
|
+
readonly localPort?: number | undefined;
|
|
100
136
|
}
|
|
101
137
|
|
|
102
138
|
/**
|
|
@@ -120,8 +156,9 @@ export interface RequestIdentity {
|
|
|
120
156
|
|
|
121
157
|
/**
|
|
122
158
|
* The request hook of a server: it reads the credentials of a network request (a cookie, a header,
|
|
123
|
-
* the query) and gives the identity of the request. Undefined, or an
|
|
124
|
-
* with `UNAUTHORIZED`. The hook runs before the token check and before
|
|
159
|
+
* the query, the connection of the socket) and gives the identity of the request. Undefined, or an
|
|
160
|
+
* error, refuses the request with `UNAUTHORIZED`. The hook runs before the token check and before
|
|
161
|
+
* `authenticate`.
|
|
125
162
|
*
|
|
126
163
|
* The hook can run more than one time for one request or one task. `admits()` runs it at a
|
|
127
164
|
* WebSocket upgrade, and then each call over the connection runs it again. Thus a hook with a
|
|
@@ -133,7 +170,8 @@ export interface RequestIdentity {
|
|
|
133
170
|
* again for them. For `admission()`, `admits()` and `identify()`, the request of the hook has an
|
|
134
171
|
* empty method (`{ service: "", operation: "" }`), the id `admission`, `connection` or `identify`,
|
|
135
172
|
* no payload and no metadata. Thus a hook must not branch on the method, the payload, the
|
|
136
|
-
* metadata or the transport of the request. It reads only the credentials.
|
|
173
|
+
* metadata or the transport of the request. It reads only the credentials. The credentials of a
|
|
174
|
+
* network request also have the connection of its socket (`credentials.connection`, CLI-71).
|
|
137
175
|
*/
|
|
138
176
|
export type PrincipalOf = (
|
|
139
177
|
credentials: Credentials,
|