godprotocol 2.3.63 → 2.3.65

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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "godprotocol",
3
- "version": "2.3.63",
3
+ "version": "2.3.65",
4
4
  "description": "A distributed computing environment for Web 4.0 — integrating AI, decentralisation, and virtual computation.",
5
5
  "main": "Index.js",
6
6
  "type": "module",
@@ -247,21 +247,26 @@ class Headers extends Services {
247
247
  return requirements.every((r) => checks[r]?.());
248
248
  };
249
249
 
250
- handle_security = async (name, request, version) => {
251
- let config;
252
- if (version === "*") {
253
- let starVersion = this.versions[version];
254
-
255
- config = starVersion.config;
256
- }
257
-
258
- let route = await this.get_route(name, version);
259
- if (!route) {
260
- route = await this.get_route("*", version);
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
- execute = async (name, payload, version_name = this.active_version) => {
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
- let route = await this.get_route(name, version_name);
644
+ if (!route) {
645
+ route = await this.get_route(name, version_name);
646
+ }
627
647
 
628
648
  if (!route) {
629
649
  return {
@@ -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
- if (req.method === "GET") {
331
- body = query;
332
- } else {
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
- body = await parseMultipart(req, routers.gp);
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(name, req, version); // was: const securityResult =
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 !== "*") {