@contrast/route-coverage 1.6.0 → 1.8.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/lib/index.d.ts +1 -1
- package/lib/index.js +1 -1
- package/lib/install/express.js +104 -44
- package/lib/install/fastify.js +3 -2
- package/lib/install/koa.js +3 -2
- package/package.json +2 -2
package/lib/index.d.ts
CHANGED
|
@@ -25,7 +25,7 @@ export interface RouteCoverage extends Installable {
|
|
|
25
25
|
discover(info: RouteInfo): void;
|
|
26
26
|
delete(info: RouteInfo): void;
|
|
27
27
|
discoveryFinished(): void;
|
|
28
|
-
observe(info: Pick<RouteInfo
|
|
28
|
+
observe(info: Pick<RouteInfo>): void;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
export interface Core {
|
package/lib/index.js
CHANGED
|
@@ -46,7 +46,7 @@ module.exports = function init(core) {
|
|
|
46
46
|
},
|
|
47
47
|
|
|
48
48
|
observe(info) {
|
|
49
|
-
const route = routeInfo.get(routeIdentifier(info));
|
|
49
|
+
const route = info.signature ? info : routeInfo.get(routeIdentifier(info));
|
|
50
50
|
|
|
51
51
|
if (!route) {
|
|
52
52
|
logger.debug(info, 'unable to observe undiscovered route');
|
package/lib/install/express.js
CHANGED
|
@@ -25,8 +25,10 @@ const METHODS = [
|
|
|
25
25
|
'options',
|
|
26
26
|
'head',
|
|
27
27
|
];
|
|
28
|
-
|
|
29
|
-
const
|
|
28
|
+
// eslint-disable-next-line node/no-extraneous-require
|
|
29
|
+
const fnInspect = require('@contrast/fn-inspect');
|
|
30
|
+
const { createSignature, patchType } = require('../utils/route-info');
|
|
31
|
+
const { join, replace, toLowerCase } = require('@contrast/common');
|
|
30
32
|
|
|
31
33
|
/**
|
|
32
34
|
* @param {import('..').Core & {
|
|
@@ -37,27 +39,81 @@ const { createSignature, patchType } = require('./../utils/route-info');
|
|
|
37
39
|
*/
|
|
38
40
|
module.exports = function init(core) {
|
|
39
41
|
const routers = new Map();
|
|
42
|
+
const signatureMap = new Map();
|
|
40
43
|
const { patcher, depHooks, routeCoverage } = core;
|
|
41
44
|
|
|
42
|
-
function
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
45
|
+
function formatUrl(url) {
|
|
46
|
+
if (Array.isArray(url)) {
|
|
47
|
+
return `/[${join(url, ', ')}]`;
|
|
48
|
+
} else if (url instanceof RegExp) {
|
|
49
|
+
return `/{${replace(url.toString(), /(^\/?)|(\/?$)/g, '')}}`;
|
|
50
|
+
} else {
|
|
51
|
+
return url;
|
|
52
|
+
}
|
|
48
53
|
}
|
|
49
54
|
|
|
50
|
-
function
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
return;
|
|
55
|
+
function getLastLayer(router) {
|
|
56
|
+
if (router.stack) {
|
|
57
|
+
const len = router.stack.length;
|
|
58
|
+
return router.stack[len - 1];
|
|
54
59
|
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function getLayerHandleMethod(layer) {
|
|
63
|
+
let methodName = 'handle';
|
|
64
|
+
const __handleData = fnInspect.funcInfo(layer.__handle);
|
|
65
|
+
if (__handleData && __handleData.file.includes('express-async-errors')) {
|
|
66
|
+
methodName = '__handle';
|
|
67
|
+
}
|
|
68
|
+
return methodName;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function patchHandler(layer, route) {
|
|
72
|
+
if (!layer) return;
|
|
73
|
+
const handle = getLayerHandleMethod(layer);
|
|
74
|
+
patcher.patch(layer, handle, {
|
|
75
|
+
name: 'express.Router.handle',
|
|
76
|
+
patchType,
|
|
77
|
+
post(data) {
|
|
78
|
+
const [req] = data.args;
|
|
79
|
+
const method = req?.method && toLowerCase(req.method);
|
|
80
|
+
const url = `${req.baseUrl}${req._parsedUrl.pathname}`;
|
|
81
|
+
const { signature } = signatureMap.get(route.signature);
|
|
82
|
+
if (method) routeCoverage.observe({ signature, url, method });
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function createRoute(url, method, id) {
|
|
88
|
+
const signature = createSignature(url, method);
|
|
89
|
+
const route = { signature, url, method, id: id || signature };
|
|
90
|
+
signatureMap.set(signature, route);
|
|
91
|
+
return route;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function discoverRoute({ signature, url, method }) {
|
|
95
|
+
routeCoverage.discover({ signature, url, method });
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function instrumentRoute(router, route) {
|
|
99
|
+
if (!router) return;
|
|
100
|
+
const layer = getLastLayer(router);
|
|
101
|
+
patchHandler(layer, route);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function updateRoutes(prefix, router, updatedRouter) {
|
|
105
|
+
const routes = routers.get(router);
|
|
106
|
+
const updatedLayers = router === updatedRouter
|
|
107
|
+
? [] : (routers.get(updatedRouter) || []);
|
|
108
|
+
if (routes) {
|
|
109
|
+
routes.forEach((route) => {
|
|
110
|
+
const { url, method, id } = route;
|
|
111
|
+
const newRoute = createRoute(`${prefix}${url}`, method, id);
|
|
112
|
+
updatedLayers.push(newRoute);
|
|
113
|
+
signatureMap.set(id, newRoute);
|
|
58
114
|
});
|
|
59
|
-
|
|
60
|
-
|
|
115
|
+
routers.set(router, updatedLayers);
|
|
116
|
+
routers.set(updatedRouter, updatedLayers);
|
|
61
117
|
}
|
|
62
118
|
}
|
|
63
119
|
|
|
@@ -66,36 +122,29 @@ module.exports = function init(core) {
|
|
|
66
122
|
depHooks.resolve(
|
|
67
123
|
{ name: 'express' },
|
|
68
124
|
(express) => {
|
|
69
|
-
patcher.patch(express.Router, 'handle', {
|
|
70
|
-
name: 'express.Router.handle',
|
|
71
|
-
patchType,
|
|
72
|
-
post(data) {
|
|
73
|
-
// TODO: Can this handle all route observation?
|
|
74
|
-
const [req] = data.args;
|
|
75
|
-
const method = req?.method?.toLowerCase();
|
|
76
|
-
const url = `${req.baseUrl}${req._parsedUrl.pathname}`;
|
|
77
|
-
if (method) routeCoverage.observe({ url, method });
|
|
78
|
-
}
|
|
79
|
-
});
|
|
80
125
|
|
|
81
126
|
patcher.patch(express.Router, 'use', {
|
|
82
127
|
name: 'express.Router.use',
|
|
83
128
|
patchType,
|
|
84
129
|
post({ args, result }) {
|
|
85
130
|
const [prefix, router] = args;
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
131
|
+
if (typeof prefix === 'string' && prefix !== '/') {
|
|
132
|
+
updateRoutes(prefix, router, result);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
patcher.patch(express.application, 'use', {
|
|
138
|
+
name: 'express.application.use',
|
|
139
|
+
patchType,
|
|
140
|
+
post({ args }) {
|
|
141
|
+
const idx = args.length;
|
|
142
|
+
const router = args[idx - 1];
|
|
143
|
+
const routes = routers.get(router);
|
|
144
|
+
if (routes) {
|
|
145
|
+
routes.forEach((route) => {
|
|
146
|
+
discoverRoute(route);
|
|
96
147
|
});
|
|
97
|
-
routers.delete(router);
|
|
98
|
-
routers.set(result, updatedLayers);
|
|
99
148
|
}
|
|
100
149
|
}
|
|
101
150
|
});
|
|
@@ -104,16 +153,27 @@ module.exports = function init(core) {
|
|
|
104
153
|
patcher.patch(express.application, method, {
|
|
105
154
|
name: `express.application.${method}`,
|
|
106
155
|
patchType,
|
|
107
|
-
post(
|
|
108
|
-
|
|
156
|
+
post({ args, result }) {
|
|
157
|
+
const [url, fn] = args;
|
|
158
|
+
if (!url || !fn) return;
|
|
159
|
+
const route = createRoute(formatUrl(url), method);
|
|
160
|
+
instrumentRoute(result?._router, route);
|
|
161
|
+
discoverRoute(route);
|
|
109
162
|
}
|
|
110
163
|
});
|
|
111
164
|
|
|
112
165
|
patcher.patch(express.Router, method, {
|
|
113
166
|
name: `express.Router.${method}`,
|
|
114
167
|
patchType,
|
|
115
|
-
post(
|
|
116
|
-
|
|
168
|
+
post({ args, obj: router }) {
|
|
169
|
+
const [url, fn] = args;
|
|
170
|
+
if (!url || !fn) return;
|
|
171
|
+
const route = createRoute(formatUrl(url), method);
|
|
172
|
+
const routes = routers.get(router) || [];
|
|
173
|
+
|
|
174
|
+
instrumentRoute(router, route);
|
|
175
|
+
routes.push(route);
|
|
176
|
+
routers.set(router, routes);
|
|
117
177
|
}
|
|
118
178
|
});
|
|
119
179
|
});
|
package/lib/install/fastify.js
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
'use strict';
|
|
17
17
|
|
|
18
18
|
const { createSignature } = require('./../utils/route-info');
|
|
19
|
+
const { toLowerCase } = require('@contrast/common');
|
|
19
20
|
|
|
20
21
|
/** @typedef {Parameters<import('fastify3.0.0').onRouteHookHandler>[0]} RouteOptions */
|
|
21
22
|
|
|
@@ -67,7 +68,7 @@ module.exports = function init(core) {
|
|
|
67
68
|
* @param {string} method
|
|
68
69
|
*/
|
|
69
70
|
function emitRouteCoverage(url, method) {
|
|
70
|
-
method =
|
|
71
|
+
method = toLowerCase(method);
|
|
71
72
|
const event = { signature: createSignature(url, method), url, method };
|
|
72
73
|
routeCoverage.discover(event);
|
|
73
74
|
}
|
|
@@ -77,7 +78,7 @@ module.exports = function init(core) {
|
|
|
77
78
|
* @param {string=} method
|
|
78
79
|
*/
|
|
79
80
|
function emitObservation(url, method) {
|
|
80
|
-
method = method
|
|
81
|
+
method = method && toLowerCase(method);
|
|
81
82
|
routeCoverage.observe({ method, url });
|
|
82
83
|
}
|
|
83
84
|
|
package/lib/install/koa.js
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
'use strict';
|
|
16
16
|
|
|
17
17
|
const { createSignature, patchType } = require('./../utils/route-info');
|
|
18
|
+
const { toLowerCase } = require('@contrast/common');
|
|
18
19
|
|
|
19
20
|
module.exports = function init(core) {
|
|
20
21
|
const { patcher, depHooks, routeCoverage } = core;
|
|
@@ -30,7 +31,7 @@ module.exports = function init(core) {
|
|
|
30
31
|
|
|
31
32
|
if (req) {
|
|
32
33
|
const { method } = req;
|
|
33
|
-
routeCoverage.observe({ url: path, method:
|
|
34
|
+
routeCoverage.observe({ url: path, method: toLowerCase(method || '') });
|
|
34
35
|
}
|
|
35
36
|
|
|
36
37
|
await next();
|
|
@@ -50,7 +51,7 @@ module.exports = function init(core) {
|
|
|
50
51
|
emitRouteCoverage(path, 'use');
|
|
51
52
|
} else {
|
|
52
53
|
methods.forEach((method) => {
|
|
53
|
-
emitRouteCoverage(path,
|
|
54
|
+
emitRouteCoverage(path, toLowerCase(method || ''));
|
|
54
55
|
});
|
|
55
56
|
}
|
|
56
57
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contrast/route-coverage",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"author": "Contrast Security <nodejs@contrastsecurity.com> (https://www.contrastsecurity.com)",
|
|
@@ -14,6 +14,6 @@
|
|
|
14
14
|
"test": "../scripts/test.sh"
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"@contrast/common": "1.
|
|
17
|
+
"@contrast/common": "1.12.0"
|
|
18
18
|
}
|
|
19
19
|
}
|