@nocobase/server 2.2.0-beta.8 → 2.3.0-alpha.1

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.
@@ -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>;
@@ -78,6 +78,9 @@ const _MainOnlyAdapter = class _MainOnlyAdapter {
78
78
  getApps() {
79
79
  return Object.values(this.apps);
80
80
  }
81
+ async listAppModels() {
82
+ return [];
83
+ }
81
84
  hasApp(appName) {
82
85
  if (appName !== "main") {
83
86
  return false;
@@ -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>;
@@ -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",
@@ -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) {
@@ -202,6 +213,17 @@ const _Gateway = class _Gateway extends import_events.EventEmitter {
202
213
  req.url = rewrittenUrl;
203
214
  }
204
215
  }
216
+ const fileAccessRestPath = parsedUrl.pathname ? getFileAccessRestPath(parsedUrl.pathname, import_node_process.default.env.APP_PUBLIC_PATH || "/") : null;
217
+ if (fileAccessRestPath) {
218
+ const restPath = fileAccessRestPath;
219
+ const [pathAppName] = restPath.split("/");
220
+ if (pathAppName) {
221
+ try {
222
+ ctx.resolvedAppName = decodeURIComponent(pathAppName);
223
+ } catch (error) {
224
+ }
225
+ }
226
+ }
205
227
  await next();
206
228
  },
207
229
  {
@@ -337,7 +359,7 @@ const _Gateway = class _Gateway extends import_events.EventEmitter {
337
359
  return (0, import_utils3.injectRuntimeScript)(html, this.getV2RuntimeConfigScript());
338
360
  }
339
361
  async requestHandler(req, res) {
340
- const { pathname } = (0, import_url.parse)(req.url);
362
+ const { pathname, search } = (0, import_url.parse)(req.url);
341
363
  const { PLUGIN_STATICS_PATH } = import_node_process.default.env;
342
364
  const APP_PUBLIC_PATH = this.getAppPublicPath();
343
365
  if (pathname.endsWith("/__umi/api/bundle-status")) {
@@ -345,6 +367,12 @@ const _Gateway = class _Gateway extends import_events.EventEmitter {
345
367
  res.end("ok");
346
368
  return;
347
369
  }
370
+ if (APP_PUBLIC_PATH !== "/" && pathname.startsWith("/files/")) {
371
+ res.statusCode = 302;
372
+ res.setHeader("Location", `${APP_PUBLIC_PATH.replace(/\/$/, "")}${pathname}${search || ""}`);
373
+ res.end();
374
+ return;
375
+ }
348
376
  const supervisor = import_app_supervisor.AppSupervisor.getInstance();
349
377
  let handleApp = "main";
350
378
  try {
@@ -361,7 +389,7 @@ const _Gateway = class _Gateway extends import_events.EventEmitter {
361
389
  return;
362
390
  }
363
391
  }
364
- const headers = (0, import_static_file_security.getStorageUploadSecurityHeaders)(pathname);
392
+ const headers = (0, import_static_file_security.getStorageUploadSecurityHeaders)(`${pathname}${search || ""}`);
365
393
  for (const [key, value] of Object.entries(headers)) {
366
394
  res.setHeader(key, value);
367
395
  }
@@ -407,7 +435,8 @@ const _Gateway = class _Gateway extends import_events.EventEmitter {
407
435
  ]
408
436
  });
409
437
  }
410
- if (!pathname.startsWith(import_node_process.default.env.API_BASE_PATH)) {
438
+ const isFilesRequest = Boolean(getFileAccessRestPath(pathname, APP_PUBLIC_PATH));
439
+ if (!pathname.startsWith(import_node_process.default.env.API_BASE_PATH) && !isFilesRequest) {
411
440
  if (this.isV2Request(pathname)) {
412
441
  if (handleApp !== "main") {
413
442
  const isProxy = await this.proxyRequestToSubApp(supervisor, handleApp, req, res);
@@ -47,6 +47,15 @@ function stripQueryAndHash(pathname = "") {
47
47
  return pathname.split("?")[0].split("#")[0];
48
48
  }
49
49
  __name(stripQueryAndHash, "stripQueryAndHash");
50
+ function shouldDownload(pathname = "") {
51
+ var _a;
52
+ const query = (_a = pathname.split("?")[1]) == null ? void 0 : _a.split("#")[0];
53
+ if (!query) {
54
+ return false;
55
+ }
56
+ return new URLSearchParams(query).get("download") === "1";
57
+ }
58
+ __name(shouldDownload, "shouldDownload");
50
59
  function hasActiveContentExtension(pathname = "") {
51
60
  const ext = import_node_path.default.extname(stripQueryAndHash(pathname)).toLowerCase();
52
61
  return ACTIVE_CONTENT_EXTENSIONS.has(ext);
@@ -56,7 +65,7 @@ function getStorageUploadSecurityHeaders(pathname = "") {
56
65
  const headers = {
57
66
  "X-Content-Type-Options": "nosniff"
58
67
  };
59
- if (hasActiveContentExtension(pathname)) {
68
+ if (hasActiveContentExtension(pathname) || shouldDownload(pathname)) {
60
69
  headers["Content-Disposition"] = "attachment";
61
70
  }
62
71
  return headers;
package/lib/helper.js CHANGED
@@ -76,23 +76,28 @@ function createResourcer(options) {
76
76
  return new import_resourcer.Resourcer({ ...options.resourcer });
77
77
  }
78
78
  __name(createResourcer, "createResourcer");
79
+ function isWhitelistedCorsOrigin(ctx) {
80
+ const origin = ctx.get("origin");
81
+ const whitelist = (0, import_utils.getCorsWhitelist)();
82
+ if (!origin) {
83
+ return false;
84
+ }
85
+ if (!whitelist) {
86
+ return (0, import_utils.isTrustedOrigin)(ctx, origin);
87
+ }
88
+ return whitelist.has(origin);
89
+ }
90
+ __name(isWhitelistedCorsOrigin, "isWhitelistedCorsOrigin");
79
91
  function resolveCorsOrigin(ctx) {
80
92
  const origin = ctx.get("origin");
81
93
  const disallowNoOrigin = process.env.CORS_DISALLOW_NO_ORIGIN === "true";
82
- const whitelistString = process.env.CORS_ORIGIN_WHITELIST;
83
94
  if (!origin && disallowNoOrigin) {
84
95
  return false;
85
96
  }
86
- if (!whitelistString) {
97
+ if (isWhitelistedCorsOrigin(ctx)) {
87
98
  return origin;
88
99
  }
89
- const whitelist = new Set(
90
- whitelistString.split(",").map((item) => item.trim()).filter(Boolean)
91
- );
92
- if (whitelist.has(origin)) {
93
- return origin;
94
- }
95
- return false;
100
+ return (0, import_utils.getCorsWhitelist)() ? false : origin;
96
101
  }
97
102
  __name(resolveCorsOrigin, "resolveCorsOrigin");
98
103
  function registerMiddlewares(app, options) {
@@ -108,6 +113,7 @@ function registerMiddlewares(app, options) {
108
113
  app.use((0, import_logger.requestLogger)(app.name, app.requestLogger, (_a = options.logger) == null ? void 0 : _a.request), { tag: "logger" });
109
114
  app.use(
110
115
  (0, import_cors.default)({
116
+ credentials: isWhitelistedCorsOrigin,
111
117
  exposeHeaders: ["content-disposition"],
112
118
  origin: resolveCorsOrigin,
113
119
  ...options.cors
@@ -134,8 +140,18 @@ function registerMiddlewares(app, options) {
134
140
  }
135
141
  app.use(/* @__PURE__ */ __name(async function getBearerToken(ctx, next) {
136
142
  ctx.getBearerToken = () => {
137
- const token = ctx.get("Authorization").replace(/^Bearer\s+/gi, "");
138
- return token || ctx.query.token;
143
+ const authorization = ctx.get("Authorization");
144
+ if (authorization) {
145
+ ctx.state.pendingAuthTokenSource = "authorization";
146
+ return authorization.replace(/^Bearer\s+/gi, "");
147
+ }
148
+ if (ctx.query.token) {
149
+ ctx.state.pendingAuthTokenSource = "query";
150
+ return ctx.query.token;
151
+ }
152
+ const cookieToken = ctx.cookies.get((0, import_utils.getAuthCookieName)("authToken", app.name));
153
+ ctx.state.pendingAuthTokenSource = cookieToken ? "cookie" : void 0;
154
+ return cookieToken;
139
155
  };
140
156
  await next();
141
157
  }, "getBearerToken"));
@@ -62,6 +62,7 @@ async function trim(packageNames) {
62
62
  }
63
63
  __name(trim, "trim");
64
64
  const excludes = [
65
+ "external-db-data-source",
65
66
  "@nocobase/plugin-audit-logs",
66
67
  "@nocobase/plugin-backup-restore",
67
68
  "@nocobase/plugin-charts",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nocobase/server",
3
- "version": "2.2.0-beta.8",
3
+ "version": "2.3.0-alpha.1",
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.0-beta.8",
14
- "@nocobase/actions": "2.2.0-beta.8",
15
- "@nocobase/ai": "2.2.0-beta.8",
16
- "@nocobase/auth": "2.2.0-beta.8",
17
- "@nocobase/cache": "2.2.0-beta.8",
18
- "@nocobase/data-source-manager": "2.2.0-beta.8",
19
- "@nocobase/database": "2.2.0-beta.8",
20
- "@nocobase/evaluators": "2.2.0-beta.8",
21
- "@nocobase/lock-manager": "2.2.0-beta.8",
22
- "@nocobase/logger": "2.2.0-beta.8",
23
- "@nocobase/resourcer": "2.2.0-beta.8",
24
- "@nocobase/sdk": "2.2.0-beta.8",
25
- "@nocobase/snowflake-id": "2.2.0-beta.8",
26
- "@nocobase/telemetry": "2.2.0-beta.8",
27
- "@nocobase/utils": "2.2.0-beta.8",
13
+ "@nocobase/acl": "2.3.0-alpha.1",
14
+ "@nocobase/actions": "2.3.0-alpha.1",
15
+ "@nocobase/ai": "2.3.0-alpha.1",
16
+ "@nocobase/auth": "2.3.0-alpha.1",
17
+ "@nocobase/cache": "2.3.0-alpha.1",
18
+ "@nocobase/data-source-manager": "2.3.0-alpha.1",
19
+ "@nocobase/database": "2.3.0-alpha.1",
20
+ "@nocobase/evaluators": "2.3.0-alpha.1",
21
+ "@nocobase/lock-manager": "2.3.0-alpha.1",
22
+ "@nocobase/logger": "2.3.0-alpha.1",
23
+ "@nocobase/resourcer": "2.3.0-alpha.1",
24
+ "@nocobase/sdk": "2.3.0-alpha.1",
25
+ "@nocobase/snowflake-id": "2.3.0-alpha.1",
26
+ "@nocobase/telemetry": "2.3.0-alpha.1",
27
+ "@nocobase/utils": "2.3.0-alpha.1",
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": "fa2502c1e9faf6d74b3f51b42dbc6546638d46af"
64
+ "gitHead": "2377df8ceb12549149017f7f14a61207bf6e49a2"
65
65
  }