@orpc/server 2.0.0-beta.24 → 2.0.0-beta.26
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/dist/adapters/aws-lambda/index.d.mts +1 -12
- package/dist/adapters/aws-lambda/index.d.ts +1 -12
- package/dist/adapters/aws-lambda/index.mjs +2 -9
- package/dist/adapters/crossws/index.d.mts +1 -1
- package/dist/adapters/crossws/index.d.ts +1 -1
- package/dist/adapters/crossws/index.mjs +1 -1
- package/dist/adapters/fastify/index.d.mts +1 -12
- package/dist/adapters/fastify/index.d.ts +1 -12
- package/dist/adapters/fastify/index.mjs +2 -9
- package/dist/adapters/fetch/index.d.mts +1 -12
- package/dist/adapters/fetch/index.d.ts +1 -12
- package/dist/adapters/fetch/index.mjs +2 -9
- package/dist/adapters/message-port/index.d.mts +1 -1
- package/dist/adapters/message-port/index.d.ts +1 -1
- package/dist/adapters/message-port/index.mjs +1 -1
- package/dist/adapters/node/index.d.mts +1 -12
- package/dist/adapters/node/index.d.ts +1 -12
- package/dist/adapters/node/index.mjs +2 -9
- package/dist/adapters/standard/index.d.mts +1 -1
- package/dist/adapters/standard/index.d.ts +1 -1
- package/dist/adapters/standard/index.mjs +1 -1
- package/dist/adapters/websocket/index.d.mts +1 -1
- package/dist/adapters/websocket/index.d.ts +1 -1
- package/dist/adapters/websocket/index.mjs +1 -1
- package/dist/index.d.mts +260 -0
- package/dist/index.d.ts +260 -0
- package/dist/index.mjs +73 -0
- package/dist/plugins/index.d.mts +49 -11
- package/dist/plugins/index.d.ts +49 -11
- package/dist/plugins/index.mjs +75 -3
- package/dist/shared/{server.BUvC0G_8.d.ts → server.C2n16pp0.d.ts} +24 -2
- package/dist/shared/{server.DEGjTAPW.mjs → server.Cbv46U7N.mjs} +19 -3
- package/dist/shared/{server.zySQaS-n.d.mts → server.CkNnZ5F-.d.mts} +24 -2
- package/package.json +5 -4
- package/dist/shared/server.D_QauotT.mjs +0 -30
package/dist/plugins/index.mjs
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { toArray, value, isCompressibleContentType, isAsyncIteratorObject, stringifyJSON } from '@orpc/shared';
|
|
2
2
|
import { flattenStandardHeader, parseStandardUrl, generateContentDisposition, mergeStandardHeaders } from '@standardserver/core';
|
|
3
3
|
import { isClientPeerSendMessage, ServerPeer, encodePeerMessage } from '@standardserver/peer';
|
|
4
|
-
export { C as CSRFGuardHandlerPlugin } from '../shared/server.D_QauotT.mjs';
|
|
5
4
|
import { toFetchHeaders, toStandardBody, toStandardHeaders } from '@standardserver/fetch';
|
|
6
5
|
export { R as RequestLimitHandlerPlugin } from '../shared/server.Cd4Z1hpV.mjs';
|
|
7
|
-
import '@orpc/client';
|
|
6
|
+
import { ORPCError } from '@orpc/client';
|
|
8
7
|
|
|
9
8
|
const BATCH_CONTENT_TYPE = "application/vnd.orpc.batch";
|
|
10
9
|
class BatchHandlerPlugin {
|
|
@@ -62,6 +61,12 @@ class BatchHandlerPlugin {
|
|
|
62
61
|
response: { status: 400, headers: {}, body: "Invalid batch request data parameter" }
|
|
63
62
|
};
|
|
64
63
|
}
|
|
64
|
+
if (mightBeMessages.some((m) => m.kind === "request" && m.json.method !== "GET")) {
|
|
65
|
+
return {
|
|
66
|
+
matched: true,
|
|
67
|
+
response: { status: 400, headers: {}, body: "GET batch requests only accept GET sub-requests" }
|
|
68
|
+
};
|
|
69
|
+
}
|
|
65
70
|
messages = mightBeMessages;
|
|
66
71
|
} else {
|
|
67
72
|
const mightBeMessages = await interceptorOptions.request.resolveBody();
|
|
@@ -286,6 +291,48 @@ class CORSHandlerPlugin {
|
|
|
286
291
|
}
|
|
287
292
|
}
|
|
288
293
|
|
|
294
|
+
class MethodOverrideHandlerPlugin {
|
|
295
|
+
name = "~method-override";
|
|
296
|
+
/**
|
|
297
|
+
* Should override batch sub-request methods, not the original batch request.
|
|
298
|
+
*/
|
|
299
|
+
before = ["~batch"];
|
|
300
|
+
param;
|
|
301
|
+
methods;
|
|
302
|
+
constructor(options = {}) {
|
|
303
|
+
this.param = options.param ?? "method";
|
|
304
|
+
this.methods = new Set((options.methods ?? ["PUT", "PATCH", "DELETE"]).map((method) => method.toUpperCase()));
|
|
305
|
+
}
|
|
306
|
+
init(options) {
|
|
307
|
+
const routingInterceptor = async ({ next, ...interceptorOptions }) => {
|
|
308
|
+
const { request } = interceptorOptions;
|
|
309
|
+
if (request.method !== "POST") {
|
|
310
|
+
return next();
|
|
311
|
+
}
|
|
312
|
+
const [pathname, search, hash] = parseStandardUrl(request.url);
|
|
313
|
+
const params = new URLSearchParams(search);
|
|
314
|
+
const raw = params.getAll(this.param).at(-1);
|
|
315
|
+
if (raw === void 0) {
|
|
316
|
+
return next();
|
|
317
|
+
}
|
|
318
|
+
const method = raw.toUpperCase();
|
|
319
|
+
if (!this.methods.has(method)) {
|
|
320
|
+
return next();
|
|
321
|
+
}
|
|
322
|
+
params.delete(this.param);
|
|
323
|
+
const url = `${pathname}${params.size ? `?${params}` : ""}${hash ?? ""}`;
|
|
324
|
+
return next({
|
|
325
|
+
...interceptorOptions,
|
|
326
|
+
request: { ...request, method, url }
|
|
327
|
+
});
|
|
328
|
+
};
|
|
329
|
+
return {
|
|
330
|
+
...options,
|
|
331
|
+
routingInterceptors: [routingInterceptor, ...toArray(options.routingInterceptors)]
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
289
336
|
class RequestCompressionHandlerPlugin {
|
|
290
337
|
name = "~request-compression";
|
|
291
338
|
/**
|
|
@@ -633,4 +680,29 @@ class RethrowHandlerPlugin {
|
|
|
633
680
|
}
|
|
634
681
|
}
|
|
635
682
|
|
|
636
|
-
|
|
683
|
+
class SimpleCsrfProtectionHandlerPlugin {
|
|
684
|
+
name = "~simple-csrf-protection";
|
|
685
|
+
init(options) {
|
|
686
|
+
const interceptor = async (interceptorOptions) => {
|
|
687
|
+
const mode = flattenStandardHeader(
|
|
688
|
+
interceptorOptions.request.headers["sec-fetch-mode"]
|
|
689
|
+
)?.toLowerCase();
|
|
690
|
+
if (mode === void 0) {
|
|
691
|
+
return interceptorOptions.next();
|
|
692
|
+
}
|
|
693
|
+
if (mode === "cors" || mode === "same-origin") {
|
|
694
|
+
return interceptorOptions.next();
|
|
695
|
+
}
|
|
696
|
+
throw new ORPCError("FORBIDDEN", {
|
|
697
|
+
message: "Request blocked by CSRF protection."
|
|
698
|
+
});
|
|
699
|
+
};
|
|
700
|
+
return {
|
|
701
|
+
...options,
|
|
702
|
+
// appended last so user's interceptors can catch ORPCError
|
|
703
|
+
interceptors: [...toArray(options.interceptors), interceptor]
|
|
704
|
+
};
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
export { BATCH_CONTENT_TYPE, BatchHandlerPlugin, CORSHandlerPlugin, CORSHandlerPlugin as CORSPlugin, MethodOverrideHandlerPlugin, RequestCompressionHandlerPlugin, RequestHeadersHandlerPlugin, RequestHeadersHandlerPlugin as RequestHeadersPlugin, ResponseCompressionHandlerPlugin, ResponseHeadersHandlerPlugin, ResponseHeadersHandlerPlugin as ResponseHeadersPlugin, RethrowHandlerPlugin, SimpleCsrfProtectionHandlerPlugin };
|
|
@@ -6,6 +6,16 @@ import { A as AnyRouter } from './server.DtfwuV6U.js';
|
|
|
6
6
|
import { AnyProcedureContract } from '@orpc/contract';
|
|
7
7
|
import { c as StandardHandlerHandleOptions, d as StandardHandlerCodec, e as StandardHandlerCodecResolvedProcedure } from './server.DrN1Pj1-.js';
|
|
8
8
|
|
|
9
|
+
/**
|
|
10
|
+
* Methods that can invoke procedures by default. Browsers cannot trigger them
|
|
11
|
+
* cross-site without a CORS preflight or an HTML form, unlike `GET`, which a plain
|
|
12
|
+
* `<a>` click or redirect can trigger with `SameSite=Lax` cookies attached. Other
|
|
13
|
+
* methods (`HEAD`, `OPTIONS`, `QUERY`, ...) have safe semantics that should not
|
|
14
|
+
* invoke a procedure that can modify data.
|
|
15
|
+
*
|
|
16
|
+
* @see {@link https://orpc.dev/docs/rpc/handler#supported-http-methods | RPC Handler - Supported HTTP Methods}
|
|
17
|
+
*/
|
|
18
|
+
declare const RPC_DEFAULT_ALLOW_METHODS: readonly StandardMethod[];
|
|
9
19
|
interface RPCMatcherOptions {
|
|
10
20
|
/**
|
|
11
21
|
* Filter which procedures are exposed for matching. Return `false` to exclude.
|
|
@@ -13,15 +23,27 @@ interface RPCMatcherOptions {
|
|
|
13
23
|
* @default true
|
|
14
24
|
*/
|
|
15
25
|
filter?: Value<boolean, [procedure: AnyProcedureContract | AnyProcedure, path: string[]]>;
|
|
26
|
+
/**
|
|
27
|
+
* Restricts which HTTP methods can invoke procedures, either with a list of allowed
|
|
28
|
+
* methods or decided per request via a function. Requests using a disallowed method
|
|
29
|
+
* are treated as unmatched. `GET` is excluded by default because it is exposed to
|
|
30
|
+
* Cross-Site Request Forgery (CSRF) attacks.
|
|
31
|
+
*
|
|
32
|
+
* @default RPC_DEFAULT_ALLOW_METHODS (['POST', 'PUT', 'PATCH', 'DELETE'])
|
|
33
|
+
* @see {@link https://orpc.dev/docs/rpc/handler#supported-http-methods | RPC Handler - Supported HTTP Methods}
|
|
34
|
+
*/
|
|
35
|
+
allowMethods?: readonly StandardMethod[] | ((method: StandardMethod, procedure: AnyProcedure, path: string[]) => boolean);
|
|
16
36
|
}
|
|
17
37
|
declare class RPCMatcher {
|
|
18
38
|
private readonly filter;
|
|
39
|
+
private readonly allowMethodsSet;
|
|
40
|
+
private readonly allowMethodsFn;
|
|
19
41
|
private readonly rootRouter;
|
|
20
42
|
private readonly tree;
|
|
21
43
|
private readonly pendingLazyRouters;
|
|
22
44
|
constructor(router: AnyRouter, options?: RPCMatcherOptions);
|
|
23
45
|
private index;
|
|
24
|
-
match(
|
|
46
|
+
match(method: StandardMethod, pathname: `/${string}`, prefix: `/${string}` | undefined): Promise<{
|
|
25
47
|
path: string[];
|
|
26
48
|
procedure: AnyProcedure;
|
|
27
49
|
} | undefined>;
|
|
@@ -65,5 +87,5 @@ declare class RPCHandlerCodec<T extends Context> implements StandardHandlerCodec
|
|
|
65
87
|
encodeError(error: AnyORPCError, _procedure: AnyProcedure, _path: string[], _options: StandardHandlerHandleOptions<T>): Promisable<StandardResponse>;
|
|
66
88
|
}
|
|
67
89
|
|
|
68
|
-
export { RPCHandlerCodec as a, RPCMatcher as b };
|
|
90
|
+
export { RPCHandlerCodec as a, RPCMatcher as b, RPC_DEFAULT_ALLOW_METHODS as d };
|
|
69
91
|
export type { RPCHandlerCodecOptions as R, RPCMatcherOptions as c };
|
|
@@ -121,13 +121,22 @@ class OtelHandlerPlugin {
|
|
|
121
121
|
}
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
+
const RPC_DEFAULT_ALLOW_METHODS = ["POST", "PUT", "PATCH", "DELETE"];
|
|
124
125
|
class RPCMatcher {
|
|
125
126
|
filter;
|
|
127
|
+
allowMethodsSet;
|
|
128
|
+
allowMethodsFn;
|
|
126
129
|
rootRouter;
|
|
127
130
|
tree = /* @__PURE__ */ new Map();
|
|
128
131
|
pendingLazyRouters = /* @__PURE__ */ new Map();
|
|
129
132
|
constructor(router, options = {}) {
|
|
130
133
|
this.filter = options.filter ?? true;
|
|
134
|
+
const allowMethods = options.allowMethods ?? RPC_DEFAULT_ALLOW_METHODS;
|
|
135
|
+
if (typeof allowMethods !== "function") {
|
|
136
|
+
this.allowMethodsSet = new Set(allowMethods);
|
|
137
|
+
} else {
|
|
138
|
+
this.allowMethodsFn = allowMethods;
|
|
139
|
+
}
|
|
131
140
|
this.rootRouter = router;
|
|
132
141
|
this.index(router);
|
|
133
142
|
}
|
|
@@ -147,7 +156,10 @@ class RPCMatcher {
|
|
|
147
156
|
this.pendingLazyRouters.set(pathToHttpPath(result.path), result);
|
|
148
157
|
}
|
|
149
158
|
}
|
|
150
|
-
async match(
|
|
159
|
+
async match(method, pathname, prefix) {
|
|
160
|
+
if (this.allowMethodsSet && !this.allowMethodsSet.has(method)) {
|
|
161
|
+
return void 0;
|
|
162
|
+
}
|
|
151
163
|
if (pathname.length > 1 && pathname.endsWith("/")) {
|
|
152
164
|
pathname = pathname.slice(0, -1);
|
|
153
165
|
}
|
|
@@ -182,9 +194,13 @@ class RPCMatcher {
|
|
|
182
194
|
if (entry === void 0) {
|
|
183
195
|
return void 0;
|
|
184
196
|
}
|
|
197
|
+
const procedure = entry.procedure ?? await this.resolveProcedure(entry);
|
|
198
|
+
if (this.allowMethodsFn && !this.allowMethodsFn(method, procedure, entry.path)) {
|
|
199
|
+
return void 0;
|
|
200
|
+
}
|
|
185
201
|
return {
|
|
186
202
|
path: entry.path,
|
|
187
|
-
procedure
|
|
203
|
+
procedure
|
|
188
204
|
};
|
|
189
205
|
}
|
|
190
206
|
resolvePendingLazyRouters(pathname) {
|
|
@@ -285,4 +301,4 @@ class RPCHandlerCodec {
|
|
|
285
301
|
}
|
|
286
302
|
}
|
|
287
303
|
|
|
288
|
-
export { CompositeStandardHandlerPlugin as C, OtelHandlerPlugin as O, RPCHandlerCodec as R, StandardHandler as S, RPCMatcher as a };
|
|
304
|
+
export { CompositeStandardHandlerPlugin as C, OtelHandlerPlugin as O, RPCHandlerCodec as R, StandardHandler as S, RPCMatcher as a, RPC_DEFAULT_ALLOW_METHODS as b };
|
|
@@ -6,6 +6,16 @@ import { A as AnyRouter } from './server.Dm0os-OP.mjs';
|
|
|
6
6
|
import { AnyProcedureContract } from '@orpc/contract';
|
|
7
7
|
import { c as StandardHandlerHandleOptions, d as StandardHandlerCodec, e as StandardHandlerCodecResolvedProcedure } from './server.JbCIPL4P.mjs';
|
|
8
8
|
|
|
9
|
+
/**
|
|
10
|
+
* Methods that can invoke procedures by default. Browsers cannot trigger them
|
|
11
|
+
* cross-site without a CORS preflight or an HTML form, unlike `GET`, which a plain
|
|
12
|
+
* `<a>` click or redirect can trigger with `SameSite=Lax` cookies attached. Other
|
|
13
|
+
* methods (`HEAD`, `OPTIONS`, `QUERY`, ...) have safe semantics that should not
|
|
14
|
+
* invoke a procedure that can modify data.
|
|
15
|
+
*
|
|
16
|
+
* @see {@link https://orpc.dev/docs/rpc/handler#supported-http-methods | RPC Handler - Supported HTTP Methods}
|
|
17
|
+
*/
|
|
18
|
+
declare const RPC_DEFAULT_ALLOW_METHODS: readonly StandardMethod[];
|
|
9
19
|
interface RPCMatcherOptions {
|
|
10
20
|
/**
|
|
11
21
|
* Filter which procedures are exposed for matching. Return `false` to exclude.
|
|
@@ -13,15 +23,27 @@ interface RPCMatcherOptions {
|
|
|
13
23
|
* @default true
|
|
14
24
|
*/
|
|
15
25
|
filter?: Value<boolean, [procedure: AnyProcedureContract | AnyProcedure, path: string[]]>;
|
|
26
|
+
/**
|
|
27
|
+
* Restricts which HTTP methods can invoke procedures, either with a list of allowed
|
|
28
|
+
* methods or decided per request via a function. Requests using a disallowed method
|
|
29
|
+
* are treated as unmatched. `GET` is excluded by default because it is exposed to
|
|
30
|
+
* Cross-Site Request Forgery (CSRF) attacks.
|
|
31
|
+
*
|
|
32
|
+
* @default RPC_DEFAULT_ALLOW_METHODS (['POST', 'PUT', 'PATCH', 'DELETE'])
|
|
33
|
+
* @see {@link https://orpc.dev/docs/rpc/handler#supported-http-methods | RPC Handler - Supported HTTP Methods}
|
|
34
|
+
*/
|
|
35
|
+
allowMethods?: readonly StandardMethod[] | ((method: StandardMethod, procedure: AnyProcedure, path: string[]) => boolean);
|
|
16
36
|
}
|
|
17
37
|
declare class RPCMatcher {
|
|
18
38
|
private readonly filter;
|
|
39
|
+
private readonly allowMethodsSet;
|
|
40
|
+
private readonly allowMethodsFn;
|
|
19
41
|
private readonly rootRouter;
|
|
20
42
|
private readonly tree;
|
|
21
43
|
private readonly pendingLazyRouters;
|
|
22
44
|
constructor(router: AnyRouter, options?: RPCMatcherOptions);
|
|
23
45
|
private index;
|
|
24
|
-
match(
|
|
46
|
+
match(method: StandardMethod, pathname: `/${string}`, prefix: `/${string}` | undefined): Promise<{
|
|
25
47
|
path: string[];
|
|
26
48
|
procedure: AnyProcedure;
|
|
27
49
|
} | undefined>;
|
|
@@ -65,5 +87,5 @@ declare class RPCHandlerCodec<T extends Context> implements StandardHandlerCodec
|
|
|
65
87
|
encodeError(error: AnyORPCError, _procedure: AnyProcedure, _path: string[], _options: StandardHandlerHandleOptions<T>): Promisable<StandardResponse>;
|
|
66
88
|
}
|
|
67
89
|
|
|
68
|
-
export { RPCHandlerCodec as a, RPCMatcher as b };
|
|
90
|
+
export { RPCHandlerCodec as a, RPCMatcher as b, RPC_DEFAULT_ALLOW_METHODS as d };
|
|
69
91
|
export type { RPCHandlerCodecOptions as R, RPCMatcherOptions as c };
|
package/package.json
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@orpc/server",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "2.0.0-beta.
|
|
4
|
+
"version": "2.0.0-beta.26",
|
|
5
5
|
"license": "MIT",
|
|
6
|
+
"funding": "https://github.com/sponsors/dinwwwh",
|
|
6
7
|
"homepage": "https://orpc.dev",
|
|
7
8
|
"repository": {
|
|
8
9
|
"type": "git",
|
|
@@ -106,9 +107,9 @@
|
|
|
106
107
|
"@standardserver/node": "^0.7.1",
|
|
107
108
|
"@standardserver/peer": "^0.7.1",
|
|
108
109
|
"cookie": "^2.0.1",
|
|
109
|
-
"@orpc/client": "2.0.0-beta.
|
|
110
|
-
"@orpc/contract": "2.0.0-beta.
|
|
111
|
-
"@orpc/shared": "2.0.0-beta.
|
|
110
|
+
"@orpc/client": "2.0.0-beta.26",
|
|
111
|
+
"@orpc/contract": "2.0.0-beta.26",
|
|
112
|
+
"@orpc/shared": "2.0.0-beta.26"
|
|
112
113
|
},
|
|
113
114
|
"devDependencies": {
|
|
114
115
|
"crossws": "^0.4.6",
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import { ORPCError } from '@orpc/client';
|
|
2
|
-
import { toArray } from '@orpc/shared';
|
|
3
|
-
import { flattenStandardHeader } from '@standardserver/core';
|
|
4
|
-
|
|
5
|
-
class CSRFGuardHandlerPlugin {
|
|
6
|
-
name = "~csrf-guard";
|
|
7
|
-
init(options) {
|
|
8
|
-
const interceptor = async (interceptorOptions) => {
|
|
9
|
-
const mode = flattenStandardHeader(
|
|
10
|
-
interceptorOptions.request.headers["sec-fetch-mode"]
|
|
11
|
-
)?.toLowerCase();
|
|
12
|
-
if (mode === void 0) {
|
|
13
|
-
return interceptorOptions.next();
|
|
14
|
-
}
|
|
15
|
-
if (mode === "cors" || mode === "same-origin") {
|
|
16
|
-
return interceptorOptions.next();
|
|
17
|
-
}
|
|
18
|
-
throw new ORPCError("FORBIDDEN", {
|
|
19
|
-
message: "Request blocked by CSRF protection."
|
|
20
|
-
});
|
|
21
|
-
};
|
|
22
|
-
return {
|
|
23
|
-
...options,
|
|
24
|
-
// appended last so user's interceptors can catch ORPCError
|
|
25
|
-
interceptors: [...toArray(options.interceptors), interceptor]
|
|
26
|
-
};
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export { CSRFGuardHandlerPlugin as C };
|