godprotocol 2.3.64 → 2.3.66
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/package.json
CHANGED
|
@@ -247,21 +247,26 @@ class Headers extends Services {
|
|
|
247
247
|
return requirements.every((r) => checks[r]?.());
|
|
248
248
|
};
|
|
249
249
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
250
|
+
/**
|
|
251
|
+
* NOTE: `route` must already be resolved (via Route_table.get_route)
|
|
252
|
+
* by the caller. This method no longer resolves routing itself —
|
|
253
|
+
* that responsibility now lives entirely in Route_table.get_route,
|
|
254
|
+
* called once per request by version_middleware BEFORE the body is
|
|
255
|
+
* read. This keeps security a pure "given this route's requirements,
|
|
256
|
+
* is this request authorized" check, with no routing side effects.
|
|
257
|
+
*
|
|
258
|
+
* `route` will be `null`/`undefined` for the special "*" catch-all
|
|
259
|
+
* version, since that version has no route table to resolve against
|
|
260
|
+
* (see Route_table.execute's version_name === "*" branch). In that
|
|
261
|
+
* case no security requirement can be read, so this returns `true`
|
|
262
|
+
* (no enforcement) — identical in effect to the previous behavior,
|
|
263
|
+
* where that code path was unreachable/inert for the "*" version.
|
|
264
|
+
*/
|
|
265
|
+
handle_security = async (route, request, version) => {
|
|
262
266
|
if (!route?.config?.security?.length) return true;
|
|
263
267
|
|
|
264
|
-
config = route.config;
|
|
268
|
+
const config = route.config;
|
|
269
|
+
|
|
265
270
|
if (!this.check_security(config.security, request)) {
|
|
266
271
|
// debug("Unauthorized access attempt to route:", name);
|
|
267
272
|
return {
|
|
@@ -263,6 +263,12 @@ class Route_table extends Headers {
|
|
|
263
263
|
}
|
|
264
264
|
|
|
265
265
|
for (const route of version.regex_routes) {
|
|
266
|
+
// route.regex may have been constructed with a "g"/"y" flag, which
|
|
267
|
+
// makes .test() stateful (it advances lastIndex on match). Reset
|
|
268
|
+
// it before every use so route matching stays deterministic
|
|
269
|
+
// regardless of how the route's regex was authored.
|
|
270
|
+
route.regex.lastIndex = 0;
|
|
271
|
+
|
|
266
272
|
if (route.regex.test(name)) {
|
|
267
273
|
return {
|
|
268
274
|
handler: route.handler,
|
|
@@ -616,14 +622,28 @@ class Route_table extends Headers {
|
|
|
616
622
|
// =========================
|
|
617
623
|
// 🚀 EXECUTION PIPELINE
|
|
618
624
|
// =========================
|
|
619
|
-
|
|
625
|
+
// `route`, when provided, is a route object already resolved by
|
|
626
|
+
// Route_table.get_route (e.g. by version_middleware, which now
|
|
627
|
+
// resolves the route once up front, before the request body is
|
|
628
|
+
// even read). If omitted, execute() falls back to resolving it
|
|
629
|
+
// itself, exactly as before — so any existing caller that invokes
|
|
630
|
+
// execute(name, payload, version) without a route keeps working
|
|
631
|
+
// unchanged.
|
|
632
|
+
execute = async (
|
|
633
|
+
name,
|
|
634
|
+
payload,
|
|
635
|
+
version_name = this.active_version,
|
|
636
|
+
route = null,
|
|
637
|
+
) => {
|
|
620
638
|
if (version_name === "*") {
|
|
621
639
|
let version = this.versions[version_name];
|
|
622
640
|
|
|
623
641
|
return await version.handler(payload);
|
|
624
642
|
}
|
|
625
643
|
|
|
626
|
-
|
|
644
|
+
if (!route) {
|
|
645
|
+
route = await this.get_route(name, version_name);
|
|
646
|
+
}
|
|
627
647
|
|
|
628
648
|
if (!route) {
|
|
629
649
|
return {
|
|
@@ -155,6 +155,7 @@ class Services {
|
|
|
155
155
|
|
|
156
156
|
version_db = async (ver) => {
|
|
157
157
|
let version = await this.get_version(ver);
|
|
158
|
+
|
|
158
159
|
if (!version) {
|
|
159
160
|
let vers = Object.keys(this.versions);
|
|
160
161
|
|
|
@@ -170,7 +171,7 @@ class Services {
|
|
|
170
171
|
return;
|
|
171
172
|
}
|
|
172
173
|
|
|
173
|
-
let db = await this.platform_db();
|
|
174
|
+
let db = await this.platform_db(version?.db_name);
|
|
174
175
|
|
|
175
176
|
return db;
|
|
176
177
|
};
|
|
@@ -261,6 +261,7 @@ const version_middleware = async (req, res, routers) => {
|
|
|
261
261
|
let body = {};
|
|
262
262
|
let name;
|
|
263
263
|
let router;
|
|
264
|
+
let route; // resolved route object (new — see ROUTE RESOLUTION stage below)
|
|
264
265
|
let securityResult;
|
|
265
266
|
let callback;
|
|
266
267
|
let payload;
|
|
@@ -327,20 +328,28 @@ const version_middleware = async (req, res, routers) => {
|
|
|
327
328
|
|
|
328
329
|
version = versionRouter.version;
|
|
329
330
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
331
|
+
// Body is parsed lazily, on demand — see ROUTE RESOLUTION / SECURITY
|
|
332
|
+
// below for why this moved out of being unconditional up front.
|
|
333
|
+
const parseBody = async () => {
|
|
334
|
+
if (req.method === "GET") return query;
|
|
335
|
+
|
|
333
336
|
const contentType = req.headers["content-type"] || "";
|
|
334
337
|
|
|
335
338
|
if (contentType.includes("multipart/form-data")) {
|
|
336
|
-
|
|
337
|
-
} else {
|
|
338
|
-
error_stage = "Body Parser";
|
|
339
|
-
body = await getBody(req);
|
|
339
|
+
return await parseMultipart(req, routers.gp);
|
|
340
340
|
}
|
|
341
|
-
}
|
|
342
341
|
|
|
342
|
+
return await getBody(req);
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
// Legacy ("is_old") versions don't go through the route table or the
|
|
346
|
+
// new security pipeline at all — they hand the raw req/res straight
|
|
347
|
+
// to their own handler, same as before. They still need the body
|
|
348
|
+
// read up front since there's no route-based gate to check first.
|
|
343
349
|
if (versionRouter.config?.is_old) {
|
|
350
|
+
error_stage = "Body Parser";
|
|
351
|
+
body = await parseBody();
|
|
352
|
+
|
|
344
353
|
req.body = body;
|
|
345
354
|
return versionRouter.routes(req, res);
|
|
346
355
|
}
|
|
@@ -364,14 +373,66 @@ const version_middleware = async (req, res, routers) => {
|
|
|
364
373
|
|
|
365
374
|
const isWebhook = req.headers["is-webhook"];
|
|
366
375
|
|
|
376
|
+
// -----------------------------------------------------------------
|
|
377
|
+
// ROUTE RESOLUTION
|
|
378
|
+
// -----------------------------------------------------------------
|
|
379
|
+
//
|
|
380
|
+
// Resolve the route BEFORE touching the request body. This is what
|
|
381
|
+
// actually fixes the multipart-before-routing-check problem: a
|
|
382
|
+
// request to a route that doesn't exist (or that fails auth) now
|
|
383
|
+
// never has its body read/parsed at all — for multipart uploads in
|
|
384
|
+
// particular, that means never buffering/streaming a file for a
|
|
385
|
+
// request that was always going to be rejected.
|
|
386
|
+
//
|
|
387
|
+
// The "*" catch-all version has no per-route table (its handler is
|
|
388
|
+
// registered directly against the version, not through add_route),
|
|
389
|
+
// so route resolution is skipped for it and `route` stays undefined
|
|
390
|
+
// — execute() already special-cases version_name === "*" and calls
|
|
391
|
+
// the version's handler directly, exactly as before.
|
|
392
|
+
//
|
|
393
|
+
error_stage = "Route";
|
|
394
|
+
|
|
395
|
+
if (version !== "*") {
|
|
396
|
+
route = await router.get_route(name, version);
|
|
397
|
+
|
|
398
|
+
if (!route) {
|
|
399
|
+
return respond(
|
|
400
|
+
{
|
|
401
|
+
ok: false,
|
|
402
|
+
status: 404,
|
|
403
|
+
message: "Route not found",
|
|
404
|
+
data: { path: `/${name}`, name, version },
|
|
405
|
+
},
|
|
406
|
+
res,
|
|
407
|
+
);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// -----------------------------------------------------------------
|
|
367
412
|
// SECURITY
|
|
413
|
+
// -----------------------------------------------------------------
|
|
414
|
+
//
|
|
415
|
+
// Runs against the already-resolved route's config — no routing
|
|
416
|
+
// side effects here anymore (see Header.js handle_security).
|
|
417
|
+
//
|
|
368
418
|
error_stage = "Header";
|
|
369
|
-
securityResult = await router.handle_security(
|
|
419
|
+
securityResult = await router.handle_security(route, req, version); // was: name-based lookup inside handle_security
|
|
370
420
|
|
|
371
421
|
if (securityResult !== true) {
|
|
372
422
|
return respond(securityResult, res);
|
|
373
423
|
}
|
|
374
424
|
|
|
425
|
+
// -----------------------------------------------------------------
|
|
426
|
+
// BODY
|
|
427
|
+
// -----------------------------------------------------------------
|
|
428
|
+
//
|
|
429
|
+
// Only now — after we know the route exists and the request is
|
|
430
|
+
// authorized — do we read the body. This is the stage that used to
|
|
431
|
+
// run unconditionally before route/security checks.
|
|
432
|
+
//
|
|
433
|
+
error_stage = "Body Parser";
|
|
434
|
+
body = await parseBody();
|
|
435
|
+
|
|
375
436
|
// DB
|
|
376
437
|
const db = await router.resolve_db(req, {
|
|
377
438
|
version,
|
|
@@ -416,7 +477,7 @@ const version_middleware = async (req, res, routers) => {
|
|
|
416
477
|
params: {},
|
|
417
478
|
memory: router.gp.memory,
|
|
418
479
|
};
|
|
419
|
-
result = await router.execute(name, payload, version);
|
|
480
|
+
result = await router.execute(name, payload, version, route); // pass the already-resolved route through — no second lookup
|
|
420
481
|
|
|
421
482
|
// WEBHOOK AFTER
|
|
422
483
|
if (!isWebhook && version !== "*") {
|
|
@@ -427,6 +488,7 @@ const version_middleware = async (req, res, routers) => {
|
|
|
427
488
|
result,
|
|
428
489
|
headers: req.headers,
|
|
429
490
|
version,
|
|
491
|
+
matched_route_name: name,
|
|
430
492
|
route: payload.route_pattern || name,
|
|
431
493
|
db: db.db,
|
|
432
494
|
});
|