mbkbucket 1.5.0 → 2.0.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/.env.example +2 -2
- package/README.md +72 -0
- package/index.d.ts +87 -49
- package/index.js +15 -13
- package/lib/config/index.js +9 -89
- package/lib/config/validation.js +86 -0
- package/lib/routes/api.js +139 -45
- package/lib/routes/index.js +70 -1
- package/lib/routes/view.js +71 -18
- package/lib/s3.js +64 -32
- package/package.json +2 -2
- package/public/bucketadmin.css +447 -17
- package/public/bucketadmin.js +272 -82
- package/test/config-validation.test.js +116 -0
- package/views/bucket.handlebars +66 -31
- package/views/bucketadmincontent.handlebars +30 -9
- package/views/bucketportal.handlebars +1 -0
package/.env.example
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
mbkautheVar={"APP_NAME":"
|
|
1
|
+
mbkautheVar={"APP_NAME":"portal","loginRedirectURL":"/dashboard","bucket":"R2_Bucket"}
|
|
2
2
|
mbkauthShared={"Main_SECRET_TOKEN": 123,"DOMAIN":"mbktech.org","IS_DEPLOYED":"false","EncPass":"false","DEVICE_TRUST_DURATION_DAYS":7,"SESSION_SECRET_KEY":"123","LOGIN_DB":"postgres://","MBKAUTH_TWO_FA_ENABLE":"true","COOKIE_EXPIRE_TIME":2,"GITHUB_LOGIN_ENABLED":"f","GITHUB_CLIENT_ID":"123","GITHUB_CLIENT_SECRET":"123","GOOGLE_LOGIN_ENABLED":"f","GOOGLE_CLIENT_ID":"123","GOOGLE_CLIENT_SECRET":"123"}
|
|
3
3
|
# See @node_modules/mbkauthe/docs/env.md for more details
|
|
4
4
|
NODE_ENV=development
|
|
5
5
|
|
|
6
|
-
BucketConnection={"
|
|
6
|
+
BucketConnection={"R2_Bucket":{"BUCKET_NAME":"my-r2-bucket","ACCESS_KEY_ID":"your-r2-access-key","SECRET_ACCESS_KEY":"your-r2-secret","ENDPOINT":"https://accountid.r2.cloudflarestorage.com"},"S3_Bucket":{"BUCKET_NAME":"my-s3-bucket","ACCESS_KEY_ID":"your-s3-access-key","SECRET_ACCESS_KEY":"your-s3-secret","ENDPOINT":"https://s3.ap-southeast-1.amazonaws.com"}}
|
|
7
7
|
# Region is optional
|
|
8
8
|
|
|
9
9
|
mbkbucketVar={"publiView_enabled":"true","p_view_inline":"true"}
|
package/README.md
CHANGED
|
@@ -75,6 +75,78 @@ For complete API documentation, refer to the TypeScript declarations in [index.d
|
|
|
75
75
|
|
|
76
76
|
---
|
|
77
77
|
|
|
78
|
+
## Environment Configuration
|
|
79
|
+
|
|
80
|
+
The package validates both `mbkbucketVar` and `BucketConnection` at startup.
|
|
81
|
+
|
|
82
|
+
### 1 `mbkautheVar`
|
|
83
|
+
|
|
84
|
+
`mbkautheVar.bucket` is used as the default bucket.
|
|
85
|
+
|
|
86
|
+
Example:
|
|
87
|
+
|
|
88
|
+
```env
|
|
89
|
+
mbkautheVar={"APP_NAME":"portal","loginRedirectURL":"/dashboard","bucket":"R2_Bucket"}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Notes:
|
|
93
|
+
|
|
94
|
+
- If `APP_NAME` is `portal`, mbkbucket works from bucket root (no `portal/` folder prefix).
|
|
95
|
+
- If `bucket` is missing, mbkbucket falls back to the first bucket key in `BucketConnection`.
|
|
96
|
+
|
|
97
|
+
### 2 `BucketConnection`
|
|
98
|
+
|
|
99
|
+
Use a JSON object where each key is a selectable connection name.
|
|
100
|
+
|
|
101
|
+
Example:
|
|
102
|
+
|
|
103
|
+
```env
|
|
104
|
+
BucketConnection={"R2_Bucket":{"BUCKET_NAME":"my-r2-bucket","ACCESS_KEY_ID":"...","SECRET_ACCESS_KEY":"...","ENDPOINT":"https://<account-id>.r2.cloudflarestorage.com"},"S3_Bucket":{"BUCKET_NAME":"my-s3-bucket","ACCESS_KEY_ID":"...","SECRET_ACCESS_KEY":"...","ENDPOINT":"https://s3.ap-southeast-1.amazonaws.com"}}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Required fields per bucket:
|
|
108
|
+
|
|
109
|
+
- `BUCKET_NAME`
|
|
110
|
+
- `ACCESS_KEY_ID`
|
|
111
|
+
- `SECRET_ACCESS_KEY`
|
|
112
|
+
- `ENDPOINT`
|
|
113
|
+
|
|
114
|
+
### Common Configuration Mistakes
|
|
115
|
+
|
|
116
|
+
1. Quoted inner object (invalid)
|
|
117
|
+
|
|
118
|
+
```env
|
|
119
|
+
BucketConnection={"R2_Bucket":"{\"BUCKET_NAME\":\"...\"}"}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
2. Correct inner object (valid)
|
|
123
|
+
|
|
124
|
+
```env
|
|
125
|
+
BucketConnection={"R2_Bucket":{"BUCKET_NAME":"...","ACCESS_KEY_ID":"...","SECRET_ACCESS_KEY":"...","ENDPOINT":"https://..."}}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Runtime Selection
|
|
129
|
+
|
|
130
|
+
- Admin page bucket selection: `/mbkbucket?bucket=R2_Bucket`
|
|
131
|
+
- Public view route `/mbkbucket/p_view/:key(*)` always uses the default bucket from `mbkautheVar.bucket`.
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## Automated Tests
|
|
136
|
+
|
|
137
|
+
Run tests:
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
npm test
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Current tests cover:
|
|
144
|
+
|
|
145
|
+
- `mbkbucketVar` parsing/defaults/boolean normalization
|
|
146
|
+
- `BucketConnection` shape validation and required fields
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
78
150
|
## Contact & Support
|
|
79
151
|
|
|
80
152
|
For questions, issues, or contributions, please reach out:
|
package/index.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
// Type definitions for mbkbucket
|
|
1
|
+
// Type definitions for mbkbucket
|
|
2
2
|
// Project: https://github.com/MIbnEKhalid/mbkbucket
|
|
3
|
-
// Definitions by: Muhammad Bin Khalid <https://github.com/MIbnEKhalid">
|
|
4
3
|
|
|
5
4
|
import { Readable } from "stream";
|
|
6
5
|
import { Router, Application } from "express";
|
|
@@ -17,6 +16,8 @@ export interface BucketConfig {
|
|
|
17
16
|
[key: string]: any;
|
|
18
17
|
}
|
|
19
18
|
|
|
19
|
+
export type BucketConnectionMap = Record<string, BucketConfig>;
|
|
20
|
+
|
|
20
21
|
export interface S3ObjectResult {
|
|
21
22
|
Body: Readable;
|
|
22
23
|
ContentLength?: number;
|
|
@@ -26,18 +27,16 @@ export interface S3ObjectResult {
|
|
|
26
27
|
[key: string]: any;
|
|
27
28
|
}
|
|
28
29
|
|
|
29
|
-
// Options passed to uploadFile
|
|
30
30
|
export interface UploadOptions {
|
|
31
31
|
metadata?: Record<string, string>;
|
|
32
32
|
cacheControl?: string;
|
|
33
33
|
storageClass?: string;
|
|
34
34
|
serverSideEncryption?: string;
|
|
35
35
|
bucketName?: string;
|
|
36
|
-
preventOverwrite?: boolean;
|
|
36
|
+
preventOverwrite?: boolean;
|
|
37
37
|
[key: string]: any;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
// Options for listing files
|
|
41
40
|
export interface ListFilesOptions {
|
|
42
41
|
maxKeys?: number;
|
|
43
42
|
continuationToken?: string | null;
|
|
@@ -48,7 +47,6 @@ export interface ListFilesOptions {
|
|
|
48
47
|
[key: string]: any;
|
|
49
48
|
}
|
|
50
49
|
|
|
51
|
-
// Result returned from listfiles calls
|
|
52
50
|
export interface ListFilesResult extends Record<string, any> {
|
|
53
51
|
requestedAt: string;
|
|
54
52
|
totalFiles: number;
|
|
@@ -56,7 +54,6 @@ export interface ListFilesResult extends Record<string, any> {
|
|
|
56
54
|
nextToken: string | null;
|
|
57
55
|
}
|
|
58
56
|
|
|
59
|
-
// Incomplete multipart upload descriptor returned by AWS
|
|
60
57
|
export interface IncompleteMultipartUpload {
|
|
61
58
|
Key?: string;
|
|
62
59
|
UploadId?: string;
|
|
@@ -65,7 +62,6 @@ export interface IncompleteMultipartUpload {
|
|
|
65
62
|
[key: string]: any;
|
|
66
63
|
}
|
|
67
64
|
|
|
68
|
-
// Result returned by cleanupIncompleteMultipartUploads
|
|
69
65
|
export interface CleanupResult {
|
|
70
66
|
abortedCount: number;
|
|
71
67
|
uploads: Array<{ key: string; uploadId: string; initiated?: string }>;
|
|
@@ -73,23 +69,31 @@ export interface CleanupResult {
|
|
|
73
69
|
[key: string]: any;
|
|
74
70
|
}
|
|
75
71
|
|
|
72
|
+
export interface ConfigVars {
|
|
73
|
+
publiView_enabled: boolean;
|
|
74
|
+
p_view_inline: boolean;
|
|
75
|
+
[key: string]: any;
|
|
76
|
+
}
|
|
77
|
+
|
|
76
78
|
/**
|
|
77
|
-
* S3
|
|
79
|
+
* S3 exports (re-exported from lib/s3.js at package root)
|
|
78
80
|
*/
|
|
81
|
+
export function getAvailableBucketNames(): string[];
|
|
82
|
+
export function resolveBucketName(bucketName?: string | null): string;
|
|
79
83
|
export function getBucketConfig(bucketName?: string): BucketConfig;
|
|
80
84
|
export const bucketClient: any;
|
|
81
85
|
export function getAppName(): string;
|
|
82
86
|
export function ensureKeyHasAppPrefix(key?: string): string;
|
|
83
87
|
export function ensurePrefix(prefix?: string): string;
|
|
84
88
|
export function getBucketClient(bucketName?: string): any;
|
|
85
|
-
export function checkHealth(): Promise<{ status: string; responseTime?: number; bucket?: string; error?: string; checkedAt: string; region?: string }>;
|
|
89
|
+
export function checkHealth(): Promise<{ status: string; responseTime?: number; bucket?: string; error?: string; checkedAt: string; region?: string; }>;
|
|
86
90
|
|
|
87
91
|
export function uploadFile(
|
|
88
92
|
key: string,
|
|
89
93
|
fileBuffer: Buffer | Uint8Array,
|
|
90
94
|
contentType: string,
|
|
91
95
|
options?: UploadOptions
|
|
92
|
-
): Promise<{ fileSize?: number; key: string; contentType?: string; uploadedAt?: string } & Record<string, any>>;
|
|
96
|
+
): Promise<{ fileSize?: number; key: string; contentType?: string; uploadedAt?: string; } & Record<string, any>>;
|
|
93
97
|
|
|
94
98
|
export function downloadFile(
|
|
95
99
|
key: string,
|
|
@@ -121,10 +125,7 @@ export function deleteFolder(prefix: string, bucketName?: string): Promise<{
|
|
|
121
125
|
prefix?: string;
|
|
122
126
|
}>;
|
|
123
127
|
|
|
124
|
-
export function listfiles(
|
|
125
|
-
prefix?: string,
|
|
126
|
-
options?: ListFilesOptions
|
|
127
|
-
): Promise<ListFilesResult>;
|
|
128
|
+
export function listfiles(prefix?: string, options?: ListFilesOptions): Promise<ListFilesResult>;
|
|
128
129
|
|
|
129
130
|
export function getFileMetadata(key: string, bucketName?: string): Promise<{
|
|
130
131
|
key: string;
|
|
@@ -141,8 +142,9 @@ export function getFileSize(key: string, bucketName?: string): Promise<number |
|
|
|
141
142
|
|
|
142
143
|
export function generateSignedUrl(
|
|
143
144
|
key: string,
|
|
144
|
-
operation?:
|
|
145
|
-
expiresIn?: number
|
|
145
|
+
operation?: "getObject" | "putObject" | string,
|
|
146
|
+
expiresIn?: number,
|
|
147
|
+
bucketName?: string
|
|
146
148
|
): Promise<{
|
|
147
149
|
url: string;
|
|
148
150
|
key: string;
|
|
@@ -152,31 +154,32 @@ export function generateSignedUrl(
|
|
|
152
154
|
generatedAt: string;
|
|
153
155
|
}>;
|
|
154
156
|
|
|
155
|
-
/**
|
|
156
|
-
* S3 Multipart Upload Functions
|
|
157
|
-
*/
|
|
158
157
|
export function createMultipartUpload(
|
|
159
158
|
key: string,
|
|
160
159
|
contentType?: string,
|
|
161
|
-
metadata?: Record<string, string
|
|
160
|
+
metadata?: Record<string, string>,
|
|
161
|
+
bucketName?: string
|
|
162
162
|
): Promise<{ uploadId: string; key: string }>;
|
|
163
163
|
|
|
164
164
|
export function uploadPart(
|
|
165
165
|
key: string,
|
|
166
166
|
uploadId: string,
|
|
167
167
|
partNumber: number,
|
|
168
|
-
buffer: Buffer | Uint8Array
|
|
168
|
+
buffer: Buffer | Uint8Array,
|
|
169
|
+
bucketName?: string
|
|
169
170
|
): Promise<{ ETag: string; partNumber: number }>;
|
|
170
171
|
|
|
171
172
|
export function completeMultipartUpload(
|
|
172
173
|
key: string,
|
|
173
174
|
uploadId: string,
|
|
174
|
-
parts: Array<{ partNumber: number; ETag: string }
|
|
175
|
+
parts: Array<{ partNumber: number; ETag: string }>,
|
|
176
|
+
bucketName?: string
|
|
175
177
|
): Promise<{ key: string }>;
|
|
176
178
|
|
|
177
179
|
export function abortMultipartUpload(
|
|
178
180
|
key: string,
|
|
179
|
-
uploadId: string
|
|
181
|
+
uploadId: string,
|
|
182
|
+
bucketName?: string
|
|
180
183
|
): Promise<{ key: string; abortedAt: string }>;
|
|
181
184
|
|
|
182
185
|
export function listIncompleteMultipartUploads(
|
|
@@ -191,35 +194,33 @@ export function cleanupIncompleteMultipartUploads(
|
|
|
191
194
|
): Promise<CleanupResult>;
|
|
192
195
|
|
|
193
196
|
/**
|
|
194
|
-
*
|
|
197
|
+
* Config exports (re-exported from lib/config/index.js at package root)
|
|
195
198
|
*/
|
|
196
|
-
export
|
|
197
|
-
|
|
198
|
-
p_view_inline: boolean;
|
|
199
|
-
[key: string]: any;
|
|
200
|
-
}
|
|
201
|
-
|
|
199
|
+
export const packageJson: Record<string, any>;
|
|
200
|
+
export const appVersion: string;
|
|
202
201
|
export const mbkbucketVar: ConfigVars;
|
|
202
|
+
export function getLatestVersion(): Promise<string | null>;
|
|
203
|
+
export function checkVersion(): Promise<void>;
|
|
204
|
+
export function parseAndValidateMbkbucketVar(rawValue?: string): ConfigVars;
|
|
205
|
+
export function parseAndValidateBucketConnection(rawValue?: string): BucketConnectionMap | null;
|
|
203
206
|
|
|
204
207
|
/**
|
|
205
|
-
*
|
|
208
|
+
* Router export (re-exported from lib/routes/index.js at package root as `bucket`)
|
|
206
209
|
*/
|
|
207
210
|
export const bucket: Router;
|
|
208
211
|
|
|
209
212
|
/**
|
|
210
|
-
* Root default export:
|
|
213
|
+
* Root default export from index.js: configured Express application
|
|
211
214
|
*/
|
|
212
215
|
declare const server: Application;
|
|
213
216
|
export default server;
|
|
214
217
|
|
|
215
218
|
/**
|
|
216
|
-
*
|
|
217
|
-
* import { uploadFile } from 'mbkbucket/lib/s3';
|
|
218
|
-
* import { packageJson } from 'mbkbucket/lib/config/index';
|
|
219
|
-
* import bucketRouter from 'mbkbucket/lib/bucket';
|
|
219
|
+
* Subpath module declarations
|
|
220
220
|
*/
|
|
221
221
|
declare module "mbkbucket/lib/s3" {
|
|
222
222
|
import { Readable } from "stream";
|
|
223
|
+
|
|
223
224
|
export interface BucketConfig {
|
|
224
225
|
BUCKET_NAME: string;
|
|
225
226
|
ENDPOINT?: string;
|
|
@@ -228,6 +229,9 @@ declare module "mbkbucket/lib/s3" {
|
|
|
228
229
|
region?: string;
|
|
229
230
|
[key: string]: any;
|
|
230
231
|
}
|
|
232
|
+
|
|
233
|
+
export type BucketConnectionMap = Record<string, BucketConfig>;
|
|
234
|
+
|
|
231
235
|
export interface S3ObjectResult {
|
|
232
236
|
Body: Readable;
|
|
233
237
|
ContentLength?: number;
|
|
@@ -237,7 +241,6 @@ declare module "mbkbucket/lib/s3" {
|
|
|
237
241
|
[key: string]: any;
|
|
238
242
|
}
|
|
239
243
|
|
|
240
|
-
// re-use shared interfaces defined at top of index.d.ts
|
|
241
244
|
export interface UploadOptions {
|
|
242
245
|
metadata?: Record<string, string>;
|
|
243
246
|
cacheControl?: string;
|
|
@@ -247,6 +250,7 @@ declare module "mbkbucket/lib/s3" {
|
|
|
247
250
|
preventOverwrite?: boolean;
|
|
248
251
|
[key: string]: any;
|
|
249
252
|
}
|
|
253
|
+
|
|
250
254
|
export interface ListFilesOptions {
|
|
251
255
|
maxKeys?: number;
|
|
252
256
|
continuationToken?: string | null;
|
|
@@ -256,12 +260,14 @@ declare module "mbkbucket/lib/s3" {
|
|
|
256
260
|
bucketName?: string;
|
|
257
261
|
[key: string]: any;
|
|
258
262
|
}
|
|
263
|
+
|
|
259
264
|
export interface ListFilesResult extends Record<string, any> {
|
|
260
265
|
requestedAt: string;
|
|
261
266
|
totalFiles: number;
|
|
262
267
|
hasMore: boolean;
|
|
263
268
|
nextToken: string | null;
|
|
264
269
|
}
|
|
270
|
+
|
|
265
271
|
export interface IncompleteMultipartUpload {
|
|
266
272
|
Key?: string;
|
|
267
273
|
UploadId?: string;
|
|
@@ -269,6 +275,7 @@ declare module "mbkbucket/lib/s3" {
|
|
|
269
275
|
Initiator?: { DisplayName?: string; [key: string]: any };
|
|
270
276
|
[key: string]: any;
|
|
271
277
|
}
|
|
278
|
+
|
|
272
279
|
export interface CleanupResult {
|
|
273
280
|
abortedCount: number;
|
|
274
281
|
uploads: Array<{ key: string; uploadId: string; initiated?: string }>;
|
|
@@ -276,13 +283,15 @@ declare module "mbkbucket/lib/s3" {
|
|
|
276
283
|
[key: string]: any;
|
|
277
284
|
}
|
|
278
285
|
|
|
286
|
+
export function getAvailableBucketNames(): string[];
|
|
287
|
+
export function resolveBucketName(bucketName?: string | null): string;
|
|
279
288
|
export function getBucketConfig(bucketName?: string): BucketConfig;
|
|
280
289
|
export const bucketClient: any;
|
|
281
290
|
export function getAppName(): string;
|
|
282
291
|
export function ensureKeyHasAppPrefix(key?: string): string;
|
|
283
292
|
export function ensurePrefix(prefix?: string): string;
|
|
284
293
|
export function getBucketClient(bucketName?: string): any;
|
|
285
|
-
export function checkHealth(): Promise<{ status: string; responseTime?: number; bucket?: string; error?: string; checkedAt: string; region?: string }>;
|
|
294
|
+
export function checkHealth(): Promise<{ status: string; responseTime?: number; bucket?: string; error?: string; checkedAt: string; region?: string; }>;
|
|
286
295
|
|
|
287
296
|
export function uploadFile(
|
|
288
297
|
key: string,
|
|
@@ -320,10 +329,7 @@ declare module "mbkbucket/lib/s3" {
|
|
|
320
329
|
prefix?: string;
|
|
321
330
|
}>;
|
|
322
331
|
|
|
323
|
-
export function listfiles(
|
|
324
|
-
prefix?: string,
|
|
325
|
-
options?: ListFilesOptions
|
|
326
|
-
): Promise<ListFilesResult>;
|
|
332
|
+
export function listfiles(prefix?: string, options?: ListFilesOptions): Promise<ListFilesResult>;
|
|
327
333
|
|
|
328
334
|
export function getFileMetadata(key: string, bucketName?: string): Promise<{
|
|
329
335
|
key: string;
|
|
@@ -337,10 +343,12 @@ declare module "mbkbucket/lib/s3" {
|
|
|
337
343
|
|
|
338
344
|
export function fileExists(key: string, bucketName?: string): Promise<boolean>;
|
|
339
345
|
export function getFileSize(key: string, bucketName?: string): Promise<number | null>;
|
|
346
|
+
|
|
340
347
|
export function generateSignedUrl(
|
|
341
348
|
key: string,
|
|
342
|
-
operation?:
|
|
343
|
-
expiresIn?: number
|
|
349
|
+
operation?: "getObject" | "putObject" | string,
|
|
350
|
+
expiresIn?: number,
|
|
351
|
+
bucketName?: string
|
|
344
352
|
): Promise<{
|
|
345
353
|
url: string;
|
|
346
354
|
key: string;
|
|
@@ -353,25 +361,29 @@ declare module "mbkbucket/lib/s3" {
|
|
|
353
361
|
export function createMultipartUpload(
|
|
354
362
|
key: string,
|
|
355
363
|
contentType?: string,
|
|
356
|
-
metadata?: Record<string, string
|
|
364
|
+
metadata?: Record<string, string>,
|
|
365
|
+
bucketName?: string
|
|
357
366
|
): Promise<{ uploadId: string; key: string }>;
|
|
358
367
|
|
|
359
368
|
export function uploadPart(
|
|
360
369
|
key: string,
|
|
361
370
|
uploadId: string,
|
|
362
371
|
partNumber: number,
|
|
363
|
-
buffer: Buffer | Uint8Array
|
|
372
|
+
buffer: Buffer | Uint8Array,
|
|
373
|
+
bucketName?: string
|
|
364
374
|
): Promise<{ ETag: string; partNumber: number }>;
|
|
365
375
|
|
|
366
376
|
export function completeMultipartUpload(
|
|
367
377
|
key: string,
|
|
368
378
|
uploadId: string,
|
|
369
|
-
parts: Array<{ partNumber: number; ETag: string }
|
|
379
|
+
parts: Array<{ partNumber: number; ETag: string }>,
|
|
380
|
+
bucketName?: string
|
|
370
381
|
): Promise<{ key: string }>;
|
|
371
382
|
|
|
372
383
|
export function abortMultipartUpload(
|
|
373
384
|
key: string,
|
|
374
|
-
uploadId: string
|
|
385
|
+
uploadId: string,
|
|
386
|
+
bucketName?: string
|
|
375
387
|
): Promise<{ key: string; abortedAt: string }>;
|
|
376
388
|
|
|
377
389
|
export function listIncompleteMultipartUploads(
|
|
@@ -392,11 +404,37 @@ declare module "mbkbucket/lib/config/index" {
|
|
|
392
404
|
p_view_inline: boolean;
|
|
393
405
|
[key: string]: any;
|
|
394
406
|
}
|
|
407
|
+
|
|
408
|
+
export interface BucketConfig {
|
|
409
|
+
BUCKET_NAME: string;
|
|
410
|
+
ENDPOINT?: string;
|
|
411
|
+
ACCESS_KEY_ID?: string;
|
|
412
|
+
SECRET_ACCESS_KEY?: string;
|
|
413
|
+
region?: string;
|
|
414
|
+
[key: string]: any;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
export type BucketConnectionMap = Record<string, BucketConfig>;
|
|
418
|
+
|
|
395
419
|
export const packageJson: Record<string, any>;
|
|
396
420
|
export const appVersion: string;
|
|
397
421
|
export const mbkbucketVar: ConfigVars;
|
|
398
422
|
export function getLatestVersion(): Promise<string | null>;
|
|
399
423
|
export function checkVersion(): Promise<void>;
|
|
424
|
+
export function parseAndValidateMbkbucketVar(rawValue?: string): ConfigVars;
|
|
425
|
+
export function parseAndValidateBucketConnection(rawValue?: string): BucketConnectionMap | null;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
declare module "mbkbucket/lib/config/validation" {
|
|
429
|
+
export function normalizeBooleanLike(value: any): any;
|
|
430
|
+
export function parseAndValidateMbkbucketVar(rawValue?: string): { [key: string]: any };
|
|
431
|
+
export function parseAndValidateBucketConnection(rawValue?: string): Record<string, any> | null;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
declare module "mbkbucket/lib/routes/index" {
|
|
435
|
+
import { Router } from "express";
|
|
436
|
+
const router: Router;
|
|
437
|
+
export default router;
|
|
400
438
|
}
|
|
401
439
|
|
|
402
440
|
declare module "mbkbucket/lib/bucket" {
|
package/index.js
CHANGED
|
@@ -8,22 +8,17 @@ import compression from "compression";
|
|
|
8
8
|
import rateLimit from 'express-rate-limit';
|
|
9
9
|
import bucketRoutes from "./lib/routes/index.js";
|
|
10
10
|
import { checkVersion } from "./lib/config/index.js";
|
|
11
|
-
import {
|
|
11
|
+
import { packageJson } from "./lib/config/index.js";
|
|
12
12
|
|
|
13
13
|
dotenv.config();
|
|
14
14
|
|
|
15
15
|
const __filename = fileURLToPath(import.meta.url);
|
|
16
16
|
const __dirname = path.dirname(__filename);
|
|
17
|
-
const assetVersion = appVersion;
|
|
18
17
|
|
|
19
18
|
const server = express();
|
|
20
19
|
server.set('trust proxy', 1);
|
|
21
20
|
|
|
22
21
|
server.use(compression());
|
|
23
|
-
server.use((req, res, next) => {
|
|
24
|
-
res.locals.assetVersion = assetVersion;
|
|
25
|
-
next();
|
|
26
|
-
});
|
|
27
22
|
|
|
28
23
|
// Rate limiting: general limiter for typical browsing/API usage and a stricter
|
|
29
24
|
// limiter for dashboard (admin) routes.
|
|
@@ -37,21 +32,24 @@ const generalLimiter = rateLimit({
|
|
|
37
32
|
}
|
|
38
33
|
});
|
|
39
34
|
|
|
40
|
-
|
|
41
|
-
|
|
35
|
+
// Increase body size limits to support larger form submissions (e.g., multipart upload init/complete payloads).
|
|
36
|
+
// Multipart/form-data uploads themselves are handled separately via multer in the upload endpoints.
|
|
37
|
+
server.use(express.json({ limit: '10mb' }));
|
|
38
|
+
server.use(express.urlencoded({ extended: true, limit: '10mb' }));
|
|
42
39
|
|
|
43
|
-
// Serve only specific static assets (limit exposure): CSS and JS used by the dashboard
|
|
44
|
-
//
|
|
45
|
-
|
|
40
|
+
// Serve only specific static assets (limit exposure): CSS and JS used by the dashboard.
|
|
41
|
+
// Avoid immutable long cache here because dashboard routes change frequently and stale JS
|
|
42
|
+
// can keep writing old URLs in browser history.
|
|
43
|
+
const dashboardAssetCacheControl = 'public, max-age=300, must-revalidate';
|
|
46
44
|
server.get('/mbkbucket/bucketadmin.css', (req, res) => {
|
|
47
|
-
res.set('Cache-Control',
|
|
45
|
+
res.set('Cache-Control', dashboardAssetCacheControl);
|
|
48
46
|
res.sendFile(path.join(__dirname, 'public', 'bucketadmin.css'), (err) => {
|
|
49
47
|
if (err) res.status(err.status || 404).end();
|
|
50
48
|
});
|
|
51
49
|
});
|
|
52
50
|
|
|
53
51
|
server.get('/mbkbucket/bucketadmin.js', (req, res) => {
|
|
54
|
-
res.set('Cache-Control',
|
|
52
|
+
res.set('Cache-Control', dashboardAssetCacheControl);
|
|
55
53
|
res.sendFile(path.join(__dirname, 'public', 'bucketadmin.js'), (err) => {
|
|
56
54
|
if (err) res.status(err.status || 404).end();
|
|
57
55
|
});
|
|
@@ -61,6 +59,7 @@ server.get('/mbkbucket/bucketadmin.js', (req, res) => {
|
|
|
61
59
|
server.engine("handlebars", engine({
|
|
62
60
|
extname: ".handlebars",
|
|
63
61
|
defaultLayout: "main",
|
|
62
|
+
layoutsDir: path.join(__dirname, "../../views/layouts"),
|
|
64
63
|
partialsDir: [
|
|
65
64
|
path.join(__dirname, "views/templates"),
|
|
66
65
|
path.join(__dirname, "views/templates/notice"),
|
|
@@ -73,6 +72,9 @@ server.engine("handlebars", engine({
|
|
|
73
72
|
helpers: {
|
|
74
73
|
eq: function (a, b) {
|
|
75
74
|
return a === b;
|
|
75
|
+
},
|
|
76
|
+
mbkbucket_cachebuster: function () {
|
|
77
|
+
return "?v=" + packageJson.version;
|
|
76
78
|
}
|
|
77
79
|
}
|
|
78
80
|
}));
|
package/lib/config/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createRequire } from "module";
|
|
2
2
|
import dotenv from "dotenv";
|
|
3
|
+
import { parseAndValidateMbkbucketVar, parseAndValidateBucketConnection } from "./validation.js";
|
|
3
4
|
|
|
4
5
|
dotenv.config();
|
|
5
6
|
|
|
@@ -60,78 +61,16 @@ async function checkVersion() {
|
|
|
60
61
|
|
|
61
62
|
// Comprehensive validation function
|
|
62
63
|
function validateConfiguration() {
|
|
63
|
-
const errors = [];
|
|
64
|
-
|
|
65
|
-
// Parse and validate mbkbucketVar
|
|
66
|
-
let mbkbucketVar;
|
|
67
64
|
try {
|
|
65
|
+
const parsed = parseAndValidateMbkbucketVar(process.env.mbkbucketVar);
|
|
68
66
|
if (!process.env.mbkbucketVar) {
|
|
69
|
-
// If mbkbucketVar is not defined, use empty object (defaults will be applied later)
|
|
70
|
-
mbkbucketVar = {};
|
|
71
67
|
console.log(`[mbkbucket] mbkbucketVar not defined, using defaults`);
|
|
72
|
-
} else {
|
|
73
|
-
mbkbucketVar = JSON.parse(process.env.mbkbucketVar);
|
|
74
68
|
}
|
|
69
|
+
console.log(`[mbkbucket] Configuration loaded`);
|
|
70
|
+
return parsed;
|
|
75
71
|
} catch (error) {
|
|
76
|
-
|
|
77
|
-
throw new Error(`[mbkbucket] Configuration Error:\n - ${errors.join('\n - ')}`);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
if (!mbkbucketVar || typeof mbkbucketVar !== 'object') {
|
|
81
|
-
errors.push("mbkbucketVar must be a valid object");
|
|
82
|
-
throw new Error(`[mbkbucket] Configuration Error:\n - ${errors.join('\n - ')}`);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// Apply defaults for optional keys (booleans now)
|
|
86
|
-
const defaults = {
|
|
87
|
-
p_view_inline: true,
|
|
88
|
-
publiView_enabled: false
|
|
89
|
-
};
|
|
90
|
-
|
|
91
|
-
const usedDefaults = [];
|
|
92
|
-
Object.entries(defaults).forEach(([key, defaultVal]) => {
|
|
93
|
-
const current = mbkbucketVar[key];
|
|
94
|
-
if (current === undefined || (typeof current === 'string' && current.trim() === '')) {
|
|
95
|
-
mbkbucketVar[key] = defaultVal;
|
|
96
|
-
usedDefaults.push(key);
|
|
97
|
-
}
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
// Helper to normalize various truthy/falsy representations
|
|
101
|
-
function normalizeBool(val) {
|
|
102
|
-
if (typeof val === 'boolean') return val;
|
|
103
|
-
if (typeof val === 'string') {
|
|
104
|
-
const norm = val.trim().toLowerCase();
|
|
105
|
-
if (['true', 't', '1', 'yes', 'y', 'on'].includes(norm)) return true;
|
|
106
|
-
if (['false', 'f', '0', 'no', 'n', 'off'].includes(norm)) return false;
|
|
107
|
-
return val; // return original so later validation will flag it
|
|
108
|
-
}
|
|
109
|
-
return val;
|
|
72
|
+
throw new Error(`[mbkbucket] Configuration Validation Failed:\n - ${error.message}`);
|
|
110
73
|
}
|
|
111
|
-
|
|
112
|
-
// Normalize boolean-like values early so validation works on cleaned values;
|
|
113
|
-
// results are actual booleans.
|
|
114
|
-
['p_view_inline', 'publiView_enabled'].forEach(k => {
|
|
115
|
-
if (k in mbkbucketVar) {
|
|
116
|
-
mbkbucketVar[k] = normalizeBool(mbkbucketVar[k]);
|
|
117
|
-
}
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
// Validate that applied values are proper booleans (after defaults and normalization)
|
|
121
|
-
// Note: p_view_inline and publiView_enabled are optional - defaults are applied above
|
|
122
|
-
['p_view_inline', 'publiView_enabled'].forEach(key => {
|
|
123
|
-
if (mbkbucketVar[key] !== undefined && typeof mbkbucketVar[key] !== 'boolean') {
|
|
124
|
-
errors.push(`mbkbucketVar.${key} must be a boolean if provided`);
|
|
125
|
-
}
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
if (errors.length > 0) {
|
|
129
|
-
throw new Error(`[mbkbucket] Configuration Validation Failed:\n - ${errors.join('\n - ')}`);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
const summary = usedDefaults.length > 0 ? ` (defaults applied: ${usedDefaults.join(', ')})` : '';
|
|
133
|
-
console.log(`[mbkbucket] Configuration loaded${summary}`);
|
|
134
|
-
return mbkbucketVar;
|
|
135
74
|
}
|
|
136
75
|
|
|
137
76
|
// Validate BucketConnection configuration
|
|
@@ -144,28 +83,8 @@ function validateBucketConnection() {
|
|
|
144
83
|
}
|
|
145
84
|
|
|
146
85
|
try {
|
|
147
|
-
const bucketConfig =
|
|
148
|
-
|
|
149
|
-
if (!bucketConfig || typeof bucketConfig !== 'object' || Object.keys(bucketConfig).length === 0) {
|
|
150
|
-
throw new Error('BucketConnection must be a non-empty object');
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
// Validate each bucket configuration
|
|
154
|
-
const bucketNames = Object.keys(bucketConfig);
|
|
155
|
-
for (const bucketName of bucketNames) {
|
|
156
|
-
const config = bucketConfig[bucketName];
|
|
157
|
-
|
|
158
|
-
if (!config || typeof config !== 'object') {
|
|
159
|
-
throw new Error(`Bucket '${bucketName}' configuration must be an object, not a string. Remove quotes around the inner JSON object.`);
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
const requiredFields = ['BUCKET_NAME', 'ACCESS_KEY_ID', 'SECRET_ACCESS_KEY', 'ENDPOINT'];
|
|
163
|
-
const missingFields = requiredFields.filter(field => !config[field]);
|
|
164
|
-
|
|
165
|
-
if (missingFields.length > 0) {
|
|
166
|
-
throw new Error(`Bucket '${bucketName}' is missing required fields: ${missingFields.join(', ')}`);
|
|
167
|
-
}
|
|
168
|
-
}
|
|
86
|
+
const bucketConfig = parseAndValidateBucketConnection(process.env.BucketConnection);
|
|
87
|
+
const bucketNames = Object.keys(bucketConfig || {});
|
|
169
88
|
|
|
170
89
|
console.log(`[mbkbucket] ✓ BucketConnection validated (${bucketNames.length} bucket(s): ${bucketNames.join(', ')})`);
|
|
171
90
|
return bucketConfig;
|
|
@@ -187,4 +106,5 @@ console.log(`[mbkbucket] mbkbucketVar configuration:`, mbkbucketVar);
|
|
|
187
106
|
validateBucketConnection();
|
|
188
107
|
|
|
189
108
|
export { packageJson, appVersion, getLatestVersion, checkVersion };
|
|
190
|
-
export { mbkbucketVar };
|
|
109
|
+
export { mbkbucketVar };
|
|
110
|
+
export { parseAndValidateMbkbucketVar, parseAndValidateBucketConnection };
|