@reldens/server-utils 0.25.0 → 0.26.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.
@@ -25,10 +25,17 @@ class UploaderFactory
25
25
  this.maxFilenameLength = props.maxFilenameLength || 255;
26
26
  }
27
27
 
28
+ setError(message, additionalData = {})
29
+ {
30
+ if(!this.error.message){
31
+ this.error = {message, ...additionalData};
32
+ }
33
+ }
34
+
28
35
  createUploader(fields, buckets, allowedFileTypes)
29
36
  {
30
37
  if(!this.validateInputs(fields, buckets, allowedFileTypes)){
31
- this.error = {message: 'Invalid uploader configuration: ' + this.error.message};
38
+ this.setError('Invalid uploader configuration: ' + this.error.message);
32
39
  return false;
33
40
  }
34
41
  let storage = multer.diskStorage({
@@ -36,17 +43,23 @@ class UploaderFactory
36
43
  try{
37
44
  let dest = buckets[file.fieldname];
38
45
  if(!FileHandler.isValidPath(dest)){
46
+ this.setError('Invalid destination path', {dest, fieldname: file.fieldname});
39
47
  return cb(new Error('Invalid destination path'));
40
48
  }
41
- FileHandler.createFolder(dest);
49
+ let folderCreated = FileHandler.createFolder(dest);
50
+ if(!folderCreated){
51
+ this.setError('Cannot create destination folder', {dest, fileHandlerError: FileHandler.error});
52
+ return cb(new Error('Cannot create destination folder'));
53
+ }
42
54
  cb(null, dest);
43
55
  } catch(error){
44
- this.error = {message: 'Cannot prepare destination.', error: error};
56
+ this.setError('Cannot prepare destination.', {error: error});
45
57
  cb(error);
46
58
  }
47
59
  },
48
60
  filename: (req, file, cb) => {
49
61
  if(!this.validateFilenameSecurity(file.originalname)){
62
+ this.setError('Invalid filename', {originalname: file.originalname});
50
63
  return cb(new Error('Invalid filename'));
51
64
  }
52
65
  if(!this.applySecureFileNames) {
@@ -80,6 +93,7 @@ class UploaderFactory
80
93
  if(multerError instanceof multer.MulterError){
81
94
  if('LIMIT_FILE_SIZE' === multerError.code){
82
95
  let messageFile = 'File too large.';
96
+ this.setError(messageFile, {multerError});
83
97
  if('function' === typeof this.processErrorResponse){
84
98
  return this.processErrorResponse(413, messageFile, req, res);
85
99
  }
@@ -87,18 +101,21 @@ class UploaderFactory
87
101
  }
88
102
  if('LIMIT_FILE_COUNT' === multerError.code){
89
103
  let messageTooMany = 'Too many files.';
104
+ this.setError(messageTooMany, {multerError});
90
105
  if('function' === typeof this.processErrorResponse){
91
106
  return this.processErrorResponse(413, messageTooMany, req, res);
92
107
  }
93
108
  return res.status(413).send(messageTooMany);
94
109
  }
95
110
  let messageUpload = 'File upload error.';
111
+ this.setError(messageUpload, {multerError});
96
112
  if('function' === typeof this.processErrorResponse){
97
113
  return this.processErrorResponse(400, messageUpload, multerError, req, res);
98
114
  }
99
115
  return res.status(400).send(messageUpload);
100
116
  }
101
- let messageServer = this.error && this.error.message ? this.error.message : 'Server error during file upload.';
117
+ let messageServer = this.error?.message ? this.error.message : 'Server error during file upload.';
118
+ this.setError(messageServer, {multerError});
102
119
  if('function' === typeof this.processErrorResponse){
103
120
  return this.processErrorResponse(415, messageServer, req, res);
104
121
  }
@@ -109,8 +126,9 @@ class UploaderFactory
109
126
  }
110
127
  let validationResult = await this.validateAllUploadedFiles(req, allowedFileTypes);
111
128
  if(!validationResult){
112
- FileHandler.removeMultiple(Object.values(req.files).flat().map(file => file.path));
113
- let messageContents = this.error && this.error.message ? this.error.message : 'File validation failed.';
129
+ let filePaths = Object.values(req.files).flat().map(file => file.path);
130
+ FileHandler.removeMultiple(filePaths);
131
+ let messageContents = this.error?.message ? this.error.message : 'File validation failed.';
114
132
  if('function' === typeof this.processErrorResponse){
115
133
  return this.processErrorResponse(415, messageContents, req, res);
116
134
  }
@@ -126,7 +144,8 @@ class UploaderFactory
126
144
  try {
127
145
  for(let fieldName in req.files){
128
146
  for(let file of req.files[fieldName]){
129
- if(!await this.validateFileContents(file, allowedFileTypes[fieldName])){
147
+ let validationResult = await this.validateFileContents(file, allowedFileTypes[fieldName]);
148
+ if(!validationResult){
130
149
  FileHandler.remove(file.path);
131
150
  return false;
132
151
  }
@@ -134,7 +153,7 @@ class UploaderFactory
134
153
  }
135
154
  return true;
136
155
  } catch(error){
137
- this.error = {message: 'Error processing uploaded files.', error};
156
+ this.setError('Error processing uploaded files.', {error});
138
157
  return false;
139
158
  }
140
159
  }
@@ -163,28 +182,28 @@ class UploaderFactory
163
182
  validateInputs(fields, buckets, allowedFileTypes)
164
183
  {
165
184
  if(!Array.isArray(fields)){
166
- this.error = {message: 'Fields must be an array'};
185
+ this.setError('Fields must be an array');
167
186
  return false;
168
187
  }
169
188
  if(!buckets || 'object' !== typeof buckets){
170
- this.error = {message: 'Buckets must be an object'};
189
+ this.setError('Buckets must be an object');
171
190
  return false;
172
191
  }
173
192
  if(!allowedFileTypes || 'object' !== typeof allowedFileTypes){
174
- this.error = {message: 'AllowedFileTypes must be an object'};
193
+ this.setError('AllowedFileTypes must be an object');
175
194
  return false;
176
195
  }
177
196
  for(let field of fields){
178
197
  if(!field.name || 'string' !== typeof field.name){
179
- this.error = {message: 'Field name is invalid'};
198
+ this.setError('Field name is invalid');
180
199
  return false;
181
200
  }
182
201
  if(!buckets[field.name]){
183
- this.error = {message: 'Missing bucket for field: ' + field.name};
202
+ this.setError('Missing bucket for field: ' + field.name);
184
203
  return false;
185
204
  }
186
205
  if(!allowedFileTypes[field.name]){
187
- this.error = {message: 'Missing allowedFileType for field: ' + field.name};
206
+ this.setError('Missing allowedFileType for field: ' + field.name);
188
207
  return false;
189
208
  }
190
209
  }
@@ -197,23 +216,33 @@ class UploaderFactory
197
216
  return cb(null, true);
198
217
  }
199
218
  if(!this.validateFilenameSecurity(file.originalname)){
200
- this.error = {message: 'Insecure filename: '+file.originalname};
219
+ this.setError('Insecure filename: '+file.originalname, {originalname: file.originalname});
201
220
  return cb(new Error('Insecure filename: '+file.originalname));
202
221
  }
203
222
  let fileExtension = FileHandler.extension(file.originalname).toLowerCase();
204
223
  let allowedExtensions = this.allowedExtensions && this.allowedExtensions[allowedFileType];
205
224
  if(allowedExtensions && !allowedExtensions.includes(fileExtension)){
206
- this.error = {message: 'Invalid file extension: '+fileExtension};
225
+ this.setError('Invalid file extension: '+fileExtension, {
226
+ extension: fileExtension,
227
+ allowedExtensions,
228
+ allowedFileType,
229
+ filename: file.originalname
230
+ });
207
231
  return cb(new Error('Invalid file extension: '+fileExtension));
208
232
  }
209
233
  let allowedFileTypeRegex = this.convertToRegex(allowedFileType);
210
234
  if(!allowedFileTypeRegex){
211
- this.error = {message: 'File type could not be converted to regex.', allowedFileType};
235
+ this.setError('File type could not be converted to regex.', {allowedFileType});
212
236
  return cb(new Error('File type could not be converted to regex'));
213
237
  }
214
238
  let mimeTypeValid = allowedFileTypeRegex.test(file.mimetype);
215
239
  if(!mimeTypeValid){
216
- this.error = {message: 'Invalid MIME type: '+file.mimetype};
240
+ this.setError('Invalid MIME type: '+file.mimetype, {
241
+ mimetype: file.mimetype,
242
+ allowedFileType,
243
+ filename: file.originalname,
244
+ regex: allowedFileTypeRegex
245
+ });
217
246
  return cb(new Error('Invalid MIME type: '+file.mimetype));
218
247
  }
219
248
  return cb(null, true);
@@ -222,21 +251,39 @@ class UploaderFactory
222
251
  async validateFileContents(file, allowedFileType)
223
252
  {
224
253
  if(!FileHandler.isFile(file.path)){
225
- this.error = {message: 'File path must be provided.', file};
254
+ this.setError('File path must be provided.', {file});
226
255
  return false;
227
256
  }
228
257
  let detectedType = FileHandler.detectFileType(file.path);
229
258
  if(detectedType && 'application/octet-stream' !== detectedType){
230
259
  let expectedMimeTypes = this.mimeTypes[allowedFileType] || [];
231
260
  if(0 < expectedMimeTypes.length && -1 === expectedMimeTypes.indexOf(detectedType)){
232
- this.error = {
233
- message: 'File content type mismatch.',
234
- detected: detectedType, expected: expectedMimeTypes
235
- };
261
+ this.setError('File content type mismatch.', {
262
+ detected: detectedType,
263
+ expected: expectedMimeTypes,
264
+ filename: file.filename,
265
+ path: file.path,
266
+ allowedFileType
267
+ });
236
268
  return false;
237
269
  }
238
270
  }
239
- return FileHandler.validateFileType(file.path, allowedFileType, this.allowedExtensions, this.maxFileSize);
271
+ let typeValidationResult = FileHandler.validateFileType(
272
+ file.path,
273
+ allowedFileType,
274
+ this.allowedExtensions,
275
+ this.maxFileSize
276
+ );
277
+ if(!typeValidationResult){
278
+ this.setError('File type validation failed.', {
279
+ fileHandlerError: FileHandler.error,
280
+ filename: file.filename,
281
+ path: file.path,
282
+ allowedFileType
283
+ });
284
+ return false;
285
+ }
286
+ return true;
240
287
  }
241
288
 
242
289
  convertToRegex(key)
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@reldens/server-utils",
3
3
  "scope": "@reldens",
4
- "version": "0.25.0",
4
+ "version": "0.26.0",
5
5
  "description": "Reldens - Server Utils",
6
6
  "author": "Damian A. Pastorini",
7
7
  "license": "MIT",