@reldens/server-utils 0.18.0 → 0.20.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/README.md +304 -10
- package/lib/app-server-factory/cors-configurer.js +80 -0
- package/lib/app-server-factory/development-mode-detector.js +75 -0
- package/lib/app-server-factory/protocol-enforcer.js +48 -0
- package/lib/app-server-factory/rate-limit-configurer.js +75 -0
- package/lib/app-server-factory/security-configurer.js +157 -0
- package/lib/app-server-factory.js +144 -104
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -1,22 +1,313 @@
|
|
|
1
1
|
# Reldens - Server Utils
|
|
2
2
|
|
|
3
|
-
A
|
|
4
|
-
|
|
3
|
+
A Node.js server toolkit providing secure application server creation, file handling, encryption, and file upload capabilities for production-ready applications.
|
|
5
4
|
|
|
6
5
|
[](https://github.com/damian-pastorini/reldens)
|
|
7
6
|
|
|
8
|
-
|
|
7
|
+
## Features
|
|
9
8
|
|
|
10
|
-
###
|
|
9
|
+
### AppServerFactory
|
|
10
|
+
- Complete Express.js server configuration with security defaults
|
|
11
|
+
- HTTPS/HTTP server creation with SSL certificate management
|
|
12
|
+
- SNI (Server Name Indication) support for multi-domain hosting
|
|
13
|
+
- Virtual host management with domain mapping
|
|
14
|
+
- Development mode detection with appropriate configurations
|
|
15
|
+
- CORS configuration with flexible origin management
|
|
16
|
+
- Rate limiting with customizable thresholds
|
|
17
|
+
- Security headers and XSS protection
|
|
18
|
+
- Helmet integration for enhanced security
|
|
19
|
+
- Protocol enforcement (HTTP to HTTPS redirection)
|
|
20
|
+
- Trusted proxy configuration
|
|
21
|
+
- Request parsing with size limits and validation
|
|
22
|
+
- Static file serving with security headers
|
|
23
|
+
- Input validation utilities
|
|
11
24
|
|
|
12
|
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
25
|
+
### FileHandler
|
|
26
|
+
- Secure file system operations with path validation
|
|
27
|
+
- File and folder creation, copying, and removal
|
|
28
|
+
- JSON file parsing and validation
|
|
29
|
+
- File type detection based on magic numbers
|
|
30
|
+
- Secure filename generation
|
|
31
|
+
- Path sanitization and traversal protection
|
|
32
|
+
- File permissions checking
|
|
33
|
+
- Folder content listing and filtering
|
|
34
|
+
- Temporary file creation
|
|
35
|
+
- File quarantine functionality for security threats
|
|
36
|
+
- Binary file head reading for type detection
|
|
37
|
+
- Comprehensive error handling with detailed context
|
|
16
38
|
|
|
17
|
-
|
|
39
|
+
### Encryptor
|
|
40
|
+
- Password hashing using PBKDF2 with configurable iterations
|
|
41
|
+
- Password validation against stored hashes
|
|
42
|
+
- AES-256-GCM data encryption and decryption
|
|
43
|
+
- Secure token generation with customizable length
|
|
44
|
+
- TOTP (Time-based One-Time Password) generation
|
|
45
|
+
- Data hashing with multiple algorithms (SHA-256, SHA-512, MD5)
|
|
46
|
+
- HMAC generation and verification
|
|
47
|
+
- Constant-time string comparison for security
|
|
48
|
+
- Cryptographically secure random value generation
|
|
18
49
|
|
|
19
|
-
|
|
50
|
+
### UploaderFactory
|
|
51
|
+
- Multer-based file upload handling with security validation
|
|
52
|
+
- Multiple file upload support with field mapping
|
|
53
|
+
- File type validation using MIME types and extensions
|
|
54
|
+
- Filename security validation and sanitization
|
|
55
|
+
- File size limits and upload count restrictions
|
|
56
|
+
- Secure filename generation option
|
|
57
|
+
- File content validation based on magic numbers
|
|
58
|
+
- Dangerous file extension filtering
|
|
59
|
+
- Automatic file cleanup on validation failure
|
|
60
|
+
- Custom error response handling
|
|
61
|
+
- Upload destination mapping per field
|
|
62
|
+
|
|
63
|
+
## Installation
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
npm install @reldens/server-utils
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Quick Start
|
|
70
|
+
|
|
71
|
+
### Basic Server Setup
|
|
72
|
+
|
|
73
|
+
```javascript
|
|
74
|
+
const { AppServerFactory } = require('@reldens/server-utils');
|
|
75
|
+
|
|
76
|
+
let appServerFactory = new AppServerFactory();
|
|
77
|
+
let serverResult = appServerFactory.createAppServer({
|
|
78
|
+
port: 3000,
|
|
79
|
+
useHttps: false,
|
|
80
|
+
autoListen: true
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
if(serverResult){
|
|
84
|
+
let { app, appServer } = serverResult;
|
|
85
|
+
console.log('Server running on port 3000');
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### File Operations
|
|
90
|
+
|
|
91
|
+
```javascript
|
|
92
|
+
const { FileHandler } = require('@reldens/server-utils');
|
|
93
|
+
|
|
94
|
+
// Read a JSON configuration file
|
|
95
|
+
let config = FileHandler.fetchFileJson('/path/to/config.json');
|
|
96
|
+
if(config){
|
|
97
|
+
console.log('Configuration loaded:', config);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Create a folder securely
|
|
101
|
+
if(FileHandler.createFolder('/path/to/new/folder')){
|
|
102
|
+
console.log('Folder created successfully');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Generate a secure filename
|
|
106
|
+
let secureFilename = FileHandler.generateSecureFilename('user-upload.jpg');
|
|
107
|
+
console.log('Secure filename:', secureFilename);
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Password Encryption
|
|
111
|
+
|
|
112
|
+
```javascript
|
|
113
|
+
const { Encryptor } = require('@reldens/server-utils');
|
|
114
|
+
|
|
115
|
+
// Hash a password
|
|
116
|
+
let hashedPassword = Encryptor.encryptPassword('userPassword123');
|
|
117
|
+
if(hashedPassword){
|
|
118
|
+
console.log('Password hashed:', hashedPassword);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Validate password
|
|
122
|
+
let isValid = Encryptor.validatePassword('userPassword123', hashedPassword);
|
|
123
|
+
console.log('Password valid:', isValid);
|
|
124
|
+
|
|
125
|
+
// Generate secure token
|
|
126
|
+
let secureToken = Encryptor.generateSecureToken(32);
|
|
127
|
+
console.log('Secure token:', secureToken);
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### File Upload Configuration
|
|
131
|
+
|
|
132
|
+
```javascript
|
|
133
|
+
const { UploaderFactory } = require('@reldens/server-utils');
|
|
134
|
+
|
|
135
|
+
let uploaderFactory = new UploaderFactory({
|
|
136
|
+
maxFileSize: 10 * 1024 * 1024, // 10MB
|
|
137
|
+
mimeTypes: {
|
|
138
|
+
image: ['image/jpeg', 'image/png', 'image/gif'],
|
|
139
|
+
document: ['application/pdf', 'text/plain']
|
|
140
|
+
},
|
|
141
|
+
allowedExtensions: {
|
|
142
|
+
image: ['.jpg', '.jpeg', '.png', '.gif'],
|
|
143
|
+
document: ['.pdf', '.txt']
|
|
144
|
+
},
|
|
145
|
+
applySecureFileNames: true
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
let uploader = uploaderFactory.createUploader(
|
|
149
|
+
[{ name: 'avatar' }, { name: 'document' }],
|
|
150
|
+
{ avatar: '/uploads/avatars', document: '/uploads/docs' },
|
|
151
|
+
{ avatar: 'image', document: 'document' }
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
// Use with Express
|
|
155
|
+
app.post('/upload', uploader, (req, res) => {
|
|
156
|
+
console.log('Files uploaded:', req.files);
|
|
157
|
+
res.json({ success: true });
|
|
158
|
+
});
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## Advanced Configuration
|
|
162
|
+
|
|
163
|
+
### HTTPS Server with Multiple Domains
|
|
164
|
+
|
|
165
|
+
```javascript
|
|
166
|
+
let appServerFactory = new AppServerFactory();
|
|
167
|
+
|
|
168
|
+
appServerFactory.addDomain({
|
|
169
|
+
hostname: 'example.com',
|
|
170
|
+
keyPath: '/ssl/example.com.key',
|
|
171
|
+
certPath: '/ssl/example.com.crt',
|
|
172
|
+
aliases: ['www.example.com']
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
appServerFactory.addDomain({
|
|
176
|
+
hostname: 'api.example.com',
|
|
177
|
+
keyPath: '/ssl/api.example.com.key',
|
|
178
|
+
certPath: '/ssl/api.example.com.crt'
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
let serverResult = appServerFactory.createAppServer({
|
|
182
|
+
useHttps: true,
|
|
183
|
+
useVirtualHosts: true,
|
|
184
|
+
keyPath: '/ssl/default.key',
|
|
185
|
+
certPath: '/ssl/default.crt',
|
|
186
|
+
port: 443
|
|
187
|
+
});
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### Development Mode Configuration
|
|
191
|
+
|
|
192
|
+
```javascript
|
|
193
|
+
let appServerFactory = new AppServerFactory();
|
|
194
|
+
|
|
195
|
+
// Add development domains
|
|
196
|
+
appServerFactory.addDevelopmentDomain('localhost');
|
|
197
|
+
appServerFactory.addDevelopmentDomain('dev.myapp.local');
|
|
198
|
+
|
|
199
|
+
let serverResult = appServerFactory.createAppServer({
|
|
200
|
+
port: 3000,
|
|
201
|
+
corsOrigin: ['http://localhost:3000', 'http://dev.myapp.local:3000'],
|
|
202
|
+
developmentMultiplier: 5, // More lenient rate limiting in dev
|
|
203
|
+
});
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### Custom Security Configuration
|
|
207
|
+
|
|
208
|
+
```javascript
|
|
209
|
+
let appServerFactory = new AppServerFactory();
|
|
210
|
+
|
|
211
|
+
let serverResult = appServerFactory.createAppServer({
|
|
212
|
+
useHelmet: true,
|
|
213
|
+
helmetConfig: {
|
|
214
|
+
contentSecurityPolicy: {
|
|
215
|
+
directives: {
|
|
216
|
+
defaultSrc: ["'self'"],
|
|
217
|
+
styleSrc: ["'self'", "'unsafe-inline'"],
|
|
218
|
+
scriptSrc: ["'self'"]
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
},
|
|
222
|
+
globalRateLimit: 100, // requests per window
|
|
223
|
+
windowMs: 60000, // 1 minute
|
|
224
|
+
maxRequests: 30,
|
|
225
|
+
trustedProxy: '127.0.0.1'
|
|
226
|
+
});
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
## API Reference
|
|
230
|
+
|
|
231
|
+
### AppServerFactory Methods
|
|
232
|
+
|
|
233
|
+
- `createAppServer(config)` - Creates and configures Express server
|
|
234
|
+
- `addDomain(domainConfig)` - Adds domain configuration for virtual hosting
|
|
235
|
+
- `addDevelopmentDomain(domain)` - Adds development domain pattern
|
|
236
|
+
- `setDomainMapping(mapping)` - Sets domain to configuration mapping
|
|
237
|
+
- `enableServeHome(app, callback)` - Enables homepage serving
|
|
238
|
+
- `serveStatics(app, staticPath)` - Serves static files
|
|
239
|
+
- `serveStaticsPath(app, route, staticPath)` - Serves static files on specific route
|
|
240
|
+
- `validateInput(input, type)` - Validates input against predefined patterns
|
|
241
|
+
- `enableCSP(cspOptions)` - Enables Content Security Policy
|
|
242
|
+
- `listen(port)` - Starts server listening
|
|
243
|
+
- `close()` - Gracefully closes server
|
|
244
|
+
|
|
245
|
+
### FileHandler Methods
|
|
246
|
+
|
|
247
|
+
- `exists(path)` - Checks if file or folder exists
|
|
248
|
+
- `createFolder(path)` - Creates folder with recursive option
|
|
249
|
+
- `remove(path)` - Removes file or folder recursively
|
|
250
|
+
- `copyFile(source, destination)` - Copies file to destination
|
|
251
|
+
- `copyFolderSync(source, destination)` - Copies folder recursively
|
|
252
|
+
- `readFile(path)` - Reads file contents as string
|
|
253
|
+
- `writeFile(path, content)` - Writes content to file
|
|
254
|
+
- `fetchFileJson(path)` - Reads and parses JSON file
|
|
255
|
+
- `fetchFileContents(path)` - Reads file with validation
|
|
256
|
+
- `updateFileContents(path, content)` - Updates existing file
|
|
257
|
+
- `isFile(path)` - Checks if path is file
|
|
258
|
+
- `isFolder(path)` - Checks if path is folder
|
|
259
|
+
- `getFilesInFolder(path, extensions)` - Lists files with optional filtering
|
|
260
|
+
- `validateFileType(path, type, allowedTypes, maxSize)` - Validates file type and size
|
|
261
|
+
- `detectFileType(path)` - Detects MIME type from file signature
|
|
262
|
+
- `generateSecureFilename(originalName)` - Generates cryptographically secure filename
|
|
263
|
+
- `quarantineFile(path, reason)` - Moves file to quarantine folder
|
|
264
|
+
- `createTempFile(prefix, extension)` - Creates temporary file path
|
|
265
|
+
|
|
266
|
+
### Encryptor Methods
|
|
267
|
+
|
|
268
|
+
- `encryptPassword(password)` - Hashes password with salt
|
|
269
|
+
- `validatePassword(password, hash)` - Validates password against hash
|
|
270
|
+
- `generateSecretKey()` - Generates 256-bit secret key
|
|
271
|
+
- `encryptData(data, key)` - Encrypts data with AES-256-GCM
|
|
272
|
+
- `decryptData(encryptedData, key)` - Decrypts AES-256-GCM data
|
|
273
|
+
- `generateSecureToken(length)` - Generates base64url token
|
|
274
|
+
- `generateTOTP(secret, timeStep)` - Generates time-based OTP
|
|
275
|
+
- `hashData(data, algorithm)` - Hashes data with specified algorithm
|
|
276
|
+
- `generateHMAC(data, secret, algorithm)` - Generates HMAC signature
|
|
277
|
+
- `verifyHMAC(data, secret, signature, algorithm)` - Verifies HMAC signature
|
|
278
|
+
- `constantTimeCompare(a, b)` - Performs constant-time string comparison
|
|
279
|
+
|
|
280
|
+
### UploaderFactory Methods
|
|
281
|
+
|
|
282
|
+
- `createUploader(fields, buckets, allowedTypes)` - Creates multer upload middleware
|
|
283
|
+
- `validateFilenameSecurity(filename)` - Validates filename for security
|
|
284
|
+
- `validateFile(file, allowedType, callback)` - Validates file during upload
|
|
285
|
+
- `validateFileContents(file, allowedType)` - Validates file content after upload
|
|
286
|
+
- `cleanupFiles(files)` - Removes uploaded files on error
|
|
287
|
+
|
|
288
|
+
## Security Features
|
|
289
|
+
|
|
290
|
+
### Path Traversal Protection
|
|
291
|
+
All file operations include comprehensive path validation to prevent directory traversal attacks and access to system files.
|
|
292
|
+
|
|
293
|
+
### Secure File Upload
|
|
294
|
+
File uploads are validated at multiple levels including filename, MIME type, file extension, file size, and content validation using magic number detection.
|
|
295
|
+
|
|
296
|
+
### Rate Limiting
|
|
297
|
+
Configurable rate limiting with development mode detection for appropriate thresholds in different environments.
|
|
298
|
+
|
|
299
|
+
### HTTPS Support
|
|
300
|
+
Full SSL/TLS support with SNI for multi-domain hosting and automatic certificate management.
|
|
301
|
+
|
|
302
|
+
### Input Validation
|
|
303
|
+
Built-in validators for common input types including email, username, strong passwords, alphanumeric strings, and IP addresses.
|
|
304
|
+
|
|
305
|
+
### Cryptographic Security
|
|
306
|
+
Industry-standard encryption using PBKDF2 for passwords, AES-256-GCM for data encryption, and secure random generation for tokens.
|
|
307
|
+
|
|
308
|
+
## Error Handling
|
|
309
|
+
|
|
310
|
+
All methods include comprehensive error handling with detailed error objects containing context information. Errors are logged appropriately and never expose sensitive system information.
|
|
20
311
|
|
|
21
312
|
---
|
|
22
313
|
|
|
@@ -24,6 +315,9 @@ Need something specific?
|
|
|
24
315
|
|
|
25
316
|
[https://www.reldens.com/documentation/utils/](https://www.reldens.com/documentation/utils/)
|
|
26
317
|
|
|
318
|
+
Need something specific?
|
|
319
|
+
|
|
320
|
+
[Request a feature here: https://www.reldens.com/features-request](https://www.reldens.com/features-request)
|
|
27
321
|
|
|
28
322
|
---
|
|
29
323
|
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* Reldens - CorsConfigurer
|
|
4
|
+
*
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const cors = require('cors');
|
|
8
|
+
|
|
9
|
+
class CorsConfigurer
|
|
10
|
+
{
|
|
11
|
+
|
|
12
|
+
constructor()
|
|
13
|
+
{
|
|
14
|
+
this.isDevelopmentMode = false;
|
|
15
|
+
this.useCors = true;
|
|
16
|
+
this.corsOrigin = '*';
|
|
17
|
+
this.corsMethods = ['GET','POST'];
|
|
18
|
+
this.corsHeaders = ['Content-Type','Authorization'];
|
|
19
|
+
this.developmentCorsOrigins = [];
|
|
20
|
+
this.developmentPorts = [3000, 8080, 8081];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
setup(app, config)
|
|
24
|
+
{
|
|
25
|
+
this.isDevelopmentMode = config.isDevelopmentMode || false;
|
|
26
|
+
this.useCors = config.useCors !== false;
|
|
27
|
+
this.corsOrigin = config.corsOrigin || this.corsOrigin;
|
|
28
|
+
this.corsMethods = config.corsMethods || this.corsMethods;
|
|
29
|
+
this.corsHeaders = config.corsHeaders || this.corsHeaders;
|
|
30
|
+
this.developmentPorts = config.developmentPorts || this.developmentPorts;
|
|
31
|
+
if(!this.useCors){
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
if(this.isDevelopmentMode && config.domainMapping){
|
|
35
|
+
this.developmentCorsOrigins = this.extractDevelopmentOrigins(config.domainMapping);
|
|
36
|
+
}
|
|
37
|
+
let corsOptions = {
|
|
38
|
+
origin: this.corsOrigin,
|
|
39
|
+
methods: this.corsMethods,
|
|
40
|
+
allowedHeaders: this.corsHeaders,
|
|
41
|
+
credentials: true
|
|
42
|
+
};
|
|
43
|
+
if(this.isDevelopmentMode && 0 < this.developmentCorsOrigins.length){
|
|
44
|
+
corsOptions.origin = (origin, callback) => {
|
|
45
|
+
if(!origin){
|
|
46
|
+
return callback(null, true);
|
|
47
|
+
}
|
|
48
|
+
if(-1 !== this.developmentCorsOrigins.indexOf(origin)){
|
|
49
|
+
return callback(null, true);
|
|
50
|
+
}
|
|
51
|
+
if('*' === this.corsOrigin){
|
|
52
|
+
return callback(null, true);
|
|
53
|
+
}
|
|
54
|
+
return callback(null, false);
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
app.use(cors(corsOptions));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
extractDevelopmentOrigins(domainMapping)
|
|
61
|
+
{
|
|
62
|
+
let origins = [];
|
|
63
|
+
let mappingKeys = Object.keys(domainMapping);
|
|
64
|
+
for(let domain of mappingKeys){
|
|
65
|
+
origins.push('http://'+domain);
|
|
66
|
+
origins.push('https://'+domain);
|
|
67
|
+
if(domain.includes(':')){
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
for(let port of this.developmentPorts){
|
|
71
|
+
origins.push('http://'+domain+':'+port);
|
|
72
|
+
origins.push('https://'+domain+':'+port);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return origins;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
module.exports.CorsConfigurer = CorsConfigurer;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* Reldens - DevelopmentModeDetector
|
|
4
|
+
*
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
class DevelopmentModeDetector
|
|
8
|
+
{
|
|
9
|
+
|
|
10
|
+
constructor()
|
|
11
|
+
{
|
|
12
|
+
this.developmentPatterns = [
|
|
13
|
+
'localhost',
|
|
14
|
+
'127.0.0.1',
|
|
15
|
+
// domain ends:
|
|
16
|
+
'.local',
|
|
17
|
+
'.test',
|
|
18
|
+
'.dev',
|
|
19
|
+
'.acc',
|
|
20
|
+
'.staging',
|
|
21
|
+
// sub-domains:
|
|
22
|
+
'local.',
|
|
23
|
+
'test.',
|
|
24
|
+
'dev.',
|
|
25
|
+
'acc.',
|
|
26
|
+
'staging.'
|
|
27
|
+
];
|
|
28
|
+
this.developmentEnvironments = ['development', 'dev', 'test'];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
detect(config = {})
|
|
32
|
+
{
|
|
33
|
+
if(config.developmentPatterns){
|
|
34
|
+
this.developmentPatterns = config.developmentPatterns;
|
|
35
|
+
}
|
|
36
|
+
if(config.developmentEnvironments){
|
|
37
|
+
this.developmentEnvironments = config.developmentEnvironments;
|
|
38
|
+
}
|
|
39
|
+
let env = process.env.NODE_ENV;
|
|
40
|
+
if(this.developmentEnvironments.includes(env)){
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
if(config.developmentDomains && 0 < config.developmentDomains.length){
|
|
44
|
+
for(let domain of config.developmentDomains){
|
|
45
|
+
if(this.matchesPattern(domain)){
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if(config.domains && 0 < config.domains.length){
|
|
51
|
+
for(let domainConfig of config.domains){
|
|
52
|
+
if(!domainConfig.hostname){
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
if(this.matchesPattern(domainConfig.hostname)){
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
matchesPattern(domain)
|
|
64
|
+
{
|
|
65
|
+
for(let pattern of this.developmentPatterns){
|
|
66
|
+
if(domain.includes(pattern)){
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
module.exports.DevelopmentModeDetector = DevelopmentModeDetector;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* Reldens - ProtocolEnforcer
|
|
4
|
+
*
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
class ProtocolEnforcer
|
|
8
|
+
{
|
|
9
|
+
|
|
10
|
+
constructor()
|
|
11
|
+
{
|
|
12
|
+
this.isDevelopmentMode = false;
|
|
13
|
+
this.useHttps = false;
|
|
14
|
+
this.enforceProtocol = true;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
setup(app, config)
|
|
18
|
+
{
|
|
19
|
+
this.isDevelopmentMode = config.isDevelopmentMode || false;
|
|
20
|
+
this.useHttps = config.useHttps || false;
|
|
21
|
+
this.enforceProtocol = config.enforceProtocol !== false;
|
|
22
|
+
app.use((req, res, next) => {
|
|
23
|
+
let protocol = req.get('X-Forwarded-Proto') || req.protocol;
|
|
24
|
+
let host = req.get('host');
|
|
25
|
+
if(this.isDevelopmentMode){
|
|
26
|
+
res.removeHeader('Origin-Agent-Cluster');
|
|
27
|
+
res.removeHeader('Strict-Transport-Security');
|
|
28
|
+
res.removeHeader('upgrade-insecure-requests');
|
|
29
|
+
res.set('Origin-Agent-Cluster', '?0');
|
|
30
|
+
if(this.enforceProtocol && host){
|
|
31
|
+
if(!this.useHttps && 'https' === protocol){
|
|
32
|
+
let redirectUrl = 'http://'+host+req.url;
|
|
33
|
+
return res.redirect(301, redirectUrl);
|
|
34
|
+
}
|
|
35
|
+
if(this.useHttps && 'http' === protocol){
|
|
36
|
+
let redirectUrl = 'https://'+host+req.url;
|
|
37
|
+
return res.redirect(301, redirectUrl);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
res.set('X-Forwarded-Proto', protocol);
|
|
42
|
+
next();
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
module.exports.ProtocolEnforcer = ProtocolEnforcer;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* Reldens - RateLimitConfigurer
|
|
4
|
+
*
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const rateLimit = require('express-rate-limit');
|
|
8
|
+
|
|
9
|
+
class RateLimitConfigurer
|
|
10
|
+
{
|
|
11
|
+
|
|
12
|
+
constructor()
|
|
13
|
+
{
|
|
14
|
+
this.isDevelopmentMode = false;
|
|
15
|
+
this.globalRateLimit = 0;
|
|
16
|
+
this.windowMs = 60000;
|
|
17
|
+
this.maxRequests = 30;
|
|
18
|
+
this.developmentMultiplier = 10;
|
|
19
|
+
this.applyKeyGenerator = false;
|
|
20
|
+
this.tooManyRequestsMessage = 'Too many requests, please try again later.';
|
|
21
|
+
this.rateLimit = rateLimit;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
setup(app, config)
|
|
25
|
+
{
|
|
26
|
+
this.isDevelopmentMode = config.isDevelopmentMode || false;
|
|
27
|
+
this.globalRateLimit = config.globalRateLimit || 0;
|
|
28
|
+
this.windowMs = config.windowMs || this.windowMs;
|
|
29
|
+
this.maxRequests = config.maxRequests || this.maxRequests;
|
|
30
|
+
this.developmentMultiplier = config.developmentMultiplier || this.developmentMultiplier;
|
|
31
|
+
this.applyKeyGenerator = config.applyKeyGenerator || false;
|
|
32
|
+
this.tooManyRequestsMessage = config.tooManyRequestsMessage || this.tooManyRequestsMessage;
|
|
33
|
+
if(!this.globalRateLimit){
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
let limiterParams = {
|
|
37
|
+
windowMs: this.windowMs,
|
|
38
|
+
max: this.maxRequests,
|
|
39
|
+
standardHeaders: true,
|
|
40
|
+
legacyHeaders: false,
|
|
41
|
+
message: this.tooManyRequestsMessage
|
|
42
|
+
};
|
|
43
|
+
if(this.isDevelopmentMode){
|
|
44
|
+
limiterParams.max = this.maxRequests * this.developmentMultiplier;
|
|
45
|
+
}
|
|
46
|
+
if(this.applyKeyGenerator){
|
|
47
|
+
limiterParams.keyGenerator = function(req){
|
|
48
|
+
return req.ip;
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
app.use(this.rateLimit(limiterParams));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
createHomeLimiter()
|
|
55
|
+
{
|
|
56
|
+
let limiterParams = {
|
|
57
|
+
windowMs: this.windowMs,
|
|
58
|
+
max: this.maxRequests,
|
|
59
|
+
standardHeaders: true,
|
|
60
|
+
legacyHeaders: false
|
|
61
|
+
};
|
|
62
|
+
if(this.isDevelopmentMode){
|
|
63
|
+
limiterParams.max = this.maxRequests * this.developmentMultiplier;
|
|
64
|
+
}
|
|
65
|
+
if(this.applyKeyGenerator){
|
|
66
|
+
limiterParams.keyGenerator = function(req){
|
|
67
|
+
return req.ip;
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
return this.rateLimit(limiterParams);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
module.exports.RateLimitConfigurer = RateLimitConfigurer;
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* Reldens - SecurityConfigurer
|
|
4
|
+
*
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const helmet = require('helmet');
|
|
8
|
+
const sanitizeHtml = require('sanitize-html');
|
|
9
|
+
|
|
10
|
+
class SecurityConfigurer
|
|
11
|
+
{
|
|
12
|
+
|
|
13
|
+
constructor()
|
|
14
|
+
{
|
|
15
|
+
this.isDevelopmentMode = false;
|
|
16
|
+
this.useHelmet = true;
|
|
17
|
+
this.useXssProtection = true;
|
|
18
|
+
this.helmetConfig = false;
|
|
19
|
+
this.sanitizeOptions = {allowedTags: [], allowedAttributes: {}};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
setupHelmet(app, config)
|
|
23
|
+
{
|
|
24
|
+
this.isDevelopmentMode = config.isDevelopmentMode || false;
|
|
25
|
+
this.useHelmet = config.useHelmet !== false;
|
|
26
|
+
this.helmetConfig = config.helmetConfig || false;
|
|
27
|
+
if(!this.useHelmet){
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
let helmetOptions = {
|
|
31
|
+
crossOriginEmbedderPolicy: false,
|
|
32
|
+
crossOriginOpenerPolicy: false,
|
|
33
|
+
crossOriginResourcePolicy: false,
|
|
34
|
+
originAgentCluster: false
|
|
35
|
+
};
|
|
36
|
+
if(this.isDevelopmentMode){
|
|
37
|
+
helmetOptions.contentSecurityPolicy = false;
|
|
38
|
+
helmetOptions.hsts = false;
|
|
39
|
+
helmetOptions.noSniff = false;
|
|
40
|
+
} else {
|
|
41
|
+
helmetOptions.contentSecurityPolicy = {
|
|
42
|
+
directives: {
|
|
43
|
+
defaultSrc: ["'self'"],
|
|
44
|
+
scriptSrc: ["'self'"],
|
|
45
|
+
scriptSrcElem: ["'self'"],
|
|
46
|
+
styleSrc: ["'self'", "'unsafe-inline'"],
|
|
47
|
+
styleSrcElem: ["'self'", "'unsafe-inline'"],
|
|
48
|
+
imgSrc: ["'self'", "data:", "https:"],
|
|
49
|
+
fontSrc: ["'self'"],
|
|
50
|
+
connectSrc: ["'self'"],
|
|
51
|
+
frameAncestors: ["'none'"],
|
|
52
|
+
baseUri: ["'self'"],
|
|
53
|
+
formAction: ["'self'"]
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
if(config.developmentExternalDomains){
|
|
57
|
+
this.addExternalDomainsToCsp(helmetOptions.contentSecurityPolicy.directives, config.developmentExternalDomains);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if(this.helmetConfig){
|
|
61
|
+
Object.assign(helmetOptions, this.helmetConfig);
|
|
62
|
+
}
|
|
63
|
+
app.use(helmet(helmetOptions));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
addExternalDomainsToCsp(directives, externalDomains)
|
|
67
|
+
{
|
|
68
|
+
let keys = Object.keys(externalDomains);
|
|
69
|
+
for(let directiveKey of keys){
|
|
70
|
+
let domains = externalDomains[directiveKey];
|
|
71
|
+
if(!Array.isArray(domains)){
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
for(let domain of domains){
|
|
75
|
+
if(directives[directiveKey]){
|
|
76
|
+
directives[directiveKey].push(domain);
|
|
77
|
+
}
|
|
78
|
+
let elemKey = directiveKey.replace('-src', '-src-elem');
|
|
79
|
+
if(directives[elemKey]){
|
|
80
|
+
directives[elemKey].push(domain);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
setupXssProtection(app, config)
|
|
87
|
+
{
|
|
88
|
+
this.useXssProtection = config.useXssProtection !== false;
|
|
89
|
+
this.sanitizeOptions = config.sanitizeOptions || this.sanitizeOptions;
|
|
90
|
+
if(!this.useXssProtection){
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
app.use((req, res, next) => {
|
|
94
|
+
if(!req.body){
|
|
95
|
+
return next();
|
|
96
|
+
}
|
|
97
|
+
if('object' === typeof req.body){
|
|
98
|
+
this.sanitizeRequestBody(req.body);
|
|
99
|
+
}
|
|
100
|
+
next();
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
sanitizeRequestBody(body)
|
|
105
|
+
{
|
|
106
|
+
let bodyKeys = Object.keys(body);
|
|
107
|
+
for(let i = 0; i < bodyKeys.length; i++){
|
|
108
|
+
let key = bodyKeys[i];
|
|
109
|
+
if('string' === typeof body[key]){
|
|
110
|
+
body[key] = sanitizeHtml(body[key], this.sanitizeOptions);
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
if('object' === typeof body[key] && null !== body[key]){
|
|
114
|
+
this.sanitizeRequestBody(body[key]);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
enableCSP(app, cspOptions)
|
|
120
|
+
{
|
|
121
|
+
let defaults = {
|
|
122
|
+
'default-src': ["'self'"],
|
|
123
|
+
'script-src': ["'self'"],
|
|
124
|
+
'style-src': ["'self'", "'unsafe-inline'"],
|
|
125
|
+
'img-src': ["'self'", "data:", "https:"],
|
|
126
|
+
'font-src': ["'self'"],
|
|
127
|
+
'connect-src': ["'self'"],
|
|
128
|
+
'frame-ancestors': ["'none'"],
|
|
129
|
+
'base-uri': ["'self'"],
|
|
130
|
+
'form-action': ["'self'"]
|
|
131
|
+
};
|
|
132
|
+
if(this.isDevelopmentMode){
|
|
133
|
+
defaults['script-src'].push("'unsafe-eval'");
|
|
134
|
+
defaults['connect-src'].push("ws:");
|
|
135
|
+
defaults['connect-src'].push("wss:");
|
|
136
|
+
}
|
|
137
|
+
let csp = Object.assign({}, defaults, cspOptions);
|
|
138
|
+
let policyString = '';
|
|
139
|
+
let keys = Object.keys(csp);
|
|
140
|
+
for(let i = 0; i < keys.length; i++){
|
|
141
|
+
let directive = keys[i];
|
|
142
|
+
let sources = csp[directive];
|
|
143
|
+
if(0 < i){
|
|
144
|
+
policyString += '; ';
|
|
145
|
+
}
|
|
146
|
+
policyString += directive + ' ' + sources.join(' ');
|
|
147
|
+
}
|
|
148
|
+
app.use((req, res, next) => {
|
|
149
|
+
res.setHeader('Content-Security-Policy', policyString);
|
|
150
|
+
next();
|
|
151
|
+
});
|
|
152
|
+
return true;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
module.exports.SecurityConfigurer = SecurityConfigurer;
|
|
@@ -5,15 +5,16 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
const { FileHandler } = require('./file-handler');
|
|
8
|
+
const { DevelopmentModeDetector } = require('./app-server-factory/development-mode-detector');
|
|
9
|
+
const { ProtocolEnforcer } = require('./app-server-factory/protocol-enforcer');
|
|
10
|
+
const { SecurityConfigurer } = require('./app-server-factory/security-configurer');
|
|
11
|
+
const { CorsConfigurer } = require('./app-server-factory/cors-configurer');
|
|
12
|
+
const { RateLimitConfigurer } = require('./app-server-factory/rate-limit-configurer');
|
|
8
13
|
const http = require('http');
|
|
9
14
|
const https = require('https');
|
|
10
15
|
const express = require('express');
|
|
11
16
|
const bodyParser = require('body-parser');
|
|
12
17
|
const session = require('express-session');
|
|
13
|
-
const rateLimit = require('express-rate-limit');
|
|
14
|
-
const cors = require('cors');
|
|
15
|
-
const helmet = require('helmet');
|
|
16
|
-
const sanitizeHtml = require('sanitize-html');
|
|
17
18
|
|
|
18
19
|
class AppServerFactory
|
|
19
20
|
{
|
|
@@ -25,7 +26,6 @@ class AppServerFactory
|
|
|
25
26
|
this.session = session;
|
|
26
27
|
this.appServer = false;
|
|
27
28
|
this.app = express();
|
|
28
|
-
this.rateLimit = rateLimit;
|
|
29
29
|
this.useCors = true;
|
|
30
30
|
this.useExpressJson = true;
|
|
31
31
|
this.useUrlencoded = true;
|
|
@@ -68,6 +68,26 @@ class AppServerFactory
|
|
|
68
68
|
res.set('X-Frame-Options', 'DENY');
|
|
69
69
|
}
|
|
70
70
|
};
|
|
71
|
+
this.isDevelopmentMode = false;
|
|
72
|
+
this.developmentDomains = [];
|
|
73
|
+
this.domainMapping = {};
|
|
74
|
+
this.enforceProtocol = true;
|
|
75
|
+
this.developmentPatterns = [
|
|
76
|
+
'localhost',
|
|
77
|
+
'127.0.0.1',
|
|
78
|
+
'.local',
|
|
79
|
+
'.test',
|
|
80
|
+
'.dev',
|
|
81
|
+
'.staging'
|
|
82
|
+
];
|
|
83
|
+
this.developmentEnvironments = ['development', 'dev', 'test'];
|
|
84
|
+
this.developmentPorts = [3000, 8080, 8081];
|
|
85
|
+
this.developmentMultiplier = 10;
|
|
86
|
+
this.developmentModeDetector = new DevelopmentModeDetector();
|
|
87
|
+
this.protocolEnforcer = new ProtocolEnforcer();
|
|
88
|
+
this.securityConfigurer = new SecurityConfigurer();
|
|
89
|
+
this.corsConfigurer = new CorsConfigurer();
|
|
90
|
+
this.rateLimitConfigurer = new RateLimitConfigurer();
|
|
71
91
|
}
|
|
72
92
|
|
|
73
93
|
createAppServer(appServerConfig)
|
|
@@ -75,45 +95,104 @@ class AppServerFactory
|
|
|
75
95
|
if(appServerConfig){
|
|
76
96
|
Object.assign(this, appServerConfig);
|
|
77
97
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
98
|
+
this.detectDevelopmentMode();
|
|
99
|
+
this.setupDevelopmentConfiguration();
|
|
100
|
+
this.setupProtocolEnforcement();
|
|
101
|
+
this.setupSecurity();
|
|
102
|
+
this.setupVirtualHosts();
|
|
103
|
+
this.setupCors();
|
|
104
|
+
this.setupRateLimiting();
|
|
105
|
+
this.setupRequestParsing();
|
|
106
|
+
this.setupTrustedProxy();
|
|
107
|
+
this.appServer = this.createServer();
|
|
108
|
+
if(!this.appServer){
|
|
109
|
+
this.error = {message: 'Failed to create app server'};
|
|
110
|
+
return false;
|
|
83
111
|
}
|
|
84
|
-
if(this.
|
|
85
|
-
|
|
86
|
-
origin: this.corsOrigin,
|
|
87
|
-
methods: this.corsMethods,
|
|
88
|
-
allowedHeaders: this.corsHeaders
|
|
89
|
-
};
|
|
90
|
-
this.app.use(cors(corsOptions));
|
|
112
|
+
if(this.autoListen){
|
|
113
|
+
this.listen();
|
|
91
114
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
115
|
+
return {app: this.app, appServer: this.appServer};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
detectDevelopmentMode()
|
|
119
|
+
{
|
|
120
|
+
this.isDevelopmentMode = this.developmentModeDetector.detect({
|
|
121
|
+
developmentPatterns: this.developmentPatterns,
|
|
122
|
+
developmentEnvironments: this.developmentEnvironments,
|
|
123
|
+
developmentDomains: this.developmentDomains,
|
|
124
|
+
domains: this.domains
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
setupDevelopmentConfiguration()
|
|
129
|
+
{
|
|
130
|
+
if(!this.isDevelopmentMode){
|
|
131
|
+
return;
|
|
106
132
|
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
133
|
+
this.staticOptions.setHeaders = (res, path) => {
|
|
134
|
+
res.set('X-Content-Type-Options', 'nosniff');
|
|
135
|
+
res.set('X-Frame-Options', 'SAMEORIGIN');
|
|
136
|
+
res.set('Cache-Control', 'no-cache, no-store, must-revalidate');
|
|
137
|
+
res.set('Pragma', 'no-cache');
|
|
138
|
+
res.set('Expires', '0');
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
setupProtocolEnforcement()
|
|
143
|
+
{
|
|
144
|
+
this.protocolEnforcer.setup(this.app, {
|
|
145
|
+
isDevelopmentMode: this.isDevelopmentMode,
|
|
146
|
+
useHttps: this.useHttps,
|
|
147
|
+
enforceProtocol: this.enforceProtocol
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
setupSecurity()
|
|
152
|
+
{
|
|
153
|
+
this.securityConfigurer.setupHelmet(this.app, {
|
|
154
|
+
isDevelopmentMode: this.isDevelopmentMode,
|
|
155
|
+
useHelmet: this.useHelmet,
|
|
156
|
+
helmetConfig: this.helmetConfig,
|
|
157
|
+
developmentExternalDomains: this.developmentExternalDomains
|
|
158
|
+
});
|
|
159
|
+
this.securityConfigurer.setupXssProtection(this.app, {
|
|
160
|
+
useXssProtection: this.useXssProtection,
|
|
161
|
+
sanitizeOptions: this.sanitizeOptions
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
setupCors()
|
|
166
|
+
{
|
|
167
|
+
this.corsConfigurer.setup(this.app, {
|
|
168
|
+
isDevelopmentMode: this.isDevelopmentMode,
|
|
169
|
+
useCors: this.useCors,
|
|
170
|
+
corsOrigin: this.corsOrigin,
|
|
171
|
+
corsMethods: this.corsMethods,
|
|
172
|
+
corsHeaders: this.corsHeaders,
|
|
173
|
+
domainMapping: this.domainMapping,
|
|
174
|
+
developmentPorts: this.developmentPorts
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
setupRateLimiting()
|
|
179
|
+
{
|
|
180
|
+
this.rateLimitConfigurer.setup(this.app, {
|
|
181
|
+
isDevelopmentMode: this.isDevelopmentMode,
|
|
182
|
+
globalRateLimit: this.globalRateLimit,
|
|
183
|
+
windowMs: this.windowMs,
|
|
184
|
+
maxRequests: this.maxRequests,
|
|
185
|
+
developmentMultiplier: this.developmentMultiplier,
|
|
186
|
+
applyKeyGenerator: this.applyKeyGenerator,
|
|
187
|
+
tooManyRequestsMessage: this.tooManyRequestsMessage
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
setupRequestParsing()
|
|
192
|
+
{
|
|
193
|
+
if(this.maxRequestSize){
|
|
194
|
+
this.jsonLimit = this.maxRequestSize;
|
|
195
|
+
this.urlencodedLimit = this.maxRequestSize;
|
|
117
196
|
}
|
|
118
197
|
if(this.useExpressJson){
|
|
119
198
|
this.app.use(this.applicationFramework.json({
|
|
@@ -127,32 +206,12 @@ class AppServerFactory
|
|
|
127
206
|
limit: this.urlencodedLimit
|
|
128
207
|
}));
|
|
129
208
|
}
|
|
130
|
-
if('' !== this.trustedProxy){
|
|
131
|
-
this.app.enable('trust proxy', this.trustedProxy);
|
|
132
|
-
}
|
|
133
|
-
this.appServer = this.createServer();
|
|
134
|
-
if(!this.appServer){
|
|
135
|
-
this.error = {message: 'Failed to create app server'};
|
|
136
|
-
return false;
|
|
137
|
-
}
|
|
138
|
-
if(this.autoListen){
|
|
139
|
-
this.listen();
|
|
140
|
-
}
|
|
141
|
-
return {app: this.app, appServer: this.appServer};
|
|
142
209
|
}
|
|
143
210
|
|
|
144
|
-
|
|
211
|
+
setupTrustedProxy()
|
|
145
212
|
{
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
let key = bodyKeys[i];
|
|
149
|
-
if('string' === typeof body[key]){
|
|
150
|
-
body[key] = sanitizeHtml(body[key], this.sanitizeOptions);
|
|
151
|
-
continue;
|
|
152
|
-
}
|
|
153
|
-
if('object' === typeof body[key] && null !== body[key]){
|
|
154
|
-
this.sanitizeRequestBody(body[key]);
|
|
155
|
-
}
|
|
213
|
+
if('' !== this.trustedProxy){
|
|
214
|
+
this.app.enable('trust proxy', this.trustedProxy);
|
|
156
215
|
}
|
|
157
216
|
}
|
|
158
217
|
|
|
@@ -172,7 +231,7 @@ class AppServerFactory
|
|
|
172
231
|
|
|
173
232
|
setupVirtualHosts()
|
|
174
233
|
{
|
|
175
|
-
if(0 === this.domains.length){
|
|
234
|
+
if(!this.useVirtualHosts || 0 === this.domains.length){
|
|
176
235
|
return;
|
|
177
236
|
}
|
|
178
237
|
this.app.use((req, res, next) => {
|
|
@@ -306,18 +365,7 @@ class AppServerFactory
|
|
|
306
365
|
|
|
307
366
|
async enableServeHome(app, homePageLoadCallback)
|
|
308
367
|
{
|
|
309
|
-
let
|
|
310
|
-
windowMs: this.windowMs,
|
|
311
|
-
max: this.maxRequests,
|
|
312
|
-
standardHeaders: true,
|
|
313
|
-
legacyHeaders: false
|
|
314
|
-
};
|
|
315
|
-
if(this.applyKeyGenerator){
|
|
316
|
-
limiterParams.keyGenerator = function(req){
|
|
317
|
-
return req.ip;
|
|
318
|
-
};
|
|
319
|
-
}
|
|
320
|
-
let limiter = this.rateLimit(limiterParams);
|
|
368
|
+
let limiter = this.rateLimitConfigurer.createHomeLimiter();
|
|
321
369
|
app.post('/', limiter);
|
|
322
370
|
app.post('/', async (req, res, next) => {
|
|
323
371
|
if('/' === req._parsedUrl.pathname){
|
|
@@ -376,6 +424,24 @@ class AppServerFactory
|
|
|
376
424
|
return true;
|
|
377
425
|
}
|
|
378
426
|
|
|
427
|
+
addDevelopmentDomain(domain)
|
|
428
|
+
{
|
|
429
|
+
if(!domain || 'string' !== typeof domain){
|
|
430
|
+
return false;
|
|
431
|
+
}
|
|
432
|
+
this.developmentDomains.push(domain);
|
|
433
|
+
return true;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
setDomainMapping(mapping)
|
|
437
|
+
{
|
|
438
|
+
if(!mapping || 'object' !== typeof mapping){
|
|
439
|
+
return false;
|
|
440
|
+
}
|
|
441
|
+
this.domainMapping = mapping;
|
|
442
|
+
return true;
|
|
443
|
+
}
|
|
444
|
+
|
|
379
445
|
async close()
|
|
380
446
|
{
|
|
381
447
|
if(!this.appServer){
|
|
@@ -386,33 +452,7 @@ class AppServerFactory
|
|
|
386
452
|
|
|
387
453
|
enableCSP(cspOptions)
|
|
388
454
|
{
|
|
389
|
-
|
|
390
|
-
'default-src': ["'self'"],
|
|
391
|
-
'script-src': ["'self'"],
|
|
392
|
-
'style-src': ["'self'", "'unsafe-inline'"],
|
|
393
|
-
'img-src': ["'self'", "data:", "https:"],
|
|
394
|
-
'font-src': ["'self'"],
|
|
395
|
-
'connect-src': ["'self'"],
|
|
396
|
-
'frame-ancestors': ["'none'"],
|
|
397
|
-
'base-uri': ["'self'"],
|
|
398
|
-
'form-action': ["'self'"]
|
|
399
|
-
};
|
|
400
|
-
let csp = Object.assign({}, defaults, cspOptions);
|
|
401
|
-
let policyString = '';
|
|
402
|
-
let keys = Object.keys(csp);
|
|
403
|
-
for(let i = 0; i < keys.length; i++){
|
|
404
|
-
let directive = keys[i];
|
|
405
|
-
let sources = csp[directive];
|
|
406
|
-
if(0 < i){
|
|
407
|
-
policyString += '; ';
|
|
408
|
-
}
|
|
409
|
-
policyString += directive + ' ' + sources.join(' ');
|
|
410
|
-
}
|
|
411
|
-
this.app.use((req, res, next) => {
|
|
412
|
-
res.setHeader('Content-Security-Policy', policyString);
|
|
413
|
-
next();
|
|
414
|
-
});
|
|
415
|
-
return true;
|
|
455
|
+
return this.securityConfigurer.enableCSP(this.app, cspOptions);
|
|
416
456
|
}
|
|
417
457
|
|
|
418
458
|
validateInput(input, type)
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reldens/server-utils",
|
|
3
3
|
"scope": "@reldens",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.20.0",
|
|
5
5
|
"description": "Reldens - Server Utils",
|
|
6
6
|
"author": "Damian A. Pastorini",
|
|
7
7
|
"license": "MIT",
|
|
@@ -38,10 +38,10 @@
|
|
|
38
38
|
"body-parser": "2.2.0",
|
|
39
39
|
"cors": "2.8.5",
|
|
40
40
|
"express": "4.21.2",
|
|
41
|
-
"express-rate-limit": "7.5.
|
|
41
|
+
"express-rate-limit": "7.5.1",
|
|
42
42
|
"express-session": "1.18.1",
|
|
43
43
|
"helmet": "8.1.0",
|
|
44
|
-
"multer": "2.0.
|
|
45
|
-
"sanitize-html": "
|
|
44
|
+
"multer": "2.0.1",
|
|
45
|
+
"sanitize-html": "2.17.0"
|
|
46
46
|
}
|
|
47
47
|
}
|