@flusys/nestjs-storage 1.0.0-beta → 1.0.0-rc
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/README.md +131 -13
- package/cjs/config/storage-config.service.js +5 -0
- package/cjs/config/storage.constants.js +0 -8
- package/cjs/controllers/file-manager.controller.js +44 -1
- package/cjs/controllers/folder.controller.js +44 -1
- package/cjs/controllers/storage-config.controller.js +44 -1
- package/cjs/controllers/upload.controller.js +6 -12
- package/cjs/dtos/file-manager.dto.js +8 -5
- package/cjs/dtos/storage-config.dto.js +41 -1
- package/cjs/dtos/upload.dto.js +7 -0
- package/cjs/entities/storage-config-base.entity.js +31 -2
- package/cjs/interfaces/index.js +0 -1
- package/cjs/middlewares/file-serve.middleware.js +6 -0
- package/cjs/providers/local-provider.js +52 -2
- package/cjs/providers/storage-factory.service.js +2 -2
- package/cjs/services/file-manager.service.js +37 -24
- package/cjs/services/folder.service.js +5 -8
- package/cjs/services/storage-provider-config.service.js +18 -35
- package/cjs/services/upload.service.js +39 -27
- package/cjs/utils/file-validator.util.js +470 -0
- package/cjs/utils/image-compressor.util.js +1 -3
- package/config/storage-config.service.d.ts +5 -2
- package/config/storage.constants.d.ts +0 -2
- package/controllers/upload.controller.d.ts +2 -6
- package/dtos/file-manager.dto.d.ts +2 -4
- package/dtos/folder.dto.d.ts +2 -4
- package/dtos/storage-config.dto.d.ts +9 -6
- package/entities/storage-config-base.entity.d.ts +2 -0
- package/fesm/config/storage-config.service.js +5 -0
- package/fesm/config/storage.constants.js +0 -2
- package/fesm/controllers/file-manager.controller.js +45 -2
- package/fesm/controllers/folder.controller.js +45 -2
- package/fesm/controllers/storage-config.controller.js +45 -2
- package/fesm/controllers/upload.controller.js +7 -13
- package/fesm/dtos/file-manager.dto.js +8 -5
- package/fesm/dtos/storage-config.dto.js +45 -11
- package/fesm/dtos/upload.dto.js +8 -1
- package/fesm/entities/index.js +1 -5
- package/fesm/entities/storage-config-base.entity.js +33 -7
- package/fesm/interfaces/index.js +0 -1
- package/fesm/interfaces/storage-config.interface.js +1 -3
- package/fesm/middlewares/file-serve.middleware.js +7 -1
- package/fesm/providers/local-provider.js +52 -2
- package/fesm/providers/storage-factory.service.js +2 -2
- package/fesm/services/file-manager.service.js +38 -25
- package/fesm/services/folder.service.js +5 -8
- package/fesm/services/storage-provider-config.service.js +18 -35
- package/fesm/services/upload.service.js +40 -28
- package/fesm/utils/file-validator.util.js +463 -0
- package/fesm/utils/image-compressor.util.js +1 -3
- package/interfaces/file-manager.interface.d.ts +7 -4
- package/interfaces/index.d.ts +0 -1
- package/interfaces/storage-config.interface.d.ts +2 -20
- package/package.json +6 -6
- package/providers/local-provider.d.ts +2 -0
- package/services/file-manager.service.d.ts +2 -2
- package/utils/file-validator.util.d.ts +16 -0
- package/cjs/interfaces/file-upload-response.interface.js +0 -4
- package/fesm/interfaces/file-upload-response.interface.js +0 -1
- package/interfaces/file-upload-response.interface.d.ts +0 -6
|
@@ -0,0 +1,463 @@
|
|
|
1
|
+
function _define_property(obj, key, value) {
|
|
2
|
+
if (key in obj) {
|
|
3
|
+
Object.defineProperty(obj, key, {
|
|
4
|
+
value: value,
|
|
5
|
+
enumerable: true,
|
|
6
|
+
configurable: true,
|
|
7
|
+
writable: true
|
|
8
|
+
});
|
|
9
|
+
} else {
|
|
10
|
+
obj[key] = value;
|
|
11
|
+
}
|
|
12
|
+
return obj;
|
|
13
|
+
}
|
|
14
|
+
import { Logger } from '@nestjs/common';
|
|
15
|
+
/**
|
|
16
|
+
* Magic byte signatures for common file types.
|
|
17
|
+
* Each entry maps a hex signature pattern to its MIME type.
|
|
18
|
+
*/ const MAGIC_BYTES = [
|
|
19
|
+
// Images
|
|
20
|
+
{
|
|
21
|
+
signature: [
|
|
22
|
+
0xff,
|
|
23
|
+
0xd8,
|
|
24
|
+
0xff
|
|
25
|
+
],
|
|
26
|
+
offset: 0,
|
|
27
|
+
mimeType: 'image/jpeg'
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
signature: [
|
|
31
|
+
0x89,
|
|
32
|
+
0x50,
|
|
33
|
+
0x4e,
|
|
34
|
+
0x47,
|
|
35
|
+
0x0d,
|
|
36
|
+
0x0a,
|
|
37
|
+
0x1a,
|
|
38
|
+
0x0a
|
|
39
|
+
],
|
|
40
|
+
offset: 0,
|
|
41
|
+
mimeType: 'image/png'
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
signature: [
|
|
45
|
+
0x47,
|
|
46
|
+
0x49,
|
|
47
|
+
0x46,
|
|
48
|
+
0x38,
|
|
49
|
+
0x37,
|
|
50
|
+
0x61
|
|
51
|
+
],
|
|
52
|
+
offset: 0,
|
|
53
|
+
mimeType: 'image/gif'
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
signature: [
|
|
57
|
+
0x47,
|
|
58
|
+
0x49,
|
|
59
|
+
0x46,
|
|
60
|
+
0x38,
|
|
61
|
+
0x39,
|
|
62
|
+
0x61
|
|
63
|
+
],
|
|
64
|
+
offset: 0,
|
|
65
|
+
mimeType: 'image/gif'
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
signature: [
|
|
69
|
+
0x42,
|
|
70
|
+
0x4d
|
|
71
|
+
],
|
|
72
|
+
offset: 0,
|
|
73
|
+
mimeType: 'image/bmp'
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
signature: [
|
|
77
|
+
0x52,
|
|
78
|
+
0x49,
|
|
79
|
+
0x46,
|
|
80
|
+
0x46
|
|
81
|
+
],
|
|
82
|
+
offset: 0,
|
|
83
|
+
mimeType: 'image/webp'
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
signature: [
|
|
87
|
+
0x00,
|
|
88
|
+
0x00,
|
|
89
|
+
0x01,
|
|
90
|
+
0x00
|
|
91
|
+
],
|
|
92
|
+
offset: 0,
|
|
93
|
+
mimeType: 'image/x-icon'
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
signature: [
|
|
97
|
+
0x00,
|
|
98
|
+
0x00,
|
|
99
|
+
0x02,
|
|
100
|
+
0x00
|
|
101
|
+
],
|
|
102
|
+
offset: 0,
|
|
103
|
+
mimeType: 'image/x-icon'
|
|
104
|
+
},
|
|
105
|
+
// Documents
|
|
106
|
+
{
|
|
107
|
+
signature: [
|
|
108
|
+
0x25,
|
|
109
|
+
0x50,
|
|
110
|
+
0x44,
|
|
111
|
+
0x46
|
|
112
|
+
],
|
|
113
|
+
offset: 0,
|
|
114
|
+
mimeType: 'application/pdf'
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
signature: [
|
|
118
|
+
0x50,
|
|
119
|
+
0x4b,
|
|
120
|
+
0x03,
|
|
121
|
+
0x04
|
|
122
|
+
],
|
|
123
|
+
offset: 0,
|
|
124
|
+
mimeType: 'application/zip'
|
|
125
|
+
},
|
|
126
|
+
// Audio
|
|
127
|
+
{
|
|
128
|
+
signature: [
|
|
129
|
+
0x49,
|
|
130
|
+
0x44,
|
|
131
|
+
0x33
|
|
132
|
+
],
|
|
133
|
+
offset: 0,
|
|
134
|
+
mimeType: 'audio/mpeg'
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
signature: [
|
|
138
|
+
0xff,
|
|
139
|
+
0xfb
|
|
140
|
+
],
|
|
141
|
+
offset: 0,
|
|
142
|
+
mimeType: 'audio/mpeg'
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
signature: [
|
|
146
|
+
0xff,
|
|
147
|
+
0xfa
|
|
148
|
+
],
|
|
149
|
+
offset: 0,
|
|
150
|
+
mimeType: 'audio/mpeg'
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
signature: [
|
|
154
|
+
0x4f,
|
|
155
|
+
0x67,
|
|
156
|
+
0x67,
|
|
157
|
+
0x53
|
|
158
|
+
],
|
|
159
|
+
offset: 0,
|
|
160
|
+
mimeType: 'audio/ogg'
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
signature: [
|
|
164
|
+
0x66,
|
|
165
|
+
0x4c,
|
|
166
|
+
0x61,
|
|
167
|
+
0x43
|
|
168
|
+
],
|
|
169
|
+
offset: 0,
|
|
170
|
+
mimeType: 'audio/flac'
|
|
171
|
+
},
|
|
172
|
+
// Video
|
|
173
|
+
{
|
|
174
|
+
signature: [
|
|
175
|
+
0x00,
|
|
176
|
+
0x00,
|
|
177
|
+
0x00,
|
|
178
|
+
0x1c,
|
|
179
|
+
0x66,
|
|
180
|
+
0x74,
|
|
181
|
+
0x79,
|
|
182
|
+
0x70
|
|
183
|
+
],
|
|
184
|
+
offset: 0,
|
|
185
|
+
mimeType: 'video/mp4'
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
signature: [
|
|
189
|
+
0x00,
|
|
190
|
+
0x00,
|
|
191
|
+
0x00,
|
|
192
|
+
0x20,
|
|
193
|
+
0x66,
|
|
194
|
+
0x74,
|
|
195
|
+
0x79,
|
|
196
|
+
0x70
|
|
197
|
+
],
|
|
198
|
+
offset: 0,
|
|
199
|
+
mimeType: 'video/mp4'
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
signature: [
|
|
203
|
+
0x1a,
|
|
204
|
+
0x45,
|
|
205
|
+
0xdf,
|
|
206
|
+
0xa3
|
|
207
|
+
],
|
|
208
|
+
offset: 0,
|
|
209
|
+
mimeType: 'video/webm'
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
signature: [
|
|
213
|
+
0x52,
|
|
214
|
+
0x49,
|
|
215
|
+
0x46,
|
|
216
|
+
0x46
|
|
217
|
+
],
|
|
218
|
+
offset: 0,
|
|
219
|
+
mimeType: 'video/avi'
|
|
220
|
+
},
|
|
221
|
+
// Archives
|
|
222
|
+
{
|
|
223
|
+
signature: [
|
|
224
|
+
0x1f,
|
|
225
|
+
0x8b
|
|
226
|
+
],
|
|
227
|
+
offset: 0,
|
|
228
|
+
mimeType: 'application/gzip'
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
signature: [
|
|
232
|
+
0x37,
|
|
233
|
+
0x7a,
|
|
234
|
+
0xbc,
|
|
235
|
+
0xaf,
|
|
236
|
+
0x27,
|
|
237
|
+
0x1c
|
|
238
|
+
],
|
|
239
|
+
offset: 0,
|
|
240
|
+
mimeType: 'application/x-7z-compressed'
|
|
241
|
+
},
|
|
242
|
+
{
|
|
243
|
+
signature: [
|
|
244
|
+
0x52,
|
|
245
|
+
0x61,
|
|
246
|
+
0x72,
|
|
247
|
+
0x21,
|
|
248
|
+
0x1a,
|
|
249
|
+
0x07
|
|
250
|
+
],
|
|
251
|
+
offset: 0,
|
|
252
|
+
mimeType: 'application/x-rar-compressed'
|
|
253
|
+
}
|
|
254
|
+
];
|
|
255
|
+
/**
|
|
256
|
+
* MIME type aliases - types that are equivalent.
|
|
257
|
+
*/ const MIME_ALIASES = {
|
|
258
|
+
'image/jpeg': [
|
|
259
|
+
'image/jpg'
|
|
260
|
+
],
|
|
261
|
+
'image/jpg': [
|
|
262
|
+
'image/jpeg'
|
|
263
|
+
],
|
|
264
|
+
'video/mp4': [
|
|
265
|
+
'video/quicktime'
|
|
266
|
+
],
|
|
267
|
+
'application/zip': [
|
|
268
|
+
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
269
|
+
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
270
|
+
'application/vnd.openxmlformats-officedocument.presentationml.presentation'
|
|
271
|
+
]
|
|
272
|
+
};
|
|
273
|
+
/**
|
|
274
|
+
* File types that are text-based and don't have magic bytes.
|
|
275
|
+
* SECURITY NOTE: Dangerous types (HTML, JS, SVG) are excluded as they can contain scripts.
|
|
276
|
+
* These types require explicit allowlisting and additional content scanning.
|
|
277
|
+
*/ const TEXT_BASED_TYPES = [
|
|
278
|
+
'text/plain',
|
|
279
|
+
'text/csv',
|
|
280
|
+
'text/markdown',
|
|
281
|
+
'application/json',
|
|
282
|
+
'application/xml',
|
|
283
|
+
'application/typescript',
|
|
284
|
+
'text/css'
|
|
285
|
+
];
|
|
286
|
+
/**
|
|
287
|
+
* Dangerous text-based types that can execute scripts.
|
|
288
|
+
* These bypass magic-bytes validation but require explicit allowlisting.
|
|
289
|
+
*/ const DANGEROUS_TEXT_TYPES = [
|
|
290
|
+
'text/html',
|
|
291
|
+
'application/javascript',
|
|
292
|
+
'text/javascript',
|
|
293
|
+
'image/svg+xml',
|
|
294
|
+
'application/xhtml+xml'
|
|
295
|
+
];
|
|
296
|
+
/**
|
|
297
|
+
* Utility class for validating file content using magic bytes.
|
|
298
|
+
* Prevents file type spoofing by checking actual file content.
|
|
299
|
+
*/ export class FileValidator {
|
|
300
|
+
/**
|
|
301
|
+
* Detect file type from buffer using magic bytes.
|
|
302
|
+
* @param buffer - File buffer to analyze
|
|
303
|
+
* @returns Detected MIME type or null if unknown
|
|
304
|
+
*/ static detectFileType(buffer) {
|
|
305
|
+
for (const { signature, offset, mimeType } of MAGIC_BYTES){
|
|
306
|
+
if (buffer.length < offset + signature.length) continue;
|
|
307
|
+
let matches = true;
|
|
308
|
+
for(let i = 0; i < signature.length; i++){
|
|
309
|
+
if (buffer[offset + i] !== signature[i]) {
|
|
310
|
+
matches = false;
|
|
311
|
+
break;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
if (matches) {
|
|
315
|
+
return mimeType;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
return null;
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Check if a MIME type is text-based (doesn't have magic bytes).
|
|
322
|
+
*/ static isTextBasedType(mimeType) {
|
|
323
|
+
return TEXT_BASED_TYPES.some((t)=>mimeType.startsWith(t) || mimeType === t);
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Check if a MIME type is a dangerous text type that can execute scripts.
|
|
327
|
+
*/ static isDangerousTextType(mimeType) {
|
|
328
|
+
return DANGEROUS_TEXT_TYPES.some((t)=>mimeType === t);
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Check if two MIME types are compatible (exact match or aliases).
|
|
332
|
+
*/ static mimeTypesMatch(detected, declared) {
|
|
333
|
+
// Exact match
|
|
334
|
+
if (detected === declared) return true;
|
|
335
|
+
// Check aliases
|
|
336
|
+
const aliases = MIME_ALIASES[detected];
|
|
337
|
+
if (aliases?.includes(declared)) return true;
|
|
338
|
+
// Check reverse aliases
|
|
339
|
+
const reverseAliases = MIME_ALIASES[declared];
|
|
340
|
+
if (reverseAliases?.includes(detected)) return true;
|
|
341
|
+
// Check if both are in same category (e.g., both images)
|
|
342
|
+
const detectedCategory = detected.split('/')[0];
|
|
343
|
+
const declaredCategory = declared.split('/')[0];
|
|
344
|
+
// For ZIP-based formats, allow any ZIP-detected file if declared is a ZIP variant
|
|
345
|
+
if (detected === 'application/zip') {
|
|
346
|
+
const zipVariants = [
|
|
347
|
+
'application/vnd.openxmlformats-officedocument',
|
|
348
|
+
'application/x-zip',
|
|
349
|
+
'application/x-compressed'
|
|
350
|
+
];
|
|
351
|
+
if (zipVariants.some((v)=>declared.startsWith(v))) {
|
|
352
|
+
return true;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
return detectedCategory === declaredCategory;
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* Check if a MIME type is in the allowed list.
|
|
359
|
+
*/ static isTypeAllowed(mimeType, allowedTypes) {
|
|
360
|
+
// Wildcard allows all
|
|
361
|
+
if (allowedTypes.includes('*/*')) return true;
|
|
362
|
+
return allowedTypes.some((allowed)=>{
|
|
363
|
+
// Category wildcard (e.g., "image/*")
|
|
364
|
+
if (allowed.endsWith('/*')) {
|
|
365
|
+
const category = allowed.slice(0, -2);
|
|
366
|
+
return mimeType.startsWith(category);
|
|
367
|
+
}
|
|
368
|
+
return allowed === mimeType;
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* Validate file content matches declared MIME type using magic bytes.
|
|
373
|
+
* @param buffer - File buffer
|
|
374
|
+
* @param declaredMimeType - MIME type declared by client
|
|
375
|
+
* @param allowedTypes - List of allowed MIME types/patterns
|
|
376
|
+
* @returns Validation result
|
|
377
|
+
*/ static validateFileContent(buffer, declaredMimeType, allowedTypes = [
|
|
378
|
+
'*/*'
|
|
379
|
+
]) {
|
|
380
|
+
try {
|
|
381
|
+
// Detect actual file type from magic bytes
|
|
382
|
+
const detectedType = this.detectFileType(buffer);
|
|
383
|
+
// If no type detected, check if it's a text-based type
|
|
384
|
+
if (!detectedType) {
|
|
385
|
+
// Check for dangerous text types first (HTML, JS, SVG)
|
|
386
|
+
if (this.isDangerousTextType(declaredMimeType)) {
|
|
387
|
+
// Only allow dangerous types if explicitly in allowedTypes (not via wildcard)
|
|
388
|
+
const explicitlyAllowed = allowedTypes.some((t)=>t === declaredMimeType && t !== '*/*' && !t.endsWith('/*'));
|
|
389
|
+
if (!explicitlyAllowed) {
|
|
390
|
+
this.logger.warn(`Blocked dangerous file type: ${declaredMimeType} - requires explicit allowlisting`);
|
|
391
|
+
return {
|
|
392
|
+
valid: false,
|
|
393
|
+
detectedType: declaredMimeType,
|
|
394
|
+
declaredType: declaredMimeType,
|
|
395
|
+
message: `File type "${declaredMimeType}" is potentially dangerous and not explicitly allowed`
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
this.logger.warn(`Allowing explicitly permitted dangerous file type: ${declaredMimeType}`);
|
|
399
|
+
}
|
|
400
|
+
if (this.isTextBasedType(declaredMimeType)) {
|
|
401
|
+
// Safe text-based files don't have magic bytes, trust the declared type
|
|
402
|
+
const isAllowed = this.isTypeAllowed(declaredMimeType, allowedTypes);
|
|
403
|
+
return {
|
|
404
|
+
valid: isAllowed,
|
|
405
|
+
detectedType: declaredMimeType,
|
|
406
|
+
declaredType: declaredMimeType,
|
|
407
|
+
message: isAllowed ? undefined : `File type "${declaredMimeType}" is not allowed`
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
// For binary files without recognized signatures, be cautious
|
|
411
|
+
this.logger.warn(`Unable to detect file type for declared type: ${declaredMimeType}`);
|
|
412
|
+
return {
|
|
413
|
+
valid: false,
|
|
414
|
+
declaredType: declaredMimeType,
|
|
415
|
+
message: 'Unable to verify file type. File may be corrupted or unsupported.'
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
// Check if detected type matches declared type
|
|
419
|
+
if (!this.mimeTypesMatch(detectedType, declaredMimeType)) {
|
|
420
|
+
this.logger.warn(`MIME type mismatch: declared=${declaredMimeType}, detected=${detectedType}`);
|
|
421
|
+
return {
|
|
422
|
+
valid: false,
|
|
423
|
+
detectedType,
|
|
424
|
+
declaredType: declaredMimeType,
|
|
425
|
+
message: `File content does not match declared type. Detected: ${detectedType}, Declared: ${declaredMimeType}`
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
// Check if detected type is allowed
|
|
429
|
+
if (!this.isTypeAllowed(detectedType, allowedTypes)) {
|
|
430
|
+
return {
|
|
431
|
+
valid: false,
|
|
432
|
+
detectedType,
|
|
433
|
+
declaredType: declaredMimeType,
|
|
434
|
+
message: `File type "${detectedType}" is not allowed`
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
return {
|
|
438
|
+
valid: true,
|
|
439
|
+
detectedType,
|
|
440
|
+
declaredType: declaredMimeType
|
|
441
|
+
};
|
|
442
|
+
} catch (error) {
|
|
443
|
+
this.logger.error('File validation error:', error);
|
|
444
|
+
return {
|
|
445
|
+
valid: false,
|
|
446
|
+
message: 'File validation failed'
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
/**
|
|
451
|
+
* Sanitize filename to prevent path traversal and special character issues.
|
|
452
|
+
* @param filename - Original filename
|
|
453
|
+
* @returns Sanitized filename
|
|
454
|
+
*/ static sanitizeFilename(filename) {
|
|
455
|
+
return filename// Remove path components (prevent traversal)
|
|
456
|
+
.replace(/^.*[\\\/]/, '')// Remove null bytes
|
|
457
|
+
.replace(/\0/g, '')// Replace multiple dots with single
|
|
458
|
+
.replace(/\.{2,}/g, '.')// Remove special characters except allowed ones
|
|
459
|
+
.replace(/[^a-zA-Z0-9._-]/g, '_')// Limit length
|
|
460
|
+
.substring(0, 255);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
_define_property(FileValidator, "logger", new Logger(FileValidator.name));
|
|
@@ -107,9 +107,7 @@ const sharp = sharpModule.default || sharpModule;
|
|
|
107
107
|
buffer: data,
|
|
108
108
|
format: `image/${targetFormat}`
|
|
109
109
|
};
|
|
110
|
-
} catch
|
|
111
|
-
// Fallback to original if processing fails
|
|
112
|
-
console.warn(`Image processing failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
110
|
+
} catch {
|
|
113
111
|
return {
|
|
114
112
|
buffer,
|
|
115
113
|
format: mimetype
|
|
@@ -3,12 +3,15 @@ export interface IFileManager extends IIdentity {
|
|
|
3
3
|
name?: string;
|
|
4
4
|
key?: string;
|
|
5
5
|
url?: string;
|
|
6
|
-
folder?:
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
folder?: {
|
|
7
|
+
id: string;
|
|
8
|
+
name?: string;
|
|
9
|
+
} | null;
|
|
10
|
+
size?: string;
|
|
11
|
+
expiresAt?: number | null;
|
|
9
12
|
contentType?: string;
|
|
10
13
|
location?: string;
|
|
11
|
-
storageConfigId?: string;
|
|
14
|
+
storageConfigId?: string | null;
|
|
12
15
|
providerName?: string;
|
|
13
16
|
isPrivate?: boolean;
|
|
14
17
|
companyId?: string | null;
|
package/interfaces/index.d.ts
CHANGED
|
@@ -4,25 +4,7 @@ export interface IStorageConfig extends IIdentity {
|
|
|
4
4
|
name: string;
|
|
5
5
|
storage: FileLocationEnum;
|
|
6
6
|
config: Record<string, any>;
|
|
7
|
+
isActive: boolean;
|
|
8
|
+
isDefault: boolean;
|
|
7
9
|
companyId?: string | null;
|
|
8
10
|
}
|
|
9
|
-
export interface IAwsS3Config {
|
|
10
|
-
region: string;
|
|
11
|
-
bucket: string;
|
|
12
|
-
accessKeyId: string;
|
|
13
|
-
secretAccessKey: string;
|
|
14
|
-
endpoint?: string;
|
|
15
|
-
}
|
|
16
|
-
export interface IAzureBlobConfig {
|
|
17
|
-
accountName: string;
|
|
18
|
-
accountKey: string;
|
|
19
|
-
containerName: string;
|
|
20
|
-
}
|
|
21
|
-
export interface ISftpConfig {
|
|
22
|
-
host: string;
|
|
23
|
-
port: number;
|
|
24
|
-
username: string;
|
|
25
|
-
password?: string;
|
|
26
|
-
privateKey?: string;
|
|
27
|
-
basePath: string;
|
|
28
|
-
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flusys/nestjs-storage",
|
|
3
|
-
"version": "1.0.0-
|
|
3
|
+
"version": "1.0.0-rc",
|
|
4
4
|
"description": "Modular storage package with optional AWS S3, Azure Blob, and SFTP providers",
|
|
5
5
|
"main": "cjs/index.js",
|
|
6
6
|
"module": "fesm/index.js",
|
|
@@ -94,15 +94,15 @@
|
|
|
94
94
|
"class-transformer": "^0.5.0",
|
|
95
95
|
"class-validator": "^0.14.0",
|
|
96
96
|
"typeorm": "^0.3.0",
|
|
97
|
-
"multer": "^
|
|
98
|
-
"sharp": "^0.
|
|
97
|
+
"multer": "^2.0.0",
|
|
98
|
+
"sharp": "^0.34.0",
|
|
99
99
|
"mime-types": "^2.1.0",
|
|
100
100
|
"uuid": "^9.0.0 || ^11.0.0",
|
|
101
101
|
"@aws-sdk/client-s3": "^3.400.0",
|
|
102
102
|
"@aws-sdk/lib-storage": "^3.400.0",
|
|
103
103
|
"@aws-sdk/s3-request-presigner": "^3.400.0",
|
|
104
104
|
"@azure/storage-blob": "^12.15.0",
|
|
105
|
-
"ssh2-sftp-client": "^
|
|
105
|
+
"ssh2-sftp-client": "^12.0.0"
|
|
106
106
|
},
|
|
107
107
|
"peerDependenciesMeta": {
|
|
108
108
|
"sharp": {
|
|
@@ -128,7 +128,7 @@
|
|
|
128
128
|
}
|
|
129
129
|
},
|
|
130
130
|
"dependencies": {
|
|
131
|
-
"@flusys/nestjs-core": "1.0.0-
|
|
132
|
-
"@flusys/nestjs-shared": "1.0.0-
|
|
131
|
+
"@flusys/nestjs-core": "1.0.0-rc",
|
|
132
|
+
"@flusys/nestjs-shared": "1.0.0-rc"
|
|
133
133
|
}
|
|
134
134
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { HybridCache, RequestScopedApiService } from '@flusys/nestjs-shared/classes';
|
|
2
2
|
import { DeleteDto, FilterAndPaginationDto } from '@flusys/nestjs-shared/dtos';
|
|
3
3
|
import { ILoggedUserInfo } from '@flusys/nestjs-shared/interfaces';
|
|
4
4
|
import { UtilsService } from '@flusys/nestjs-shared/modules';
|
|
5
|
-
import {
|
|
5
|
+
import { EntityTarget, QueryRunner, Repository, SelectQueryBuilder } from 'typeorm';
|
|
6
6
|
import { StorageConfigService } from '../config';
|
|
7
7
|
import { CreateFileManagerDto, FilesResponseDto, GetFilesRequestDto, UpdateFileManagerDto } from '../dtos';
|
|
8
8
|
import { FileManagerBase } from '../entities';
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface FileValidationResult {
|
|
2
|
+
valid: boolean;
|
|
3
|
+
detectedType?: string;
|
|
4
|
+
declaredType?: string;
|
|
5
|
+
message?: string;
|
|
6
|
+
}
|
|
7
|
+
export declare class FileValidator {
|
|
8
|
+
private static logger;
|
|
9
|
+
static detectFileType(buffer: Buffer): string | null;
|
|
10
|
+
static isTextBasedType(mimeType: string): boolean;
|
|
11
|
+
static isDangerousTextType(mimeType: string): boolean;
|
|
12
|
+
static mimeTypesMatch(detected: string, declared: string): boolean;
|
|
13
|
+
static isTypeAllowed(mimeType: string, allowedTypes: string[]): boolean;
|
|
14
|
+
static validateFileContent(buffer: Buffer, declaredMimeType: string, allowedTypes?: string[]): FileValidationResult;
|
|
15
|
+
static sanitizeFilename(filename: string): string;
|
|
16
|
+
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { };
|