mates-fullstack 1.0.0-beta.1
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 +311 -0
- package/dist/arctic-auth.d.ts +101 -0
- package/dist/arctic-auth.d.ts.map +1 -0
- package/dist/arctic-auth.js +538 -0
- package/dist/arctic-auth.js.map +1 -0
- package/dist/asset-manifest.d.ts +14 -0
- package/dist/asset-manifest.d.ts.map +1 -0
- package/dist/asset-manifest.js +102 -0
- package/dist/asset-manifest.js.map +1 -0
- package/dist/browser.d.ts +18 -0
- package/dist/browser.d.ts.map +1 -0
- package/dist/browser.js +25 -0
- package/dist/browser.js.map +1 -0
- package/dist/build-esbuild.d.ts +29 -0
- package/dist/build-esbuild.d.ts.map +1 -0
- package/dist/build-esbuild.js +699 -0
- package/dist/build-esbuild.js.map +1 -0
- package/dist/build-prod.d.ts +126 -0
- package/dist/build-prod.d.ts.map +1 -0
- package/dist/build-prod.js +1014 -0
- package/dist/build-prod.js.map +1 -0
- package/dist/cli-new.d.ts +14 -0
- package/dist/cli-new.d.ts.map +1 -0
- package/dist/cli-new.js +637 -0
- package/dist/cli-new.js.map +1 -0
- package/dist/client.d.ts +43 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +130 -0
- package/dist/client.js.map +1 -0
- package/dist/cors.d.ts +16 -0
- package/dist/cors.d.ts.map +1 -0
- package/dist/cors.js +60 -0
- package/dist/cors.js.map +1 -0
- package/dist/ctx.d.ts +78 -0
- package/dist/ctx.d.ts.map +1 -0
- package/dist/ctx.js +280 -0
- package/dist/ctx.js.map +1 -0
- package/dist/dev-watcher.d.ts +23 -0
- package/dist/dev-watcher.d.ts.map +1 -0
- package/dist/dev-watcher.js +136 -0
- package/dist/dev-watcher.js.map +1 -0
- package/dist/docs-generator.d.ts +69 -0
- package/dist/docs-generator.d.ts.map +1 -0
- package/dist/docs-generator.js +557 -0
- package/dist/docs-generator.js.map +1 -0
- package/dist/docs-page.d.ts +20 -0
- package/dist/docs-page.d.ts.map +1 -0
- package/dist/docs-page.js +1152 -0
- package/dist/docs-page.js.map +1 -0
- package/dist/download.d.ts +78 -0
- package/dist/download.d.ts.map +1 -0
- package/dist/download.js +202 -0
- package/dist/download.js.map +1 -0
- package/dist/env-loader.d.ts +76 -0
- package/dist/env-loader.d.ts.map +1 -0
- package/dist/env-loader.js +213 -0
- package/dist/env-loader.js.map +1 -0
- package/dist/errors.d.ts +146 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +386 -0
- package/dist/errors.js.map +1 -0
- package/dist/head.d.ts +31 -0
- package/dist/head.d.ts.map +1 -0
- package/dist/head.js +245 -0
- package/dist/head.js.map +1 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +30 -0
- package/dist/index.js.map +1 -0
- package/dist/internal-prefixes.d.ts +16 -0
- package/dist/internal-prefixes.d.ts.map +1 -0
- package/dist/internal-prefixes.js +16 -0
- package/dist/internal-prefixes.js.map +1 -0
- package/dist/internal.d.ts +25 -0
- package/dist/internal.d.ts.map +1 -0
- package/dist/internal.js +25 -0
- package/dist/internal.js.map +1 -0
- package/dist/jwt.d.ts +166 -0
- package/dist/jwt.d.ts.map +1 -0
- package/dist/jwt.js +261 -0
- package/dist/jwt.js.map +1 -0
- package/dist/log.d.ts +44 -0
- package/dist/log.d.ts.map +1 -0
- package/dist/log.js +66 -0
- package/dist/log.js.map +1 -0
- package/dist/logger.d.ts +76 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +138 -0
- package/dist/logger.js.map +1 -0
- package/dist/main-runner.d.ts +59 -0
- package/dist/main-runner.d.ts.map +1 -0
- package/dist/main-runner.js +157 -0
- package/dist/main-runner.js.map +1 -0
- package/dist/mates-auth.d.ts +82 -0
- package/dist/mates-auth.d.ts.map +1 -0
- package/dist/mates-auth.js +323 -0
- package/dist/mates-auth.js.map +1 -0
- package/dist/middleware.d.ts +30 -0
- package/dist/middleware.d.ts.map +1 -0
- package/dist/middleware.js +67 -0
- package/dist/middleware.js.map +1 -0
- package/dist/project-resolver.d.ts +102 -0
- package/dist/project-resolver.d.ts.map +1 -0
- package/dist/project-resolver.js +271 -0
- package/dist/project-resolver.js.map +1 -0
- package/dist/rate-limit.d.ts +37 -0
- package/dist/rate-limit.d.ts.map +1 -0
- package/dist/rate-limit.js +109 -0
- package/dist/rate-limit.js.map +1 -0
- package/dist/redirect.d.ts +84 -0
- package/dist/redirect.d.ts.map +1 -0
- package/dist/redirect.js +105 -0
- package/dist/redirect.js.map +1 -0
- package/dist/renderer.d.ts +91 -0
- package/dist/renderer.d.ts.map +1 -0
- package/dist/renderer.js +630 -0
- package/dist/renderer.js.map +1 -0
- package/dist/request-logger.d.ts +12 -0
- package/dist/request-logger.d.ts.map +1 -0
- package/dist/request-logger.js +55 -0
- package/dist/request-logger.js.map +1 -0
- package/dist/rest.d.ts +25 -0
- package/dist/rest.d.ts.map +1 -0
- package/dist/rest.js +93 -0
- package/dist/rest.js.map +1 -0
- package/dist/router.d.ts +71 -0
- package/dist/router.d.ts.map +1 -0
- package/dist/router.js +222 -0
- package/dist/router.js.map +1 -0
- package/dist/rpc-registry.d.ts +84 -0
- package/dist/rpc-registry.d.ts.map +1 -0
- package/dist/rpc-registry.js +271 -0
- package/dist/rpc-registry.js.map +1 -0
- package/dist/rpc-runner.d.ts +82 -0
- package/dist/rpc-runner.d.ts.map +1 -0
- package/dist/rpc-runner.js +564 -0
- package/dist/rpc-runner.js.map +1 -0
- package/dist/sanitize.d.ts +61 -0
- package/dist/sanitize.d.ts.map +1 -0
- package/dist/sanitize.js +193 -0
- package/dist/sanitize.js.map +1 -0
- package/dist/security-headers.d.ts +114 -0
- package/dist/security-headers.d.ts.map +1 -0
- package/dist/security-headers.js +121 -0
- package/dist/security-headers.js.map +1 -0
- package/dist/server-fn.d.ts +323 -0
- package/dist/server-fn.d.ts.map +1 -0
- package/dist/server-fn.js +373 -0
- package/dist/server-fn.js.map +1 -0
- package/dist/server-public.d.ts +13 -0
- package/dist/server-public.d.ts.map +1 -0
- package/dist/server-public.js +12 -0
- package/dist/server-public.js.map +1 -0
- package/dist/server-timeout.d.ts +38 -0
- package/dist/server-timeout.d.ts.map +1 -0
- package/dist/server-timeout.js +46 -0
- package/dist/server-timeout.js.map +1 -0
- package/dist/server.d.ts +100 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +1218 -0
- package/dist/server.js.map +1 -0
- package/dist/socket-router.d.ts +153 -0
- package/dist/socket-router.d.ts.map +1 -0
- package/dist/socket-router.js +612 -0
- package/dist/socket-router.js.map +1 -0
- package/dist/sso.d.ts +90 -0
- package/dist/sso.d.ts.map +1 -0
- package/dist/sso.js +261 -0
- package/dist/sso.js.map +1 -0
- package/dist/ssr-context.d.ts +49 -0
- package/dist/ssr-context.d.ts.map +1 -0
- package/dist/ssr-context.js +85 -0
- package/dist/ssr-context.js.map +1 -0
- package/dist/ssr-globals.d.ts +32 -0
- package/dist/ssr-globals.d.ts.map +1 -0
- package/dist/ssr-globals.js +1010 -0
- package/dist/ssr-globals.js.map +1 -0
- package/dist/ssr-template.d.ts +73 -0
- package/dist/ssr-template.d.ts.map +1 -0
- package/dist/ssr-template.js +507 -0
- package/dist/ssr-template.js.map +1 -0
- package/dist/stack-mapper.d.ts +25 -0
- package/dist/stack-mapper.d.ts.map +1 -0
- package/dist/stack-mapper.js +139 -0
- package/dist/stack-mapper.js.map +1 -0
- package/dist/stream.d.ts +89 -0
- package/dist/stream.d.ts.map +1 -0
- package/dist/stream.js +299 -0
- package/dist/stream.js.map +1 -0
- package/dist/upload.d.ts +69 -0
- package/dist/upload.d.ts.map +1 -0
- package/dist/upload.js +110 -0
- package/dist/upload.js.map +1 -0
- package/dist/validate.d.ts +58 -0
- package/dist/validate.d.ts.map +1 -0
- package/dist/validate.js +89 -0
- package/dist/validate.js.map +1 -0
- package/dist/verify-package.d.ts +3 -0
- package/dist/verify-package.d.ts.map +1 -0
- package/dist/verify-package.js +128 -0
- package/dist/verify-package.js.map +1 -0
- package/package.json +79 -0
|
@@ -0,0 +1,564 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* mates-fullstack — rpc-runner.ts
|
|
3
|
+
*
|
|
4
|
+
* Executes a registered RPC function for an incoming HTTP request.
|
|
5
|
+
*
|
|
6
|
+
* Execution pipeline per request:
|
|
7
|
+
* 1. Read + size-check request body
|
|
8
|
+
* 2. Parse payload (JSON or multipart/form-data)
|
|
9
|
+
* 3. Create Context from request headers
|
|
10
|
+
* 4. Run onRequest hooks (registered in server/main.ts)
|
|
11
|
+
* - throws → error response sent, onResponse SKIPPED
|
|
12
|
+
* - returns Response → that response sent, onResponse RUNS
|
|
13
|
+
* - returns nothing → continue
|
|
14
|
+
* 5. Run entry.fn(payload, ctx)
|
|
15
|
+
* - throws → error response sent, onResponse SKIPPED
|
|
16
|
+
* - returns data → onResponse hooks run, then response sent
|
|
17
|
+
* 6. Detect return type → route to correct response writer:
|
|
18
|
+
* - StreamResponse → SSE
|
|
19
|
+
* - Download → binary attachment
|
|
20
|
+
* - void/null/undefined → {} 200
|
|
21
|
+
* - anything else → JSON 200
|
|
22
|
+
* 7. Run onResponse hooks (with data + ctx)
|
|
23
|
+
* 8. Apply ctx.resHeaders to the response
|
|
24
|
+
*
|
|
25
|
+
* Error handling:
|
|
26
|
+
* - Redirect throw → 302 JSON { __type: "Redirect", url, status }
|
|
27
|
+
* - ServerError throw → 4xx/5xx JSON { __type, message, status, code, ...}
|
|
28
|
+
* - Plain Error throw → 500, message hidden in production
|
|
29
|
+
* - Unknown throw → 500, "Internal Server Error"
|
|
30
|
+
*
|
|
31
|
+
* Body limits:
|
|
32
|
+
* - JSON: 1MB default (MAX_JSON_BYTES)
|
|
33
|
+
* - Multipart: 10MB default (MAX_UPLOAD_BYTES)
|
|
34
|
+
*/
|
|
35
|
+
import { createContext, applyResHeaders } from "./ctx.js";
|
|
36
|
+
import { serializeError } from "./errors.js";
|
|
37
|
+
import { isRedirect, serializeRedirect } from "./redirect.js";
|
|
38
|
+
import { isStreamResponse } from "./stream.js";
|
|
39
|
+
import { isDownload, writeDownload } from "./download.js";
|
|
40
|
+
import { runResponseHooks, runBeforeRPCHooks, runAfterRPCHooks, } from "./middleware.js";
|
|
41
|
+
const _config = {
|
|
42
|
+
maxJsonBytes: 1 * 1024 * 1024,
|
|
43
|
+
maxJsonDepth: 50,
|
|
44
|
+
maxArrayLength: 10000,
|
|
45
|
+
maxStringLength: 100000,
|
|
46
|
+
maxUploadBytes: 10 * 1024 * 1024,
|
|
47
|
+
maxMultipartParts: 200,
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* Configure RPC runner limits. Call once at startup.
|
|
51
|
+
* All values are optional — only provided values override the defaults.
|
|
52
|
+
*/
|
|
53
|
+
export function configureRpcRunner(config) {
|
|
54
|
+
if (config.maxJsonBytes !== undefined)
|
|
55
|
+
_config.maxJsonBytes = config.maxJsonBytes;
|
|
56
|
+
if (config.maxJsonDepth !== undefined)
|
|
57
|
+
_config.maxJsonDepth = config.maxJsonDepth;
|
|
58
|
+
if (config.maxArrayLength !== undefined)
|
|
59
|
+
_config.maxArrayLength = config.maxArrayLength;
|
|
60
|
+
if (config.maxStringLength !== undefined)
|
|
61
|
+
_config.maxStringLength = config.maxStringLength;
|
|
62
|
+
if (config.maxUploadBytes !== undefined)
|
|
63
|
+
_config.maxUploadBytes = config.maxUploadBytes;
|
|
64
|
+
if (config.maxMultipartParts !== undefined)
|
|
65
|
+
_config.maxMultipartParts = config.maxMultipartParts;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Recursively validate a parsed JSON payload for safety limits.
|
|
69
|
+
* Checks depth, array size, and string lengths.
|
|
70
|
+
*/
|
|
71
|
+
function _validateJsonPayload(value, depth) {
|
|
72
|
+
if (depth > _config.maxJsonDepth) {
|
|
73
|
+
const err = new Error(`JSON payload exceeds maximum depth of ${_config.maxJsonDepth}.`);
|
|
74
|
+
err.status = 400;
|
|
75
|
+
throw err;
|
|
76
|
+
}
|
|
77
|
+
if (typeof value === "string" && value.length > _config.maxStringLength) {
|
|
78
|
+
const err = new Error(`JSON string exceeds maximum length of ${_config.maxStringLength}.`);
|
|
79
|
+
err.status = 413;
|
|
80
|
+
throw err;
|
|
81
|
+
}
|
|
82
|
+
if (Array.isArray(value)) {
|
|
83
|
+
if (value.length > _config.maxArrayLength) {
|
|
84
|
+
const err = new Error(`JSON array exceeds maximum length of ${_config.maxArrayLength}.`);
|
|
85
|
+
err.status = 413;
|
|
86
|
+
throw err;
|
|
87
|
+
}
|
|
88
|
+
for (const item of value) {
|
|
89
|
+
_validateJsonPayload(item, depth + 1);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
else if (typeof value === "object" && value !== null) {
|
|
93
|
+
for (const key of Object.keys(value)) {
|
|
94
|
+
_validateJsonPayload(key, depth + 1);
|
|
95
|
+
_validateJsonPayload(value[key], depth + 1);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/** Request timeout in milliseconds: 30 seconds */
|
|
100
|
+
export const DEFAULT_TIMEOUT_MS = 30000;
|
|
101
|
+
// ─── Runner ───────────────────────────────────────────────────────────────────
|
|
102
|
+
/**
|
|
103
|
+
* Handle a single RPC request end-to-end.
|
|
104
|
+
* Never throws under any circumstances.
|
|
105
|
+
* All errors — including unexpected framework bugs — produce a JSON error response.
|
|
106
|
+
*/
|
|
107
|
+
export async function runRpcRequest(req, res, entry, dev, timeoutMs = DEFAULT_TIMEOUT_MS,
|
|
108
|
+
/** Pre-populated ctx from the outer request handler. When supplied, auth
|
|
109
|
+
* and other middleware state already applied via onRequest are shared. */
|
|
110
|
+
sharedCtx) {
|
|
111
|
+
// Create an AbortController to cancel streams if the client disconnects
|
|
112
|
+
// or if the request times out
|
|
113
|
+
const controller = new AbortController();
|
|
114
|
+
const { signal } = controller;
|
|
115
|
+
// Use the pre-populated Context from the outer request handler if provided
|
|
116
|
+
// (preserves auth, state, and headers set by onRequest middleware),
|
|
117
|
+
// otherwise create a fresh ctx from the raw Node.js request.
|
|
118
|
+
const ctx = sharedCtx ?? createContext(req);
|
|
119
|
+
req.on("close", () => {
|
|
120
|
+
if (!res.writableEnded)
|
|
121
|
+
controller.abort();
|
|
122
|
+
});
|
|
123
|
+
req.on("error", () => controller.abort());
|
|
124
|
+
const timeoutId = setTimeout(() => {
|
|
125
|
+
if (!res.writableEnded) {
|
|
126
|
+
controller.abort();
|
|
127
|
+
const serialized = {
|
|
128
|
+
__type: "AppError",
|
|
129
|
+
message: "Request timeout",
|
|
130
|
+
status: 504,
|
|
131
|
+
code: "TIMEOUT",
|
|
132
|
+
};
|
|
133
|
+
ctx.resStatus = 504;
|
|
134
|
+
void runResponseHooks(ctx);
|
|
135
|
+
_writeErrorResponse(res, serialized, 504);
|
|
136
|
+
}
|
|
137
|
+
}, timeoutMs);
|
|
138
|
+
try {
|
|
139
|
+
await _execute(req, res, entry, dev, signal, ctx);
|
|
140
|
+
}
|
|
141
|
+
catch (err) {
|
|
142
|
+
// Absolute last resort — _execute itself has bugs or threw unexpectedly
|
|
143
|
+
if (!res.headersSent) {
|
|
144
|
+
const serialized = serializeError(err, dev);
|
|
145
|
+
ctx.resStatus = serialized.status;
|
|
146
|
+
await runResponseHooks(ctx);
|
|
147
|
+
_writeErrorResponse(res, serialized, serialized.status);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
finally {
|
|
151
|
+
clearTimeout(timeoutId);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
// ─── Execution ────────────────────────────────────────────────────────────────
|
|
155
|
+
async function _execute(req, res, entry, dev, signal, ctx) {
|
|
156
|
+
// ── 1. Parse payload ───────────────────────────────────────────────────────
|
|
157
|
+
let payload;
|
|
158
|
+
try {
|
|
159
|
+
payload = await _parseBody(req);
|
|
160
|
+
}
|
|
161
|
+
catch (err) {
|
|
162
|
+
if (res.headersSent)
|
|
163
|
+
return;
|
|
164
|
+
const status = err.status ?? 400;
|
|
165
|
+
const serialized = {
|
|
166
|
+
__type: "AppError",
|
|
167
|
+
message: err.message ?? "Bad request",
|
|
168
|
+
status,
|
|
169
|
+
code: "BAD_REQUEST",
|
|
170
|
+
};
|
|
171
|
+
ctx.resStatus = status;
|
|
172
|
+
await runResponseHooks(ctx);
|
|
173
|
+
_writeErrorResponse(res, serialized, status);
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
// ── 2. Run the server function ─────────────────────────────────────────────
|
|
177
|
+
// RPC functions receive the request payload as the first argument and
|
|
178
|
+
// the framework Context (with auth, cookies, etc.) as the second argument.
|
|
179
|
+
try {
|
|
180
|
+
const mergedPayload = payload && typeof payload === "object" && !Array.isArray(payload)
|
|
181
|
+
? payload
|
|
182
|
+
: {};
|
|
183
|
+
// Run onBeforeRPC hooks as a data pipeline — each hook can mutate the
|
|
184
|
+
// payload before it reaches the RPC function.
|
|
185
|
+
const moduleId = entry.url.replace(/^\/api\//, "").replace(/\/[^/]+$/, "");
|
|
186
|
+
const rpcPayload = await runBeforeRPCHooks(mergedPayload, ctx);
|
|
187
|
+
const startTime = performance.now();
|
|
188
|
+
const raw = await entry.fn(rpcPayload, ctx);
|
|
189
|
+
const duration = performance.now() - startTime;
|
|
190
|
+
// Run onAfterRPC hooks as a result pipeline — each hook can mutate the
|
|
191
|
+
// return value before it is sent to the client.
|
|
192
|
+
const result = await runAfterRPCHooks(raw, ctx, {
|
|
193
|
+
moduleId,
|
|
194
|
+
fnName: entry.fnName,
|
|
195
|
+
duration,
|
|
196
|
+
});
|
|
197
|
+
if (res.headersSent)
|
|
198
|
+
return;
|
|
199
|
+
// Detect special response types — before the generic JSON path
|
|
200
|
+
if (isRedirect(result)) {
|
|
201
|
+
ctx.resStatus = result.status;
|
|
202
|
+
await runResponseHooks(ctx);
|
|
203
|
+
applyResHeaders(ctx.resHeaders, res);
|
|
204
|
+
_writeJsonResponse(res, serializeRedirect(result), result.status, ctx.resHeaders);
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
if (isStreamResponse(result)) {
|
|
208
|
+
ctx.resStatus = 200;
|
|
209
|
+
await runResponseHooks(ctx);
|
|
210
|
+
applyResHeaders(ctx.resHeaders, res);
|
|
211
|
+
const { writeStream } = await import("./stream.js");
|
|
212
|
+
writeStream(result, res, signal);
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
if (isDownload(result)) {
|
|
216
|
+
ctx.resStatus = 200;
|
|
217
|
+
await runResponseHooks(ctx);
|
|
218
|
+
applyResHeaders(ctx.resHeaders, res);
|
|
219
|
+
writeDownload(result, res);
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
// Default: JSON response
|
|
223
|
+
const body = result === undefined || result === null ? {} : result;
|
|
224
|
+
ctx.resStatus = 200;
|
|
225
|
+
await runResponseHooks(ctx);
|
|
226
|
+
applyResHeaders(ctx.resHeaders, res);
|
|
227
|
+
_writeJsonResponse(res, body, 200, ctx.resHeaders);
|
|
228
|
+
}
|
|
229
|
+
catch (err) {
|
|
230
|
+
if (res.headersSent)
|
|
231
|
+
return;
|
|
232
|
+
// Check for Redirect thrown via redirect() helper
|
|
233
|
+
if (isRedirect(err)) {
|
|
234
|
+
ctx.resStatus = err.status;
|
|
235
|
+
await runResponseHooks(ctx);
|
|
236
|
+
applyResHeaders(ctx.resHeaders, res);
|
|
237
|
+
_writeJsonResponse(res, serializeRedirect(err), err.status, ctx.resHeaders);
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
const serialized = serializeError(err, dev);
|
|
241
|
+
_applyRateLimitHeader(serialized, ctx);
|
|
242
|
+
ctx.resStatus = serialized.status;
|
|
243
|
+
await runResponseHooks(ctx);
|
|
244
|
+
applyResHeaders(ctx.resHeaders, res);
|
|
245
|
+
_writeErrorResponse(res, serialized, serialized.status);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
// ─── Internal helpers ──────────────────────────────────────────────────────────────────────
|
|
249
|
+
/** Set Retry-After header for rate-limit errors. */
|
|
250
|
+
function _applyRateLimitHeader(serialized, ctx) {
|
|
251
|
+
if (serialized.code === "RATE_LIMIT" && serialized.retryAfter !== undefined) {
|
|
252
|
+
ctx.resHeaders["retry-after"] = String(serialized.retryAfter);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
// ─── Body parsing ────────────────────────────────────────────────────────────────────
|
|
256
|
+
/**
|
|
257
|
+
* Read and parse the request body.
|
|
258
|
+
*
|
|
259
|
+
* Handles:
|
|
260
|
+
* - application/json → JSON.parse
|
|
261
|
+
* - multipart/form-data → FormData-like object with File entries
|
|
262
|
+
* - no content-type → attempt JSON parse, fallback to {}
|
|
263
|
+
* - empty body → {}
|
|
264
|
+
*
|
|
265
|
+
* Throws with .status set for:
|
|
266
|
+
* - Body too large (413)
|
|
267
|
+
* - Malformed JSON (400)
|
|
268
|
+
*/
|
|
269
|
+
export async function parseRpcBody(req) {
|
|
270
|
+
return _parseBody(req);
|
|
271
|
+
}
|
|
272
|
+
async function _parseBody(req) {
|
|
273
|
+
const contentType = (req.headers["content-type"] ?? "").toLowerCase();
|
|
274
|
+
const contentLength = parseInt(req.headers["content-length"] ?? "0", 10);
|
|
275
|
+
// ── Multipart/form-data ────────────────────────────────────────────────
|
|
276
|
+
if (contentType.includes("multipart/form-data")) {
|
|
277
|
+
if (contentLength > _config.maxUploadBytes) {
|
|
278
|
+
const err = new Error(`Upload too large. Maximum size is ${_config.maxUploadBytes / 1024 / 1024}MB.`);
|
|
279
|
+
err.status = 413;
|
|
280
|
+
throw err;
|
|
281
|
+
}
|
|
282
|
+
return _parseMultipart(req, contentType);
|
|
283
|
+
}
|
|
284
|
+
// ── JSON (default) ─────────────────────────────────────────────────────
|
|
285
|
+
if (contentLength > _config.maxJsonBytes) {
|
|
286
|
+
const err = new Error(`Request body too large. Maximum size is ${_config.maxJsonBytes / 1024}KB.`);
|
|
287
|
+
err.status = 413;
|
|
288
|
+
throw err;
|
|
289
|
+
}
|
|
290
|
+
const raw = await _readBody(req, _config.maxJsonBytes);
|
|
291
|
+
// Empty body → {}
|
|
292
|
+
if (!raw || raw.length === 0)
|
|
293
|
+
return {};
|
|
294
|
+
try {
|
|
295
|
+
// Parse JSON first, then validate depth and array sizes recursively.
|
|
296
|
+
// This prevents stack exhaustion from deeply nested objects
|
|
297
|
+
// and CPU/memory exhaustion from massive arrays.
|
|
298
|
+
const parsed = JSON.parse(raw);
|
|
299
|
+
_validateJsonPayload(parsed, 0);
|
|
300
|
+
// Must be an object at the top level
|
|
301
|
+
if (typeof parsed !== "object" ||
|
|
302
|
+
parsed === null ||
|
|
303
|
+
Array.isArray(parsed)) {
|
|
304
|
+
// Allow it but wrap so payload is always an object shape
|
|
305
|
+
// The actual function signature handles this
|
|
306
|
+
return parsed;
|
|
307
|
+
}
|
|
308
|
+
return parsed;
|
|
309
|
+
}
|
|
310
|
+
catch {
|
|
311
|
+
const err = new Error("Invalid JSON in request body.");
|
|
312
|
+
err.status = 400;
|
|
313
|
+
throw err;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Read the raw request body as a string.
|
|
318
|
+
* Enforces a maximum byte limit to prevent memory exhaustion.
|
|
319
|
+
*/
|
|
320
|
+
async function _readBody(req, maxBytes) {
|
|
321
|
+
return new Promise((resolve, reject) => {
|
|
322
|
+
const chunks = [];
|
|
323
|
+
let totalBytes = 0;
|
|
324
|
+
req.on("data", (chunk) => {
|
|
325
|
+
totalBytes += chunk.length;
|
|
326
|
+
if (totalBytes > maxBytes) {
|
|
327
|
+
req.destroy(); // stop reading
|
|
328
|
+
const err = new Error(`Request body too large. Maximum size is ${maxBytes / 1024}KB.`);
|
|
329
|
+
err.status = 413;
|
|
330
|
+
reject(err);
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
chunks.push(chunk);
|
|
334
|
+
});
|
|
335
|
+
req.on("end", () => {
|
|
336
|
+
resolve(Buffer.concat(chunks).toString("utf-8"));
|
|
337
|
+
});
|
|
338
|
+
req.on("error", (err) => {
|
|
339
|
+
reject(err);
|
|
340
|
+
});
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Parse a multipart/form-data body.
|
|
345
|
+
*
|
|
346
|
+
* Returns a plain object where:
|
|
347
|
+
* - Text fields → string values
|
|
348
|
+
* - File fields → { name, type, size, buffer } objects
|
|
349
|
+
*
|
|
350
|
+
* This is a minimal parser that covers the common cases.
|
|
351
|
+
* For production use, a more robust parser (e.g. busboy) is recommended
|
|
352
|
+
* but we avoid adding dependencies.
|
|
353
|
+
*/
|
|
354
|
+
async function _parseMultipart(req, contentType) {
|
|
355
|
+
// Extract boundary from Content-Type header
|
|
356
|
+
// e.g. multipart/form-data; boundary=----WebKitFormBoundary...
|
|
357
|
+
const boundaryMatch = contentType.match(/boundary=([^\s;]+)/i);
|
|
358
|
+
if (!boundaryMatch) {
|
|
359
|
+
const err = new Error("Missing boundary in multipart/form-data Content-Type.");
|
|
360
|
+
err.status = 400;
|
|
361
|
+
throw err;
|
|
362
|
+
}
|
|
363
|
+
const boundary = boundaryMatch[1].replace(/^"(.*)"$/, "$1"); // strip quotes
|
|
364
|
+
const rawBody = await _readBodyBuffer(req, _config.maxUploadBytes);
|
|
365
|
+
return _parseMultipartBuffer(rawBody, boundary);
|
|
366
|
+
}
|
|
367
|
+
async function _readBodyBuffer(req, maxBytes) {
|
|
368
|
+
return new Promise((resolve, reject) => {
|
|
369
|
+
const chunks = [];
|
|
370
|
+
let totalBytes = 0;
|
|
371
|
+
req.on("data", (chunk) => {
|
|
372
|
+
totalBytes += chunk.length;
|
|
373
|
+
if (totalBytes > maxBytes) {
|
|
374
|
+
req.destroy();
|
|
375
|
+
const err = new Error(`Upload too large. Maximum size is ${maxBytes / 1024 / 1024}MB.`);
|
|
376
|
+
err.status = 413;
|
|
377
|
+
reject(err);
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
chunks.push(chunk);
|
|
381
|
+
});
|
|
382
|
+
req.on("end", () => resolve(Buffer.concat(chunks)));
|
|
383
|
+
req.on("error", reject);
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
/**
|
|
387
|
+
* Sanitize a filename from Content-Disposition to prevent path traversal.
|
|
388
|
+
* Strips directory separators, null bytes, and other dangerous characters.
|
|
389
|
+
*/
|
|
390
|
+
function _sanitizeFilename(filename) {
|
|
391
|
+
// Strip null bytes
|
|
392
|
+
let clean = filename.replace(/\0/g, "");
|
|
393
|
+
// Strip directory traversal patterns
|
|
394
|
+
clean = clean.replace(/\.\./g, "");
|
|
395
|
+
// Strip path separators (forward and backslash)
|
|
396
|
+
clean = clean.replace(/[/\\]/g, "");
|
|
397
|
+
// Strip any remaining dangerous characters
|
|
398
|
+
clean = clean.replace(/[<>:"|?*]/g, "_");
|
|
399
|
+
// Trim whitespace
|
|
400
|
+
clean = clean.trim();
|
|
401
|
+
// Default name for empty results
|
|
402
|
+
return clean || "unnamed";
|
|
403
|
+
}
|
|
404
|
+
function _parseMultipartBuffer(body, boundary) {
|
|
405
|
+
const result = {};
|
|
406
|
+
const delimiter = Buffer.from(`--${boundary}`);
|
|
407
|
+
const parts = _splitBuffer(body, delimiter);
|
|
408
|
+
// Enforce maximum part count to prevent CPU/memory exhaustion
|
|
409
|
+
if (parts.length > _config.maxMultipartParts) {
|
|
410
|
+
const err = new Error(`Too many multipart parts. Maximum is ${_config.maxMultipartParts}.`);
|
|
411
|
+
err.status = 413;
|
|
412
|
+
throw err;
|
|
413
|
+
}
|
|
414
|
+
for (const part of parts) {
|
|
415
|
+
// Skip preamble, epilogue, and empty parts
|
|
416
|
+
if (part.length < 4)
|
|
417
|
+
continue;
|
|
418
|
+
// Find the blank line separating headers from body (\r\n\r\n)
|
|
419
|
+
const headerEnd = _indexOfBuffer(part, Buffer.from("\r\n\r\n"));
|
|
420
|
+
if (headerEnd === -1)
|
|
421
|
+
continue;
|
|
422
|
+
const headerSection = part.slice(0, headerEnd).toString("utf-8");
|
|
423
|
+
const bodySection = part.slice(headerEnd + 4);
|
|
424
|
+
// Remove trailing \r\n from body
|
|
425
|
+
const partBody = bodySection.length >= 2 &&
|
|
426
|
+
bodySection[bodySection.length - 2] === 13 &&
|
|
427
|
+
bodySection[bodySection.length - 1] === 10
|
|
428
|
+
? bodySection.slice(0, -2)
|
|
429
|
+
: bodySection;
|
|
430
|
+
// Parse headers
|
|
431
|
+
const headers = {};
|
|
432
|
+
for (const line of headerSection.split("\r\n")) {
|
|
433
|
+
const colonIdx = line.indexOf(":");
|
|
434
|
+
if (colonIdx === -1)
|
|
435
|
+
continue;
|
|
436
|
+
headers[line.slice(0, colonIdx).trim().toLowerCase()] = line
|
|
437
|
+
.slice(colonIdx + 1)
|
|
438
|
+
.trim();
|
|
439
|
+
}
|
|
440
|
+
// Extract name and filename from Content-Disposition
|
|
441
|
+
const disposition = headers["content-disposition"] ?? "";
|
|
442
|
+
const nameMatch = disposition.match(/name="([^"]+)"/);
|
|
443
|
+
const filenameMatch = disposition.match(/filename="([^"]*)"/);
|
|
444
|
+
if (!nameMatch)
|
|
445
|
+
continue;
|
|
446
|
+
const fieldName = nameMatch[1];
|
|
447
|
+
if (filenameMatch) {
|
|
448
|
+
// File field
|
|
449
|
+
const rawFilename = filenameMatch[1];
|
|
450
|
+
const filename = _sanitizeFilename(rawFilename);
|
|
451
|
+
const mimeType = headers["content-type"] ?? "application/octet-stream";
|
|
452
|
+
// Store as a plain object with buffer — avoids File/Blob browser APIs
|
|
453
|
+
const fileEntry = {
|
|
454
|
+
name: filename,
|
|
455
|
+
type: mimeType,
|
|
456
|
+
size: partBody.length,
|
|
457
|
+
buffer: partBody,
|
|
458
|
+
// Helper to get as Buffer (server-side usage)
|
|
459
|
+
arrayBuffer: () => Promise.resolve(partBody.buffer.slice(partBody.byteOffset, partBody.byteOffset + partBody.byteLength)),
|
|
460
|
+
};
|
|
461
|
+
// Multiple files with same name → convert to array
|
|
462
|
+
if (fieldName in result) {
|
|
463
|
+
const existing = result[fieldName];
|
|
464
|
+
result[fieldName] = Array.isArray(existing)
|
|
465
|
+
? [...existing, fileEntry]
|
|
466
|
+
: [existing, fileEntry];
|
|
467
|
+
}
|
|
468
|
+
else {
|
|
469
|
+
result[fieldName] = fileEntry;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
else {
|
|
473
|
+
// Text field
|
|
474
|
+
const value = partBody.toString("utf-8");
|
|
475
|
+
if (fieldName in result) {
|
|
476
|
+
const existing = result[fieldName];
|
|
477
|
+
result[fieldName] = Array.isArray(existing)
|
|
478
|
+
? [...existing, value]
|
|
479
|
+
: [existing, value];
|
|
480
|
+
}
|
|
481
|
+
else {
|
|
482
|
+
result[fieldName] = value;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
return result;
|
|
487
|
+
}
|
|
488
|
+
/** Split a Buffer by a delimiter Buffer, returning the parts between delimiters. */
|
|
489
|
+
function _splitBuffer(buf, delimiter) {
|
|
490
|
+
const parts = [];
|
|
491
|
+
let start = 0;
|
|
492
|
+
while (true) {
|
|
493
|
+
const idx = _indexOfBuffer(buf, delimiter, start);
|
|
494
|
+
if (idx === -1) {
|
|
495
|
+
parts.push(buf.slice(start));
|
|
496
|
+
break;
|
|
497
|
+
}
|
|
498
|
+
parts.push(buf.slice(start, idx));
|
|
499
|
+
start = idx + delimiter.length;
|
|
500
|
+
// Skip \r\n after delimiter
|
|
501
|
+
if (buf[start] === 13 && buf[start + 1] === 10)
|
|
502
|
+
start += 2;
|
|
503
|
+
// Final boundary ends with --
|
|
504
|
+
if (buf[start] === 45 && buf[start + 1] === 45)
|
|
505
|
+
break;
|
|
506
|
+
}
|
|
507
|
+
return parts;
|
|
508
|
+
}
|
|
509
|
+
/** Find the index of `needle` in `haystack` starting at `offset`. */
|
|
510
|
+
function _indexOfBuffer(haystack, needle, offset = 0) {
|
|
511
|
+
outer: for (let i = offset; i <= haystack.length - needle.length; i++) {
|
|
512
|
+
for (let j = 0; j < needle.length; j++) {
|
|
513
|
+
if (haystack[i + j] !== needle[j])
|
|
514
|
+
continue outer;
|
|
515
|
+
}
|
|
516
|
+
return i;
|
|
517
|
+
}
|
|
518
|
+
return -1;
|
|
519
|
+
}
|
|
520
|
+
// ─── Response writers ─────────────────────────────────────────────────────────
|
|
521
|
+
/**
|
|
522
|
+
* Write a JSON response with the given status code.
|
|
523
|
+
* Applies ctx.resHeaders before writing.
|
|
524
|
+
*/
|
|
525
|
+
function _writeJsonResponse(res, body, status, resHeaders = {}) {
|
|
526
|
+
if (res.headersSent)
|
|
527
|
+
return;
|
|
528
|
+
let json;
|
|
529
|
+
let finalStatus = status;
|
|
530
|
+
try {
|
|
531
|
+
json = JSON.stringify(body);
|
|
532
|
+
}
|
|
533
|
+
catch {
|
|
534
|
+
finalStatus = 500;
|
|
535
|
+
json = JSON.stringify({
|
|
536
|
+
__type: "AppError",
|
|
537
|
+
message: "RPC response is not JSON-serializable",
|
|
538
|
+
status: 500,
|
|
539
|
+
code: "SERIALIZATION_ERROR",
|
|
540
|
+
});
|
|
541
|
+
}
|
|
542
|
+
const headers = {
|
|
543
|
+
"Content-Type": "application/json; charset=utf-8",
|
|
544
|
+
"Content-Length": Buffer.byteLength(json).toString(),
|
|
545
|
+
...resHeaders,
|
|
546
|
+
};
|
|
547
|
+
try {
|
|
548
|
+
res.writeHead(finalStatus, headers);
|
|
549
|
+
res.end(json);
|
|
550
|
+
}
|
|
551
|
+
catch {
|
|
552
|
+
// Connection was closed before we could write — ignore
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
/**
|
|
556
|
+
* Write a JSON error response.
|
|
557
|
+
* Does NOT apply ctx.resHeaders (caller should do that separately if needed).
|
|
558
|
+
*/
|
|
559
|
+
function _writeErrorResponse(res, error, status) {
|
|
560
|
+
if (res.headersSent)
|
|
561
|
+
return;
|
|
562
|
+
_writeJsonResponse(res, error, status);
|
|
563
|
+
}
|
|
564
|
+
//# sourceMappingURL=rpc-runner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rpc-runner.js","sourceRoot":"","sources":["../src/rpc-runner.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* mates-ssr — sanitize.ts
|
|
3
|
+
*
|
|
4
|
+
* Automatic XSS sanitization of parsed JSON request bodies.
|
|
5
|
+
*
|
|
6
|
+
* Runs after JSON.parse on every API route body and serverFn arg list.
|
|
7
|
+
* Walks the object tree and cleans every string value.
|
|
8
|
+
*
|
|
9
|
+
* No external dependencies — pure string operations.
|
|
10
|
+
*
|
|
11
|
+
* Rules:
|
|
12
|
+
* - Only string values are touched
|
|
13
|
+
* - Numbers, booleans, null, undefined pass through unchanged
|
|
14
|
+
* - Objects and arrays are recursed into
|
|
15
|
+
* - Fields in the skip list are never touched
|
|
16
|
+
* - Circular references are safely handled
|
|
17
|
+
*/
|
|
18
|
+
/// <reference types="node" />
|
|
19
|
+
export type SanitizeMode = "strip" | "escape" | "reject";
|
|
20
|
+
export interface SanitizeConfig {
|
|
21
|
+
enabled: boolean;
|
|
22
|
+
mode: SanitizeMode;
|
|
23
|
+
skip: string[];
|
|
24
|
+
}
|
|
25
|
+
export declare class SanitizeError extends Error {
|
|
26
|
+
readonly status = 400;
|
|
27
|
+
readonly code = "XSS_DETECTED";
|
|
28
|
+
readonly field: string;
|
|
29
|
+
constructor(field: string);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Sanitize a single string value.
|
|
33
|
+
* Returns the cleaned string or throws SanitizeError in "reject" mode.
|
|
34
|
+
*/
|
|
35
|
+
export declare function sanitizeString(value: string, fieldName: string, mode: SanitizeMode): string;
|
|
36
|
+
/**
|
|
37
|
+
* Recursively sanitize all string values in an object or array.
|
|
38
|
+
*
|
|
39
|
+
* @param input The parsed JSON value (object, array, primitive)
|
|
40
|
+
* @param config Sanitize configuration
|
|
41
|
+
* @param fieldName Current field path (for error messages)
|
|
42
|
+
* @param seen WeakSet to detect circular references
|
|
43
|
+
* @returns The sanitized value (mutates strings in place for objects)
|
|
44
|
+
*/
|
|
45
|
+
export declare function sanitizeBody(input: unknown, config: SanitizeConfig, fieldName?: string, seen?: WeakSet<object>): unknown;
|
|
46
|
+
/**
|
|
47
|
+
* Sanitize a WebSocket message.
|
|
48
|
+
* Parses JSON if the message is a string, sanitizes all string fields,
|
|
49
|
+
* and returns the cleaned object (or the original string if not JSON).
|
|
50
|
+
*
|
|
51
|
+
* @param msg Raw WebSocket message (string or Buffer)
|
|
52
|
+
* @param config Sanitize config
|
|
53
|
+
* @returns { parsed: any, raw: string } — parsed is the sanitized object,
|
|
54
|
+
* raw is the original string for pass-through if needed
|
|
55
|
+
*/
|
|
56
|
+
export declare function sanitizeWsMessage(msg: string | Buffer, config: SanitizeConfig): {
|
|
57
|
+
parsed: unknown;
|
|
58
|
+
raw: string;
|
|
59
|
+
isJson: boolean;
|
|
60
|
+
};
|
|
61
|
+
//# sourceMappingURL=sanitize.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sanitize.d.ts","sourceRoot":"","sources":["../src/sanitize.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;;AAkCH,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAEzD,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,YAAY,CAAC;IACnB,IAAI,EAAE,MAAM,EAAE,CAAC;CAChB;AA4CD,qBAAa,aAAc,SAAQ,KAAK;IACtC,QAAQ,CAAC,MAAM,OAAO;IACtB,QAAQ,CAAC,IAAI,kBAAkB;IAC/B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;gBAEX,KAAK,EAAE,MAAM;CAK1B;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAC5B,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,YAAY,GACjB,MAAM,CAUR;AAED;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAC1B,KAAK,EAAE,OAAO,EACd,MAAM,EAAE,cAAc,EACtB,SAAS,SAAS,EAClB,IAAI,kBAAwB,GAC3B,OAAO,CAmDT;AAED;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAC/B,GAAG,EAAE,MAAM,GAAG,MAAM,EACpB,MAAM,EAAE,cAAc,GACrB;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,OAAO,CAAA;CAAE,CAuBnD"}
|