mbkbucket 4.1.0 → 4.2.0
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/cli.js +1 -1
- package/lib/src/app.js +10 -24
- package/lib/src/controllers/api.controller.js +2 -1
- package/lib/src/controllers/dashboard.controller.js +2 -2
- package/lib/src/controllers/info.controller.js +2 -1
- package/lib/src/controllers/view.controller.js +2 -1
- package/lib/src/core/metrics.js +134 -134
- package/lib/src/core/storage/capabilities.js +58 -58
- package/lib/src/core/storage/errors.js +57 -57
- package/lib/src/core/storage/gdrive-auth.js +157 -157
- package/lib/src/core/storage/gdrive-provider.js +715 -714
- package/lib/src/core/storage/index.js +17 -17
- package/lib/src/core/storage/local-provider.js +507 -506
- package/lib/src/core/storage/models.js +121 -120
- package/lib/src/core/storage/path-strategy.js +85 -78
- package/lib/src/core/storage/s3-provider.js +472 -471
- package/lib/src/core/storage/storage-manager.js +226 -223
- package/lib/src/core/storage/storage-provider.js +146 -146
- package/lib/src/middleware/error-handler.js +15 -52
- package/lib/src/middleware/rate-limiter.js +1 -1
- package/lib/src/services/storage.service.js +1 -1
- package/lib/src/utils/cleanup-scheduler.js +91 -91
- package/lib/src/utils/helpers.js +2 -45
- package/package.json +3 -2
|
@@ -1,120 +1,121 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* MBKBucket - Normalized Storage Data Models
|
|
3
|
-
* Copyright (c) 2026 Muhammad Bin Khalid, MBKTech.org and contributors
|
|
4
|
-
* Licensed under the MIT License.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { nowIso,
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
this.
|
|
18
|
-
this.
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
this.
|
|
24
|
-
this.
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
this.
|
|
28
|
-
this.
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
this.
|
|
69
|
-
this.
|
|
70
|
-
this.
|
|
71
|
-
this.
|
|
72
|
-
this.
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
if (props.
|
|
76
|
-
if (props.
|
|
77
|
-
if (props.
|
|
78
|
-
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
this.
|
|
108
|
-
this.
|
|
109
|
-
this.
|
|
110
|
-
this.
|
|
111
|
-
this.
|
|
112
|
-
this.
|
|
113
|
-
this.
|
|
114
|
-
this.
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
this.
|
|
118
|
-
this.
|
|
119
|
-
|
|
120
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* MBKBucket - Normalized Storage Data Models
|
|
3
|
+
* Copyright (c) 2026 Muhammad Bin Khalid, MBKTech.org and contributors
|
|
4
|
+
* Licensed under the MIT License.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { nowIso, getFolderPath } from "#helpers";
|
|
8
|
+
import { getBaseName, trimLeadingSlashes, trimSlashes } from "mbkutils";
|
|
9
|
+
|
|
10
|
+
export class StorageItem {
|
|
11
|
+
constructor(props = {}) {
|
|
12
|
+
const rawPath = String(props.path || props.key || props.Key || props.prefix || props.Prefix || '');
|
|
13
|
+
this.path = props.type === 'folder'
|
|
14
|
+
? (rawPath ? (rawPath.endsWith('/') ? rawPath : `${rawPath}/`) : '')
|
|
15
|
+
: trimLeadingSlashes(rawPath);
|
|
16
|
+
|
|
17
|
+
this.type = props.type || (this.path.endsWith('/') ? 'folder' : 'file');
|
|
18
|
+
this.name = props.name || (this.type === 'folder' ? trimSlashes(getBaseName(this.path.replace(/\/+$/, ''))) : getBaseName(this.path));
|
|
19
|
+
this.parentPath = props.parentPath ?? (this.type === 'folder'
|
|
20
|
+
? trimSlashes(getFolderPath(this.path.replace(/\/+$/, '')))
|
|
21
|
+
: trimSlashes(getFolderPath(this.path)));
|
|
22
|
+
|
|
23
|
+
this.id = props.id || props.nativeId || this.path;
|
|
24
|
+
this.nativeId = props.nativeId || props.id || null;
|
|
25
|
+
this.provider = props.provider || props.storageClass || props.StorageClass || 'generic';
|
|
26
|
+
|
|
27
|
+
this.size = this.type === 'folder' ? 0 : Number(props.size ?? props.Size ?? 0);
|
|
28
|
+
this.mimeType = props.mimeType || props.contentType || props.ContentType || (this.type === 'folder' ? 'application/x-directory' : 'application/octet-stream');
|
|
29
|
+
this.etag = props.etag || props.ETag || '';
|
|
30
|
+
|
|
31
|
+
const modDate = props.lastModified || props.LastModified || props.updatedAt;
|
|
32
|
+
this.lastModified = modDate instanceof Date ? modDate : new Date(modDate || Date.now());
|
|
33
|
+
|
|
34
|
+
if (props.createdAt || props.createdTime) {
|
|
35
|
+
const createDate = props.createdAt || props.createdTime;
|
|
36
|
+
this.createdAt = createDate instanceof Date ? createDate : new Date(createDate);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
this.metadata = Object.freeze({ ...(props.metadata || {}) });
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
get isFolder() {
|
|
43
|
+
return this.type === 'folder';
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
get isFile() {
|
|
47
|
+
return this.type === 'file';
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export class StorageFile extends StorageItem {
|
|
52
|
+
constructor(props = {}) {
|
|
53
|
+
const key = props.key || props.Key || props.path || '';
|
|
54
|
+
super({
|
|
55
|
+
...props,
|
|
56
|
+
type: 'file',
|
|
57
|
+
path: key,
|
|
58
|
+
size: props.size ?? props.Size ?? 0,
|
|
59
|
+
mimeType: props.mimeType || props.contentType || props.ContentType,
|
|
60
|
+
lastModified: props.lastModified || props.LastModified,
|
|
61
|
+
etag: props.etag || props.ETag,
|
|
62
|
+
provider: props.storageClass || props.StorageClass || props.provider || 'STANDARD',
|
|
63
|
+
id: props.id,
|
|
64
|
+
nativeId: props.id || props.nativeId
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// Backward-compatible properties for S3 consumers
|
|
68
|
+
this.Key = this.path;
|
|
69
|
+
this.Size = this.size;
|
|
70
|
+
this.LastModified = this.lastModified;
|
|
71
|
+
this.ETag = this.etag;
|
|
72
|
+
this.ContentType = this.mimeType;
|
|
73
|
+
this.StorageClass = this.provider;
|
|
74
|
+
|
|
75
|
+
if (props.id) this.id = props.id;
|
|
76
|
+
if (props.mimeType) this.mimeType = props.mimeType;
|
|
77
|
+
if (props.webViewLink) this.webViewLink = props.webViewLink;
|
|
78
|
+
if (props.webContentLink) this.webContentLink = props.webContentLink;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export class StorageFolder extends StorageItem {
|
|
83
|
+
constructor(prefixOrProps, id) {
|
|
84
|
+
const props = typeof prefixOrProps === 'object' && prefixOrProps !== null
|
|
85
|
+
? prefixOrProps
|
|
86
|
+
: { prefix: prefixOrProps, id };
|
|
87
|
+
|
|
88
|
+
const rawPrefix = String(props.prefix || props.Prefix || props.path || '');
|
|
89
|
+
const cleanPrefix = rawPrefix ? (rawPrefix.endsWith('/') ? rawPrefix : `${rawPrefix}/`) : '';
|
|
90
|
+
|
|
91
|
+
super({
|
|
92
|
+
...props,
|
|
93
|
+
type: 'folder',
|
|
94
|
+
path: cleanPrefix,
|
|
95
|
+
id: props.id || id,
|
|
96
|
+
nativeId: props.id || id
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// Backward-compatible property for S3 consumers
|
|
100
|
+
this.Prefix = this.path;
|
|
101
|
+
if (this.id) this.id = this.id;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export class StorageListResult {
|
|
106
|
+
constructor(props = {}) {
|
|
107
|
+
this.Contents = props.contents || props.Contents || [];
|
|
108
|
+
this.CommonPrefixes = props.commonPrefixes || props.CommonPrefixes || [];
|
|
109
|
+
this.NextContinuationToken = props.nextToken ?? props.NextContinuationToken ?? null;
|
|
110
|
+
this.nextToken = this.NextContinuationToken;
|
|
111
|
+
this.IsTruncated = Boolean(props.isTruncated ?? props.IsTruncated ?? Boolean(this.NextContinuationToken));
|
|
112
|
+
this.hasMore = this.IsTruncated;
|
|
113
|
+
this.KeyCount = this.Contents.length;
|
|
114
|
+
this.totalFiles = props.totalFiles ?? this.Contents.length;
|
|
115
|
+
this.requestedAt = nowIso();
|
|
116
|
+
|
|
117
|
+
this.files = this.Contents;
|
|
118
|
+
this.folders = this.CommonPrefixes;
|
|
119
|
+
this.items = [...this.CommonPrefixes, ...this.Contents];
|
|
120
|
+
}
|
|
121
|
+
}
|
|
@@ -1,78 +1,85 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* MBKBucket - Path & Key Strategy
|
|
3
|
-
* Copyright (c) 2026 Muhammad Bin Khalid, MBKTech.org and contributors
|
|
4
|
-
* Licensed under the MIT License.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { mbkautheVar } from "mbkauthe";
|
|
8
|
-
import { StorageValidationError } from "./errors.js";
|
|
9
|
-
import { trimLeadingSlashes, trimSlashes } from "
|
|
10
|
-
|
|
11
|
-
const escapeRegExp = value => String(value).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
12
|
-
|
|
13
|
-
let _configuredAppName = null;
|
|
14
|
-
|
|
15
|
-
export class PathStrategy {
|
|
16
|
-
static setAppName(name) {
|
|
17
|
-
_configuredAppName = name ? String(name).trim() : null;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
static resetAppName() {
|
|
21
|
-
_configuredAppName = null;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
static getAppName() {
|
|
25
|
-
if (_configuredAppName !== null) {
|
|
26
|
-
return _configuredAppName;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
if (
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
if (
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
return p
|
|
77
|
-
|
|
78
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* MBKBucket - Path & Key Strategy
|
|
3
|
+
* Copyright (c) 2026 Muhammad Bin Khalid, MBKTech.org and contributors
|
|
4
|
+
* Licensed under the MIT License.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { mbkautheVar } from "mbkauthe";
|
|
8
|
+
import { StorageValidationError } from "./errors.js";
|
|
9
|
+
import { trimLeadingSlashes, trimSlashes } from "mbkutils";
|
|
10
|
+
|
|
11
|
+
const escapeRegExp = value => String(value).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
12
|
+
|
|
13
|
+
let _configuredAppName = null;
|
|
14
|
+
|
|
15
|
+
export class PathStrategy {
|
|
16
|
+
static setAppName(name) {
|
|
17
|
+
_configuredAppName = name ? String(name).trim() : null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
static resetAppName() {
|
|
21
|
+
_configuredAppName = null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
static getAppName() {
|
|
25
|
+
if (_configuredAppName !== null) {
|
|
26
|
+
return _configuredAppName;
|
|
27
|
+
}
|
|
28
|
+
let envAppName = null;
|
|
29
|
+
if (process.env.mbkautheVar) {
|
|
30
|
+
try {
|
|
31
|
+
const parsed = typeof process.env.mbkautheVar === 'string' ? JSON.parse(process.env.mbkautheVar) : process.env.mbkautheVar;
|
|
32
|
+
if (parsed?.APP_NAME) envAppName = parsed.APP_NAME;
|
|
33
|
+
} catch {}
|
|
34
|
+
}
|
|
35
|
+
const app = envAppName || process.env.MBKAUTHE_APP_NAME || process.env.APP_NAME || mbkautheVar?.APP_NAME;
|
|
36
|
+
return typeof app === 'string' && app.trim() ? app.trim() : '';
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
static isRootModeApp(appName) {
|
|
40
|
+
return ['portal', 'mbkbucket'].includes(String(appName || '').toLowerCase());
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
static validateSafety(str, isKey = false) {
|
|
44
|
+
if (isKey && !str) throw new StorageValidationError('Key is required');
|
|
45
|
+
if (str && (str.includes('../') || str.includes('..\\') || /\.\.[\\/]/.test(str))) {
|
|
46
|
+
throw new StorageValidationError(`Path traversal detected: invalid ${isKey ? 'key' : 'prefix'}`);
|
|
47
|
+
}
|
|
48
|
+
if (str && /[\x00-\x1f\x7f]/.test(str)) {
|
|
49
|
+
throw new StorageValidationError(`Invalid characters in ${isKey ? 'key' : 'prefix'}`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
static applyKeyPrefix(key = '') {
|
|
54
|
+
if (Array.isArray(key)) key = key.join('/');
|
|
55
|
+
this.validateSafety(key, true);
|
|
56
|
+
|
|
57
|
+
const app = this.getAppName();
|
|
58
|
+
const cleaned = trimLeadingSlashes(key);
|
|
59
|
+
if (this.isRootModeApp(app)) return cleaned;
|
|
60
|
+
if (!app) throw new StorageValidationError('APP_NAME is not configured; set mbkautheVar.APP_NAME or MBKAUTHE_APP_NAME');
|
|
61
|
+
|
|
62
|
+
const appPrefixRegex = new RegExp(`^(?:${escapeRegExp(app)}\/)+`);
|
|
63
|
+
return cleaned.startsWith(`${app}/`) ? cleaned.replace(appPrefixRegex, `${app}/`) : `${app}/${cleaned}`;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
static applyPrefix(prefix = '') {
|
|
67
|
+
if (Array.isArray(prefix)) prefix = prefix.join('/');
|
|
68
|
+
this.validateSafety(prefix, false);
|
|
69
|
+
|
|
70
|
+
const app = this.getAppName();
|
|
71
|
+
const p = trimLeadingSlashes(prefix);
|
|
72
|
+
if (this.isRootModeApp(app)) return p;
|
|
73
|
+
if (!app) throw new StorageValidationError('APP_NAME is not configured; set mbkautheVar.APP_NAME or MBKAUTHE_APP_NAME');
|
|
74
|
+
if (!p || p === app || p === `${app}/`) return p || app;
|
|
75
|
+
|
|
76
|
+
return p.startsWith(`${app}/`)
|
|
77
|
+
? p.replace(new RegExp(`^(?:${escapeRegExp(app)}\/)+`), `${app}/`)
|
|
78
|
+
: `${app}/${p}`;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
static normalizeFolderPrefix(prefix = '') {
|
|
82
|
+
const p = this.applyPrefix(trimSlashes(prefix));
|
|
83
|
+
return p ? `${p}/` : '';
|
|
84
|
+
}
|
|
85
|
+
}
|