@nocobase/server 2.3.0-alpha.1 → 2.3.0-beta.10
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/lib/acl/available-action.js +1 -1
- package/lib/app-supervisor/index.d.ts +4 -1
- package/lib/app-supervisor/index.js +29 -17
- package/lib/app-supervisor/types.d.ts +1 -1
- package/lib/event-queue.d.ts +3 -2
- package/lib/event-queue.js +22 -13
- package/lib/gateway/errors.js +5 -0
- package/lib/gateway/index.js +13 -21
- package/lib/gateway/static-file-security.js +14 -1
- package/lib/helper.d.ts +1 -0
- package/lib/helper.js +64 -8
- package/lib/index.d.ts +1 -0
- package/lib/index.js +2 -0
- package/lib/swagger/app.d.ts +17 -1
- package/lib/swagger/app.js +16 -1
- package/lib/swagger/index.d.ts +17 -1
- package/package.json +17 -17
|
@@ -53,6 +53,7 @@ export declare class AppSupervisor extends EventEmitter implements AsyncEmitter
|
|
|
53
53
|
private appSsoIssuer?;
|
|
54
54
|
appOptionsFactory: AppOptionsFactory;
|
|
55
55
|
private environmentHeartbeatInterval;
|
|
56
|
+
private environmentHeartbeatTimeout;
|
|
56
57
|
private environmentHeartbeatTimer;
|
|
57
58
|
private constructor();
|
|
58
59
|
private resolveDiscoveryAdapterName;
|
|
@@ -163,11 +164,13 @@ export declare class AppSupervisor extends EventEmitter implements AsyncEmitter
|
|
|
163
164
|
proxyWeb(appName: string, req: IncomingMessage, res: ServerResponse): Promise<boolean>;
|
|
164
165
|
proxyWs(req: IncomingMessage, socket: any, head: Buffer): Promise<boolean>;
|
|
165
166
|
registerEnvironment(mainApp: Application): Promise<void>;
|
|
167
|
+
private getEnvironmentInfo;
|
|
166
168
|
unregisterEnvironment(): Promise<void>;
|
|
167
169
|
private normalizeEnvInfo;
|
|
168
170
|
listEnvironments(): Promise<EnvironmentInfo[]>;
|
|
169
171
|
getEnvironment(environmentName: string): Promise<EnvironmentInfo>;
|
|
170
|
-
heartbeatEnvironment(): Promise<void>;
|
|
172
|
+
heartbeatEnvironment(mainApp: Application): Promise<void>;
|
|
173
|
+
private stopEnvironmentHeartbeat;
|
|
171
174
|
dispatchCommand(command: ProcessCommand): Promise<void>;
|
|
172
175
|
registerCommandHandler(mainApp: Application): void;
|
|
173
176
|
sendSyncMessage(mainApp: Application, message: {
|
|
@@ -74,6 +74,7 @@ const _AppSupervisor = class _AppSupervisor extends import_events.EventEmitter {
|
|
|
74
74
|
appSsoIssuer;
|
|
75
75
|
appOptionsFactory = import_app_options_factory.appOptionsFactory;
|
|
76
76
|
environmentHeartbeatInterval = 2 * 60 * 1e3;
|
|
77
|
+
environmentHeartbeatTimeout = 5 * 60 * 1e3;
|
|
77
78
|
environmentHeartbeatTimer = null;
|
|
78
79
|
constructor() {
|
|
79
80
|
super();
|
|
@@ -282,13 +283,11 @@ const _AppSupervisor = class _AppSupervisor extends import_events.EventEmitter {
|
|
|
282
283
|
}
|
|
283
284
|
async reset() {
|
|
284
285
|
var _a, _b, _c, _d;
|
|
286
|
+
this.stopEnvironmentHeartbeat();
|
|
285
287
|
await this.processAdapter.removeAllApps();
|
|
286
288
|
await ((_b = (_a = this.discoveryAdapter).dispose) == null ? void 0 : _b.call(_a));
|
|
287
289
|
await ((_d = (_c = this.commandAdapter) == null ? void 0 : _c.dispose) == null ? void 0 : _d.call(_c));
|
|
288
290
|
this.appManifests.clear();
|
|
289
|
-
if (this.environmentHeartbeatTimer) {
|
|
290
|
-
this.environmentHeartbeatTimer = null;
|
|
291
|
-
}
|
|
292
291
|
this.removeAllListeners();
|
|
293
292
|
this.logger.close();
|
|
294
293
|
}
|
|
@@ -389,6 +388,9 @@ const _AppSupervisor = class _AppSupervisor extends import_events.EventEmitter {
|
|
|
389
388
|
if (!appOptions) {
|
|
390
389
|
return;
|
|
391
390
|
}
|
|
391
|
+
if (appModel.environments && !appModel.environments.includes(this.environmentName)) {
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
392
394
|
const newApp = this.registerApp({ appModel, mainApp: app2 });
|
|
393
395
|
newApp.runCommand("start", "--quickstart");
|
|
394
396
|
}
|
|
@@ -601,24 +603,25 @@ const _AppSupervisor = class _AppSupervisor extends import_events.EventEmitter {
|
|
|
601
603
|
if (!this.environmentName || typeof this.discoveryAdapter.registerEnvironment !== "function") {
|
|
602
604
|
return;
|
|
603
605
|
}
|
|
604
|
-
const registered = await this.discoveryAdapter.registerEnvironment(
|
|
606
|
+
const registered = await this.discoveryAdapter.registerEnvironment(this.getEnvironmentInfo(mainApp));
|
|
607
|
+
if (registered) {
|
|
608
|
+
this.heartbeatEnvironment(mainApp);
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
getEnvironmentInfo(mainApp) {
|
|
612
|
+
return {
|
|
605
613
|
name: this.environmentName,
|
|
606
614
|
url: this.environmentUrl || "",
|
|
607
615
|
proxyUrl: this.environmentProxyUrl || this.environmentUrl || "",
|
|
608
616
|
appVersion: mainApp.getPackageVersion(),
|
|
609
617
|
lastHeartbeatAt: Date.now()
|
|
610
|
-
}
|
|
611
|
-
if (registered) {
|
|
612
|
-
this.heartbeatEnvironment();
|
|
613
|
-
}
|
|
618
|
+
};
|
|
614
619
|
}
|
|
615
620
|
async unregisterEnvironment() {
|
|
621
|
+
this.stopEnvironmentHeartbeat();
|
|
616
622
|
if (this.environmentName && typeof this.discoveryAdapter.unregisterEnvironment === "function") {
|
|
617
623
|
await this.discoveryAdapter.unregisterEnvironment();
|
|
618
624
|
}
|
|
619
|
-
if (this.environmentHeartbeatTimer) {
|
|
620
|
-
this.environmentHeartbeatTimer = null;
|
|
621
|
-
}
|
|
622
625
|
}
|
|
623
626
|
normalizeEnvInfo(environment) {
|
|
624
627
|
if (typeof environment.available === "boolean") {
|
|
@@ -631,7 +634,7 @@ const _AppSupervisor = class _AppSupervisor extends import_events.EventEmitter {
|
|
|
631
634
|
available: false
|
|
632
635
|
};
|
|
633
636
|
}
|
|
634
|
-
const available = Date.now() - lastHeartbeatAt <= this.
|
|
637
|
+
const available = Date.now() - lastHeartbeatAt <= this.environmentHeartbeatTimeout;
|
|
635
638
|
return {
|
|
636
639
|
...environment,
|
|
637
640
|
available
|
|
@@ -654,17 +657,26 @@ const _AppSupervisor = class _AppSupervisor extends import_events.EventEmitter {
|
|
|
654
657
|
}
|
|
655
658
|
return this.normalizeEnvInfo(environment);
|
|
656
659
|
}
|
|
657
|
-
async heartbeatEnvironment() {
|
|
660
|
+
async heartbeatEnvironment(mainApp) {
|
|
658
661
|
if (typeof this.discoveryAdapter.heartbeatEnvironment !== "function") {
|
|
659
662
|
return;
|
|
660
663
|
}
|
|
661
664
|
if (this.environmentHeartbeatTimer) {
|
|
662
665
|
return;
|
|
663
666
|
}
|
|
664
|
-
this.environmentHeartbeatTimer = setInterval(
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
667
|
+
this.environmentHeartbeatTimer = setInterval(async () => {
|
|
668
|
+
try {
|
|
669
|
+
await this.discoveryAdapter.heartbeatEnvironment(this.getEnvironmentInfo(mainApp));
|
|
670
|
+
} catch (error) {
|
|
671
|
+
this.logger.error(error instanceof Error ? error.message : String(error), { method: "heartbeatEnvironment" });
|
|
672
|
+
}
|
|
673
|
+
}, this.environmentHeartbeatInterval);
|
|
674
|
+
}
|
|
675
|
+
stopEnvironmentHeartbeat() {
|
|
676
|
+
if (this.environmentHeartbeatTimer) {
|
|
677
|
+
clearInterval(this.environmentHeartbeatTimer);
|
|
678
|
+
this.environmentHeartbeatTimer = null;
|
|
679
|
+
}
|
|
668
680
|
}
|
|
669
681
|
async dispatchCommand(command) {
|
|
670
682
|
var _a;
|
|
@@ -132,7 +132,7 @@ export interface AppDiscoveryAdapter {
|
|
|
132
132
|
unregisterEnvironment?(): Promise<void>;
|
|
133
133
|
listEnvironments?(): Promise<EnvironmentInfo[]>;
|
|
134
134
|
getEnvironment?(environmentName: string): Promise<EnvironmentInfo | null>;
|
|
135
|
-
heartbeatEnvironment?(): Promise<void>;
|
|
135
|
+
heartbeatEnvironment?(environment: EnvironmentInfo): Promise<void>;
|
|
136
136
|
getBootstrapLock?(appName: string): Promise<BootstrapLock | null> | BootstrapLock | null;
|
|
137
137
|
proxyWeb?(appName: string, req: IncomingMessage, res: ServerResponse): Promise<boolean>;
|
|
138
138
|
proxyWs?(req: IncomingMessage, socket: any, head: Buffer): Promise<boolean>;
|
package/lib/event-queue.d.ts
CHANGED
|
@@ -53,17 +53,18 @@ export interface EventQueueOptions {
|
|
|
53
53
|
export declare class MemoryEventQueueAdapter implements IEventQueueAdapter {
|
|
54
54
|
private options;
|
|
55
55
|
private connected;
|
|
56
|
-
private emitter;
|
|
57
56
|
private reading;
|
|
57
|
+
private scheduledChannels;
|
|
58
58
|
protected events: Map<string, QueueEventOptions>;
|
|
59
59
|
protected queues: Map<string, {
|
|
60
60
|
id: string;
|
|
61
61
|
content: any;
|
|
62
62
|
options?: QueueMessageOptions;
|
|
63
63
|
}[]>;
|
|
64
|
-
get processing(): Promise<
|
|
64
|
+
get processing(): Promise<void[]>;
|
|
65
65
|
private get storagePath();
|
|
66
66
|
listen: (channel: string) => void;
|
|
67
|
+
private scheduleListen;
|
|
67
68
|
constructor(options: {
|
|
68
69
|
appName: string;
|
|
69
70
|
logger: SystemLogger;
|
package/lib/event-queue.js
CHANGED
|
@@ -46,7 +46,6 @@ __export(event_queue_exports, {
|
|
|
46
46
|
});
|
|
47
47
|
module.exports = __toCommonJS(event_queue_exports);
|
|
48
48
|
var import_crypto = require("crypto");
|
|
49
|
-
var import_events = require("events");
|
|
50
49
|
var import_path = __toESM(require("path"));
|
|
51
50
|
var import_promises = __toESM(require("fs/promises"));
|
|
52
51
|
var import_utils = require("@nocobase/utils");
|
|
@@ -56,15 +55,14 @@ const QUEUE_DEFAULT_ACK_TIMEOUT = 15e3;
|
|
|
56
55
|
const _MemoryEventQueueAdapter = class _MemoryEventQueueAdapter {
|
|
57
56
|
constructor(options) {
|
|
58
57
|
this.options = options;
|
|
59
|
-
this.emitter.setMaxListeners(0);
|
|
60
58
|
}
|
|
61
59
|
connected = false;
|
|
62
|
-
emitter = new import_events.EventEmitter();
|
|
63
60
|
reading = /* @__PURE__ */ new Map();
|
|
61
|
+
scheduledChannels = /* @__PURE__ */ new Set();
|
|
64
62
|
events = /* @__PURE__ */ new Map();
|
|
65
63
|
queues = /* @__PURE__ */ new Map();
|
|
66
64
|
get processing() {
|
|
67
|
-
const processing = Array.from(this.reading.values());
|
|
65
|
+
const processing = Array.from(this.reading.values()).flat();
|
|
68
66
|
if (processing.length > 0) {
|
|
69
67
|
return Promise.all(processing);
|
|
70
68
|
}
|
|
@@ -80,14 +78,15 @@ const _MemoryEventQueueAdapter = class _MemoryEventQueueAdapter {
|
|
|
80
78
|
const { logger } = this.options;
|
|
81
79
|
const event = this.events.get(channel);
|
|
82
80
|
if (!event) {
|
|
83
|
-
logger.warn(`memory queue (${channel}) not found, skipping...`);
|
|
84
81
|
return;
|
|
85
82
|
}
|
|
86
83
|
if (!event.idle()) {
|
|
87
84
|
return;
|
|
88
85
|
}
|
|
89
86
|
const reading = this.reading.get(channel) || [];
|
|
90
|
-
const
|
|
87
|
+
const queue = this.queues.get(channel);
|
|
88
|
+
const concurrency = event.concurrency ?? QUEUE_DEFAULT_CONCURRENCY;
|
|
89
|
+
const count = concurrency <= 0 ? (queue == null ? void 0 : queue.length) || 0 : concurrency - reading.length;
|
|
91
90
|
if (count <= 0) {
|
|
92
91
|
return;
|
|
93
92
|
}
|
|
@@ -99,10 +98,24 @@ const _MemoryEventQueueAdapter = class _MemoryEventQueueAdapter {
|
|
|
99
98
|
if (index > -1) {
|
|
100
99
|
reading.splice(index, 1);
|
|
101
100
|
}
|
|
101
|
+
this.scheduleListen(channel);
|
|
102
102
|
});
|
|
103
103
|
});
|
|
104
104
|
this.reading.set(channel, reading);
|
|
105
105
|
}, "listen");
|
|
106
|
+
scheduleListen(channel) {
|
|
107
|
+
if (this.scheduledChannels.has(channel)) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
this.scheduledChannels.add(channel);
|
|
111
|
+
setImmediate(() => {
|
|
112
|
+
this.scheduledChannels.delete(channel);
|
|
113
|
+
if (!this.events.has(channel)) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
this.listen(channel);
|
|
117
|
+
});
|
|
118
|
+
}
|
|
106
119
|
isConnected() {
|
|
107
120
|
return this.connected;
|
|
108
121
|
}
|
|
@@ -181,7 +194,6 @@ const _MemoryEventQueueAdapter = class _MemoryEventQueueAdapter {
|
|
|
181
194
|
if (!this.queues.has(channel)) {
|
|
182
195
|
this.queues.set(channel, []);
|
|
183
196
|
}
|
|
184
|
-
this.emitter.on(channel, this.listen);
|
|
185
197
|
if (this.connected) {
|
|
186
198
|
this.consume(channel);
|
|
187
199
|
}
|
|
@@ -191,12 +203,12 @@ const _MemoryEventQueueAdapter = class _MemoryEventQueueAdapter {
|
|
|
191
203
|
return;
|
|
192
204
|
}
|
|
193
205
|
this.events.delete(channel);
|
|
194
|
-
this.emitter.off(channel, this.listen);
|
|
195
206
|
}
|
|
196
207
|
publish(channel, content, options = { timestamp: Date.now() }) {
|
|
208
|
+
const { logger } = this.options;
|
|
197
209
|
const event = this.events.get(channel);
|
|
198
210
|
if (!event) {
|
|
199
|
-
|
|
211
|
+
logger.debug(`memory queue (${channel}) not subscribed, skip`);
|
|
200
212
|
return;
|
|
201
213
|
}
|
|
202
214
|
if (!this.queues.get(channel)) {
|
|
@@ -205,11 +217,8 @@ const _MemoryEventQueueAdapter = class _MemoryEventQueueAdapter {
|
|
|
205
217
|
const queue = this.queues.get(channel);
|
|
206
218
|
const message = { id: (0, import_crypto.randomUUID)(), content, options };
|
|
207
219
|
queue.push(message);
|
|
208
|
-
const { logger } = this.options;
|
|
209
220
|
logger.debug(`memory queue (${channel}) published message`, content);
|
|
210
|
-
|
|
211
|
-
this.emitter.emit(channel, channel);
|
|
212
|
-
});
|
|
221
|
+
this.scheduleListen(channel);
|
|
213
222
|
}
|
|
214
223
|
async consume(channel, once = false) {
|
|
215
224
|
while (this.connected && this.events.get(channel)) {
|
package/lib/gateway/errors.js
CHANGED
|
@@ -54,6 +54,11 @@ function getAppName(app) {
|
|
|
54
54
|
}
|
|
55
55
|
__name(getAppName, "getAppName");
|
|
56
56
|
const errors = {
|
|
57
|
+
APP_ENVIRONMENT_UNAVAILABLE: {
|
|
58
|
+
status: 503,
|
|
59
|
+
message: /* @__PURE__ */ __name(({ appName }) => `deployment environment for application ${appName} is unavailable`, "message"),
|
|
60
|
+
maintaining: true
|
|
61
|
+
},
|
|
57
62
|
APP_NOT_FOUND: {
|
|
58
63
|
status: 404,
|
|
59
64
|
message: /* @__PURE__ */ __name(({ appName }) => `application ${appName} not found`, "message"),
|
package/lib/gateway/index.js
CHANGED
|
@@ -66,7 +66,6 @@ var import_plugin_manager = require("../plugin-manager");
|
|
|
66
66
|
var import_errors = require("./errors");
|
|
67
67
|
var import_ipc_socket_client = require("./ipc-socket-client");
|
|
68
68
|
var import_ipc_socket_server = require("./ipc-socket-server");
|
|
69
|
-
var import_static_file_security = require("./static-file-security");
|
|
70
69
|
var import_utils3 = require("./utils");
|
|
71
70
|
var import_ws_server = require("./ws-server");
|
|
72
71
|
var import_node_worker_threads = require("node:worker_threads");
|
|
@@ -122,7 +121,17 @@ const _Gateway = class _Gateway extends import_events.EventEmitter {
|
|
|
122
121
|
const internalUrl = req.url;
|
|
123
122
|
req.url = this.getOriginalRequestUrl(req);
|
|
124
123
|
try {
|
|
125
|
-
|
|
124
|
+
const proxied = await supervisor.proxyWeb(appName, req, res);
|
|
125
|
+
const adapter = supervisor.getDiscoveryAdapter();
|
|
126
|
+
if (!proxied && import_node_process.default.env.APP_MODE === "supervisor" && typeof adapter.proxyWeb === "function") {
|
|
127
|
+
let code = "APP_ENVIRONMENT_UNAVAILABLE";
|
|
128
|
+
if (typeof adapter.getAppModel === "function" && !await supervisor.getAppModel(appName)) {
|
|
129
|
+
code = "APP_NOT_FOUND";
|
|
130
|
+
}
|
|
131
|
+
this.responseErrorWithCode(code, res, { appName });
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
return proxied;
|
|
126
135
|
} finally {
|
|
127
136
|
req.url = internalUrl;
|
|
128
137
|
}
|
|
@@ -382,24 +391,6 @@ const _Gateway = class _Gateway extends import_events.EventEmitter {
|
|
|
382
391
|
this.responseErrorWithCode("APP_INITIALIZING", res, { appName: handleApp });
|
|
383
392
|
return;
|
|
384
393
|
}
|
|
385
|
-
if (pathname.startsWith(APP_PUBLIC_PATH + "storage/uploads/")) {
|
|
386
|
-
if (handleApp !== "main") {
|
|
387
|
-
const isProxy = await this.proxyRequestToSubApp(supervisor, handleApp, req, res);
|
|
388
|
-
if (isProxy) {
|
|
389
|
-
return;
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
const headers = (0, import_static_file_security.getStorageUploadSecurityHeaders)(`${pathname}${search || ""}`);
|
|
393
|
-
for (const [key, value] of Object.entries(headers)) {
|
|
394
|
-
res.setHeader(key, value);
|
|
395
|
-
}
|
|
396
|
-
req.url = req.url.substring(APP_PUBLIC_PATH.length + "storage".length);
|
|
397
|
-
await compress(req, res);
|
|
398
|
-
return (0, import_serve_handler.default)(req, res, {
|
|
399
|
-
public: (0, import_utils.resolveStorageRoot)(),
|
|
400
|
-
directoryListing: false
|
|
401
|
-
});
|
|
402
|
-
}
|
|
403
394
|
if (pathname.startsWith(APP_PUBLIC_PATH + "dist/")) {
|
|
404
395
|
if (handleApp !== "main") {
|
|
405
396
|
const isProxy = await this.proxyRequestToSubApp(supervisor, handleApp, req, res);
|
|
@@ -436,7 +427,8 @@ const _Gateway = class _Gateway extends import_events.EventEmitter {
|
|
|
436
427
|
});
|
|
437
428
|
}
|
|
438
429
|
const isFilesRequest = Boolean(getFileAccessRestPath(pathname, APP_PUBLIC_PATH));
|
|
439
|
-
|
|
430
|
+
const isLegacyUploadRequest = pathname.startsWith(APP_PUBLIC_PATH + "storage/uploads/");
|
|
431
|
+
if (!pathname.startsWith(import_node_process.default.env.API_BASE_PATH) && !isFilesRequest && !isLegacyUploadRequest) {
|
|
440
432
|
if (this.isV2Request(pathname)) {
|
|
441
433
|
if (handleApp !== "main") {
|
|
442
434
|
const isProxy = await this.proxyRequestToSubApp(supervisor, handleApp, req, res);
|
|
@@ -42,7 +42,18 @@ __export(static_file_security_exports, {
|
|
|
42
42
|
});
|
|
43
43
|
module.exports = __toCommonJS(static_file_security_exports);
|
|
44
44
|
var import_node_path = __toESM(require("node:path"));
|
|
45
|
-
const ACTIVE_CONTENT_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
45
|
+
const ACTIVE_CONTENT_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
46
|
+
".htm",
|
|
47
|
+
".html",
|
|
48
|
+
".pdf",
|
|
49
|
+
".svg",
|
|
50
|
+
".svgz",
|
|
51
|
+
".xht",
|
|
52
|
+
".xhtml",
|
|
53
|
+
".xml",
|
|
54
|
+
".xsl",
|
|
55
|
+
".xslt"
|
|
56
|
+
]);
|
|
46
57
|
function stripQueryAndHash(pathname = "") {
|
|
47
58
|
return pathname.split("?")[0].split("#")[0];
|
|
48
59
|
}
|
|
@@ -63,6 +74,8 @@ function hasActiveContentExtension(pathname = "") {
|
|
|
63
74
|
__name(hasActiveContentExtension, "hasActiveContentExtension");
|
|
64
75
|
function getStorageUploadSecurityHeaders(pathname = "") {
|
|
65
76
|
const headers = {
|
|
77
|
+
"Cache-Control": "private, no-store",
|
|
78
|
+
"Content-Security-Policy": "sandbox",
|
|
66
79
|
"X-Content-Type-Options": "nosniff"
|
|
67
80
|
};
|
|
68
81
|
if (hasActiveContentExtension(pathname) || shouldDownload(pathname)) {
|
package/lib/helper.d.ts
CHANGED
|
@@ -11,6 +11,7 @@ import { Command } from 'commander';
|
|
|
11
11
|
import Application, { ApplicationOptions } from './application';
|
|
12
12
|
export declare function createI18n(options: ApplicationOptions): import("i18next").i18n;
|
|
13
13
|
export declare function createResourcer(options: ApplicationOptions): Resourcer;
|
|
14
|
+
export declare function resolveCorsOrigin(ctx: any): any;
|
|
14
15
|
export declare function registerMiddlewares(app: Application, options: ApplicationOptions): void;
|
|
15
16
|
export declare const createAppProxy: (app: Application) => Application<import("./application").DefaultState, import("./application").DefaultContext>;
|
|
16
17
|
export declare const getCommandFullName: (command: Command) => string;
|
package/lib/helper.js
CHANGED
|
@@ -45,6 +45,7 @@ __export(helper_exports, {
|
|
|
45
45
|
getBodyLimit: () => getBodyLimit,
|
|
46
46
|
getCommandFullName: () => getCommandFullName,
|
|
47
47
|
registerMiddlewares: () => registerMiddlewares,
|
|
48
|
+
resolveCorsOrigin: () => resolveCorsOrigin,
|
|
48
49
|
tsxRerunning: () => tsxRerunning
|
|
49
50
|
});
|
|
50
51
|
module.exports = __toCommonJS(helper_exports);
|
|
@@ -56,10 +57,24 @@ var import_crypto = require("crypto");
|
|
|
56
57
|
var import_fs = __toESM(require("fs"));
|
|
57
58
|
var import_i18next = __toESM(require("i18next"));
|
|
58
59
|
var import_koa_bodyparser = __toESM(require("koa-bodyparser"));
|
|
60
|
+
var import_koa_send = __toESM(require("koa-send"));
|
|
59
61
|
var import_perf_hooks = require("perf_hooks");
|
|
60
62
|
var import_data_wrapping = require("./middlewares/data-wrapping");
|
|
61
63
|
var import_extract_client_ip = require("./middlewares/extract-client-ip");
|
|
64
|
+
var import_static_file_security = require("./gateway/static-file-security");
|
|
62
65
|
var import_i18n = require("./middlewares/i18n");
|
|
66
|
+
function resolveAppPublicPath(value = "/") {
|
|
67
|
+
const normalized = String(value || "/").trim() || "/";
|
|
68
|
+
const withLeadingSlash = normalized.startsWith("/") ? normalized : `/${normalized}`;
|
|
69
|
+
return withLeadingSlash.endsWith("/") ? withLeadingSlash : `${withLeadingSlash}/`;
|
|
70
|
+
}
|
|
71
|
+
__name(resolveAppPublicPath, "resolveAppPublicPath");
|
|
72
|
+
function resolveApiBasePath(value = "/api") {
|
|
73
|
+
const normalized = String(value || "/api").trim() || "/api";
|
|
74
|
+
const withLeadingSlash = normalized.startsWith("/") ? normalized : `/${normalized}`;
|
|
75
|
+
return withLeadingSlash.replace(/\/+$/g, "") || "/";
|
|
76
|
+
}
|
|
77
|
+
__name(resolveApiBasePath, "resolveApiBasePath");
|
|
63
78
|
function createI18n(options) {
|
|
64
79
|
const instance = import_i18next.default.createInstance();
|
|
65
80
|
instance.init({
|
|
@@ -78,26 +93,23 @@ function createResourcer(options) {
|
|
|
78
93
|
__name(createResourcer, "createResourcer");
|
|
79
94
|
function isWhitelistedCorsOrigin(ctx) {
|
|
80
95
|
const origin = ctx.get("origin");
|
|
81
|
-
const whitelist = (0, import_utils.getCorsWhitelist)();
|
|
82
96
|
if (!origin) {
|
|
83
97
|
return false;
|
|
84
98
|
}
|
|
85
|
-
|
|
86
|
-
return (0, import_utils.isTrustedOrigin)(ctx, origin);
|
|
87
|
-
}
|
|
88
|
-
return whitelist.has(origin);
|
|
99
|
+
return (0, import_utils.isTrustedOrigin)(ctx, origin);
|
|
89
100
|
}
|
|
90
101
|
__name(isWhitelistedCorsOrigin, "isWhitelistedCorsOrigin");
|
|
91
102
|
function resolveCorsOrigin(ctx) {
|
|
92
103
|
const origin = ctx.get("origin");
|
|
93
104
|
const disallowNoOrigin = process.env.CORS_DISALLOW_NO_ORIGIN === "true";
|
|
105
|
+
const whitelist = (0, import_utils.getCorsWhitelist)();
|
|
94
106
|
if (!origin && disallowNoOrigin) {
|
|
95
107
|
return false;
|
|
96
108
|
}
|
|
97
109
|
if (isWhitelistedCorsOrigin(ctx)) {
|
|
98
110
|
return origin;
|
|
99
111
|
}
|
|
100
|
-
return
|
|
112
|
+
return whitelist ? false : origin;
|
|
101
113
|
}
|
|
102
114
|
__name(resolveCorsOrigin, "resolveCorsOrigin");
|
|
103
115
|
function registerMiddlewares(app, options) {
|
|
@@ -114,7 +126,7 @@ function registerMiddlewares(app, options) {
|
|
|
114
126
|
app.use(
|
|
115
127
|
(0, import_cors.default)({
|
|
116
128
|
credentials: isWhitelistedCorsOrigin,
|
|
117
|
-
exposeHeaders: ["content-disposition"],
|
|
129
|
+
exposeHeaders: ["content-disposition", "x-new-token"],
|
|
118
130
|
origin: resolveCorsOrigin,
|
|
119
131
|
...options.cors
|
|
120
132
|
}),
|
|
@@ -140,6 +152,7 @@ function registerMiddlewares(app, options) {
|
|
|
140
152
|
}
|
|
141
153
|
app.use(/* @__PURE__ */ __name(async function getBearerToken(ctx, next) {
|
|
142
154
|
ctx.getBearerToken = () => {
|
|
155
|
+
var _a2, _b;
|
|
143
156
|
const authorization = ctx.get("Authorization");
|
|
144
157
|
if (authorization) {
|
|
145
158
|
ctx.state.pendingAuthTokenSource = "authorization";
|
|
@@ -149,7 +162,8 @@ function registerMiddlewares(app, options) {
|
|
|
149
162
|
ctx.state.pendingAuthTokenSource = "query";
|
|
150
163
|
return ctx.query.token;
|
|
151
164
|
}
|
|
152
|
-
const
|
|
165
|
+
const canUseAuthCookie = Boolean(((_a2 = ctx.state) == null ? void 0 : _a2.fileAccess) || ((_b = ctx.state) == null ? void 0 : _b.legacyFileAccess)) && ["GET", "HEAD"].includes(ctx.method);
|
|
166
|
+
const cookieToken = canUseAuthCookie ? ctx.cookies.get((0, import_utils.getAuthCookieName)("authToken", app.name)) : void 0;
|
|
153
167
|
ctx.state.pendingAuthTokenSource = cookieToken ? "cookie" : void 0;
|
|
154
168
|
return cookieToken;
|
|
155
169
|
};
|
|
@@ -159,6 +173,47 @@ function registerMiddlewares(app, options) {
|
|
|
159
173
|
if (options.dataWrapping !== false) {
|
|
160
174
|
app.use((0, import_data_wrapping.dataWrapping)(), { tag: "dataWrapping", after: "cors" });
|
|
161
175
|
}
|
|
176
|
+
app.use(
|
|
177
|
+
/* @__PURE__ */ __name(async function legacyFileAccess(ctx, next) {
|
|
178
|
+
const publicPath = resolveAppPublicPath(process.env.APP_PUBLIC_PATH);
|
|
179
|
+
const uploadsPrefix = `${publicPath}storage/uploads/`;
|
|
180
|
+
const authCheckPath = `${resolveApiBasePath(process.env.API_BASE_PATH)}/auth:checkLegacyFileAccess`;
|
|
181
|
+
const isUploadRequest = ctx.path.startsWith(uploadsPrefix);
|
|
182
|
+
const isAuthCheckRequest = ctx.path === authCheckPath || ctx.path === "/auth:checkLegacyFileAccess";
|
|
183
|
+
if (!isUploadRequest && !isAuthCheckRequest) {
|
|
184
|
+
return next();
|
|
185
|
+
}
|
|
186
|
+
if (!["GET", "HEAD"].includes(ctx.method)) {
|
|
187
|
+
return ctx.throw(405);
|
|
188
|
+
}
|
|
189
|
+
ctx.state.legacyFileAccess = true;
|
|
190
|
+
ctx.state.legacyLocalStoragePublicAccess = process.env.LEGACY_LOCAL_STORAGE_PUBLIC_ACCESS === "true";
|
|
191
|
+
if (ctx.state.legacyLocalStoragePublicAccess) {
|
|
192
|
+
ctx.skipAuthCheck = true;
|
|
193
|
+
}
|
|
194
|
+
if (isAuthCheckRequest) {
|
|
195
|
+
return next();
|
|
196
|
+
}
|
|
197
|
+
const originalPath = ctx.path;
|
|
198
|
+
ctx.path = authCheckPath;
|
|
199
|
+
try {
|
|
200
|
+
await next();
|
|
201
|
+
} finally {
|
|
202
|
+
ctx.path = originalPath;
|
|
203
|
+
}
|
|
204
|
+
const relativePath = `uploads/${originalPath.slice(uploadsPrefix.length)}`;
|
|
205
|
+
const headers = (0, import_static_file_security.getStorageUploadSecurityHeaders)(`${originalPath}${ctx.search || ""}`);
|
|
206
|
+
if (!headers["Content-Disposition"]) {
|
|
207
|
+
headers["Content-Disposition"] = "inline";
|
|
208
|
+
}
|
|
209
|
+
for (const [name, value] of Object.entries(headers)) {
|
|
210
|
+
ctx.set(name, value);
|
|
211
|
+
}
|
|
212
|
+
ctx.status = 200;
|
|
213
|
+
await (0, import_koa_send.default)(ctx, relativePath, { root: (0, import_utils.resolveStorageRoot)() });
|
|
214
|
+
}, "legacyFileAccess"),
|
|
215
|
+
{ tag: "legacyFileAccess", before: "dataSource", after: "dataWrapping" }
|
|
216
|
+
);
|
|
162
217
|
app.use(app.dataSourceManager.middleware(), { tag: "dataSource", after: "dataWrapping" });
|
|
163
218
|
app.use((0, import_extract_client_ip.extractClientIp)(), { tag: "extractClientIp", before: "cors" });
|
|
164
219
|
}
|
|
@@ -325,5 +380,6 @@ __name(createContextVariablesScope, "createContextVariablesScope");
|
|
|
325
380
|
getBodyLimit,
|
|
326
381
|
getCommandFullName,
|
|
327
382
|
registerMiddlewares,
|
|
383
|
+
resolveCorsOrigin,
|
|
328
384
|
tsxRerunning
|
|
329
385
|
});
|
package/lib/index.d.ts
CHANGED
|
@@ -13,6 +13,7 @@ export * from './gateway/ws-server';
|
|
|
13
13
|
export { Application as default } from './application';
|
|
14
14
|
export * from './audit-manager';
|
|
15
15
|
export * from './gateway';
|
|
16
|
+
export * from './gateway/static-file-security';
|
|
16
17
|
export * as middlewares from './middlewares';
|
|
17
18
|
export * from './migration';
|
|
18
19
|
export * from './plugin';
|
package/lib/index.js
CHANGED
|
@@ -55,6 +55,7 @@ __reExport(src_exports, require("./gateway/ws-server"), module.exports);
|
|
|
55
55
|
var import_application = require("./application");
|
|
56
56
|
__reExport(src_exports, require("./audit-manager"), module.exports);
|
|
57
57
|
__reExport(src_exports, require("./gateway"), module.exports);
|
|
58
|
+
__reExport(src_exports, require("./gateway/static-file-security"), module.exports);
|
|
58
59
|
var middlewares = __toESM(require("./middlewares"));
|
|
59
60
|
__reExport(src_exports, require("./migration"), module.exports);
|
|
60
61
|
__reExport(src_exports, require("./plugin"), module.exports);
|
|
@@ -85,6 +86,7 @@ var import_helper = require("./helper");
|
|
|
85
86
|
...require("./gateway/ws-server"),
|
|
86
87
|
...require("./audit-manager"),
|
|
87
88
|
...require("./gateway"),
|
|
89
|
+
...require("./gateway/static-file-security"),
|
|
88
90
|
...require("./migration"),
|
|
89
91
|
...require("./plugin"),
|
|
90
92
|
...require("./plugin-manager"),
|
package/lib/swagger/app.d.ts
CHANGED
|
@@ -12,7 +12,23 @@ declare const _default: {
|
|
|
12
12
|
readonly tags: readonly ["app"];
|
|
13
13
|
readonly summary: "Get the current application language";
|
|
14
14
|
readonly description: "Return the current locale used by the server.";
|
|
15
|
-
readonly parameters: readonly [
|
|
15
|
+
readonly parameters: readonly [{
|
|
16
|
+
readonly name: "locale";
|
|
17
|
+
readonly in: "query";
|
|
18
|
+
readonly required: false;
|
|
19
|
+
readonly schema: {
|
|
20
|
+
readonly type: "string";
|
|
21
|
+
};
|
|
22
|
+
readonly description: "Requested application locale. The server validates it against enabled languages.";
|
|
23
|
+
}, {
|
|
24
|
+
readonly name: "ns";
|
|
25
|
+
readonly in: "query";
|
|
26
|
+
readonly required: false;
|
|
27
|
+
readonly schema: {
|
|
28
|
+
readonly type: "string";
|
|
29
|
+
};
|
|
30
|
+
readonly description: "Comma-separated resource namespaces to return. Omit it to preserve the full legacy payload.";
|
|
31
|
+
}];
|
|
16
32
|
readonly responses: {
|
|
17
33
|
readonly 200: {
|
|
18
34
|
readonly description: "OK";
|
package/lib/swagger/app.js
CHANGED
|
@@ -35,7 +35,22 @@ var app_default = {
|
|
|
35
35
|
tags: ["app"],
|
|
36
36
|
summary: "Get the current application language",
|
|
37
37
|
description: "Return the current locale used by the server.",
|
|
38
|
-
parameters: [
|
|
38
|
+
parameters: [
|
|
39
|
+
{
|
|
40
|
+
name: "locale",
|
|
41
|
+
in: "query",
|
|
42
|
+
required: false,
|
|
43
|
+
schema: { type: "string" },
|
|
44
|
+
description: "Requested application locale. The server validates it against enabled languages."
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
name: "ns",
|
|
48
|
+
in: "query",
|
|
49
|
+
required: false,
|
|
50
|
+
schema: { type: "string" },
|
|
51
|
+
description: "Comma-separated resource namespaces to return. Omit it to preserve the full legacy payload."
|
|
52
|
+
}
|
|
53
|
+
],
|
|
39
54
|
responses: {
|
|
40
55
|
200: {
|
|
41
56
|
description: "OK",
|
package/lib/swagger/index.d.ts
CHANGED
|
@@ -694,7 +694,23 @@ declare const _default: {
|
|
|
694
694
|
readonly tags: readonly ["app"];
|
|
695
695
|
readonly summary: "Get the current application language";
|
|
696
696
|
readonly description: "Return the current locale used by the server.";
|
|
697
|
-
readonly parameters: readonly [
|
|
697
|
+
readonly parameters: readonly [{
|
|
698
|
+
readonly name: "locale";
|
|
699
|
+
readonly in: "query";
|
|
700
|
+
readonly required: false;
|
|
701
|
+
readonly schema: {
|
|
702
|
+
readonly type: "string";
|
|
703
|
+
};
|
|
704
|
+
readonly description: "Requested application locale. The server validates it against enabled languages.";
|
|
705
|
+
}, {
|
|
706
|
+
readonly name: "ns";
|
|
707
|
+
readonly in: "query";
|
|
708
|
+
readonly required: false;
|
|
709
|
+
readonly schema: {
|
|
710
|
+
readonly type: "string";
|
|
711
|
+
};
|
|
712
|
+
readonly description: "Comma-separated resource namespaces to return. Omit it to preserve the full legacy payload.";
|
|
713
|
+
}];
|
|
698
714
|
readonly responses: {
|
|
699
715
|
readonly 200: {
|
|
700
716
|
readonly description: "OK";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nocobase/server",
|
|
3
|
-
"version": "2.3.0-
|
|
3
|
+
"version": "2.3.0-beta.10",
|
|
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.3.0-
|
|
14
|
-
"@nocobase/actions": "2.3.0-
|
|
15
|
-
"@nocobase/ai": "2.3.0-
|
|
16
|
-
"@nocobase/auth": "2.3.0-
|
|
17
|
-
"@nocobase/cache": "2.3.0-
|
|
18
|
-
"@nocobase/data-source-manager": "2.3.0-
|
|
19
|
-
"@nocobase/database": "2.3.0-
|
|
20
|
-
"@nocobase/evaluators": "2.3.0-
|
|
21
|
-
"@nocobase/lock-manager": "2.3.0-
|
|
22
|
-
"@nocobase/logger": "2.3.0-
|
|
23
|
-
"@nocobase/resourcer": "2.3.0-
|
|
24
|
-
"@nocobase/sdk": "2.3.0-
|
|
25
|
-
"@nocobase/snowflake-id": "2.3.0-
|
|
26
|
-
"@nocobase/telemetry": "2.3.0-
|
|
27
|
-
"@nocobase/utils": "2.3.0-
|
|
13
|
+
"@nocobase/acl": "2.3.0-beta.10",
|
|
14
|
+
"@nocobase/actions": "2.3.0-beta.10",
|
|
15
|
+
"@nocobase/ai": "2.3.0-beta.10",
|
|
16
|
+
"@nocobase/auth": "2.3.0-beta.10",
|
|
17
|
+
"@nocobase/cache": "2.3.0-beta.10",
|
|
18
|
+
"@nocobase/data-source-manager": "2.3.0-beta.10",
|
|
19
|
+
"@nocobase/database": "2.3.0-beta.10",
|
|
20
|
+
"@nocobase/evaluators": "2.3.0-beta.10",
|
|
21
|
+
"@nocobase/lock-manager": "2.3.0-beta.10",
|
|
22
|
+
"@nocobase/logger": "2.3.0-beta.10",
|
|
23
|
+
"@nocobase/resourcer": "2.3.0-beta.10",
|
|
24
|
+
"@nocobase/sdk": "2.3.0-beta.10",
|
|
25
|
+
"@nocobase/snowflake-id": "2.3.0-beta.10",
|
|
26
|
+
"@nocobase/telemetry": "2.3.0-beta.10",
|
|
27
|
+
"@nocobase/utils": "2.3.0-beta.10",
|
|
28
28
|
"@types/decompress": "4.2.7",
|
|
29
29
|
"@types/ini": "^1.3.31",
|
|
30
30
|
"@types/koa-send": "^4.1.3",
|
|
@@ -61,5 +61,5 @@
|
|
|
61
61
|
"@types/serve-handler": "^6.1.1",
|
|
62
62
|
"@types/ws": "^8.5.5"
|
|
63
63
|
},
|
|
64
|
-
"gitHead": "
|
|
64
|
+
"gitHead": "7aa3dce6cf3201752882f7012c72997898015d89"
|
|
65
65
|
}
|