@nocobase/plugin-file-manager 2.1.0-alpha.45 → 2.1.0-alpha.46
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/dist/client/index.js +1 -1
- package/dist/client/templates/file.d.ts +2 -2
- package/dist/client-v2/index.js +1 -1
- package/dist/externalVersion.js +9 -9
- package/dist/node_modules/@aws-sdk/client-s3/package.json +1 -1
- package/dist/node_modules/@aws-sdk/lib-storage/package.json +1 -1
- package/dist/node_modules/ali-oss/package.json +1 -1
- package/dist/node_modules/cos-nodejs-sdk-v5/package.json +1 -1
- package/dist/node_modules/mime-match/package.json +1 -1
- package/dist/node_modules/mime-types/package.json +1 -1
- package/dist/node_modules/mkdirp/package.json +1 -1
- package/dist/node_modules/url-join/package.json +1 -1
- package/dist/server/actions/attachments.js +2 -2
- package/dist/server/actions/index.js +8 -1
- package/dist/server/actions/storage-validation.d.ts +10 -0
- package/dist/server/actions/storage-validation.js +73 -0
- package/dist/server/server.js +3 -0
- package/dist/server/storages/local.d.ts +4 -0
- package/dist/server/storages/local.js +56 -13
- package/package.json +2 -2
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
var __defProp = Object.defineProperty;
|
|
11
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
12
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
13
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
14
|
+
var __export = (target, all) => {
|
|
15
|
+
for (var name in all)
|
|
16
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
17
|
+
};
|
|
18
|
+
var __copyProps = (to, from, except, desc) => {
|
|
19
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
20
|
+
for (let key of __getOwnPropNames(from))
|
|
21
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
22
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
23
|
+
}
|
|
24
|
+
return to;
|
|
25
|
+
};
|
|
26
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
27
|
+
var storage_validation_exports = {};
|
|
28
|
+
__export(storage_validation_exports, {
|
|
29
|
+
validateStorageMiddleware: () => validateStorageMiddleware
|
|
30
|
+
});
|
|
31
|
+
module.exports = __toCommonJS(storage_validation_exports);
|
|
32
|
+
var import_local = require("../storages/local");
|
|
33
|
+
async function validateStorageMiddleware(ctx, next) {
|
|
34
|
+
const { actionName, params } = ctx.action;
|
|
35
|
+
const values = params.values || {};
|
|
36
|
+
let storage = values;
|
|
37
|
+
const hasSubmittedDocumentRoot = Object.prototype.hasOwnProperty.call(values.options || {}, "documentRoot");
|
|
38
|
+
if (actionName === "update" && params.filterByTk) {
|
|
39
|
+
const repository = ctx.db.getRepository("storages");
|
|
40
|
+
let existing = await repository.findById(params.filterByTk);
|
|
41
|
+
if (!existing) {
|
|
42
|
+
existing = await repository.findOne({
|
|
43
|
+
filter: {
|
|
44
|
+
name: params.filterByTk
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
if (existing) {
|
|
49
|
+
const existingValues = existing.toJSON();
|
|
50
|
+
storage = {
|
|
51
|
+
...existingValues,
|
|
52
|
+
...values,
|
|
53
|
+
options: {
|
|
54
|
+
...existingValues.options || {},
|
|
55
|
+
...values.options || {}
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
try {
|
|
61
|
+
(0, import_local.validateLocalStorageConfig)(storage, { validateDocumentRoot: hasSubmittedDocumentRoot });
|
|
62
|
+
} catch (error) {
|
|
63
|
+
if (error.code === "PATH_TRAVERSAL") {
|
|
64
|
+
return ctx.throw(400, error.message);
|
|
65
|
+
}
|
|
66
|
+
throw error;
|
|
67
|
+
}
|
|
68
|
+
await next();
|
|
69
|
+
}
|
|
70
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
71
|
+
0 && (module.exports = {
|
|
72
|
+
validateStorageMiddleware
|
|
73
|
+
});
|
package/dist/server/server.js
CHANGED
|
@@ -216,6 +216,9 @@ class PluginFileManagerServer extends import_server.Plugin {
|
|
|
216
216
|
this.storageTypes.register(import_constants.STORAGE_TYPE_S3, import_s3.default);
|
|
217
217
|
this.storageTypes.register(import_constants.STORAGE_TYPE_TX_COS, import_tx_cos.default);
|
|
218
218
|
const Storage = this.db.getModel("storages");
|
|
219
|
+
Storage.beforeSave((m) => {
|
|
220
|
+
(0, import_local.validateLocalStorageConfig)(m.toJSON());
|
|
221
|
+
});
|
|
219
222
|
Storage.afterSave(async (m, { transaction }) => {
|
|
220
223
|
await this.loadStorages({ transaction });
|
|
221
224
|
this.sendSyncMessage({ type: "reloadStorages" }, { transaction });
|
|
@@ -10,8 +10,12 @@
|
|
|
10
10
|
import multer from 'multer';
|
|
11
11
|
import type { Readable } from 'stream';
|
|
12
12
|
import { AttachmentModel, StorageType } from '.';
|
|
13
|
+
export declare function normalizeLocalStoragePath(storagePath?: unknown): string;
|
|
13
14
|
export declare function getDocumentRoot(storage: any): string;
|
|
14
15
|
export declare function resolveSafePath(documentRoot: string, filePath?: string, filename?: string): string;
|
|
16
|
+
export declare function validateLocalStorageConfig(storage: Pick<StorageType['storage'], 'type' | 'options' | 'path'>, { validateDocumentRoot }?: {
|
|
17
|
+
validateDocumentRoot?: boolean;
|
|
18
|
+
}): void;
|
|
15
19
|
export default class extends StorageType {
|
|
16
20
|
static defaults(): {
|
|
17
21
|
title: string;
|
|
@@ -38,7 +38,9 @@ var local_exports = {};
|
|
|
38
38
|
__export(local_exports, {
|
|
39
39
|
default: () => local_default,
|
|
40
40
|
getDocumentRoot: () => getDocumentRoot,
|
|
41
|
-
|
|
41
|
+
normalizeLocalStoragePath: () => normalizeLocalStoragePath,
|
|
42
|
+
resolveSafePath: () => resolveSafePath,
|
|
43
|
+
validateLocalStorageConfig: () => validateLocalStorageConfig
|
|
42
44
|
});
|
|
43
45
|
module.exports = __toCommonJS(local_exports);
|
|
44
46
|
var import_utils = require("@nocobase/utils");
|
|
@@ -51,25 +53,64 @@ var import__ = require(".");
|
|
|
51
53
|
var import_constants = require("../../constants");
|
|
52
54
|
var import_utils2 = require("../utils");
|
|
53
55
|
const DEFAULT_BASE_URL = "/storage/uploads";
|
|
56
|
+
function pathError(message) {
|
|
57
|
+
const error = new Error(message);
|
|
58
|
+
error.code = "PATH_TRAVERSAL";
|
|
59
|
+
return error;
|
|
60
|
+
}
|
|
61
|
+
function isInside(base, target) {
|
|
62
|
+
const relative = import_path.default.relative(base, target);
|
|
63
|
+
return !relative.startsWith("..") && !import_path.default.isAbsolute(relative);
|
|
64
|
+
}
|
|
65
|
+
function resolveDocumentRoot(documentRoot) {
|
|
66
|
+
if (typeof documentRoot !== "string" || !documentRoot || documentRoot.includes("\0")) {
|
|
67
|
+
throw pathError("Invalid local storage document root");
|
|
68
|
+
}
|
|
69
|
+
return (0, import_utils2.normalizeDocumentRoot)(documentRoot);
|
|
70
|
+
}
|
|
71
|
+
function allowedRoots() {
|
|
72
|
+
var _a;
|
|
73
|
+
const roots = [(0, import_utils.storagePathJoin)()];
|
|
74
|
+
const extra = [process.env.LOCAL_STORAGE_DEST, ...((_a = process.env.LOCAL_STORAGE_ALLOWED_ROOTS) == null ? void 0 : _a.split(",")) ?? []];
|
|
75
|
+
for (const item of extra) {
|
|
76
|
+
if (item == null ? void 0 : item.trim()) {
|
|
77
|
+
roots.push(resolveDocumentRoot(item.trim()));
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return roots;
|
|
81
|
+
}
|
|
82
|
+
function normalizeLocalStoragePath(storagePath) {
|
|
83
|
+
if (storagePath == null || storagePath === "") {
|
|
84
|
+
return "";
|
|
85
|
+
}
|
|
86
|
+
if (typeof storagePath !== "string" || storagePath.includes("\0")) {
|
|
87
|
+
throw pathError("Invalid local storage path");
|
|
88
|
+
}
|
|
89
|
+
return storagePath.replace(/\\/g, "/").replace(/^\/+/, "");
|
|
90
|
+
}
|
|
54
91
|
function getDocumentRoot(storage) {
|
|
55
92
|
var _a;
|
|
56
93
|
const raw = ((_a = storage == null ? void 0 : storage.options) == null ? void 0 : _a.documentRoot) ?? process.env.LOCAL_STORAGE_DEST ?? (0, import_utils.storagePathJoin)("uploads");
|
|
57
|
-
|
|
58
|
-
return raw;
|
|
59
|
-
}
|
|
60
|
-
return (0, import_utils2.normalizeDocumentRoot)(raw);
|
|
94
|
+
return resolveDocumentRoot(raw);
|
|
61
95
|
}
|
|
62
96
|
function resolveSafePath(documentRoot, filePath, filename) {
|
|
63
|
-
const root = (
|
|
97
|
+
const root = resolveDocumentRoot(documentRoot);
|
|
64
98
|
const target = import_path.default.resolve(root, filePath || "", filename || "");
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
const error = new Error("Access denied");
|
|
68
|
-
error.code = "PATH_TRAVERSAL";
|
|
69
|
-
throw error;
|
|
99
|
+
if (!isInside(root, target)) {
|
|
100
|
+
throw pathError("Access denied");
|
|
70
101
|
}
|
|
71
102
|
return target;
|
|
72
103
|
}
|
|
104
|
+
function validateLocalStorageConfig(storage, { validateDocumentRoot = false } = {}) {
|
|
105
|
+
if (storage.type !== import_constants.STORAGE_TYPE_LOCAL) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
const root = getDocumentRoot(storage);
|
|
109
|
+
if (validateDocumentRoot && !allowedRoots().some((allowed) => isInside(allowed, root))) {
|
|
110
|
+
throw pathError("Invalid local storage document root");
|
|
111
|
+
}
|
|
112
|
+
resolveSafePath(root, normalizeLocalStoragePath(storage.path));
|
|
113
|
+
}
|
|
73
114
|
class local_default extends import__.StorageType {
|
|
74
115
|
static defaults() {
|
|
75
116
|
return {
|
|
@@ -89,7 +130,7 @@ class local_default extends import__.StorageType {
|
|
|
89
130
|
make() {
|
|
90
131
|
return import_multer.default.diskStorage({
|
|
91
132
|
destination: (req, file, cb) => {
|
|
92
|
-
const destPath =
|
|
133
|
+
const destPath = resolveSafePath(getDocumentRoot(this.storage), normalizeLocalStoragePath(this.storage.path));
|
|
93
134
|
const mkdirp = require("mkdirp");
|
|
94
135
|
mkdirp(destPath, (err) => cb(err, destPath));
|
|
95
136
|
},
|
|
@@ -162,5 +203,7 @@ class local_default extends import__.StorageType {
|
|
|
162
203
|
// Annotate the CommonJS export names for ESM import in node:
|
|
163
204
|
0 && (module.exports = {
|
|
164
205
|
getDocumentRoot,
|
|
165
|
-
|
|
206
|
+
normalizeLocalStoragePath,
|
|
207
|
+
resolveSafePath,
|
|
208
|
+
validateLocalStorageConfig
|
|
166
209
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nocobase/plugin-file-manager",
|
|
3
|
-
"version": "2.1.0-alpha.
|
|
3
|
+
"version": "2.1.0-alpha.46",
|
|
4
4
|
"displayName": "File manager",
|
|
5
5
|
"displayName.ru-RU": "Менеджер файлов",
|
|
6
6
|
"displayName.zh-CN": "文件管理器",
|
|
@@ -60,5 +60,5 @@
|
|
|
60
60
|
"Collections",
|
|
61
61
|
"Collection fields"
|
|
62
62
|
],
|
|
63
|
-
"gitHead": "
|
|
63
|
+
"gitHead": "42b269944cdd1908d7a848c0af4936fe94c03bb7"
|
|
64
64
|
}
|