@nocobase/server 2.2.23 → 2.2.24

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.
@@ -50,7 +50,10 @@ const _MainOnlyAdapter = class _MainOnlyAdapter {
50
50
  return this.apps[appName];
51
51
  }
52
52
  async bootstrapApp(appName) {
53
- if (appName !== "main" || !this.apps[appName]) {
53
+ if (appName !== "main") {
54
+ return;
55
+ }
56
+ if (!this.apps[appName]) {
54
57
  this.setAppStatus(appName, "not_found");
55
58
  return;
56
59
  }
@@ -138,13 +141,16 @@ const _MainOnlyAdapter = class _MainOnlyAdapter {
138
141
  return this.removeApp("main");
139
142
  }
140
143
  setAppStatus(appName, status, options = {}) {
141
- if (this.status === status) {
144
+ if (appName !== "main" || this.status === status) {
142
145
  return;
143
146
  }
144
147
  this.status = status;
145
148
  this.supervisor.emit("appStatusChanged", { appName, status, options });
146
149
  }
147
150
  getAppStatus(appName, defaultStatus) {
151
+ if (appName !== "main") {
152
+ return "not_found";
153
+ }
148
154
  return this.status ?? defaultStatus ?? null;
149
155
  }
150
156
  hasAppError(appName) {
@@ -6,6 +6,11 @@
6
6
  * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
7
  * For more information, please refer to: https://www.nocobase.com/agreement.
8
8
  */
9
+ /** Raised when a request resolves to an app name that could never name a real application. */
10
+ export declare class InvalidAppNameError extends Error {
11
+ readonly appName: unknown;
12
+ constructor(appName: unknown);
13
+ }
9
14
  interface AppError {
10
15
  status: number;
11
16
  message: any;
@@ -37,6 +37,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
37
37
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
38
38
  var errors_exports = {};
39
39
  __export(errors_exports, {
40
+ InvalidAppNameError: () => InvalidAppNameError,
40
41
  applyErrorWithArgs: () => applyErrorWithArgs,
41
42
  errors: () => errors,
42
43
  getErrorWithCode: () => getErrorWithCode
@@ -44,6 +45,15 @@ __export(errors_exports, {
44
45
  module.exports = __toCommonJS(errors_exports);
45
46
  var import_app_supervisor = require("../app-supervisor");
46
47
  var import_lodash = __toESM(require("lodash"));
48
+ const _InvalidAppNameError = class _InvalidAppNameError extends Error {
49
+ constructor(appName) {
50
+ super("invalid application name");
51
+ this.appName = appName;
52
+ this.name = "InvalidAppNameError";
53
+ }
54
+ };
55
+ __name(_InvalidAppNameError, "InvalidAppNameError");
56
+ let InvalidAppNameError = _InvalidAppNameError;
47
57
  function getMaintaining(app) {
48
58
  var _a;
49
59
  return (_a = app == null ? void 0 : app.getMaintaining) == null ? void 0 : _a.call(app);
@@ -54,6 +64,11 @@ function getAppName(app) {
54
64
  }
55
65
  __name(getAppName, "getAppName");
56
66
  const errors = {
67
+ INVALID_APP_NAME: {
68
+ status: 400,
69
+ message: "invalid application name",
70
+ maintaining: false
71
+ },
57
72
  APP_ENVIRONMENT_UNAVAILABLE: {
58
73
  status: 503,
59
74
  message: /* @__PURE__ */ __name(({ appName }) => `deployment environment for application ${appName} is unavailable`, "message"),
@@ -191,6 +206,7 @@ function applyErrorWithArgs(error, options) {
191
206
  __name(applyErrorWithArgs, "applyErrorWithArgs");
192
207
  // Annotate the CommonJS export names for ESM import in node:
193
208
  0 && (module.exports = {
209
+ InvalidAppNameError,
194
210
  applyErrorWithArgs,
195
211
  errors,
196
212
  getErrorWithCode
@@ -90,6 +90,7 @@ export declare class Gateway extends EventEmitter {
90
90
  requestHandler(req: IncomingMessage, res: ServerResponse): Promise<void>;
91
91
  getAppSelectorMiddlewares(): Toposort<AppSelectorMiddleware>;
92
92
  getRequestHandleAppName(req: IncomingMessage | IncomingRequest): Promise<string>;
93
+ private handleUpgrade;
93
94
  getCallback(): any;
94
95
  watch(): Promise<void>;
95
96
  run(options: RunOptions): Promise<void>;
@@ -72,6 +72,15 @@ var import_node_worker_threads = require("node:worker_threads");
72
72
  var import_node_process = __toESM(require("node:process"));
73
73
  var import_utils4 = require("./utils");
74
74
  const compress = (0, import_node_util.promisify)((0, import_compression.default)());
75
+ const APP_NAME_PATTERN = /^[A-Za-z0-9_][A-Za-z0-9_-]*$/;
76
+ const MAX_APP_NAME_LENGTH = 255;
77
+ function isValidAppName(name) {
78
+ if (typeof name !== "string" || name.length > MAX_APP_NAME_LENGTH || !APP_NAME_PATTERN.test(name)) {
79
+ return false;
80
+ }
81
+ return !(name in Object.prototype);
82
+ }
83
+ __name(isValidAppName, "isValidAppName");
75
84
  function normalizeBasePath(path = "") {
76
85
  const normalized = path.replace(/\/+/g, "/").replace(/\/$/, "");
77
86
  return normalized || "/";
@@ -261,6 +270,9 @@ const _Gateway = class _Gateway extends import_events.EventEmitter {
261
270
  this.emit("appSelectorChanged");
262
271
  }
263
272
  getLogger(appName, res) {
273
+ if (!isValidAppName(appName)) {
274
+ throw new Error("Invalid app name");
275
+ }
264
276
  const reqId = (0, import_crypto.randomUUID)();
265
277
  res.setHeader("X-Request-Id", reqId);
266
278
  let logger = this.loggers.get(appName);
@@ -284,7 +296,9 @@ const _Gateway = class _Gateway extends import_events.EventEmitter {
284
296
  res.end(JSON.stringify({ error }));
285
297
  }
286
298
  responseErrorWithCode(code, res, options) {
287
- const log = this.getLogger(options.appName, res);
299
+ const appName = options == null ? void 0 : options.appName;
300
+ const loggerName = isValidAppName(appName) && import_app_supervisor.AppSupervisor.getInstance().hasApp(appName) ? appName : "main";
301
+ const log = this.getLogger(loggerName, res);
288
302
  const error = (0, import_errors.applyErrorWithArgs)((0, import_errors.getErrorWithCode)(code), options);
289
303
  log.error(error.message, {
290
304
  method: "responseErrorWithCode",
@@ -387,6 +401,10 @@ const _Gateway = class _Gateway extends import_events.EventEmitter {
387
401
  try {
388
402
  handleApp = await this.getRequestHandleAppName(req);
389
403
  } catch (error) {
404
+ if (error instanceof import_errors.InvalidAppNameError) {
405
+ this.responseErrorWithCode("INVALID_APP_NAME", res, {});
406
+ return;
407
+ }
390
408
  this.getLogger("main", res).error("Failed to get handle app name", { error });
391
409
  this.responseErrorWithCode("APP_INITIALIZING", res, { appName: handleApp });
392
410
  return;
@@ -538,8 +556,32 @@ const _Gateway = class _Gateway extends import_events.EventEmitter {
538
556
  if (!ctx.resolvedAppName) {
539
557
  ctx.resolvedAppName = "main";
540
558
  }
559
+ if (!isValidAppName(ctx.resolvedAppName)) {
560
+ throw new import_errors.InvalidAppNameError(ctx.resolvedAppName);
561
+ }
541
562
  return ctx.resolvedAppName;
542
563
  }
564
+ async handleUpgrade(request, socket, head) {
565
+ const isProxy = await import_app_supervisor.AppSupervisor.getInstance().proxyWs(request, socket, head);
566
+ if (isProxy) {
567
+ return;
568
+ }
569
+ const appInstance = await import_app_supervisor.AppSupervisor.getInstance().getApp("main");
570
+ for (const handle of _Gateway.wsServers) {
571
+ const result = await handle(request, socket, head, appInstance);
572
+ if (result !== false) {
573
+ return;
574
+ }
575
+ }
576
+ const { pathname } = (0, import_url.parse)(request.url);
577
+ if (pathname === import_node_process.default.env.WS_PATH) {
578
+ this.wsServer.wss.handleUpgrade(request, socket, head, (ws) => {
579
+ this.wsServer.wss.emit("connection", ws, request);
580
+ });
581
+ } else {
582
+ socket.destroy();
583
+ }
584
+ }
543
585
  getCallback() {
544
586
  return this.requestHandler.bind(this);
545
587
  }
@@ -656,26 +698,11 @@ const _Gateway = class _Gateway extends import_events.EventEmitter {
656
698
  this.getCallback()(req, res);
657
699
  });
658
700
  this.wsServer = new import_ws_server.WSServer();
659
- this.server.on("upgrade", async (request, socket, head) => {
660
- const isProxy = await import_app_supervisor.AppSupervisor.getInstance().proxyWs(request, socket, head);
661
- if (isProxy) {
662
- return;
663
- }
664
- const appInstance = await import_app_supervisor.AppSupervisor.getInstance().getApp("main");
665
- for (const handle of _Gateway.wsServers) {
666
- const result = await handle(request, socket, head, appInstance);
667
- if (result !== false) {
668
- return;
669
- }
670
- }
671
- const { pathname } = (0, import_url.parse)(request.url);
672
- if (pathname === import_node_process.default.env.WS_PATH) {
673
- this.wsServer.wss.handleUpgrade(request, socket, head, (ws) => {
674
- this.wsServer.wss.emit("connection", ws, request);
675
- });
676
- } else {
701
+ this.server.on("upgrade", (request, socket, head) => {
702
+ this.handleUpgrade(request, socket, head).catch((error) => {
703
+ import_app_supervisor.AppSupervisor.getInstance().logger.error("Failed to handle websocket upgrade", { error });
677
704
  socket.destroy();
678
- }
705
+ });
679
706
  });
680
707
  this.server.listen(this.port, this.host, () => {
681
708
  console.log(`Gateway HTTP Server running at http://${this.host}:${this.port}/`);
@@ -32,6 +32,11 @@ export declare class WSServer extends EventEmitter {
32
32
  addNewConnection(ws: WebSocketWithId, request: IncomingMessage): WebSocketClient;
33
33
  setClientTag(clientId: string, tagKey: string, tagValue: string): void;
34
34
  removeClientTag(clientId: string, tagKey: string): void;
35
+ /**
36
+ * Drop a connection whose url or headers resolve to an app name that can never exist. Closing beats tagging it with
37
+ * the raw value: the tag would end up as a map key and would bootstrap an app on every selector change.
38
+ */
39
+ private rejectInvalidAppName;
35
40
  setClientApp(client: WebSocketClient): Promise<void>;
36
41
  removeConnection(id: string): void;
37
42
  sendMessageToConnection(client: WebSocketClient, sendMessage: object): void;
@@ -80,10 +80,19 @@ const _WSServer = class _WSServer extends import_events.default {
80
80
  });
81
81
  import_gateway.Gateway.getInstance().on("appSelectorChanged", () => {
82
82
  this.loopThroughConnections(async (client) => {
83
- const handleAppName = await import_gateway.Gateway.getInstance().getRequestHandleAppName({
84
- url: client.url,
85
- headers: client.headers
86
- });
83
+ let handleAppName;
84
+ try {
85
+ handleAppName = await import_gateway.Gateway.getInstance().getRequestHandleAppName({
86
+ url: client.url,
87
+ headers: client.headers
88
+ });
89
+ } catch (error) {
90
+ if (!(error instanceof import_errors.InvalidAppNameError)) {
91
+ throw error;
92
+ }
93
+ this.rejectInvalidAppName(client);
94
+ return;
95
+ }
87
96
  for (const tag of client.tags) {
88
97
  if (tag.startsWith("app#")) {
89
98
  client.tags.delete(tag);
@@ -238,12 +247,29 @@ const _WSServer = class _WSServer extends import_events.default {
238
247
  }
239
248
  });
240
249
  }
250
+ /**
251
+ * Drop a connection whose url or headers resolve to an app name that can never exist. Closing beats tagging it with
252
+ * the raw value: the tag would end up as a map key and would bootstrap an app on every selector change.
253
+ */
254
+ rejectInvalidAppName(client) {
255
+ client.ws.close(1008, "INVALID_APP_NAME");
256
+ this.removeConnection(client.id);
257
+ }
241
258
  async setClientApp(client) {
242
259
  const req = {
243
260
  url: client.url,
244
261
  headers: client.headers
245
262
  };
246
- const handleAppName = await import_gateway.Gateway.getInstance().getRequestHandleAppName(req);
263
+ let handleAppName;
264
+ try {
265
+ handleAppName = await import_gateway.Gateway.getInstance().getRequestHandleAppName(req);
266
+ } catch (error) {
267
+ if (!(error instanceof import_errors.InvalidAppNameError)) {
268
+ throw error;
269
+ }
270
+ this.rejectInvalidAppName(client);
271
+ return;
272
+ }
247
273
  client.app = handleAppName;
248
274
  console.log(`client tags: app#${handleAppName}`);
249
275
  client.tags.add(`app#${handleAppName}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nocobase/server",
3
- "version": "2.2.23",
3
+ "version": "2.2.24",
4
4
  "main": "lib/index.js",
5
5
  "types": "./lib/index.d.ts",
6
6
  "license": "Apache-2.0",
@@ -10,21 +10,21 @@
10
10
  "@koa/cors": "^5.0.0",
11
11
  "@koa/multer": "^3.1.0",
12
12
  "@koa/router": "^13.1.0",
13
- "@nocobase/acl": "2.2.23",
14
- "@nocobase/actions": "2.2.23",
15
- "@nocobase/ai": "2.2.23",
16
- "@nocobase/auth": "2.2.23",
17
- "@nocobase/cache": "2.2.23",
18
- "@nocobase/data-source-manager": "2.2.23",
19
- "@nocobase/database": "2.2.23",
20
- "@nocobase/evaluators": "2.2.23",
21
- "@nocobase/lock-manager": "2.2.23",
22
- "@nocobase/logger": "2.2.23",
23
- "@nocobase/resourcer": "2.2.23",
24
- "@nocobase/sdk": "2.2.23",
25
- "@nocobase/snowflake-id": "2.2.23",
26
- "@nocobase/telemetry": "2.2.23",
27
- "@nocobase/utils": "2.2.23",
13
+ "@nocobase/acl": "2.2.24",
14
+ "@nocobase/actions": "2.2.24",
15
+ "@nocobase/ai": "2.2.24",
16
+ "@nocobase/auth": "2.2.24",
17
+ "@nocobase/cache": "2.2.24",
18
+ "@nocobase/data-source-manager": "2.2.24",
19
+ "@nocobase/database": "2.2.24",
20
+ "@nocobase/evaluators": "2.2.24",
21
+ "@nocobase/lock-manager": "2.2.24",
22
+ "@nocobase/logger": "2.2.24",
23
+ "@nocobase/resourcer": "2.2.24",
24
+ "@nocobase/sdk": "2.2.24",
25
+ "@nocobase/snowflake-id": "2.2.24",
26
+ "@nocobase/telemetry": "2.2.24",
27
+ "@nocobase/utils": "2.2.24",
28
28
  "@types/ini": "^1.3.31",
29
29
  "@types/koa-send": "^4.1.3",
30
30
  "@types/multer": "^1.4.12",
@@ -60,5 +60,5 @@
60
60
  "@types/serve-handler": "^6.1.1",
61
61
  "@types/ws": "^8.5.5"
62
62
  },
63
- "gitHead": "71852bf1353c21818600ffe7b2a38aa68b5e47e6"
63
+ "gitHead": "b067d4c18834112e59bb72b2084e872c59d64f9c"
64
64
  }