mbkbucket 4.0.0 → 4.0.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.
- package/.env.example +15 -15
- package/README.md +561 -561
- package/index.js +32 -32
- package/lib/cli.js +239 -239
- package/lib/public/bucketadmin.css +2042 -2042
- package/lib/public/bucketadmin.js +1622 -1622
- package/lib/src/config/validation.js +86 -86
- package/lib/src/controllers/api.controller.js +459 -459
- package/lib/src/controllers/dashboard.controller.js +69 -69
- package/lib/src/controllers/view.controller.js +233 -233
- package/lib/src/core/storage/errors.js +62 -62
- package/lib/src/core/storage/gdrive-auth.js +193 -193
- package/lib/src/core/storage/gdrive-provider.js +656 -656
- package/lib/src/core/storage/index.js +14 -14
- package/lib/src/core/storage/models.js +80 -80
- package/lib/src/core/storage/path-strategy.js +65 -65
- package/lib/src/core/storage/s3-provider.js +491 -491
- package/lib/src/core/storage/storage-manager.js +203 -203
- package/lib/src/core/storage/storage-provider.js +217 -217
- package/lib/src/middleware/bucket-resolver.js +37 -37
- package/lib/src/routes/index.js +60 -60
- package/lib/src/routes/view.routes.js +28 -28
- package/lib/src/services/s3.service.js +42 -42
- package/lib/src/services/storage.service.js +213 -213
- package/lib/src/utils/errors.js +56 -56
- package/package.json +2 -3
- package/views/bucketadmin_move_modal.hbs +35 -35
- package/views/bucketadmin_preview_modal.hbs +108 -108
- package/views/bucketadmincontent.hbs +193 -193
- package/views/mbkbucket_info.hbs +134 -134
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* MBKBucket - Storage Core
|
|
3
|
-
* Copyright (c) 2026 Muhammad Bin Khalid, MBKTech.org and contributors
|
|
4
|
-
* Licensed under the MIT License.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
export { StorageProvider } from "./storage-provider.js";
|
|
8
|
-
export { S3StorageProvider } from "./s3-provider.js";
|
|
9
|
-
export { GoogleDriveStorageProvider } from "./gdrive-provider.js";
|
|
10
|
-
export { StorageFile, StorageFolder, StorageListResult } from "./models.js";
|
|
11
|
-
export { StorageError, StorageNotFoundError, StorageAccessDeniedError, StorageConflictError, StorageValidationError, StorageConfigError, StorageQuotaExceededError, StorageUnsupportedOperationError } from "./errors.js";
|
|
12
|
-
export { PathStrategy } from "./path-strategy.js";
|
|
13
|
-
export { getGoogleAuthUrl, exchangeCodeForTokens, refreshGoogleAccessToken, getServiceAccountAccessToken, GOOGLE_DRIVE_SCOPES } from "./gdrive-auth.js";
|
|
14
|
-
export { StorageManager, storageManager } from "./storage-manager.js";
|
|
1
|
+
/**
|
|
2
|
+
* MBKBucket - Storage Core
|
|
3
|
+
* Copyright (c) 2026 Muhammad Bin Khalid, MBKTech.org and contributors
|
|
4
|
+
* Licensed under the MIT License.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export { StorageProvider } from "./storage-provider.js";
|
|
8
|
+
export { S3StorageProvider } from "./s3-provider.js";
|
|
9
|
+
export { GoogleDriveStorageProvider } from "./gdrive-provider.js";
|
|
10
|
+
export { StorageFile, StorageFolder, StorageListResult } from "./models.js";
|
|
11
|
+
export { StorageError, StorageNotFoundError, StorageAccessDeniedError, StorageConflictError, StorageValidationError, StorageConfigError, StorageQuotaExceededError, StorageUnsupportedOperationError } from "./errors.js";
|
|
12
|
+
export { PathStrategy } from "./path-strategy.js";
|
|
13
|
+
export { getGoogleAuthUrl, exchangeCodeForTokens, refreshGoogleAccessToken, getServiceAccountAccessToken, GOOGLE_DRIVE_SCOPES } from "./gdrive-auth.js";
|
|
14
|
+
export { StorageManager, storageManager } from "./storage-manager.js";
|
|
@@ -1,80 +1,80 @@
|
|
|
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 } from "#helpers";
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Standardized file item representation across all providers.
|
|
11
|
-
*/
|
|
12
|
-
export class StorageFile {
|
|
13
|
-
/**
|
|
14
|
-
* @param {object} props
|
|
15
|
-
* @param {string} props.key - Normalized virtual key / path
|
|
16
|
-
* @param {number} [props.size=0] - File size in bytes
|
|
17
|
-
* @param {Date|string} [props.lastModified] - Last modified timestamp
|
|
18
|
-
* @param {string} [props.etag] - Entity tag / checksum
|
|
19
|
-
* @param {string} [props.contentType] - MIME content type
|
|
20
|
-
* @param {string} [props.storageClass] - Storage tier / provider type
|
|
21
|
-
* @param {string} [props.id] - Native provider ID (e.g. Google Drive file ID)
|
|
22
|
-
* @param {string} [props.webViewLink] - Web preview link
|
|
23
|
-
* @param {string} [props.webContentLink] - Direct download link
|
|
24
|
-
* @param {object} [props.metadata={}] - Custom user metadata
|
|
25
|
-
*/
|
|
26
|
-
constructor(props) {
|
|
27
|
-
this.Key = props.key || props.Key || '';
|
|
28
|
-
this.Size = Number(props.size ?? props.Size ?? 0);
|
|
29
|
-
this.LastModified = props.lastModified instanceof Date
|
|
30
|
-
? props.lastModified
|
|
31
|
-
: new Date(props.lastModified || props.LastModified || Date.now());
|
|
32
|
-
this.ETag = props.etag || props.ETag || '';
|
|
33
|
-
this.ContentType = props.contentType || props.ContentType || 'application/octet-stream';
|
|
34
|
-
this.StorageClass = props.storageClass || props.StorageClass || 'STANDARD';
|
|
35
|
-
if (props.id) this.id = props.id;
|
|
36
|
-
if (props.mimeType) this.mimeType = props.mimeType;
|
|
37
|
-
if (props.webViewLink) this.webViewLink = props.webViewLink;
|
|
38
|
-
if (props.webContentLink) this.webContentLink = props.webContentLink;
|
|
39
|
-
if (props.metadata) this.metadata = props.metadata;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Standardized folder representation.
|
|
45
|
-
*/
|
|
46
|
-
export class StorageFolder {
|
|
47
|
-
/**
|
|
48
|
-
* @param {string} prefix - Folder prefix with trailing slash
|
|
49
|
-
* @param {string} [id] - Native provider folder ID
|
|
50
|
-
*/
|
|
51
|
-
constructor(prefix, id) {
|
|
52
|
-
this.Prefix = String(prefix || '').endsWith('/') ? String(prefix) : `${prefix}/`;
|
|
53
|
-
if (id) this.id = id;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Standardized paginated listing result.
|
|
59
|
-
*/
|
|
60
|
-
export class StorageListResult {
|
|
61
|
-
/**
|
|
62
|
-
* @param {object} props
|
|
63
|
-
* @param {StorageFile[]} [props.contents=[]]
|
|
64
|
-
* @param {StorageFolder[]} [props.commonPrefixes=[]]
|
|
65
|
-
* @param {string|null} [props.nextToken=null]
|
|
66
|
-
* @param {boolean} [props.isTruncated=false]
|
|
67
|
-
* @param {number} [props.totalFiles]
|
|
68
|
-
*/
|
|
69
|
-
constructor(props = {}) {
|
|
70
|
-
this.Contents = props.contents || props.Contents || [];
|
|
71
|
-
this.CommonPrefixes = props.commonPrefixes || props.CommonPrefixes || [];
|
|
72
|
-
this.NextContinuationToken = props.nextToken ?? props.NextContinuationToken ?? null;
|
|
73
|
-
this.nextToken = this.NextContinuationToken;
|
|
74
|
-
this.IsTruncated = Boolean(props.isTruncated ?? props.IsTruncated ?? Boolean(this.NextContinuationToken));
|
|
75
|
-
this.hasMore = this.IsTruncated;
|
|
76
|
-
this.KeyCount = this.Contents.length;
|
|
77
|
-
this.totalFiles = props.totalFiles ?? this.Contents.length;
|
|
78
|
-
this.requestedAt = nowIso();
|
|
79
|
-
}
|
|
80
|
-
}
|
|
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 } from "#helpers";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Standardized file item representation across all providers.
|
|
11
|
+
*/
|
|
12
|
+
export class StorageFile {
|
|
13
|
+
/**
|
|
14
|
+
* @param {object} props
|
|
15
|
+
* @param {string} props.key - Normalized virtual key / path
|
|
16
|
+
* @param {number} [props.size=0] - File size in bytes
|
|
17
|
+
* @param {Date|string} [props.lastModified] - Last modified timestamp
|
|
18
|
+
* @param {string} [props.etag] - Entity tag / checksum
|
|
19
|
+
* @param {string} [props.contentType] - MIME content type
|
|
20
|
+
* @param {string} [props.storageClass] - Storage tier / provider type
|
|
21
|
+
* @param {string} [props.id] - Native provider ID (e.g. Google Drive file ID)
|
|
22
|
+
* @param {string} [props.webViewLink] - Web preview link
|
|
23
|
+
* @param {string} [props.webContentLink] - Direct download link
|
|
24
|
+
* @param {object} [props.metadata={}] - Custom user metadata
|
|
25
|
+
*/
|
|
26
|
+
constructor(props) {
|
|
27
|
+
this.Key = props.key || props.Key || '';
|
|
28
|
+
this.Size = Number(props.size ?? props.Size ?? 0);
|
|
29
|
+
this.LastModified = props.lastModified instanceof Date
|
|
30
|
+
? props.lastModified
|
|
31
|
+
: new Date(props.lastModified || props.LastModified || Date.now());
|
|
32
|
+
this.ETag = props.etag || props.ETag || '';
|
|
33
|
+
this.ContentType = props.contentType || props.ContentType || 'application/octet-stream';
|
|
34
|
+
this.StorageClass = props.storageClass || props.StorageClass || 'STANDARD';
|
|
35
|
+
if (props.id) this.id = props.id;
|
|
36
|
+
if (props.mimeType) this.mimeType = props.mimeType;
|
|
37
|
+
if (props.webViewLink) this.webViewLink = props.webViewLink;
|
|
38
|
+
if (props.webContentLink) this.webContentLink = props.webContentLink;
|
|
39
|
+
if (props.metadata) this.metadata = props.metadata;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Standardized folder representation.
|
|
45
|
+
*/
|
|
46
|
+
export class StorageFolder {
|
|
47
|
+
/**
|
|
48
|
+
* @param {string} prefix - Folder prefix with trailing slash
|
|
49
|
+
* @param {string} [id] - Native provider folder ID
|
|
50
|
+
*/
|
|
51
|
+
constructor(prefix, id) {
|
|
52
|
+
this.Prefix = String(prefix || '').endsWith('/') ? String(prefix) : `${prefix}/`;
|
|
53
|
+
if (id) this.id = id;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Standardized paginated listing result.
|
|
59
|
+
*/
|
|
60
|
+
export class StorageListResult {
|
|
61
|
+
/**
|
|
62
|
+
* @param {object} props
|
|
63
|
+
* @param {StorageFile[]} [props.contents=[]]
|
|
64
|
+
* @param {StorageFolder[]} [props.commonPrefixes=[]]
|
|
65
|
+
* @param {string|null} [props.nextToken=null]
|
|
66
|
+
* @param {boolean} [props.isTruncated=false]
|
|
67
|
+
* @param {number} [props.totalFiles]
|
|
68
|
+
*/
|
|
69
|
+
constructor(props = {}) {
|
|
70
|
+
this.Contents = props.contents || props.Contents || [];
|
|
71
|
+
this.CommonPrefixes = props.commonPrefixes || props.CommonPrefixes || [];
|
|
72
|
+
this.NextContinuationToken = props.nextToken ?? props.NextContinuationToken ?? null;
|
|
73
|
+
this.nextToken = this.NextContinuationToken;
|
|
74
|
+
this.IsTruncated = Boolean(props.isTruncated ?? props.IsTruncated ?? Boolean(this.NextContinuationToken));
|
|
75
|
+
this.hasMore = this.IsTruncated;
|
|
76
|
+
this.KeyCount = this.Contents.length;
|
|
77
|
+
this.totalFiles = props.totalFiles ?? this.Contents.length;
|
|
78
|
+
this.requestedAt = nowIso();
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -1,65 +1,65 @@
|
|
|
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 "#helpers";
|
|
10
|
-
|
|
11
|
-
const escapeRegExp = value => String(value).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
12
|
-
|
|
13
|
-
export class PathStrategy {
|
|
14
|
-
static getAppName() {
|
|
15
|
-
const app = mbkautheVar?.APP_NAME;
|
|
16
|
-
return typeof app === 'string' && app.trim() ? app.trim() : '';
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
static isRootModeApp(appName) {
|
|
20
|
-
return ['portal', 'mbkbucket'].includes(String(appName || '').toLowerCase());
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
static validateSafety(str, isKey = false) {
|
|
24
|
-
if (isKey && !str) throw new StorageValidationError('Key is required');
|
|
25
|
-
if (str && (str.includes('../') || str.includes('..\\') || /\.\.[\\/]/.test(str))) {
|
|
26
|
-
throw new StorageValidationError(`Path traversal detected: invalid ${isKey ? 'key' : 'prefix'}`);
|
|
27
|
-
}
|
|
28
|
-
if (str && /[\x00-\x1f\x7f]/.test(str)) {
|
|
29
|
-
throw new StorageValidationError(`Invalid characters in ${isKey ? 'key' : 'prefix'}`);
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
static applyKeyPrefix(key = '') {
|
|
34
|
-
if (Array.isArray(key)) key = key.join('/');
|
|
35
|
-
this.validateSafety(key, true);
|
|
36
|
-
|
|
37
|
-
const app = this.getAppName();
|
|
38
|
-
const cleaned = trimLeadingSlashes(key);
|
|
39
|
-
if (this.isRootModeApp(app)) return cleaned;
|
|
40
|
-
if (!app) throw new StorageValidationError('APP_NAME is not configured; set mbkautheVar.APP_NAME or MBKAUTHE_APP_NAME');
|
|
41
|
-
|
|
42
|
-
const appPrefixRegex = new RegExp(`^(?:${escapeRegExp(app)}\/)+`);
|
|
43
|
-
return cleaned.startsWith(`${app}/`) ? cleaned.replace(appPrefixRegex, `${app}/`) : `${app}/${cleaned}`;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
static applyPrefix(prefix = '') {
|
|
47
|
-
if (Array.isArray(prefix)) prefix = prefix.join('/');
|
|
48
|
-
this.validateSafety(prefix, false);
|
|
49
|
-
|
|
50
|
-
const app = this.getAppName();
|
|
51
|
-
const p = trimLeadingSlashes(prefix);
|
|
52
|
-
if (this.isRootModeApp(app)) return p;
|
|
53
|
-
if (!app) throw new StorageValidationError('APP_NAME is not configured; set mbkautheVar.APP_NAME or MBKAUTHE_APP_NAME');
|
|
54
|
-
if (!p || p === app || p === `${app}/`) return p || app;
|
|
55
|
-
|
|
56
|
-
return p.startsWith(`${app}/`)
|
|
57
|
-
? p.replace(new RegExp(`^(?:${escapeRegExp(app)}\/)+`), `${app}/`)
|
|
58
|
-
: `${app}/${p}`;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
static normalizeFolderPrefix(prefix = '') {
|
|
62
|
-
const p = this.applyPrefix(trimSlashes(prefix));
|
|
63
|
-
return p ? `${p}/` : '';
|
|
64
|
-
}
|
|
65
|
-
}
|
|
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 "#helpers";
|
|
10
|
+
|
|
11
|
+
const escapeRegExp = value => String(value).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
12
|
+
|
|
13
|
+
export class PathStrategy {
|
|
14
|
+
static getAppName() {
|
|
15
|
+
const app = mbkautheVar?.APP_NAME;
|
|
16
|
+
return typeof app === 'string' && app.trim() ? app.trim() : '';
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
static isRootModeApp(appName) {
|
|
20
|
+
return ['portal', 'mbkbucket'].includes(String(appName || '').toLowerCase());
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
static validateSafety(str, isKey = false) {
|
|
24
|
+
if (isKey && !str) throw new StorageValidationError('Key is required');
|
|
25
|
+
if (str && (str.includes('../') || str.includes('..\\') || /\.\.[\\/]/.test(str))) {
|
|
26
|
+
throw new StorageValidationError(`Path traversal detected: invalid ${isKey ? 'key' : 'prefix'}`);
|
|
27
|
+
}
|
|
28
|
+
if (str && /[\x00-\x1f\x7f]/.test(str)) {
|
|
29
|
+
throw new StorageValidationError(`Invalid characters in ${isKey ? 'key' : 'prefix'}`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
static applyKeyPrefix(key = '') {
|
|
34
|
+
if (Array.isArray(key)) key = key.join('/');
|
|
35
|
+
this.validateSafety(key, true);
|
|
36
|
+
|
|
37
|
+
const app = this.getAppName();
|
|
38
|
+
const cleaned = trimLeadingSlashes(key);
|
|
39
|
+
if (this.isRootModeApp(app)) return cleaned;
|
|
40
|
+
if (!app) throw new StorageValidationError('APP_NAME is not configured; set mbkautheVar.APP_NAME or MBKAUTHE_APP_NAME');
|
|
41
|
+
|
|
42
|
+
const appPrefixRegex = new RegExp(`^(?:${escapeRegExp(app)}\/)+`);
|
|
43
|
+
return cleaned.startsWith(`${app}/`) ? cleaned.replace(appPrefixRegex, `${app}/`) : `${app}/${cleaned}`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
static applyPrefix(prefix = '') {
|
|
47
|
+
if (Array.isArray(prefix)) prefix = prefix.join('/');
|
|
48
|
+
this.validateSafety(prefix, false);
|
|
49
|
+
|
|
50
|
+
const app = this.getAppName();
|
|
51
|
+
const p = trimLeadingSlashes(prefix);
|
|
52
|
+
if (this.isRootModeApp(app)) return p;
|
|
53
|
+
if (!app) throw new StorageValidationError('APP_NAME is not configured; set mbkautheVar.APP_NAME or MBKAUTHE_APP_NAME');
|
|
54
|
+
if (!p || p === app || p === `${app}/`) return p || app;
|
|
55
|
+
|
|
56
|
+
return p.startsWith(`${app}/`)
|
|
57
|
+
? p.replace(new RegExp(`^(?:${escapeRegExp(app)}\/)+`), `${app}/`)
|
|
58
|
+
: `${app}/${p}`;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
static normalizeFolderPrefix(prefix = '') {
|
|
62
|
+
const p = this.applyPrefix(trimSlashes(prefix));
|
|
63
|
+
return p ? `${p}/` : '';
|
|
64
|
+
}
|
|
65
|
+
}
|