@nocobase/server 2.1.31 → 2.2.0-alpha.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/app-supervisor/index.d.ts +12 -0
- package/lib/app-supervisor/index.js +71 -0
- package/lib/app-supervisor/main-only-adapter.d.ts +1 -0
- package/lib/app-supervisor/main-only-adapter.js +3 -0
- package/lib/app-supervisor/types.d.ts +7 -0
- package/lib/application.js +1 -0
- package/lib/gateway/index.d.ts +5 -0
- package/lib/gateway/index.js +138 -5
- package/lib/gateway/static-file-security.js +10 -1
- package/lib/gateway/utils.d.ts +6 -0
- package/lib/gateway/utils.js +30 -0
- package/lib/helper.js +24 -11
- 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
|
@@ -49,6 +49,8 @@ export declare class AppSupervisor extends EventEmitter implements AsyncEmitter
|
|
|
49
49
|
private commandAdapterName;
|
|
50
50
|
private appDbCreator;
|
|
51
51
|
private appConditions;
|
|
52
|
+
private appManifests;
|
|
53
|
+
private appSsoIssuer?;
|
|
52
54
|
appOptionsFactory: AppOptionsFactory;
|
|
53
55
|
private environmentHeartbeatInterval;
|
|
54
56
|
private environmentHeartbeatTimer;
|
|
@@ -81,6 +83,8 @@ export declare class AppSupervisor extends EventEmitter implements AsyncEmitter
|
|
|
81
83
|
registerAppDbCreator(condition: Predicate<AppDbCreatorOptions>, creator: AppDbCreator, priority?: number): void;
|
|
82
84
|
createDatabase(options: AppDbCreatorOptions): Promise<void>;
|
|
83
85
|
setAppOptionsFactory(factory: AppOptionsFactory): void;
|
|
86
|
+
setAppSsoIssuer(issuer?: string): void;
|
|
87
|
+
getAppSsoIssuer(): string;
|
|
84
88
|
bootstrapApp(appName: string): Promise<void>;
|
|
85
89
|
initApp({ appName, options }: {
|
|
86
90
|
appName: string;
|
|
@@ -108,6 +112,14 @@ export declare class AppSupervisor extends EventEmitter implements AsyncEmitter
|
|
|
108
112
|
getAppLastSeenAt(appName: string): Promise<number>;
|
|
109
113
|
addAppModel(appModel: AppModel): Promise<void>;
|
|
110
114
|
getAppModel(appName: string): Promise<AppModel>;
|
|
115
|
+
listAppModels(): Promise<AppModel[]>;
|
|
116
|
+
setAppManifestItem(appName: string, namespace: string, itemKey: string, item: unknown): Promise<void>;
|
|
117
|
+
removeAppManifestItem(appName: string, namespace: string, itemKey: string): Promise<void>;
|
|
118
|
+
removeAppManifest(appName: string, namespace: string): Promise<void>;
|
|
119
|
+
getAppManifestItems<T = unknown>(appName: string, namespace: string): Promise<T[]>;
|
|
120
|
+
getAppManifests<T = unknown>(namespace: string, appNames: string[]): Promise<Record<string, T[]>>;
|
|
121
|
+
private getAppManifestKey;
|
|
122
|
+
private getOrCreateAppManifest;
|
|
111
123
|
registerAppCondition(name: string, condition: AppCondition): void;
|
|
112
124
|
unregisterAppCondition(name: string): void;
|
|
113
125
|
getAppCondition(name: string): AppCondition;
|
|
@@ -70,6 +70,8 @@ const _AppSupervisor = class _AppSupervisor extends import_events.EventEmitter {
|
|
|
70
70
|
commandAdapterName;
|
|
71
71
|
appDbCreator = new import_condition_registry.ConditionalRegistry();
|
|
72
72
|
appConditions = /* @__PURE__ */ new Map();
|
|
73
|
+
appManifests = /* @__PURE__ */ new Map();
|
|
74
|
+
appSsoIssuer;
|
|
73
75
|
appOptionsFactory = import_app_options_factory.appOptionsFactory;
|
|
74
76
|
environmentHeartbeatInterval = 2 * 60 * 1e3;
|
|
75
77
|
environmentHeartbeatTimer = null;
|
|
@@ -228,6 +230,13 @@ const _AppSupervisor = class _AppSupervisor extends import_events.EventEmitter {
|
|
|
228
230
|
setAppOptionsFactory(factory) {
|
|
229
231
|
this.appOptionsFactory = factory ?? import_app_options_factory.appOptionsFactory;
|
|
230
232
|
}
|
|
233
|
+
setAppSsoIssuer(issuer) {
|
|
234
|
+
const normalized = String(issuer || "").trim().replace(/\/+$/, "");
|
|
235
|
+
this.appSsoIssuer = normalized || void 0;
|
|
236
|
+
}
|
|
237
|
+
getAppSsoIssuer() {
|
|
238
|
+
return this.appSsoIssuer;
|
|
239
|
+
}
|
|
231
240
|
async bootstrapApp(appName) {
|
|
232
241
|
return this.processAdapter.bootstrapApp(appName);
|
|
233
242
|
}
|
|
@@ -276,6 +285,7 @@ const _AppSupervisor = class _AppSupervisor extends import_events.EventEmitter {
|
|
|
276
285
|
await this.processAdapter.removeAllApps();
|
|
277
286
|
await ((_b = (_a = this.discoveryAdapter).dispose) == null ? void 0 : _b.call(_a));
|
|
278
287
|
await ((_d = (_c = this.commandAdapter) == null ? void 0 : _c.dispose) == null ? void 0 : _d.call(_c));
|
|
288
|
+
this.appManifests.clear();
|
|
279
289
|
if (this.environmentHeartbeatTimer) {
|
|
280
290
|
this.environmentHeartbeatTimer = null;
|
|
281
291
|
}
|
|
@@ -421,6 +431,67 @@ const _AppSupervisor = class _AppSupervisor extends import_events.EventEmitter {
|
|
|
421
431
|
async getAppModel(appName) {
|
|
422
432
|
return this.discoveryAdapter.getAppModel(appName);
|
|
423
433
|
}
|
|
434
|
+
async listAppModels() {
|
|
435
|
+
if (typeof this.discoveryAdapter.listAppModels !== "function") {
|
|
436
|
+
return [];
|
|
437
|
+
}
|
|
438
|
+
return this.discoveryAdapter.listAppModels();
|
|
439
|
+
}
|
|
440
|
+
async setAppManifestItem(appName, namespace, itemKey, item) {
|
|
441
|
+
if (typeof this.discoveryAdapter.setAppManifestItem === "function") {
|
|
442
|
+
return this.discoveryAdapter.setAppManifestItem(appName, namespace, itemKey, item);
|
|
443
|
+
}
|
|
444
|
+
const manifest = this.getOrCreateAppManifest(appName, namespace);
|
|
445
|
+
manifest.set(itemKey, item);
|
|
446
|
+
}
|
|
447
|
+
async removeAppManifestItem(appName, namespace, itemKey) {
|
|
448
|
+
var _a;
|
|
449
|
+
if (typeof this.discoveryAdapter.removeAppManifestItem === "function") {
|
|
450
|
+
return this.discoveryAdapter.removeAppManifestItem(appName, namespace, itemKey);
|
|
451
|
+
}
|
|
452
|
+
(_a = this.appManifests.get(this.getAppManifestKey(appName, namespace))) == null ? void 0 : _a.delete(itemKey);
|
|
453
|
+
}
|
|
454
|
+
async removeAppManifest(appName, namespace) {
|
|
455
|
+
if (typeof this.discoveryAdapter.removeAppManifest === "function") {
|
|
456
|
+
return this.discoveryAdapter.removeAppManifest(appName, namespace);
|
|
457
|
+
}
|
|
458
|
+
this.appManifests.delete(this.getAppManifestKey(appName, namespace));
|
|
459
|
+
}
|
|
460
|
+
async getAppManifestItems(appName, namespace) {
|
|
461
|
+
var _a;
|
|
462
|
+
if (typeof this.discoveryAdapter.getAppManifestItems === "function") {
|
|
463
|
+
return this.discoveryAdapter.getAppManifestItems(appName, namespace);
|
|
464
|
+
}
|
|
465
|
+
return Array.from(((_a = this.appManifests.get(this.getAppManifestKey(appName, namespace))) == null ? void 0 : _a.values()) || []);
|
|
466
|
+
}
|
|
467
|
+
async getAppManifests(namespace, appNames) {
|
|
468
|
+
if (typeof this.discoveryAdapter.getAppManifests === "function") {
|
|
469
|
+
return this.discoveryAdapter.getAppManifests(namespace, appNames);
|
|
470
|
+
}
|
|
471
|
+
const result = {};
|
|
472
|
+
await Promise.all(
|
|
473
|
+
appNames.map(async (appName) => {
|
|
474
|
+
const manifest = await this.getAppManifestItems(appName, namespace);
|
|
475
|
+
if (manifest.length > 0) {
|
|
476
|
+
result[appName] = manifest;
|
|
477
|
+
}
|
|
478
|
+
})
|
|
479
|
+
);
|
|
480
|
+
return result;
|
|
481
|
+
}
|
|
482
|
+
getAppManifestKey(appName, namespace) {
|
|
483
|
+
return `${namespace}:${appName}`;
|
|
484
|
+
}
|
|
485
|
+
getOrCreateAppManifest(appName, namespace) {
|
|
486
|
+
const key = this.getAppManifestKey(appName, namespace);
|
|
487
|
+
const manifest = this.appManifests.get(key);
|
|
488
|
+
if (manifest) {
|
|
489
|
+
return manifest;
|
|
490
|
+
}
|
|
491
|
+
const nextManifest = /* @__PURE__ */ new Map();
|
|
492
|
+
this.appManifests.set(key, nextManifest);
|
|
493
|
+
return nextManifest;
|
|
494
|
+
}
|
|
424
495
|
registerAppCondition(name, condition) {
|
|
425
496
|
this.appConditions.set(name, condition);
|
|
426
497
|
}
|
|
@@ -21,6 +21,7 @@ export declare class MainOnlyAdapter implements AppDiscoveryAdapter, AppProcessA
|
|
|
21
21
|
bootstrapApp(appName: string): Promise<void>;
|
|
22
22
|
addApp(app: Application): Application<import("../application").DefaultState, import("../application").DefaultContext>;
|
|
23
23
|
getApps(): Application<import("../application").DefaultState, import("../application").DefaultContext>[];
|
|
24
|
+
listAppModels(): Promise<any[]>;
|
|
24
25
|
hasApp(appName: string): boolean;
|
|
25
26
|
startApp(appName: string): Promise<void>;
|
|
26
27
|
stopApp(appName: string): Promise<void>;
|
|
@@ -66,6 +66,7 @@ export type AppModel = {
|
|
|
66
66
|
environments?: string[];
|
|
67
67
|
options: AppModelOptions;
|
|
68
68
|
};
|
|
69
|
+
export type AppManifestValue = unknown;
|
|
69
70
|
export type AppCondition = {
|
|
70
71
|
filter?: Record<string, any>;
|
|
71
72
|
match?: (appModel: AppModel) => boolean;
|
|
@@ -115,11 +116,17 @@ export interface AppDiscoveryAdapter {
|
|
|
115
116
|
loadAppModels?(mainApp: Application): Promise<void>;
|
|
116
117
|
getAppsStatuses?(appNames?: string[]): Promise<AppStatusesResult> | AppStatusesResult;
|
|
117
118
|
getAppsByCondition?(conditionName: string, condition: AppCondition, options?: GetAppsByConditionOptions): Promise<string[]>;
|
|
119
|
+
listAppModels?(): Promise<AppModel[]>;
|
|
118
120
|
addAppsToCondition?(conditionName: string, environmentName: string, appNames: string[]): Promise<void>;
|
|
119
121
|
removeAppsFromCondition?(conditionName: string, environmentName: string, appNames: string[]): Promise<void>;
|
|
120
122
|
addAppModel?(appModel: AppModel): Promise<void>;
|
|
121
123
|
getAppModel?(appName: string): Promise<AppModel>;
|
|
122
124
|
removeAppModel?(appName: string): Promise<void>;
|
|
125
|
+
setAppManifestItem?(appName: string, namespace: string, itemKey: string, item: AppManifestValue): Promise<void>;
|
|
126
|
+
removeAppManifestItem?(appName: string, namespace: string, itemKey: string): Promise<void>;
|
|
127
|
+
removeAppManifest?(appName: string, namespace: string): Promise<void>;
|
|
128
|
+
getAppManifestItems?<T = AppManifestValue>(appName: string, namespace: string): Promise<T[]>;
|
|
129
|
+
getAppManifests?<T = AppManifestValue>(namespace: string, appNames: string[]): Promise<Record<string, T[]>>;
|
|
123
130
|
getAppNameByCName?(cname: string): Promise<string | null>;
|
|
124
131
|
registerEnvironment?(environment: EnvironmentInfo): Promise<boolean>;
|
|
125
132
|
unregisterEnvironment?(): Promise<void>;
|
package/lib/application.js
CHANGED
|
@@ -944,6 +944,7 @@ const _Application = class _Application extends import_koa.default {
|
|
|
944
944
|
}
|
|
945
945
|
});
|
|
946
946
|
this._dataSourceManager.use(this._authManager.middleware(), { tag: "auth", before: "default" });
|
|
947
|
+
this._dataSourceManager.use(import_auth.csrfMiddleware, { tag: "csrf", after: "auth", before: "default" });
|
|
947
948
|
this._dataSourceManager.use(import_validate_filter_params.default, { tag: "validate-filter-params", before: ["auth"] });
|
|
948
949
|
this._dataSourceManager.use(import_middlewares.parseVariables, {
|
|
949
950
|
group: "parseVariables",
|
package/lib/gateway/index.d.ts
CHANGED
|
@@ -80,6 +80,11 @@ export declare class Gateway extends EventEmitter {
|
|
|
80
80
|
responseErrorWithCode(code: any, res: any, options: any): void;
|
|
81
81
|
private getV2PublicPath;
|
|
82
82
|
private getAppPublicPath;
|
|
83
|
+
private getPortalRootPublicPath;
|
|
84
|
+
private getPortalAppPublicPath;
|
|
85
|
+
private getPortalMatch;
|
|
86
|
+
private isPortalIndexRequest;
|
|
87
|
+
private getPortalDistRoot;
|
|
83
88
|
private isV2Request;
|
|
84
89
|
private isV2IndexRequest;
|
|
85
90
|
private getV2RuntimeConfig;
|
package/lib/gateway/index.js
CHANGED
|
@@ -78,6 +78,17 @@ function normalizeBasePath(path = "") {
|
|
|
78
78
|
return normalized || "/";
|
|
79
79
|
}
|
|
80
80
|
__name(normalizeBasePath, "normalizeBasePath");
|
|
81
|
+
function getFilesPathPrefixes(appPublicPath = "/") {
|
|
82
|
+
const normalizedPublicPath = normalizeBasePath(appPublicPath);
|
|
83
|
+
const canonicalPrefix = `${normalizedPublicPath === "/" ? "" : normalizedPublicPath}/files/`;
|
|
84
|
+
return canonicalPrefix === "/files/" ? ["/files/"] : [canonicalPrefix, "/files/"];
|
|
85
|
+
}
|
|
86
|
+
__name(getFilesPathPrefixes, "getFilesPathPrefixes");
|
|
87
|
+
function getFileAccessRestPath(pathname, appPublicPath = "/") {
|
|
88
|
+
const prefix = getFilesPathPrefixes(appPublicPath).find((prefix2) => pathname.startsWith(prefix2));
|
|
89
|
+
return prefix ? pathname.slice(prefix.length) : null;
|
|
90
|
+
}
|
|
91
|
+
__name(getFileAccessRestPath, "getFileAccessRestPath");
|
|
81
92
|
function getSocketPath() {
|
|
82
93
|
const socketPath = import_node_process.default.env.SOCKET_PATH;
|
|
83
94
|
if (socketPath) {
|
|
@@ -177,19 +188,28 @@ const _Gateway = class _Gateway extends import_events.EventEmitter {
|
|
|
177
188
|
this.selectorMiddlewares = new import_utils.Toposort();
|
|
178
189
|
this.addAppSelectorMiddleware(
|
|
179
190
|
async (ctx, next) => {
|
|
180
|
-
var _a, _b;
|
|
191
|
+
var _a, _b, _c;
|
|
181
192
|
const { req } = ctx;
|
|
182
193
|
const parsedUrl = (0, import_url.parse)(req.url);
|
|
183
194
|
const appName = (_a = import_qs.default.parse(parsedUrl.query)) == null ? void 0 : _a.__appName;
|
|
184
195
|
const apiBasePath = normalizeBasePath(import_node_process.default.env.API_BASE_PATH || "/api");
|
|
185
196
|
const appPathPrefix = `${apiBasePath}/__app/`;
|
|
197
|
+
const appPublicPath = (0, import_utils3.resolvePublicPath)(import_node_process.default.env.APP_PUBLIC_PATH || "/");
|
|
198
|
+
const portalAppsPathPrefix = `${appPublicPath.replace(/\/$/, "")}/${import_utils3.PORTAL_CLIENT_PREFIX}/apps/`;
|
|
186
199
|
if (req.headers["x-app"]) {
|
|
187
200
|
ctx.resolvedAppName = req.headers["x-app"];
|
|
188
201
|
}
|
|
189
202
|
if (appName) {
|
|
190
203
|
ctx.resolvedAppName = appName;
|
|
191
204
|
}
|
|
192
|
-
if ((_b = parsedUrl.pathname) == null ? void 0 : _b.startsWith(
|
|
205
|
+
if ((_b = parsedUrl.pathname) == null ? void 0 : _b.startsWith(portalAppsPathPrefix)) {
|
|
206
|
+
const restPath = parsedUrl.pathname.slice(portalAppsPathPrefix.length);
|
|
207
|
+
const [pathAppName] = restPath.split("/");
|
|
208
|
+
if (pathAppName) {
|
|
209
|
+
ctx.resolvedAppName = (0, import_utils3.normalizePortalAppName)(pathAppName);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
if ((_c = parsedUrl.pathname) == null ? void 0 : _c.startsWith(appPathPrefix)) {
|
|
193
213
|
const restPath = parsedUrl.pathname.slice(appPathPrefix.length);
|
|
194
214
|
const [pathAppName, ...segments] = restPath.split("/");
|
|
195
215
|
if (pathAppName) {
|
|
@@ -202,6 +222,17 @@ const _Gateway = class _Gateway extends import_events.EventEmitter {
|
|
|
202
222
|
req.url = rewrittenUrl;
|
|
203
223
|
}
|
|
204
224
|
}
|
|
225
|
+
const fileAccessRestPath = parsedUrl.pathname ? getFileAccessRestPath(parsedUrl.pathname, import_node_process.default.env.APP_PUBLIC_PATH || "/") : null;
|
|
226
|
+
if (fileAccessRestPath) {
|
|
227
|
+
const restPath = fileAccessRestPath;
|
|
228
|
+
const [pathAppName] = restPath.split("/");
|
|
229
|
+
if (pathAppName) {
|
|
230
|
+
try {
|
|
231
|
+
ctx.resolvedAppName = decodeURIComponent(pathAppName);
|
|
232
|
+
} catch (error) {
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
205
236
|
await next();
|
|
206
237
|
},
|
|
207
238
|
{
|
|
@@ -270,6 +301,62 @@ const _Gateway = class _Gateway extends import_events.EventEmitter {
|
|
|
270
301
|
getAppPublicPath() {
|
|
271
302
|
return (0, import_utils3.resolvePublicPath)(import_node_process.default.env.APP_PUBLIC_PATH || "/");
|
|
272
303
|
}
|
|
304
|
+
getPortalRootPublicPath() {
|
|
305
|
+
return `${this.getAppPublicPath().replace(/\/$/, "")}/${import_utils3.PORTAL_CLIENT_PREFIX}/`;
|
|
306
|
+
}
|
|
307
|
+
getPortalAppPublicPath(appName) {
|
|
308
|
+
if (appName === import_utils3.DEFAULT_PORTAL_APP_NAME) {
|
|
309
|
+
return this.getPortalRootPublicPath();
|
|
310
|
+
}
|
|
311
|
+
return `${this.getPortalRootPublicPath()}apps/${(0, import_utils3.normalizePortalAppName)(appName)}/`;
|
|
312
|
+
}
|
|
313
|
+
getPortalMatch(pathname) {
|
|
314
|
+
const portalRootPublicPath = this.getPortalRootPublicPath();
|
|
315
|
+
if (!pathname.startsWith(portalRootPublicPath)) {
|
|
316
|
+
return null;
|
|
317
|
+
}
|
|
318
|
+
let appName = import_utils3.DEFAULT_PORTAL_APP_NAME;
|
|
319
|
+
let publicRoot = portalRootPublicPath;
|
|
320
|
+
let restPath = pathname.slice(portalRootPublicPath.length).replace(/^\/+/, "");
|
|
321
|
+
const [firstSegment, secondSegment, ...remainingSegments] = restPath.split("/");
|
|
322
|
+
if (firstSegment === "apps") {
|
|
323
|
+
if (!secondSegment || !/^[A-Za-z0-9_-]+$/.test(secondSegment)) {
|
|
324
|
+
return null;
|
|
325
|
+
}
|
|
326
|
+
appName = (0, import_utils3.normalizePortalAppName)(secondSegment);
|
|
327
|
+
publicRoot = this.getPortalAppPublicPath(appName);
|
|
328
|
+
if (!pathname.startsWith(publicRoot)) {
|
|
329
|
+
return null;
|
|
330
|
+
}
|
|
331
|
+
restPath = remainingSegments.join("/").replace(/^\/+/, "");
|
|
332
|
+
}
|
|
333
|
+
const [portalName] = restPath.split("/");
|
|
334
|
+
if (!portalName || !/^[A-Za-z0-9_-]+$/.test(portalName)) {
|
|
335
|
+
return null;
|
|
336
|
+
}
|
|
337
|
+
return {
|
|
338
|
+
appName,
|
|
339
|
+
portalName,
|
|
340
|
+
publicPath: `${publicRoot}${portalName}/`
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
isPortalIndexRequest(pathname, portalPublicPath) {
|
|
344
|
+
if (pathname === portalPublicPath || pathname === portalPublicPath.slice(0, -1) || pathname === `${portalPublicPath}index.html`) {
|
|
345
|
+
return true;
|
|
346
|
+
}
|
|
347
|
+
return !(0, import_path.extname)(pathname);
|
|
348
|
+
}
|
|
349
|
+
getPortalDistRoot(portalMatch) {
|
|
350
|
+
const scopedRoot = (0, import_utils.storagePathJoin)("portals", portalMatch.appName, portalMatch.portalName, "dist");
|
|
351
|
+
if (portalMatch.appName !== import_utils3.DEFAULT_PORTAL_APP_NAME) {
|
|
352
|
+
return scopedRoot;
|
|
353
|
+
}
|
|
354
|
+
const legacyRoot = (0, import_utils.storagePathJoin)("portals", portalMatch.portalName, "dist");
|
|
355
|
+
if (!import_fs.default.existsSync((0, import_path.resolve)(scopedRoot, "index.html")) && import_fs.default.existsSync((0, import_path.resolve)(legacyRoot, "index.html"))) {
|
|
356
|
+
return legacyRoot;
|
|
357
|
+
}
|
|
358
|
+
return scopedRoot;
|
|
359
|
+
}
|
|
273
360
|
isV2Request(pathname) {
|
|
274
361
|
const v2PublicPath = this.getV2PublicPath();
|
|
275
362
|
return pathname === v2PublicPath.slice(0, -1) || pathname.startsWith(v2PublicPath);
|
|
@@ -337,7 +424,7 @@ const _Gateway = class _Gateway extends import_events.EventEmitter {
|
|
|
337
424
|
return (0, import_utils3.injectRuntimeScript)(html, this.getV2RuntimeConfigScript());
|
|
338
425
|
}
|
|
339
426
|
async requestHandler(req, res) {
|
|
340
|
-
const { pathname } = (0, import_url.parse)(req.url);
|
|
427
|
+
const { pathname, search } = (0, import_url.parse)(req.url);
|
|
341
428
|
const { PLUGIN_STATICS_PATH } = import_node_process.default.env;
|
|
342
429
|
const APP_PUBLIC_PATH = this.getAppPublicPath();
|
|
343
430
|
if (pathname.endsWith("/__umi/api/bundle-status")) {
|
|
@@ -345,6 +432,12 @@ const _Gateway = class _Gateway extends import_events.EventEmitter {
|
|
|
345
432
|
res.end("ok");
|
|
346
433
|
return;
|
|
347
434
|
}
|
|
435
|
+
if (APP_PUBLIC_PATH !== "/" && pathname.startsWith("/files/")) {
|
|
436
|
+
res.statusCode = 302;
|
|
437
|
+
res.setHeader("Location", `${APP_PUBLIC_PATH.replace(/\/$/, "")}${pathname}${search || ""}`);
|
|
438
|
+
res.end();
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
348
441
|
const supervisor = import_app_supervisor.AppSupervisor.getInstance();
|
|
349
442
|
let handleApp = "main";
|
|
350
443
|
try {
|
|
@@ -361,7 +454,7 @@ const _Gateway = class _Gateway extends import_events.EventEmitter {
|
|
|
361
454
|
return;
|
|
362
455
|
}
|
|
363
456
|
}
|
|
364
|
-
const headers = (0, import_static_file_security.getStorageUploadSecurityHeaders)(pathname);
|
|
457
|
+
const headers = (0, import_static_file_security.getStorageUploadSecurityHeaders)(`${pathname}${search || ""}`);
|
|
365
458
|
req.url = req.url.substring(APP_PUBLIC_PATH.length + "storage".length);
|
|
366
459
|
await compress(req, res);
|
|
367
460
|
return (0, import_serve_handler.default)(req, res, {
|
|
@@ -410,7 +503,47 @@ const _Gateway = class _Gateway extends import_events.EventEmitter {
|
|
|
410
503
|
]
|
|
411
504
|
});
|
|
412
505
|
}
|
|
413
|
-
|
|
506
|
+
const isFilesRequest = Boolean(getFileAccessRestPath(pathname, APP_PUBLIC_PATH));
|
|
507
|
+
if (!pathname.startsWith(import_node_process.default.env.API_BASE_PATH) && !isFilesRequest) {
|
|
508
|
+
const portalMatch = this.getPortalMatch(pathname);
|
|
509
|
+
if (portalMatch) {
|
|
510
|
+
if (handleApp !== "main" && handleApp !== portalMatch.appName) {
|
|
511
|
+
const isProxy = await this.proxyRequestToSubApp(supervisor, handleApp, req, res);
|
|
512
|
+
if (isProxy) {
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
if (!pathname.startsWith(portalMatch.publicPath)) {
|
|
517
|
+
res.statusCode = 302;
|
|
518
|
+
res.setHeader("Location", `${portalMatch.publicPath}${search || ""}`);
|
|
519
|
+
res.end();
|
|
520
|
+
return;
|
|
521
|
+
}
|
|
522
|
+
const portalDistRoot = this.getPortalDistRoot(portalMatch);
|
|
523
|
+
const portalIndex = (0, import_path.resolve)(portalDistRoot, "index.html");
|
|
524
|
+
if (!import_fs.default.existsSync(portalIndex)) {
|
|
525
|
+
res.statusCode = 404;
|
|
526
|
+
res.end();
|
|
527
|
+
return;
|
|
528
|
+
}
|
|
529
|
+
if (this.isPortalIndexRequest(pathname, portalMatch.publicPath)) {
|
|
530
|
+
res.setHeader("Content-Type", "text/html; charset=utf-8");
|
|
531
|
+
res.end(import_fs.default.readFileSync(portalIndex, "utf-8"));
|
|
532
|
+
return;
|
|
533
|
+
}
|
|
534
|
+
req.url = req.url.substring(portalMatch.publicPath.length - 1);
|
|
535
|
+
await compress(req, res);
|
|
536
|
+
return (0, import_serve_handler.default)(req, res, {
|
|
537
|
+
public: portalDistRoot,
|
|
538
|
+
directoryListing: false
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
const portalRootPublicPath = this.getPortalRootPublicPath();
|
|
542
|
+
if (pathname === portalRootPublicPath.slice(0, -1) || pathname.startsWith(portalRootPublicPath)) {
|
|
543
|
+
res.statusCode = 404;
|
|
544
|
+
res.end();
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
414
547
|
if (this.isV2Request(pathname)) {
|
|
415
548
|
if (handleApp !== "main") {
|
|
416
549
|
const isProxy = await this.proxyRequestToSubApp(supervisor, handleApp, req, res);
|
|
@@ -58,6 +58,15 @@ function stripQueryAndHash(pathname = "") {
|
|
|
58
58
|
return pathname.split("?")[0].split("#")[0];
|
|
59
59
|
}
|
|
60
60
|
__name(stripQueryAndHash, "stripQueryAndHash");
|
|
61
|
+
function shouldDownload(pathname = "") {
|
|
62
|
+
var _a;
|
|
63
|
+
const query = (_a = pathname.split("?")[1]) == null ? void 0 : _a.split("#")[0];
|
|
64
|
+
if (!query) {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
return new URLSearchParams(query).get("download") === "1";
|
|
68
|
+
}
|
|
69
|
+
__name(shouldDownload, "shouldDownload");
|
|
61
70
|
function hasActiveContentExtension(pathname = "") {
|
|
62
71
|
const ext = import_node_path.default.extname(stripQueryAndHash(pathname)).toLowerCase();
|
|
63
72
|
return ACTIVE_CONTENT_EXTENSIONS.has(ext);
|
|
@@ -68,7 +77,7 @@ function getStorageUploadSecurityHeaders(pathname = "") {
|
|
|
68
77
|
"Content-Security-Policy": "sandbox",
|
|
69
78
|
"X-Content-Type-Options": "nosniff"
|
|
70
79
|
};
|
|
71
|
-
if (hasActiveContentExtension(pathname)) {
|
|
80
|
+
if (hasActiveContentExtension(pathname) || shouldDownload(pathname)) {
|
|
72
81
|
headers["Content-Disposition"] = "attachment";
|
|
73
82
|
}
|
|
74
83
|
return headers;
|
package/lib/gateway/utils.d.ts
CHANGED
|
@@ -10,9 +10,15 @@
|
|
|
10
10
|
import { IncomingMessage } from 'http';
|
|
11
11
|
import { IncomingRequest } from '.';
|
|
12
12
|
export declare const MODERN_CLIENT_DIST_DIR = "v";
|
|
13
|
+
export declare const PORTAL_CLIENT_PREFIX = "x";
|
|
14
|
+
export declare const DEFAULT_PORTAL_APP_NAME = "main";
|
|
15
|
+
export declare const DEFAULT_PORTAL_NAME = "admin";
|
|
13
16
|
export declare function resolvePublicPath(appPublicPath?: string): string;
|
|
14
17
|
export declare function normalizeModernClientPrefix(value?: string): string;
|
|
15
18
|
export declare function resolveV2PublicPath(appPublicPath?: string): string;
|
|
19
|
+
export declare function normalizePortalName(value?: string): string;
|
|
20
|
+
export declare function normalizePortalAppName(value?: string): string;
|
|
21
|
+
export declare function resolvePortalPublicPath(portalName: string, appPublicPath?: string): string;
|
|
16
22
|
export declare function rewriteV2AssetPublicPath(html: string, assetPublicPath: string): string;
|
|
17
23
|
export declare function injectRuntimeScript(html: string, runtimeScript: string): string;
|
|
18
24
|
export declare function getHost(req: IncomingMessage | IncomingRequest): any;
|
package/lib/gateway/utils.js
CHANGED
|
@@ -27,17 +27,26 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
27
27
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
28
28
|
var utils_exports = {};
|
|
29
29
|
__export(utils_exports, {
|
|
30
|
+
DEFAULT_PORTAL_APP_NAME: () => DEFAULT_PORTAL_APP_NAME,
|
|
31
|
+
DEFAULT_PORTAL_NAME: () => DEFAULT_PORTAL_NAME,
|
|
30
32
|
MODERN_CLIENT_DIST_DIR: () => MODERN_CLIENT_DIST_DIR,
|
|
33
|
+
PORTAL_CLIENT_PREFIX: () => PORTAL_CLIENT_PREFIX,
|
|
31
34
|
getHost: () => getHost,
|
|
32
35
|
getHostname: () => getHostname,
|
|
33
36
|
injectRuntimeScript: () => injectRuntimeScript,
|
|
34
37
|
normalizeModernClientPrefix: () => normalizeModernClientPrefix,
|
|
38
|
+
normalizePortalAppName: () => normalizePortalAppName,
|
|
39
|
+
normalizePortalName: () => normalizePortalName,
|
|
40
|
+
resolvePortalPublicPath: () => resolvePortalPublicPath,
|
|
35
41
|
resolvePublicPath: () => resolvePublicPath,
|
|
36
42
|
resolveV2PublicPath: () => resolveV2PublicPath,
|
|
37
43
|
rewriteV2AssetPublicPath: () => rewriteV2AssetPublicPath
|
|
38
44
|
});
|
|
39
45
|
module.exports = __toCommonJS(utils_exports);
|
|
40
46
|
const MODERN_CLIENT_DIST_DIR = "v";
|
|
47
|
+
const PORTAL_CLIENT_PREFIX = "x";
|
|
48
|
+
const DEFAULT_PORTAL_APP_NAME = "main";
|
|
49
|
+
const DEFAULT_PORTAL_NAME = "admin";
|
|
41
50
|
function resolvePublicPath(appPublicPath = "/") {
|
|
42
51
|
const normalized = String(appPublicPath || "/").trim() || "/";
|
|
43
52
|
const withLeadingSlash = normalized.startsWith("/") ? normalized : `/${normalized}`;
|
|
@@ -55,6 +64,21 @@ function resolveV2PublicPath(appPublicPath = "/") {
|
|
|
55
64
|
return `${publicPath.replace(/\/$/, "")}/${prefix}/`;
|
|
56
65
|
}
|
|
57
66
|
__name(resolveV2PublicPath, "resolveV2PublicPath");
|
|
67
|
+
function normalizePortalName(value) {
|
|
68
|
+
const segment = String(value || "").trim().replace(/^\/+|\/+$/g, "");
|
|
69
|
+
return segment || DEFAULT_PORTAL_NAME;
|
|
70
|
+
}
|
|
71
|
+
__name(normalizePortalName, "normalizePortalName");
|
|
72
|
+
function normalizePortalAppName(value) {
|
|
73
|
+
const segment = String(value || "").trim().replace(/^\/+|\/+$/g, "");
|
|
74
|
+
return segment || DEFAULT_PORTAL_APP_NAME;
|
|
75
|
+
}
|
|
76
|
+
__name(normalizePortalAppName, "normalizePortalAppName");
|
|
77
|
+
function resolvePortalPublicPath(portalName, appPublicPath = "/") {
|
|
78
|
+
const publicPath = resolvePublicPath(appPublicPath);
|
|
79
|
+
return `${publicPath.replace(/\/$/, "")}/${PORTAL_CLIENT_PREFIX}/${normalizePortalName(portalName)}/`;
|
|
80
|
+
}
|
|
81
|
+
__name(resolvePortalPublicPath, "resolvePortalPublicPath");
|
|
58
82
|
function ensureTrailingSlash(value) {
|
|
59
83
|
return value.endsWith("/") ? value : `${value}/`;
|
|
60
84
|
}
|
|
@@ -117,11 +141,17 @@ function getHostname(req) {
|
|
|
117
141
|
__name(getHostname, "getHostname");
|
|
118
142
|
// Annotate the CommonJS export names for ESM import in node:
|
|
119
143
|
0 && (module.exports = {
|
|
144
|
+
DEFAULT_PORTAL_APP_NAME,
|
|
145
|
+
DEFAULT_PORTAL_NAME,
|
|
120
146
|
MODERN_CLIENT_DIST_DIR,
|
|
147
|
+
PORTAL_CLIENT_PREFIX,
|
|
121
148
|
getHost,
|
|
122
149
|
getHostname,
|
|
123
150
|
injectRuntimeScript,
|
|
124
151
|
normalizeModernClientPrefix,
|
|
152
|
+
normalizePortalAppName,
|
|
153
|
+
normalizePortalName,
|
|
154
|
+
resolvePortalPublicPath,
|
|
125
155
|
resolvePublicPath,
|
|
126
156
|
resolveV2PublicPath,
|
|
127
157
|
rewriteV2AssetPublicPath
|
package/lib/helper.js
CHANGED
|
@@ -77,23 +77,25 @@ function createResourcer(options) {
|
|
|
77
77
|
return new import_resourcer.Resourcer({ ...options.resourcer });
|
|
78
78
|
}
|
|
79
79
|
__name(createResourcer, "createResourcer");
|
|
80
|
+
function isWhitelistedCorsOrigin(ctx) {
|
|
81
|
+
const origin = ctx.get("origin");
|
|
82
|
+
if (!origin) {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
return (0, import_utils.isTrustedOrigin)(ctx, origin);
|
|
86
|
+
}
|
|
87
|
+
__name(isWhitelistedCorsOrigin, "isWhitelistedCorsOrigin");
|
|
80
88
|
function resolveCorsOrigin(ctx) {
|
|
81
89
|
const origin = ctx.get("origin");
|
|
82
90
|
const disallowNoOrigin = process.env.CORS_DISALLOW_NO_ORIGIN === "true";
|
|
83
|
-
const
|
|
91
|
+
const whitelist = (0, import_utils.getCorsWhitelist)();
|
|
84
92
|
if (!origin && disallowNoOrigin) {
|
|
85
93
|
return false;
|
|
86
94
|
}
|
|
87
|
-
if (
|
|
95
|
+
if (isWhitelistedCorsOrigin(ctx)) {
|
|
88
96
|
return origin;
|
|
89
97
|
}
|
|
90
|
-
|
|
91
|
-
whitelistString.split(",").map((item) => item.trim()).filter(Boolean)
|
|
92
|
-
);
|
|
93
|
-
if (whitelist.has("*") || whitelist.has(origin)) {
|
|
94
|
-
return origin;
|
|
95
|
-
}
|
|
96
|
-
return false;
|
|
98
|
+
return whitelist ? false : origin;
|
|
97
99
|
}
|
|
98
100
|
__name(resolveCorsOrigin, "resolveCorsOrigin");
|
|
99
101
|
function registerMiddlewares(app, options) {
|
|
@@ -109,6 +111,7 @@ function registerMiddlewares(app, options) {
|
|
|
109
111
|
app.use((0, import_logger.requestLogger)(app.name, app.requestLogger, (_a = options.logger) == null ? void 0 : _a.request), { tag: "logger" });
|
|
110
112
|
app.use(
|
|
111
113
|
(0, import_cors.default)({
|
|
114
|
+
credentials: isWhitelistedCorsOrigin,
|
|
112
115
|
exposeHeaders: ["content-disposition"],
|
|
113
116
|
origin: resolveCorsOrigin,
|
|
114
117
|
...options.cors
|
|
@@ -135,8 +138,18 @@ function registerMiddlewares(app, options) {
|
|
|
135
138
|
}
|
|
136
139
|
app.use(/* @__PURE__ */ __name(async function getBearerToken(ctx, next) {
|
|
137
140
|
ctx.getBearerToken = () => {
|
|
138
|
-
const
|
|
139
|
-
|
|
141
|
+
const authorization = ctx.get("Authorization");
|
|
142
|
+
if (authorization) {
|
|
143
|
+
ctx.state.pendingAuthTokenSource = "authorization";
|
|
144
|
+
return authorization.replace(/^Bearer\s+/gi, "");
|
|
145
|
+
}
|
|
146
|
+
if (ctx.query.token) {
|
|
147
|
+
ctx.state.pendingAuthTokenSource = "query";
|
|
148
|
+
return ctx.query.token;
|
|
149
|
+
}
|
|
150
|
+
const cookieToken = ctx.cookies.get((0, import_utils.getAuthCookieName)("authToken", app.name));
|
|
151
|
+
ctx.state.pendingAuthTokenSource = cookieToken ? "cookie" : void 0;
|
|
152
|
+
return cookieToken;
|
|
140
153
|
};
|
|
141
154
|
await next();
|
|
142
155
|
}, "getBearerToken"));
|
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
|
+
"version": "2.2.0-alpha.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.
|
|
14
|
-
"@nocobase/actions": "2.
|
|
15
|
-
"@nocobase/ai": "2.
|
|
16
|
-
"@nocobase/auth": "2.
|
|
17
|
-
"@nocobase/cache": "2.
|
|
18
|
-
"@nocobase/data-source-manager": "2.
|
|
19
|
-
"@nocobase/database": "2.
|
|
20
|
-
"@nocobase/evaluators": "2.
|
|
21
|
-
"@nocobase/lock-manager": "2.
|
|
22
|
-
"@nocobase/logger": "2.
|
|
23
|
-
"@nocobase/resourcer": "2.
|
|
24
|
-
"@nocobase/sdk": "2.
|
|
25
|
-
"@nocobase/snowflake-id": "2.
|
|
26
|
-
"@nocobase/telemetry": "2.
|
|
27
|
-
"@nocobase/utils": "2.
|
|
13
|
+
"@nocobase/acl": "2.2.0-alpha.10",
|
|
14
|
+
"@nocobase/actions": "2.2.0-alpha.10",
|
|
15
|
+
"@nocobase/ai": "2.2.0-alpha.10",
|
|
16
|
+
"@nocobase/auth": "2.2.0-alpha.10",
|
|
17
|
+
"@nocobase/cache": "2.2.0-alpha.10",
|
|
18
|
+
"@nocobase/data-source-manager": "2.2.0-alpha.10",
|
|
19
|
+
"@nocobase/database": "2.2.0-alpha.10",
|
|
20
|
+
"@nocobase/evaluators": "2.2.0-alpha.10",
|
|
21
|
+
"@nocobase/lock-manager": "2.2.0-alpha.10",
|
|
22
|
+
"@nocobase/logger": "2.2.0-alpha.10",
|
|
23
|
+
"@nocobase/resourcer": "2.2.0-alpha.10",
|
|
24
|
+
"@nocobase/sdk": "2.2.0-alpha.10",
|
|
25
|
+
"@nocobase/snowflake-id": "2.2.0-alpha.10",
|
|
26
|
+
"@nocobase/telemetry": "2.2.0-alpha.10",
|
|
27
|
+
"@nocobase/utils": "2.2.0-alpha.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": "b5a3f131bb0a4a2fbfd96852896bccadc933d834"
|
|
65
65
|
}
|