slower 1.1.11 → 1.1.13
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/lib/router.js +36 -25
- package/lib/utils.js +56 -1
- package/package.json +1 -1
- package/readme.md +124 -1
package/lib/router.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const http = require('http');
|
|
2
2
|
const fs = require('fs');
|
|
3
|
-
const { noop, slugify, isSparseEqual, last, renderDynamicHTML, setSocketLocals } = require('./utils');
|
|
3
|
+
const { clone, noop, slugify, isSparseEqual, last, renderDynamicHTML, setSocketLocals, setSocketSecurityHeaders } = require('./utils');
|
|
4
4
|
|
|
5
5
|
class Route {
|
|
6
6
|
constructor (path, type, callback) {
|
|
@@ -33,13 +33,28 @@ class SlowerRouter {
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
constructor () {
|
|
36
|
+
this.strictHeaders = false;
|
|
36
37
|
this.routes = [];
|
|
37
38
|
this.middleware = [noop];
|
|
38
39
|
this.fallback = noop;
|
|
39
|
-
this.allowedMethods =
|
|
40
|
+
this.allowedMethods = clone(SlowerRouter.http_methods);
|
|
40
41
|
this.blockedMethodCallback = noop;
|
|
41
42
|
}
|
|
42
43
|
|
|
44
|
+
enableStrictHeaders () {
|
|
45
|
+
// This activates the responses with many security-related response headers.
|
|
46
|
+
// It is not enabled by default, as some header are very restrictive
|
|
47
|
+
this.strictHeaders = true;
|
|
48
|
+
return this;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
disableStrictHeaders () {
|
|
52
|
+
// This deactivates the responses with many security-related response headers.
|
|
53
|
+
// It is not enabled by default, as some headers are very restrictive
|
|
54
|
+
this.strictHeaders = false;
|
|
55
|
+
return this;
|
|
56
|
+
}
|
|
57
|
+
|
|
43
58
|
/**
|
|
44
59
|
* Defines a route for a determined request method
|
|
45
60
|
* @category Router
|
|
@@ -57,7 +72,7 @@ class SlowerRouter {
|
|
|
57
72
|
setRoute = function (path = '/', type = 'GET', callback) {
|
|
58
73
|
let stat = new Route(path, type, callback);
|
|
59
74
|
this.routes.push(stat);
|
|
60
|
-
return
|
|
75
|
+
return this;
|
|
61
76
|
}
|
|
62
77
|
|
|
63
78
|
/**
|
|
@@ -67,13 +82,10 @@ class SlowerRouter {
|
|
|
67
82
|
* @returns {Array} The used middlewares list
|
|
68
83
|
*/
|
|
69
84
|
setMiddleware = function (callback) {
|
|
70
|
-
|
|
85
|
+
this.middleware.push((typeof callback == 'function' ? callback : noop));
|
|
86
|
+
return this;
|
|
71
87
|
}
|
|
72
88
|
|
|
73
|
-
// SERVES FILES FOR SPECIFIC PATHS. WILDCARS ALLOWED. BE CAREFUL - PATH TRAVERSAL MAY BE POSSIBLE
|
|
74
|
-
// Use app.setStatic('/*', __dirname+'/') for serving all local files.
|
|
75
|
-
// or use app.setStatic('/*', __dirname+'/public/') for serving files in a specific folder (public).
|
|
76
|
-
// This is a one-liner for the setRoute function, when the route responds with a simple page or document
|
|
77
89
|
/**
|
|
78
90
|
* Defines a route for a determined request method
|
|
79
91
|
* @category Router
|
|
@@ -82,8 +94,6 @@ class SlowerRouter {
|
|
|
82
94
|
* @param {String} mime The file's mime type
|
|
83
95
|
* @param {Object} replacementData The replacement data map for the dynamic HTML rendering
|
|
84
96
|
* @returns {Object} An Route object already configured
|
|
85
|
-
* @example <caption> Defining a simple GET route:</caption>
|
|
86
|
-
* setStatic('/login', __dirname+'/public/static/views/login.html', 'text/html', 'utf8');
|
|
87
97
|
*/
|
|
88
98
|
/**
|
|
89
99
|
* Dynamic rendering example:
|
|
@@ -102,7 +112,7 @@ class SlowerRouter {
|
|
|
102
112
|
* age: 29
|
|
103
113
|
* }));
|
|
104
114
|
*/
|
|
105
|
-
setDynamic = function (path, file = '', mime = '', replacementData) {
|
|
115
|
+
setDynamic = function (path, file = '', mime = '', replacementData = null) {
|
|
106
116
|
let encoding = (mime === SlowerRouter.mime_table['default'] ? undefined : 'utf-8')
|
|
107
117
|
let stat = new Route(path, 'GET', (req, res) => {
|
|
108
118
|
let data, targetFile, extension, targetMime, targetEncoding;
|
|
@@ -128,7 +138,7 @@ class SlowerRouter {
|
|
|
128
138
|
res.end();
|
|
129
139
|
});
|
|
130
140
|
this.routes.push(stat);
|
|
131
|
-
return
|
|
141
|
+
return this;
|
|
132
142
|
}
|
|
133
143
|
|
|
134
144
|
// SERVES FILES FOR SPECIFIC PATHS. WILDCARS ALLOWED. BE CAREFUL - PATH TRAVERSAL MAY BE POSSIBLE
|
|
@@ -141,10 +151,9 @@ class SlowerRouter {
|
|
|
141
151
|
* @param {String} path The route that will be defined
|
|
142
152
|
* @param {String} file The file path that will be used to respond to the route
|
|
143
153
|
* @param {String} mime The file's mime type
|
|
144
|
-
* @param {String} encoding The file's encoding (defaults to UTF-8)
|
|
145
154
|
* @returns {Object} An Route object already configured
|
|
146
155
|
* @example <caption> Defining a simple GET route:</caption>
|
|
147
|
-
* setStatic('/login', __dirname+'/public/static/views/login.html', 'text/html'
|
|
156
|
+
* setStatic('/login', __dirname+'/public/static/views/login.html', 'text/html');
|
|
148
157
|
*/
|
|
149
158
|
setStatic = function (path, file = '', mime = '') {
|
|
150
159
|
let encoding = (mime === SlowerRouter.mime_table['default'] ? undefined : 'utf-8')
|
|
@@ -169,7 +178,7 @@ class SlowerRouter {
|
|
|
169
178
|
res.end();
|
|
170
179
|
});
|
|
171
180
|
this.routes.push(stat);
|
|
172
|
-
return
|
|
181
|
+
return this;
|
|
173
182
|
}
|
|
174
183
|
|
|
175
184
|
/**
|
|
@@ -177,10 +186,8 @@ class SlowerRouter {
|
|
|
177
186
|
* @category Router
|
|
178
187
|
* @param {String} callback The function to execute for unhandled routes
|
|
179
188
|
* @returns {undefined}
|
|
180
|
-
* @example <caption> Defining a simple GET route:</caption>
|
|
181
|
-
* setStatic('/login', __dirname+'/public/static/views/login.html', 'text/html', 'utf8');
|
|
182
189
|
*/
|
|
183
|
-
setFallback = function (callback) { this.fallback = callback; }
|
|
190
|
+
setFallback = function (callback) { this.fallback = callback; return this; }
|
|
184
191
|
|
|
185
192
|
// SERVES FILES FOR SPECIFIC PATHS. WILDCARS ALLOWED. BE CAREFUL - PATH TRAVERSAL MAY BE POSSIBLE
|
|
186
193
|
// Use app.setStatic('/*', __dirname+'/') for serving all local files.
|
|
@@ -214,7 +221,7 @@ class SlowerRouter {
|
|
|
214
221
|
* age: 29
|
|
215
222
|
* }));
|
|
216
223
|
*/
|
|
217
|
-
setFallbackFile = function (file = '', mime = '', replacementData) {
|
|
224
|
+
setFallbackFile = function (file = '', mime = '', replacementData = null) {
|
|
218
225
|
this.fallback = function fb (req,res) {
|
|
219
226
|
let encoding = (mime === SlowerRouter.mime_table['default'] ? undefined : 'utf-8')
|
|
220
227
|
let data, targetFile, extension, targetMime, targetEncoding;
|
|
@@ -239,6 +246,7 @@ class SlowerRouter {
|
|
|
239
246
|
res.write(data);
|
|
240
247
|
res.end();
|
|
241
248
|
}
|
|
249
|
+
return this;
|
|
242
250
|
}
|
|
243
251
|
|
|
244
252
|
// Sets the methods that the application will respond to. The rest is simply discarded with empty responses.
|
|
@@ -267,7 +275,7 @@ class SlowerRouter {
|
|
|
267
275
|
res.writeHead(405); // sends the meme error 418 'i am a teapot'
|
|
268
276
|
res.end();
|
|
269
277
|
};
|
|
270
|
-
return this
|
|
278
|
+
return this;
|
|
271
279
|
}
|
|
272
280
|
|
|
273
281
|
// Starts the server - listening at <host>:<port>
|
|
@@ -275,10 +283,8 @@ class SlowerRouter {
|
|
|
275
283
|
* @category Router
|
|
276
284
|
* @param {Number} port The port number the server will listen to.
|
|
277
285
|
* @param {String} host The host's network interface address the server will listen into (use a falsy value or '0.0.0.0' for listening on all).
|
|
278
|
-
* @param {Function}
|
|
279
|
-
* @returns {Object} The
|
|
280
|
-
* @example <caption> Allowing only GET and POST:</caption>
|
|
281
|
-
* app.setAllowedMethods(['GET', 'POST']);
|
|
286
|
+
* @param {Function} callback The function to execute after starting the server.
|
|
287
|
+
* @returns {Object<http.Server>} The server instance
|
|
282
288
|
*/
|
|
283
289
|
start = function (port = 8080, host = undefined, callback = () => {}) {
|
|
284
290
|
let routes = this.routes;
|
|
@@ -291,6 +297,10 @@ class SlowerRouter {
|
|
|
291
297
|
try {
|
|
292
298
|
// Sets local req.session object
|
|
293
299
|
setSocketLocals(req);
|
|
300
|
+
// Sets security headers in req.headers
|
|
301
|
+
// This is set before custom callbacks are applies
|
|
302
|
+
// so it is possible to override all of the headers
|
|
303
|
+
if (!!this.strictHeaders) setSocketSecurityHeaders(req);
|
|
294
304
|
// Runs all middlewares
|
|
295
305
|
for (let i = 0; i < middle.length; i++) { middle[i](req, res); }
|
|
296
306
|
// Only respond to allowed methods with callbacks, else, use the default empty response.
|
|
@@ -320,7 +330,8 @@ class SlowerRouter {
|
|
|
320
330
|
});
|
|
321
331
|
callback(server);
|
|
322
332
|
if (!host) host = undefined; // Turn falsy values into undefined, for default behaviour
|
|
323
|
-
return server.listen(port, host);
|
|
333
|
+
// return server.listen(port, host);
|
|
334
|
+
return this;
|
|
324
335
|
}
|
|
325
336
|
}
|
|
326
337
|
|
package/lib/utils.js
CHANGED
|
@@ -86,6 +86,61 @@ const setSocketLocals = (socket) => {
|
|
|
86
86
|
return socket;
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
+
const setSocketSecurityHeaders = (req) => {
|
|
90
|
+
// This should be set in a regular website:
|
|
91
|
+
// Forces the use of HTTPS for a long time, including subDomains - and prevent MitM attacks
|
|
92
|
+
// Does not work in servers that don't allow HTTPS (like this one)
|
|
93
|
+
// req.setHeader('Strict-Transport-Security', ['max-age=31536000', 'includeSubDomains']); // Only works on HTTPS
|
|
94
|
+
|
|
95
|
+
// This blocks requests with MIME type different from style/css and */script
|
|
96
|
+
// TODO test this - probably gonna break the server, as MIMEs are not overriden here
|
|
97
|
+
// req.setHeader('X-Content-Type-Options', 'nosniff');
|
|
98
|
+
|
|
99
|
+
// This deines the main security policy - Usually, when using a website,
|
|
100
|
+
// this should be highly customized, but, for simple sites, it can be leaved like that:
|
|
101
|
+
req.setHeader('Content-Security-Policy', [
|
|
102
|
+
'default-src=none',
|
|
103
|
+
'script-src=self',
|
|
104
|
+
'connect-src=self',
|
|
105
|
+
'img-src=self',
|
|
106
|
+
'style-src=self',
|
|
107
|
+
'frame-ancestors=none',
|
|
108
|
+
'form-action=self',
|
|
109
|
+
// 'upgrade-insecure-requests' // Only works on HTTPS
|
|
110
|
+
]);
|
|
111
|
+
// Isolates the browsing context exclusively to same-origin documents.
|
|
112
|
+
// Cross-origin documents are not loaded in the same browsing context.
|
|
113
|
+
req.setHeader('Cross-Origin-Opener-Policy', 'same-origin');
|
|
114
|
+
// The HTTP Cross-Origin-Resource-Policy response header conveys a desire
|
|
115
|
+
// that the browser blocks no-cors cross-origin/cross-site requests to the given resource.
|
|
116
|
+
req.setHeader('Cross-Origin-Resource-Policy', 'same-site');
|
|
117
|
+
// A new HTTP response header that instructs the browser
|
|
118
|
+
// to prevent synchronous scripting access between same-site cross-origin pages.
|
|
119
|
+
req.setHeader('Origin-Agent-Cluster', '?1');
|
|
120
|
+
// Blocks information about the website when sending
|
|
121
|
+
// local requests or redirections to other sites
|
|
122
|
+
req.setHeader('Referrer-Policy', 'no-referrer');
|
|
123
|
+
// Enabling this makes all URLs in a page (even the cross domain ones)
|
|
124
|
+
// to be prefetched - This is dangerouns in terms of DNS queries
|
|
125
|
+
req.setHeader('X-DNS-Prefetch-Control', 'off');
|
|
126
|
+
// A legacy header, just for IE, and is highly dangerous
|
|
127
|
+
// Not setting this as disabled can cause malicious
|
|
128
|
+
// HTML+JS code to be loaded in the wrong context
|
|
129
|
+
req.setHeader('X-Download-Options', 'noopen');
|
|
130
|
+
// Blocks attempts to display the website as an IFrame
|
|
131
|
+
// If another website tries to display this website as a frame,
|
|
132
|
+
// this header will block it
|
|
133
|
+
req.setHeader('X-Frame-Options', 'DENY');
|
|
134
|
+
// Set the unnecessary XSS-Protection legacy header to disabled
|
|
135
|
+
// This header increases the number of vulnerabilities, and is used only in IE
|
|
136
|
+
req.setHeader('X-XSS-Protection', 0);
|
|
137
|
+
// Remove X-Powered-By - This header allows attackers to
|
|
138
|
+
// gather information about the application engine
|
|
139
|
+
if (req.hasHeader('X-Powered-By')) req.removeHeader('X-Powered-By');
|
|
140
|
+
}
|
|
141
|
+
|
|
89
142
|
const toBool = [() => true, () => false];
|
|
90
143
|
|
|
91
|
-
|
|
144
|
+
const clone = (object) => JSON.parse(JSON.stringify(object));
|
|
145
|
+
|
|
146
|
+
module.exports = { clone, noop, slugify, isSparseEqual, toBool, last, renderDynamicHTML, setSocketLocals, setSocketSecurityHeaders };
|
package/package.json
CHANGED
package/readme.md
CHANGED
|
@@ -3,6 +3,106 @@
|
|
|
3
3
|
Slower is a small web framework, express-like, but simpler and limited.
|
|
4
4
|
It allows for generic route-declaration, fallback pages, and multiple middleware functions.
|
|
5
5
|
|
|
6
|
+
### API Methods:
|
|
7
|
+
|
|
8
|
+
```
|
|
9
|
+
app.enableStrictHeaders(): this
|
|
10
|
+
|
|
11
|
+
> Enables the use of the set of 'Strict Headers'.
|
|
12
|
+
> These headers increase security levels, and are a good practice to apply.
|
|
13
|
+
> However, using these headers in testing scenarios are not a need,
|
|
14
|
+
and may have buggy or negative effects. So, apply those to simulate scenarios only.
|
|
15
|
+
> Headers configured:
|
|
16
|
+
> Content-Security-Policy
|
|
17
|
+
> Cross-Origin-Opener-Policy
|
|
18
|
+
> Cross-Origin-Resource-Policy
|
|
19
|
+
> Origin-Agent-Cluster
|
|
20
|
+
> Referrer-Policy
|
|
21
|
+
> X-DNS-Prefetch-Control (disabled)
|
|
22
|
+
> X-Download-Options
|
|
23
|
+
> X-Frame-Options
|
|
24
|
+
> X-XSS-Protection (disabled)
|
|
25
|
+
> X-Powered-By (removed)
|
|
26
|
+
> Returns the own object instance, so that methods can be chained.
|
|
27
|
+
```
|
|
28
|
+
```
|
|
29
|
+
app.disableStrictHeaders(): this
|
|
30
|
+
|
|
31
|
+
> Disables the use of the set of 'Strict Headers'.
|
|
32
|
+
> See 'enableStrictHeaders()' for more information.
|
|
33
|
+
> Returns the own object instance, so that methods can be chained.
|
|
34
|
+
```
|
|
35
|
+
```
|
|
36
|
+
app.setRoute(string: path = '/', string: type = 'GET', function: callback): this
|
|
37
|
+
|
|
38
|
+
> Creates a new route for path defined in 'path', responding to the HTTP verb defined in 'type' argument.
|
|
39
|
+
> The callback is executed when the route is accessed.
|
|
40
|
+
> Returns the own object instance, so that methods can be chained.
|
|
41
|
+
```
|
|
42
|
+
```
|
|
43
|
+
app.setMiddleware(function: callback): this
|
|
44
|
+
|
|
45
|
+
> Sets a new middleware function: callback function will be accessed for every server access.
|
|
46
|
+
> Many middlewares can be defined, and will be applied in the order they are defined.
|
|
47
|
+
> Returns the own object instance, so that methods can be chained.
|
|
48
|
+
```
|
|
49
|
+
```
|
|
50
|
+
app.setDynamic(string: path, string: file = '', string: mime = '', object: replacementData = null): this
|
|
51
|
+
|
|
52
|
+
> Creates a new GET route for path defined in 'path'.
|
|
53
|
+
> This is a custom file-response route, configured for template rendering just before response.
|
|
54
|
+
> Providing an object as 'replacementData' in this format { valueToBeReplaced: valueToReplace },
|
|
55
|
+
allows for template rendering. The value to replace in the file, uses this notation: '<{content}>'.
|
|
56
|
+
> Example:
|
|
57
|
+
Responding a route for '/custom' with file 'custom.html':
|
|
58
|
+
app.setDynamic('/custom', './templates/custom.html', 'text/html', { smile: ':)' })
|
|
59
|
+
In file './templates/custom.html':
|
|
60
|
+
"<h2> This is a custom thing: <{smile}> </h2>"
|
|
61
|
+
Rendered in browser:
|
|
62
|
+
<h2> This is a custom thing: :) </h2>
|
|
63
|
+
> Returns the own object instance, so that methods can be chained.
|
|
64
|
+
```
|
|
65
|
+
```
|
|
66
|
+
app.setStatic(string: path, string: file = '', string: mime = ''): this
|
|
67
|
+
|
|
68
|
+
> Creates a new GET route for path defined in 'path', responding with the specified file and MIME type.
|
|
69
|
+
> Example: A route for '/login' page, responding with 'login.html' file
|
|
70
|
+
setStatic('/login', __dirname+'/public/static/views/login.html', 'text/html');
|
|
71
|
+
> Returns the own object instance, so that methods can be chained.
|
|
72
|
+
```
|
|
73
|
+
```
|
|
74
|
+
app.setFallback(function: callback): this
|
|
75
|
+
|
|
76
|
+
> Creates a function for fallback state. When no other routes intercept the route, this will be used.
|
|
77
|
+
> Special use for 'page not found' fallback pages or highly customized routes and situations.
|
|
78
|
+
> Returns the own object instance, so that methods can be chained.
|
|
79
|
+
```
|
|
80
|
+
```
|
|
81
|
+
app.setFallbackFile (string: file = '', string: mime = '', object: replacementData = null): this
|
|
82
|
+
|
|
83
|
+
> Creates a function for fallback state. When no other routes intercept the route, this will be used.
|
|
84
|
+
> Equivalent to setFallback, but responds with a file. Allows for template rendering.
|
|
85
|
+
> See 'setDynamic' for more information about template rendering.
|
|
86
|
+
> Special use for 'page not found' fallback pages, ex: './e404.html'.
|
|
87
|
+
> Returns the own object instance, so that methods can be chained.
|
|
88
|
+
```
|
|
89
|
+
```
|
|
90
|
+
app.setAllowedMethods(array: methods = []): this
|
|
91
|
+
|
|
92
|
+
> Sets a list of methods to respond to.
|
|
93
|
+
> By using this, it is possible to restrict the application to avoid
|
|
94
|
+
responding to dangerous HTTP verbs, such as 'DELETE'.
|
|
95
|
+
> By default, all methods are allowed (see Slower.constructor.http_methods)
|
|
96
|
+
> Calling this function without parameters is an easy way to block responses to all requests (lock server).
|
|
97
|
+
> Returns the own object instance, so that methods can be chained.
|
|
98
|
+
```
|
|
99
|
+
```
|
|
100
|
+
app.start(number|string: port = 8080, string: host = undefined, function: callback = ()=>{}): this
|
|
101
|
+
|
|
102
|
+
> Starts the server, at a specific host and port, then calling the callback function.
|
|
103
|
+
> Not defining a specific port or host will start the server at '0.0.0.0:8080'.
|
|
104
|
+
> Returns the own object instance, so that methods can be chained.
|
|
105
|
+
```
|
|
6
106
|
|
|
7
107
|
Example usage:
|
|
8
108
|
```
|
|
@@ -46,6 +146,7 @@ app.start(port, null, () => {
|
|
|
46
146
|
console.log(`Running on localhost:${port}`);
|
|
47
147
|
console.log(app);
|
|
48
148
|
});
|
|
149
|
+
|
|
49
150
|
```
|
|
50
151
|
### API modifications on 'net.Socket' instances:
|
|
51
152
|
- The API modifies every ```net.Socket``` instance BEFORE it is passed
|
|
@@ -63,4 +164,26 @@ a socket will receive the modified socket instead.
|
|
|
63
164
|
during the lifetime of a single connection. Useful for keeping short-life local variables.
|
|
64
165
|
|
|
65
166
|
- In HTTP ```http.IncomingMessage``` instances, the 'socket' instance is found over 'request'.
|
|
66
|
-
So, considering the common callback of ```(req, res)```, the session container will be ```req.session```
|
|
167
|
+
So, considering the common callback of ```(req, res)```, the session container will be ```req.session```
|
|
168
|
+
|
|
169
|
+
### API security headers implementation:
|
|
170
|
+
- It is possible to enforce a higher set of security headers on responses
|
|
171
|
+
without having to set them manually. The API 'enableStrictHeaders' and 'disableStrictHeaders'
|
|
172
|
+
methods do exacly that. The strict headers are disabled by default, as some resources are too strict,
|
|
173
|
+
but it is also possible to enable them all, and then set a middleware to override any header.
|
|
174
|
+
- Headers set by 'enableStrictHeaders':
|
|
175
|
+
```
|
|
176
|
+
Content-Security-Policy: default-src=none; script-src=self; connect-src=self; img-src=self;
|
|
177
|
+
style-src=self; frame-ancestors=none; form-action=self;
|
|
178
|
+
Cross-Origin-Opener-Policy: same-origin
|
|
179
|
+
Cross-Origin-Resource-Policy: same-site
|
|
180
|
+
Origin-Agent-Cluster: ?1
|
|
181
|
+
Referrer-Policy: no-referrer
|
|
182
|
+
Strict-Transport-Security: max-age=31536000; includeSubDomains // temporarily disabled for maintenance
|
|
183
|
+
X-Content-Type-Options: nosniff // temporarily disabled for maintenance
|
|
184
|
+
X-DNS-Prefetch-Control: off
|
|
185
|
+
X-Download-Options: noopen
|
|
186
|
+
X-Frame-Options: DENY
|
|
187
|
+
X-Powered-By: (This header is removed if a response includes it)
|
|
188
|
+
X-XSS-Protection: 0
|
|
189
|
+
```
|