@reldens/server-utils 0.37.0 → 0.39.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 +234 -4
- package/index.js +9 -1
- package/lib/app-server-factory/reverse-proxy-configurer.js +132 -0
- package/lib/app-server-factory.js +88 -39
- package/lib/http2-cdn-server.js +161 -0
- package/lib/server-default-configurations.js +57 -0
- package/lib/server-factory-utils.js +56 -0
- package/lib/server-headers.js +40 -0
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Reldens - Server Utils
|
|
2
2
|
|
|
3
|
-
A Node.js server toolkit providing secure application server creation, file handling, encryption, and file upload capabilities for production-ready applications with modular security configurations.
|
|
3
|
+
A Node.js server toolkit providing secure application server creation, HTTP/2 CDN support, file handling, encryption, and file upload capabilities for production-ready applications with modular security configurations.
|
|
4
4
|
|
|
5
5
|
[](https://github.com/damian-pastorini/reldens)
|
|
6
6
|
|
|
@@ -9,12 +9,14 @@ A Node.js server toolkit providing secure application server creation, file hand
|
|
|
9
9
|
### AppServerFactory
|
|
10
10
|
- Complete Express.js server configuration with modular security
|
|
11
11
|
- HTTPS/HTTP server creation with SSL certificate management
|
|
12
|
+
- HTTP/2 CDN server with optimized static asset delivery
|
|
12
13
|
- Optimized static asset caching for CSS, JS, fonts, and images
|
|
13
14
|
- SNI (Server Name Indication) support for multi-domain hosting
|
|
14
15
|
- Virtual host management with domain mapping
|
|
15
16
|
- Development mode detection with appropriate configurations
|
|
16
17
|
- CORS configuration with flexible origin management
|
|
17
18
|
- Rate limiting with customizable thresholds
|
|
19
|
+
- Reverse proxy support for routing multiple domains to backend servers
|
|
18
20
|
- Security headers and XSS protection
|
|
19
21
|
- Helmet integration for enhanced security
|
|
20
22
|
- Protocol enforcement (HTTP to HTTPS redirection)
|
|
@@ -24,6 +26,18 @@ A Node.js server toolkit providing secure application server creation, file hand
|
|
|
24
26
|
- Compression middleware with smart filtering
|
|
25
27
|
- Input validation utilities
|
|
26
28
|
|
|
29
|
+
### Http2CdnServer
|
|
30
|
+
- Dedicated HTTP/2 server for static asset delivery
|
|
31
|
+
- Optimized for serving CSS, JavaScript, images, and fonts
|
|
32
|
+
- Dynamic CORS origin validation with regex pattern support
|
|
33
|
+
- Configurable cache headers per file extension
|
|
34
|
+
- Comprehensive MIME type detection
|
|
35
|
+
- HTTP/1.1 fallback support
|
|
36
|
+
- Security headers (X-Content-Type-Options, X-Frame-Options, Vary)
|
|
37
|
+
- Standalone or integrated with AppServerFactory
|
|
38
|
+
- Multiple static path support
|
|
39
|
+
- Separate SSL certificate support from main server
|
|
40
|
+
|
|
27
41
|
#### Modular Security Components
|
|
28
42
|
The AppServerFactory now uses specialized security configurers:
|
|
29
43
|
|
|
@@ -31,6 +45,7 @@ The AppServerFactory now uses specialized security configurers:
|
|
|
31
45
|
- **DevelopmentModeDetector** - Automatic development environment detection
|
|
32
46
|
- **ProtocolEnforcer** - Protocol redirection with development mode awareness
|
|
33
47
|
- **RateLimitConfigurer** - Global and endpoint-specific rate limiting
|
|
48
|
+
- **ReverseProxyConfigurer** - Domain-based reverse proxy with WebSocket support
|
|
34
49
|
- **SecurityConfigurer** - Helmet integration with CSP management and XSS protection
|
|
35
50
|
|
|
36
51
|
### FileHandler
|
|
@@ -100,6 +115,65 @@ if(serverResult){
|
|
|
100
115
|
}
|
|
101
116
|
```
|
|
102
117
|
|
|
118
|
+
### HTTP/2 CDN Server with Express
|
|
119
|
+
|
|
120
|
+
Serve your main application through Express on port 443, and static assets through HTTP/2 CDN on port 8443:
|
|
121
|
+
|
|
122
|
+
```javascript
|
|
123
|
+
let appServerFactory = new AppServerFactory();
|
|
124
|
+
let serverResult = appServerFactory.createAppServer({
|
|
125
|
+
port: 443,
|
|
126
|
+
useHttps: true,
|
|
127
|
+
keyPath: '/ssl/main-server.key',
|
|
128
|
+
certPath: '/ssl/main-server.crt',
|
|
129
|
+
http2CdnEnabled: true,
|
|
130
|
+
http2CdnPort: 8443,
|
|
131
|
+
http2CdnKeyPath: '/ssl/cdn-server.key',
|
|
132
|
+
http2CdnCertPath: '/ssl/cdn-server.crt',
|
|
133
|
+
http2CdnStaticPaths: ['/var/www/public'],
|
|
134
|
+
http2CdnCorsOrigins: [
|
|
135
|
+
'https://example.com',
|
|
136
|
+
/^https:\/\/(www\.)?example\.(com|net)$/
|
|
137
|
+
],
|
|
138
|
+
autoListen: true
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
if(serverResult){
|
|
142
|
+
let { app, appServer, http2CdnServer } = serverResult;
|
|
143
|
+
console.log('Express server on port 443');
|
|
144
|
+
console.log('HTTP/2 CDN server on port 8443');
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
**Note:** If `http2CdnKeyPath` and `http2CdnCertPath` are not specified, the CDN server will use the same certificates as the main server (`keyPath` and `certPath`).
|
|
149
|
+
|
|
150
|
+
Browser usage:
|
|
151
|
+
```html
|
|
152
|
+
<link rel="stylesheet" href="https://cdn.example.com:8443/css/style.css">
|
|
153
|
+
<script src="https://cdn.example.com:8443/js/app.js"></script>
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Standalone HTTP/2 CDN Server
|
|
157
|
+
|
|
158
|
+
```javascript
|
|
159
|
+
const { Http2CdnServer } = require('@reldens/server-utils');
|
|
160
|
+
|
|
161
|
+
let cdnServer = new Http2CdnServer();
|
|
162
|
+
cdnServer.port = 8443;
|
|
163
|
+
cdnServer.keyPath = '/ssl/cdn.key';
|
|
164
|
+
cdnServer.certPath = '/ssl/cdn.crt';
|
|
165
|
+
cdnServer.staticPaths = ['/var/www/public', '/var/www/assets'];
|
|
166
|
+
cdnServer.corsOrigins = [
|
|
167
|
+
'https://main-site.com',
|
|
168
|
+
/^https:\/\/(new\.)?((site1|site2)\.(com|net))$/
|
|
169
|
+
];
|
|
170
|
+
|
|
171
|
+
if(cdnServer.create()){
|
|
172
|
+
cdnServer.listen();
|
|
173
|
+
console.log('HTTP/2 CDN running on port 8443');
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
103
177
|
### HTTPS Server with Optimized Caching
|
|
104
178
|
|
|
105
179
|
```javascript
|
|
@@ -206,6 +280,56 @@ app.post('/upload', uploader, (req, res) => {
|
|
|
206
280
|
|
|
207
281
|
## Advanced Configuration
|
|
208
282
|
|
|
283
|
+
### HTTP/2 CDN with Separate Certificates
|
|
284
|
+
|
|
285
|
+
Configure separate SSL certificates for your CDN server:
|
|
286
|
+
|
|
287
|
+
```javascript
|
|
288
|
+
let appServerFactory = new AppServerFactory();
|
|
289
|
+
let serverResult = appServerFactory.createAppServer({
|
|
290
|
+
useHttps: true,
|
|
291
|
+
port: 443,
|
|
292
|
+
keyPath: '/ssl/app-server.key',
|
|
293
|
+
certPath: '/ssl/app-server.crt',
|
|
294
|
+
http2CdnEnabled: true,
|
|
295
|
+
http2CdnPort: 8443,
|
|
296
|
+
http2CdnKeyPath: '/ssl/cdn-server.key',
|
|
297
|
+
http2CdnCertPath: '/ssl/cdn-server.crt',
|
|
298
|
+
http2CdnHttpsChain: '/ssl/cdn-chain.pem',
|
|
299
|
+
http2CdnStaticPaths: ['/var/www/public'],
|
|
300
|
+
http2CdnCorsOrigins: [
|
|
301
|
+
'https://main-site.com',
|
|
302
|
+
'https://app.main-site.com',
|
|
303
|
+
/^https:\/\/(new\.)?main-site\.(com|net)$/
|
|
304
|
+
],
|
|
305
|
+
http2CdnCacheConfig: {
|
|
306
|
+
'.css': 31536000,
|
|
307
|
+
'.js': 31536000,
|
|
308
|
+
'.woff2': 31536000,
|
|
309
|
+
'.png': 2592000
|
|
310
|
+
}
|
|
311
|
+
});
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
### HTTP/2 CDN with Multiple Origins
|
|
315
|
+
|
|
316
|
+
The HTTP/2 CDN server supports multiple origin validation methods:
|
|
317
|
+
|
|
318
|
+
```javascript
|
|
319
|
+
let appServerFactory = new AppServerFactory();
|
|
320
|
+
let serverResult = appServerFactory.createAppServer({
|
|
321
|
+
http2CdnEnabled: true,
|
|
322
|
+
http2CdnPort: 8443,
|
|
323
|
+
http2CdnStaticPaths: ['/var/www/public'],
|
|
324
|
+
http2CdnCorsOrigins: [
|
|
325
|
+
'https://main-site.com',
|
|
326
|
+
'https://app.main-site.com',
|
|
327
|
+
/^https:\/\/(new\.)?main-site\.(com|net)$/,
|
|
328
|
+
/^https:\/\/(app|admin)\.secondary-site\.com$/
|
|
329
|
+
]
|
|
330
|
+
});
|
|
331
|
+
```
|
|
332
|
+
|
|
209
333
|
### HTTPS Server with Multiple Domains
|
|
210
334
|
|
|
211
335
|
```javascript
|
|
@@ -350,6 +474,68 @@ let serverResult = appServerFactory.createAppServer({
|
|
|
350
474
|
});
|
|
351
475
|
```
|
|
352
476
|
|
|
477
|
+
### Reverse Proxy Configuration
|
|
478
|
+
|
|
479
|
+
Route multiple domains to different backend servers through a single SSL-enabled entry point:
|
|
480
|
+
|
|
481
|
+
```javascript
|
|
482
|
+
let appServerFactory = new AppServerFactory();
|
|
483
|
+
|
|
484
|
+
let serverResult = appServerFactory.createAppServer({
|
|
485
|
+
port: 443,
|
|
486
|
+
useHttps: true,
|
|
487
|
+
useVirtualHosts: true,
|
|
488
|
+
keyPath: '/ssl/server.key',
|
|
489
|
+
certPath: '/ssl/server.crt',
|
|
490
|
+
reverseProxyEnabled: true,
|
|
491
|
+
reverseProxyRules: [
|
|
492
|
+
{
|
|
493
|
+
hostname: 'demo.reldens.com',
|
|
494
|
+
target: 'https://localhost:8444',
|
|
495
|
+
pathPrefix: '/',
|
|
496
|
+
websocket: true,
|
|
497
|
+
secure: false
|
|
498
|
+
},
|
|
499
|
+
{
|
|
500
|
+
hostname: 'api.example.com',
|
|
501
|
+
target: 'https://localhost:8445',
|
|
502
|
+
pathPrefix: '/',
|
|
503
|
+
websocket: false
|
|
504
|
+
}
|
|
505
|
+
],
|
|
506
|
+
autoListen: true
|
|
507
|
+
});
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
#### Reverse Proxy Features
|
|
511
|
+
|
|
512
|
+
- Multiple backend routing with independent configuration per domain
|
|
513
|
+
- WebSocket support for real-time applications
|
|
514
|
+
- SSL termination at entry point
|
|
515
|
+
- Header preservation (X-Forwarded-For, X-Forwarded-Proto, X-Forwarded-Host)
|
|
516
|
+
- Virtual host integration
|
|
517
|
+
- Path-based routing
|
|
518
|
+
- Graceful error handling
|
|
519
|
+
|
|
520
|
+
#### Rule Properties
|
|
521
|
+
|
|
522
|
+
- `hostname` (string, required) - Domain to match
|
|
523
|
+
- `target` (string, required) - Backend URL
|
|
524
|
+
- `pathPrefix` (string, optional) - Path prefix, default: '/'
|
|
525
|
+
- `websocket` (boolean, optional) - Enable WebSocket, default: true
|
|
526
|
+
- `changeOrigin` (boolean, optional) - Change origin header, default: true
|
|
527
|
+
- `secure` (boolean, optional) - Verify SSL certificates, default: false
|
|
528
|
+
- `logLevel` (string, optional) - 'debug', 'info', 'warn', 'error', 'silent'
|
|
529
|
+
|
|
530
|
+
#### Example: Multiple Game Servers
|
|
531
|
+
|
|
532
|
+
```javascript
|
|
533
|
+
reverseProxyRules: [
|
|
534
|
+
{ hostname: 'demo.game.com', target: 'https://localhost:8444', websocket: true },
|
|
535
|
+
{ hostname: 'staging.game.com', target: 'https://localhost:8445', websocket: true }
|
|
536
|
+
]
|
|
537
|
+
```
|
|
538
|
+
|
|
353
539
|
## API Reference
|
|
354
540
|
|
|
355
541
|
### AppServerFactory Methods
|
|
@@ -365,6 +551,47 @@ let serverResult = appServerFactory.createAppServer({
|
|
|
365
551
|
- `listen(port)` - Starts server listening
|
|
366
552
|
- `close()` - Gracefully closes server
|
|
367
553
|
|
|
554
|
+
### AppServerFactory HTTP/2 CDN Configuration
|
|
555
|
+
|
|
556
|
+
- `http2CdnEnabled` - Enable HTTP/2 CDN server (default: false)
|
|
557
|
+
- `http2CdnPort` - HTTP/2 CDN port (default: 8443)
|
|
558
|
+
- `http2CdnKeyPath` - CDN SSL private key path (falls back to `keyPath`)
|
|
559
|
+
- `http2CdnCertPath` - CDN SSL certificate path (falls back to `certPath`)
|
|
560
|
+
- `http2CdnHttpsChain` - CDN certificate chain path (falls back to `httpsChain`)
|
|
561
|
+
- `http2CdnStaticPaths` - Paths to serve from CDN (default: [])
|
|
562
|
+
- `http2CdnCorsOrigins` - Allowed CORS origins for CDN (default: [])
|
|
563
|
+
- `http2CdnCorsAllowAll` - Allow all origins (default: false)
|
|
564
|
+
- `http2CdnMimeTypes` - Override default MIME types (default: {})
|
|
565
|
+
- `http2CdnCacheConfig` - Override default cache config (default: {})
|
|
566
|
+
|
|
567
|
+
### AppServerFactory Reverse Proxy Configuration
|
|
568
|
+
|
|
569
|
+
- `reverseProxyEnabled` - Enable reverse proxy (default: false)
|
|
570
|
+
- `reverseProxyRules` - Array of proxy rules (default: [])
|
|
571
|
+
|
|
572
|
+
### Http2CdnServer Methods
|
|
573
|
+
|
|
574
|
+
- `create()` - Creates HTTP/2 secure server
|
|
575
|
+
- `listen()` - Starts listening on configured port
|
|
576
|
+
- `close()` - Gracefully closes HTTP/2 server
|
|
577
|
+
|
|
578
|
+
### Http2CdnServer Configuration
|
|
579
|
+
|
|
580
|
+
- `port` - Server port (default: 8443)
|
|
581
|
+
- `keyPath` - SSL private key path
|
|
582
|
+
- `certPath` - SSL certificate path
|
|
583
|
+
- `httpsChain` - Certificate chain path (optional)
|
|
584
|
+
- `staticPaths` - Array of static file directories
|
|
585
|
+
- `cacheConfig` - Cache max-age per extension
|
|
586
|
+
- `allowHTTP1` - Allow HTTP/1.1 fallback (default: true)
|
|
587
|
+
- `corsOrigins` - Array of allowed origins (strings or RegExp)
|
|
588
|
+
- `corsAllowAll` - Allow all origins (default: false)
|
|
589
|
+
- `corsMethods` - Allowed HTTP methods (default: 'GET, OPTIONS')
|
|
590
|
+
- `corsHeaders` - Allowed request headers (default: 'Content-Type')
|
|
591
|
+
- `securityHeaders` - Custom security headers
|
|
592
|
+
- `varyHeader` - Vary header value (default: 'Accept-Encoding, Origin')
|
|
593
|
+
- `mimeTypes` - MIME type mappings
|
|
594
|
+
|
|
368
595
|
### FileHandler Methods
|
|
369
596
|
|
|
370
597
|
- `exists(path)` - Checks if file or folder exists
|
|
@@ -386,11 +613,11 @@ let serverResult = appServerFactory.createAppServer({
|
|
|
386
613
|
- `generateSecureFilename(originalName)` - Generates cryptographically secure filename
|
|
387
614
|
- `quarantineFile(path, reason)` - Moves file to quarantine folder
|
|
388
615
|
- `createTempFile(prefix, extension)` - Creates a temporary file path
|
|
389
|
-
- `moveFile(from, to)` - Moves file to new location
|
|
616
|
+
- `moveFile(from, to)` - Moves a file to new location
|
|
390
617
|
- `getFileSize(path)` - Gets file size in bytes
|
|
391
618
|
- `compareFiles(file1, file2)` - Compares file contents
|
|
392
|
-
- `getRelativePath(from, to)` - Calculates relative path
|
|
393
|
-
- `walkDirectory(path, callback)` - Recursively processes directory tree
|
|
619
|
+
- `getRelativePath(from, to)` - Calculates a relative path
|
|
620
|
+
- `walkDirectory(path, callback)` - Recursively processes a directory tree
|
|
394
621
|
- `getDirectorySize(path)` - Calculates total directory size
|
|
395
622
|
- `emptyDirectory(path)` - Removes all contents from directory
|
|
396
623
|
|
|
@@ -430,6 +657,9 @@ Configurable rate limiting with development mode detection for appropriate thres
|
|
|
430
657
|
### HTTPS Support
|
|
431
658
|
Full SSL/TLS support with SNI for multi-domain hosting and automatic certificate management.
|
|
432
659
|
|
|
660
|
+
### HTTP/2 CDN Security
|
|
661
|
+
The HTTP/2 CDN server includes security headers, CORS validation with pattern matching, and query string stripping to prevent cache poisoning.
|
|
662
|
+
|
|
433
663
|
### Input Validation
|
|
434
664
|
Built-in validators for common input types including email, username, strong passwords, alphanumeric strings, and IP addresses.
|
|
435
665
|
|
package/index.js
CHANGED
|
@@ -8,10 +8,18 @@ const { FileHandler } = require('./lib/file-handler');
|
|
|
8
8
|
const { AppServerFactory } = require('./lib/app-server-factory');
|
|
9
9
|
const { UploaderFactory } = require('./lib/uploader-factory');
|
|
10
10
|
const { Encryptor } = require('./lib/encryptor');
|
|
11
|
+
const { Http2CdnServer } = require('./lib/http2-cdn-server');
|
|
12
|
+
const { ServerDefaultConfigurations } = require('./lib/server-default-configurations');
|
|
13
|
+
const { ServerFactoryUtils } = require('./lib/server-factory-utils');
|
|
14
|
+
const { ServerHeaders } = require('./lib/server-headers');
|
|
11
15
|
|
|
12
16
|
module.exports = {
|
|
13
17
|
FileHandler,
|
|
14
18
|
AppServerFactory,
|
|
15
19
|
UploaderFactory,
|
|
16
|
-
Encryptor
|
|
20
|
+
Encryptor,
|
|
21
|
+
Http2CdnServer,
|
|
22
|
+
ServerDefaultConfigurations,
|
|
23
|
+
ServerFactoryUtils,
|
|
24
|
+
ServerHeaders
|
|
17
25
|
};
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* Reldens - ReverseProxyConfigurer
|
|
4
|
+
*
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const { createProxyMiddleware } = require('http-proxy-middleware');
|
|
8
|
+
const { ServerHeaders } = require('../server-headers');
|
|
9
|
+
|
|
10
|
+
class ReverseProxyConfigurer
|
|
11
|
+
{
|
|
12
|
+
|
|
13
|
+
constructor()
|
|
14
|
+
{
|
|
15
|
+
this.isDevelopmentMode = false;
|
|
16
|
+
this.useVirtualHosts = false;
|
|
17
|
+
this.reverseProxyRules = [];
|
|
18
|
+
this.reverseProxyOptions = {
|
|
19
|
+
changeOrigin: true,
|
|
20
|
+
ws: true,
|
|
21
|
+
secure: false,
|
|
22
|
+
logLevel: 'warn'
|
|
23
|
+
};
|
|
24
|
+
this.serverHeaders = new ServerHeaders();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
setup(app, config)
|
|
28
|
+
{
|
|
29
|
+
this.isDevelopmentMode = config.isDevelopmentMode || false;
|
|
30
|
+
this.useVirtualHosts = config.useVirtualHosts || false;
|
|
31
|
+
this.reverseProxyRules = config.reverseProxyRules || [];
|
|
32
|
+
this.reverseProxyOptions = config.reverseProxyOptions || this.reverseProxyOptions;
|
|
33
|
+
if(0 === this.reverseProxyRules.length){
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
this.applyRules(app);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
applyRules(app)
|
|
40
|
+
{
|
|
41
|
+
for(let rule of this.reverseProxyRules){
|
|
42
|
+
if(!this.validateProxyRule(rule)){
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
let proxyMiddleware = this.createProxyMiddleware(rule);
|
|
46
|
+
if(!proxyMiddleware){
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
let pathPrefix = rule.pathPrefix || '/';
|
|
50
|
+
if(this.useVirtualHosts){
|
|
51
|
+
app.use(pathPrefix, (req, res, next) => {
|
|
52
|
+
let hostname = this.extractHostname(req);
|
|
53
|
+
if(hostname !== rule.hostname){
|
|
54
|
+
return next();
|
|
55
|
+
}
|
|
56
|
+
return proxyMiddleware(req, res, next);
|
|
57
|
+
});
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
app.use(pathPrefix, proxyMiddleware);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
extractHostname(req)
|
|
65
|
+
{
|
|
66
|
+
if(req.domain && req.domain.hostname){
|
|
67
|
+
return req.domain.hostname;
|
|
68
|
+
}
|
|
69
|
+
let host = req.get('host') || '';
|
|
70
|
+
return host.split(':')[0].toLowerCase();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
validateProxyRule(rule)
|
|
74
|
+
{
|
|
75
|
+
if(!rule){
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
if(!rule.hostname){
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
if(!rule.target){
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
createProxyMiddleware(rule)
|
|
88
|
+
{
|
|
89
|
+
let options = Object.assign({}, this.reverseProxyOptions);
|
|
90
|
+
if('boolean' === typeof rule.changeOrigin){
|
|
91
|
+
options.changeOrigin = rule.changeOrigin;
|
|
92
|
+
}
|
|
93
|
+
if('boolean' === typeof rule.websocket){
|
|
94
|
+
options.ws = rule.websocket;
|
|
95
|
+
}
|
|
96
|
+
if('boolean' === typeof rule.secure){
|
|
97
|
+
options.secure = rule.secure;
|
|
98
|
+
}
|
|
99
|
+
if('string' === typeof rule.logLevel){
|
|
100
|
+
options.logLevel = rule.logLevel;
|
|
101
|
+
}
|
|
102
|
+
options.target = rule.target;
|
|
103
|
+
options.onError = this.handleProxyError.bind(this);
|
|
104
|
+
options.onProxyReq = (proxyReq, req) => {
|
|
105
|
+
if(req.headers['x-forwarded-for']){
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
let clientIp = req.ip || req.connection.remoteAddress || '';
|
|
109
|
+
proxyReq.setHeader(this.serverHeaders.proxyForwardedFor, clientIp);
|
|
110
|
+
proxyReq.setHeader(this.serverHeaders.proxyForwardedProto, req.protocol);
|
|
111
|
+
proxyReq.setHeader(this.serverHeaders.proxyForwardedHost, req.get('host') || '');
|
|
112
|
+
};
|
|
113
|
+
return createProxyMiddleware(options);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
handleProxyError(err, req, res)
|
|
117
|
+
{
|
|
118
|
+
if(res.headersSent){
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
if('ECONNREFUSED' === err.code){
|
|
122
|
+
return res.status(502).send('Bad Gateway - Backend server unavailable');
|
|
123
|
+
}
|
|
124
|
+
if('ETIMEDOUT' === err.code || 'ESOCKETTIMEDOUT' === err.code){
|
|
125
|
+
return res.status(504).send('Gateway Timeout');
|
|
126
|
+
}
|
|
127
|
+
return res.status(500).send('Proxy Error');
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
module.exports.ReverseProxyConfigurer = ReverseProxyConfigurer;
|
|
@@ -5,10 +5,15 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
const { FileHandler } = require('./file-handler');
|
|
8
|
+
const { Http2CdnServer } = require('./http2-cdn-server');
|
|
9
|
+
const { ServerDefaultConfigurations } = require('./server-default-configurations');
|
|
10
|
+
const { ServerFactoryUtils } = require('./server-factory-utils');
|
|
11
|
+
const { ServerHeaders } = require('./server-headers');
|
|
8
12
|
const { DevelopmentModeDetector } = require('./app-server-factory/development-mode-detector');
|
|
9
13
|
const { ProtocolEnforcer } = require('./app-server-factory/protocol-enforcer');
|
|
10
14
|
const { SecurityConfigurer } = require('./app-server-factory/security-configurer');
|
|
11
15
|
const { CorsConfigurer } = require('./app-server-factory/cors-configurer');
|
|
16
|
+
const { ReverseProxyConfigurer } = require('./app-server-factory/reverse-proxy-configurer');
|
|
12
17
|
const { RateLimitConfigurer } = require('./app-server-factory/rate-limit-configurer');
|
|
13
18
|
const http = require('http');
|
|
14
19
|
const https = require('https');
|
|
@@ -61,21 +66,8 @@ class AppServerFactory
|
|
|
61
66
|
this.defaultDomain = '';
|
|
62
67
|
this.maxRequestSize = '10mb';
|
|
63
68
|
this.sanitizeOptions = {allowedTags: [], allowedAttributes: {}};
|
|
64
|
-
this.cacheConfig =
|
|
65
|
-
|
|
66
|
-
'.js': 31536000,
|
|
67
|
-
'.woff': 31536000,
|
|
68
|
-
'.woff2': 31536000,
|
|
69
|
-
'.ttf': 31536000,
|
|
70
|
-
'.eot': 31536000,
|
|
71
|
-
'.jpg': 2592000,
|
|
72
|
-
'.jpeg': 2592000,
|
|
73
|
-
'.png': 2592000,
|
|
74
|
-
'.gif': 2592000,
|
|
75
|
-
'.webp': 2592000,
|
|
76
|
-
'.svg': 2592000,
|
|
77
|
-
'.ico': 2592000
|
|
78
|
-
};
|
|
69
|
+
this.cacheConfig = ServerDefaultConfigurations.cacheConfig;
|
|
70
|
+
this.serverHeaders = new ServerHeaders();
|
|
79
71
|
this.staticOptions = {
|
|
80
72
|
maxAge: '1d',
|
|
81
73
|
immutable: false,
|
|
@@ -98,6 +90,7 @@ class AppServerFactory
|
|
|
98
90
|
this.securityConfigurer = new SecurityConfigurer();
|
|
99
91
|
this.corsConfigurer = new CorsConfigurer();
|
|
100
92
|
this.rateLimitConfigurer = new RateLimitConfigurer();
|
|
93
|
+
this.reverseProxyConfigurer = new ReverseProxyConfigurer();
|
|
101
94
|
this.useCompression = true;
|
|
102
95
|
this.compressionOptions = {
|
|
103
96
|
level: 6,
|
|
@@ -109,28 +102,32 @@ class AppServerFactory
|
|
|
109
102
|
return compression.filter(req, res);
|
|
110
103
|
}
|
|
111
104
|
};
|
|
105
|
+
this.http2CdnEnabled = false;
|
|
106
|
+
this.http2CdnPort = 8443;
|
|
107
|
+
this.http2CdnKeyPath = '';
|
|
108
|
+
this.http2CdnCertPath = '';
|
|
109
|
+
this.http2CdnHttpsChain = '';
|
|
110
|
+
this.http2CdnStaticPaths = [];
|
|
111
|
+
this.http2CdnCorsOrigins = [];
|
|
112
|
+
this.http2CdnCorsAllowAll = false;
|
|
113
|
+
this.http2CdnMimeTypes = {};
|
|
114
|
+
this.http2CdnCacheConfig = {};
|
|
115
|
+
this.http2CdnServer = false;
|
|
116
|
+
this.reverseProxyEnabled = false;
|
|
117
|
+
this.reverseProxyRules = [];
|
|
112
118
|
}
|
|
113
119
|
|
|
114
120
|
buildStaticHeaders(res, path)
|
|
115
121
|
{
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
let cacheMaxAge = this.getCacheConfigForPath(path);
|
|
120
|
-
if(cacheMaxAge){
|
|
121
|
-
res.set('Cache-Control', 'public, max-age='+cacheMaxAge+', immutable');
|
|
122
|
+
let securityHeaderKeys = Object.keys(this.serverHeaders.expressSecurityHeaders);
|
|
123
|
+
for(let headerKey of securityHeaderKeys){
|
|
124
|
+
res.set(headerKey, this.serverHeaders.expressSecurityHeaders[headerKey]);
|
|
122
125
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
let cacheKeys = Object.keys(this.cacheConfig);
|
|
128
|
-
for(let ext of cacheKeys){
|
|
129
|
-
if(path.endsWith(ext)){
|
|
130
|
-
return this.cacheConfig[ext];
|
|
131
|
-
}
|
|
126
|
+
res.set('Vary', this.serverHeaders.expressVaryHeader);
|
|
127
|
+
let cacheMaxAge = ServerFactoryUtils.getCacheConfigForPath(path, this.cacheConfig);
|
|
128
|
+
if(cacheMaxAge){
|
|
129
|
+
res.set('Cache-Control', this.serverHeaders.buildCacheControlHeader(cacheMaxAge));
|
|
132
130
|
}
|
|
133
|
-
return false;
|
|
134
131
|
}
|
|
135
132
|
|
|
136
133
|
createAppServer(appServerConfig)
|
|
@@ -138,6 +135,7 @@ class AppServerFactory
|
|
|
138
135
|
if(appServerConfig){
|
|
139
136
|
Object.assign(this, appServerConfig);
|
|
140
137
|
}
|
|
138
|
+
this.setupReverseProxy();
|
|
141
139
|
this.addHttpDomainsAsDevelopment();
|
|
142
140
|
this.detectDevelopmentMode();
|
|
143
141
|
this.setupDevelopmentConfiguration();
|
|
@@ -157,14 +155,48 @@ class AppServerFactory
|
|
|
157
155
|
}
|
|
158
156
|
if(!this.appServer){
|
|
159
157
|
if(!this.error.message){
|
|
160
|
-
this.error = {message: 'The createServer() returned false - check certificate paths and permissions'};
|
|
158
|
+
this.error = {message: 'The createServer() returned false - check certificate paths and permissions.'};
|
|
161
159
|
}
|
|
162
160
|
return false;
|
|
163
161
|
}
|
|
162
|
+
if(this.http2CdnEnabled){
|
|
163
|
+
if(!this.createHttp2CdnServer()){
|
|
164
|
+
this.error = {message: 'The createHttp2CdnServer() returned false.'};
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
164
168
|
if(this.autoListen){
|
|
165
169
|
this.listen();
|
|
166
170
|
}
|
|
167
|
-
return {app: this.app, appServer: this.appServer};
|
|
171
|
+
return {app: this.app, appServer: this.appServer, http2CdnServer: this.http2CdnServer};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
createHttp2CdnServer()
|
|
175
|
+
{
|
|
176
|
+
this.http2CdnServer = new Http2CdnServer();
|
|
177
|
+
this.http2CdnServer.enabled = this.http2CdnEnabled;
|
|
178
|
+
this.http2CdnServer.port = this.http2CdnPort;
|
|
179
|
+
this.http2CdnServer.keyPath = this.http2CdnKeyPath || this.keyPath;
|
|
180
|
+
this.http2CdnServer.certPath = this.http2CdnCertPath || this.certPath;
|
|
181
|
+
this.http2CdnServer.httpsChain = this.http2CdnHttpsChain || this.httpsChain;
|
|
182
|
+
this.http2CdnServer.staticPaths = this.http2CdnStaticPaths;
|
|
183
|
+
this.http2CdnServer.cacheConfig = this.http2CdnCacheConfig && 0 < Object.keys(this.http2CdnCacheConfig).length
|
|
184
|
+
? this.http2CdnCacheConfig
|
|
185
|
+
: this.cacheConfig;
|
|
186
|
+
if(this.http2CdnMimeTypes && 0 < Object.keys(this.http2CdnMimeTypes).length){
|
|
187
|
+
this.http2CdnServer.mimeTypes = this.http2CdnMimeTypes;
|
|
188
|
+
}
|
|
189
|
+
this.http2CdnServer.corsOrigins = this.http2CdnCorsOrigins;
|
|
190
|
+
this.http2CdnServer.corsAllowAll = this.http2CdnCorsAllowAll;
|
|
191
|
+
if(!this.http2CdnServer.create()){
|
|
192
|
+
this.error = this.http2CdnServer.error;
|
|
193
|
+
return false;
|
|
194
|
+
}
|
|
195
|
+
if(!this.http2CdnServer.listen()){
|
|
196
|
+
this.error = this.http2CdnServer.error;
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
199
|
+
return true;
|
|
168
200
|
}
|
|
169
201
|
|
|
170
202
|
extractDomainFromHttpUrl(url)
|
|
@@ -209,12 +241,14 @@ class AppServerFactory
|
|
|
209
241
|
}
|
|
210
242
|
this.staticOptions.immutable = false;
|
|
211
243
|
this.staticOptions.setHeaders = (res, path) => {
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
res.set('
|
|
217
|
-
res.set('
|
|
244
|
+
let securityHeaderKeys = Object.keys(this.serverHeaders.expressSecurityHeaders);
|
|
245
|
+
for(let headerKey of securityHeaderKeys){
|
|
246
|
+
res.set(headerKey, this.serverHeaders.expressSecurityHeaders[headerKey]);
|
|
247
|
+
}
|
|
248
|
+
res.set('Cache-Control', this.serverHeaders.expressCacheControlNoCache);
|
|
249
|
+
res.set('Pragma', this.serverHeaders.expressPragma);
|
|
250
|
+
res.set('Expires', this.serverHeaders.expressExpires);
|
|
251
|
+
res.set('Vary', this.serverHeaders.expressVaryHeader);
|
|
218
252
|
};
|
|
219
253
|
}
|
|
220
254
|
|
|
@@ -308,6 +342,18 @@ class AppServerFactory
|
|
|
308
342
|
}
|
|
309
343
|
}
|
|
310
344
|
|
|
345
|
+
setupReverseProxy()
|
|
346
|
+
{
|
|
347
|
+
if(!this.reverseProxyEnabled || 0 === this.reverseProxyRules.length){
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
this.reverseProxyConfigurer.setup(this.app, {
|
|
351
|
+
reverseProxyRules: this.reverseProxyRules,
|
|
352
|
+
isDevelopmentMode: this.isDevelopmentMode,
|
|
353
|
+
useVirtualHosts: this.useVirtualHosts
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
|
|
311
357
|
setupTrustedProxy()
|
|
312
358
|
{
|
|
313
359
|
if('' !== this.trustedProxy){
|
|
@@ -545,6 +591,9 @@ class AppServerFactory
|
|
|
545
591
|
|
|
546
592
|
async close()
|
|
547
593
|
{
|
|
594
|
+
if(this.http2CdnServer){
|
|
595
|
+
await this.http2CdnServer.close();
|
|
596
|
+
}
|
|
548
597
|
if(!this.appServer){
|
|
549
598
|
return true;
|
|
550
599
|
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* Reldens - Http2CdnServer
|
|
4
|
+
*
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const http2 = require('http2');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const { FileHandler } = require('./file-handler');
|
|
10
|
+
const { ServerDefaultConfigurations } = require('./server-default-configurations');
|
|
11
|
+
const { ServerFactoryUtils } = require('./server-factory-utils');
|
|
12
|
+
const { ServerHeaders } = require('./server-headers');
|
|
13
|
+
|
|
14
|
+
class Http2CdnServer
|
|
15
|
+
{
|
|
16
|
+
|
|
17
|
+
constructor()
|
|
18
|
+
{
|
|
19
|
+
this.enabled = false;
|
|
20
|
+
this.port = 8443;
|
|
21
|
+
this.keyPath = '';
|
|
22
|
+
this.certPath = '';
|
|
23
|
+
this.httpsChain = '';
|
|
24
|
+
this.staticPaths = [];
|
|
25
|
+
this.cacheConfig = ServerDefaultConfigurations.cacheConfig;
|
|
26
|
+
this.allowHTTP1 = true;
|
|
27
|
+
this.http2Server = false;
|
|
28
|
+
this.error = {};
|
|
29
|
+
this.mimeTypes = ServerDefaultConfigurations.mimeTypes;
|
|
30
|
+
this.corsOrigins = [];
|
|
31
|
+
this.corsAllowAll = false;
|
|
32
|
+
this.serverHeaders = new ServerHeaders();
|
|
33
|
+
this.corsMethods = this.serverHeaders.http2CorsMethods;
|
|
34
|
+
this.corsHeaders = this.serverHeaders.http2CorsHeaders;
|
|
35
|
+
this.securityHeaders = this.serverHeaders.http2SecurityHeaders;
|
|
36
|
+
this.varyHeader = this.serverHeaders.http2VaryHeader;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
create()
|
|
40
|
+
{
|
|
41
|
+
if(!this.keyPath || !this.certPath){
|
|
42
|
+
this.error = {message: 'Missing SSL certificates'};
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
let key = FileHandler.readFile(this.keyPath);
|
|
46
|
+
if(!key){
|
|
47
|
+
this.error = {message: 'Could not read key from: '+this.keyPath};
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
let cert = FileHandler.readFile(this.certPath);
|
|
51
|
+
if(!cert){
|
|
52
|
+
this.error = {message: 'Could not read cert from: '+this.certPath};
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
let options = {key, cert, allowHTTP1: this.allowHTTP1};
|
|
56
|
+
if(this.httpsChain){
|
|
57
|
+
let ca = FileHandler.readFile(this.httpsChain);
|
|
58
|
+
if(ca){
|
|
59
|
+
options.ca = ca;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
this.http2Server = http2.createSecureServer(options);
|
|
63
|
+
this.setupStreamHandler();
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
setupStreamHandler()
|
|
68
|
+
{
|
|
69
|
+
this.http2Server.on('stream', (stream, headers) => {
|
|
70
|
+
this.handleStream(stream, headers);
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
handleStream(stream, headers)
|
|
75
|
+
{
|
|
76
|
+
let requestPath = headers[':path'];
|
|
77
|
+
if(!requestPath){
|
|
78
|
+
stream.respond({':status': 400});
|
|
79
|
+
stream.end();
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
let requestOrigin = headers['origin'] || '';
|
|
83
|
+
let allowedOrigin = ServerFactoryUtils.validateOrigin(requestOrigin, this.corsOrigins, this.corsAllowAll);
|
|
84
|
+
if('OPTIONS' === headers[':method']){
|
|
85
|
+
let optionsHeaders = {':status': 200};
|
|
86
|
+
if(allowedOrigin){
|
|
87
|
+
optionsHeaders['access-control-allow-origin'] = allowedOrigin;
|
|
88
|
+
}
|
|
89
|
+
optionsHeaders['access-control-allow-methods'] = this.corsMethods;
|
|
90
|
+
optionsHeaders['access-control-allow-headers'] = this.corsHeaders;
|
|
91
|
+
stream.respond(optionsHeaders);
|
|
92
|
+
stream.end();
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
let filePath = this.resolveFilePath(requestPath);
|
|
96
|
+
if(!filePath){
|
|
97
|
+
stream.respond({':status': 404});
|
|
98
|
+
stream.end();
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
let ext = path.extname(filePath);
|
|
102
|
+
let cacheAge = ServerFactoryUtils.getCacheConfigForPath(filePath, this.cacheConfig);
|
|
103
|
+
let responseHeaders = {':status': 200};
|
|
104
|
+
let securityKeys = Object.keys(this.securityHeaders);
|
|
105
|
+
for(let headerKey of securityKeys){
|
|
106
|
+
responseHeaders[headerKey] = this.securityHeaders[headerKey];
|
|
107
|
+
}
|
|
108
|
+
if(allowedOrigin){
|
|
109
|
+
responseHeaders['access-control-allow-origin'] = allowedOrigin;
|
|
110
|
+
}
|
|
111
|
+
if(this.varyHeader){
|
|
112
|
+
responseHeaders['vary'] = this.varyHeader;
|
|
113
|
+
}
|
|
114
|
+
if(cacheAge){
|
|
115
|
+
responseHeaders['cache-control'] = 'public, max-age='+cacheAge+', immutable';
|
|
116
|
+
}
|
|
117
|
+
if(this.mimeTypes[ext]){
|
|
118
|
+
responseHeaders['content-type'] = this.mimeTypes[ext];
|
|
119
|
+
}
|
|
120
|
+
stream.respondWithFile(filePath, responseHeaders);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
resolveFilePath(requestPath)
|
|
124
|
+
{
|
|
125
|
+
let cleanPath = ServerFactoryUtils.stripQueryString(requestPath);
|
|
126
|
+
for(let staticPath of this.staticPaths){
|
|
127
|
+
let fullPath = path.join(staticPath, cleanPath);
|
|
128
|
+
if(!FileHandler.exists(fullPath)){
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
if(!FileHandler.isFile(fullPath)){
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
return fullPath;
|
|
135
|
+
}
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
listen()
|
|
140
|
+
{
|
|
141
|
+
if(!this.http2Server){
|
|
142
|
+
this.error = {message: 'HTTP2 server not created'};
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
this.http2Server.listen(this.port);
|
|
146
|
+
return true;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async close()
|
|
150
|
+
{
|
|
151
|
+
if(!this.http2Server){
|
|
152
|
+
return true;
|
|
153
|
+
}
|
|
154
|
+
return new Promise((resolve) => {
|
|
155
|
+
this.http2Server.close(() => resolve(true));
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
module.exports.Http2CdnServer = Http2CdnServer;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* Reldens - ServerDefaultConfigurations
|
|
4
|
+
*
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
class ServerDefaultConfigurations
|
|
8
|
+
{
|
|
9
|
+
|
|
10
|
+
static get mimeTypes()
|
|
11
|
+
{
|
|
12
|
+
return {
|
|
13
|
+
'.html': 'text/html',
|
|
14
|
+
'.css': 'text/css',
|
|
15
|
+
'.js': 'application/javascript',
|
|
16
|
+
'.json': 'application/json',
|
|
17
|
+
'.xml': 'application/xml',
|
|
18
|
+
'.txt': 'text/plain',
|
|
19
|
+
'.jpg': 'image/jpeg',
|
|
20
|
+
'.jpeg': 'image/jpeg',
|
|
21
|
+
'.png': 'image/png',
|
|
22
|
+
'.gif': 'image/gif',
|
|
23
|
+
'.webp': 'image/webp',
|
|
24
|
+
'.svg': 'image/svg+xml',
|
|
25
|
+
'.ico': 'image/x-icon',
|
|
26
|
+
'.woff': 'font/woff',
|
|
27
|
+
'.woff2': 'font/woff2',
|
|
28
|
+
'.ttf': 'font/ttf',
|
|
29
|
+
'.eot': 'application/vnd.ms-fontobject',
|
|
30
|
+
'.webmanifest': 'application/manifest+json'
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
static get cacheConfig()
|
|
35
|
+
{
|
|
36
|
+
return {
|
|
37
|
+
'.css': 31536000,
|
|
38
|
+
'.js': 31536000,
|
|
39
|
+
'.woff': 31536000,
|
|
40
|
+
'.woff2': 31536000,
|
|
41
|
+
'.ttf': 31536000,
|
|
42
|
+
'.eot': 31536000,
|
|
43
|
+
'.jpg': 2592000,
|
|
44
|
+
'.jpeg': 2592000,
|
|
45
|
+
'.png': 2592000,
|
|
46
|
+
'.gif': 2592000,
|
|
47
|
+
'.webp': 2592000,
|
|
48
|
+
'.svg': 2592000,
|
|
49
|
+
'.ico': 2592000,
|
|
50
|
+
'.xml': 31536000,
|
|
51
|
+
'.webmanifest': 31536000
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
module.exports.ServerDefaultConfigurations = ServerDefaultConfigurations;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* Reldens - ServerFactoryUtils
|
|
4
|
+
*
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
class ServerFactoryUtils
|
|
8
|
+
{
|
|
9
|
+
|
|
10
|
+
static getCacheConfigForPath(path, cacheConfig)
|
|
11
|
+
{
|
|
12
|
+
if(!cacheConfig){
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
let cacheKeys = Object.keys(cacheConfig);
|
|
16
|
+
for(let ext of cacheKeys){
|
|
17
|
+
if(path.endsWith(ext)){
|
|
18
|
+
return cacheConfig[ext];
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
static validateOrigin(origin, corsOrigins, corsAllowAll)
|
|
25
|
+
{
|
|
26
|
+
if(corsAllowAll){
|
|
27
|
+
return '*';
|
|
28
|
+
}
|
|
29
|
+
if(!origin){
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
if(0 === corsOrigins.length){
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
for(let allowedOrigin of corsOrigins){
|
|
36
|
+
if('string' === typeof allowedOrigin && origin === allowedOrigin){
|
|
37
|
+
return origin;
|
|
38
|
+
}
|
|
39
|
+
if(allowedOrigin instanceof RegExp && allowedOrigin.test(origin)){
|
|
40
|
+
return origin;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
static stripQueryString(url)
|
|
47
|
+
{
|
|
48
|
+
if(!url){
|
|
49
|
+
return '';
|
|
50
|
+
}
|
|
51
|
+
return url.split('?')[0];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
module.exports.ServerFactoryUtils = ServerFactoryUtils;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* Reldens - ServerHeaders
|
|
4
|
+
*
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
class ServerHeaders
|
|
8
|
+
{
|
|
9
|
+
|
|
10
|
+
constructor()
|
|
11
|
+
{
|
|
12
|
+
this.http2SecurityHeaders = {
|
|
13
|
+
'x-content-type-options': 'nosniff',
|
|
14
|
+
'x-frame-options': 'DENY'
|
|
15
|
+
};
|
|
16
|
+
this.http2VaryHeader = 'Accept-Encoding, Origin';
|
|
17
|
+
this.http2CorsMethods = 'GET, OPTIONS';
|
|
18
|
+
this.http2CorsHeaders = 'Content-Type';
|
|
19
|
+
this.expressSecurityHeaders = {
|
|
20
|
+
'X-Content-Type-Options': 'nosniff',
|
|
21
|
+
'X-Frame-Options': 'DENY'
|
|
22
|
+
};
|
|
23
|
+
this.expressVaryHeader = 'Accept-Encoding';
|
|
24
|
+
this.expressCacheControlNoCache = 'no-cache, no-store, must-revalidate';
|
|
25
|
+
this.expressCacheControlPublic = 'public, max-age={maxAge}, immutable';
|
|
26
|
+
this.expressPragma = 'no-cache';
|
|
27
|
+
this.expressExpires = '0';
|
|
28
|
+
this.proxyForwardedFor = 'X-Forwarded-For';
|
|
29
|
+
this.proxyForwardedProto = 'X-Forwarded-Proto';
|
|
30
|
+
this.proxyForwardedHost = 'X-Forwarded-Host';
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
buildCacheControlHeader(maxAge)
|
|
34
|
+
{
|
|
35
|
+
return this.expressCacheControlPublic.replace('{maxAge}', maxAge);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
module.exports.ServerHeaders = ServerHeaders;
|
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.39.0",
|
|
5
5
|
"description": "Reldens - Server Utils",
|
|
6
6
|
"author": "Damian A. Pastorini",
|
|
7
7
|
"license": "MIT",
|
|
@@ -43,6 +43,7 @@
|
|
|
43
43
|
"express-rate-limit": "8.1.0",
|
|
44
44
|
"express-session": "1.18.2",
|
|
45
45
|
"helmet": "8.1.0",
|
|
46
|
+
"http-proxy-middleware": "3.0.5",
|
|
46
47
|
"multer": "2.0.2",
|
|
47
48
|
"sanitize-html": "2.17.0"
|
|
48
49
|
}
|