zsyp 1.1.0 → 1.2.2
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/index.js +1 -4
- package/lib/app.js +8 -3
- package/lib/converter.js +23 -0
- package/lib/event.js +75 -0
- package/lib/logger.js +18 -16
- package/lib/router.js +15 -27
- package/lib/source-map.js +57 -0
- package/package.json +5 -2
package/index.js
CHANGED
|
@@ -1,15 +1,12 @@
|
|
|
1
1
|
require('dotenv').config({ path: '/etc/default/zsyp' });
|
|
2
2
|
|
|
3
|
-
const logger = require('./lib/logger');
|
|
4
3
|
const makeApp = require('./lib/app');
|
|
5
4
|
|
|
6
5
|
const {
|
|
7
6
|
ZSYP_PORT: PORT = 3090,
|
|
8
7
|
} = process.env;
|
|
9
8
|
|
|
10
|
-
const app = makeApp(
|
|
11
|
-
logger
|
|
12
|
-
});
|
|
9
|
+
const app = makeApp();
|
|
13
10
|
|
|
14
11
|
module.exports = app;
|
|
15
12
|
|
package/lib/app.js
CHANGED
|
@@ -1,20 +1,25 @@
|
|
|
1
1
|
const connect = require('@pirxpilot/connect');
|
|
2
|
+
const mniam = require('mniam');
|
|
2
3
|
const { json } = require('body-parser');
|
|
3
4
|
const router = require('./router');
|
|
5
|
+
const event = require('./event');
|
|
4
6
|
|
|
5
7
|
module.exports = makeApp;
|
|
6
8
|
|
|
7
9
|
const {
|
|
8
10
|
ZSYP_DOMAINS: domains,
|
|
11
|
+
ZSYP_DB: database = 'mongodb://localhost/zsyp'
|
|
9
12
|
} = process.env;
|
|
10
13
|
|
|
11
|
-
|
|
12
|
-
function makeApp(opts) {
|
|
14
|
+
function makeApp(opts = {}) {
|
|
13
15
|
const app = connect();
|
|
14
16
|
|
|
17
|
+
opts.db = mniam.db(database);
|
|
18
|
+
app.db = opts.db;
|
|
19
|
+
|
|
15
20
|
app.use(json({ limit: 5000, type: ['*/json', 'application/csp-report'] }));
|
|
16
21
|
app.use('/csp', router({ ...opts, name: 'csp', domains }));
|
|
17
|
-
app.use('/event', router({ ...opts,
|
|
22
|
+
app.use('/event', router({ ...opts, converter: event.converter }));
|
|
18
23
|
|
|
19
24
|
return app;
|
|
20
25
|
}
|
package/lib/converter.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module.exports = makeConverter;
|
|
2
|
+
|
|
3
|
+
function makeConverter({ converter }) {
|
|
4
|
+
|
|
5
|
+
async function convert(req, res, next) {
|
|
6
|
+
try {
|
|
7
|
+
const { item, meta } = await converter(req.body);
|
|
8
|
+
req.item = item;
|
|
9
|
+
req.meta = meta;
|
|
10
|
+
return next();
|
|
11
|
+
} catch(e) {
|
|
12
|
+
return next(e);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return converter ? convert : keep;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function keep(req, res, next) {
|
|
20
|
+
req.item = req.body;
|
|
21
|
+
return next();
|
|
22
|
+
}
|
|
23
|
+
|
package/lib/event.js
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
const { ObjectId } = require('mongodb');
|
|
2
|
+
const { createHash } = require('node:crypto');
|
|
3
|
+
const stackParser = require('error-stack-parser');
|
|
4
|
+
const { resolve } = require('./source-map');
|
|
5
|
+
|
|
6
|
+
module.exports = {
|
|
7
|
+
converter
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
async function converter(event) {
|
|
11
|
+
const { type = 'event' } = event;
|
|
12
|
+
let item;
|
|
13
|
+
let name;
|
|
14
|
+
switch (type) {
|
|
15
|
+
case 'error':
|
|
16
|
+
case 'exception':
|
|
17
|
+
name = 'error';
|
|
18
|
+
item = await convertError(event);
|
|
19
|
+
break;
|
|
20
|
+
default:
|
|
21
|
+
name = 'event';
|
|
22
|
+
item = event;
|
|
23
|
+
break;
|
|
24
|
+
}
|
|
25
|
+
return {
|
|
26
|
+
item,
|
|
27
|
+
meta: { name }
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function convertError(error) {
|
|
32
|
+
const { an, av, stack } = error;
|
|
33
|
+
const frames = safeParseStack(error).map(mapFrame);
|
|
34
|
+
error.stack = await Promise.all(frames);
|
|
35
|
+
delete error.type;
|
|
36
|
+
if (stack) {
|
|
37
|
+
// save original stack if present
|
|
38
|
+
error.org_stack = stack;
|
|
39
|
+
}
|
|
40
|
+
return addHash(error);
|
|
41
|
+
|
|
42
|
+
function safeParseStack(error) {
|
|
43
|
+
try {
|
|
44
|
+
return stackParser.parse(error);
|
|
45
|
+
} catch {
|
|
46
|
+
return [];
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function mapFrame(frame) {
|
|
51
|
+
const { fileName, lineNumber, columnNumber = 0, functionName } = frame;
|
|
52
|
+
const resolved = await resolve({ an, av }, [normalizeFilename(fileName), lineNumber, columnNumber]);
|
|
53
|
+
resolved.splice(3, 0, functionName);
|
|
54
|
+
return resolved;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function normalizeFilename(fileName) {
|
|
59
|
+
const file = new URL(fileName, 'http://localhost');
|
|
60
|
+
// strip hostname and leading slashes
|
|
61
|
+
return file.pathname.replace(/^\/+/, '');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function addHash(error) {
|
|
65
|
+
if (error.stack.length < 1) {
|
|
66
|
+
return error;
|
|
67
|
+
}
|
|
68
|
+
const hasher = createHash('shake128', { outputLength: 12 });
|
|
69
|
+
error.stack.some(([file, line], index) => {
|
|
70
|
+
hasher.update(`${file}:${line}`);
|
|
71
|
+
return index > 9; // only hash at most 10 stack lines
|
|
72
|
+
});
|
|
73
|
+
error._hash = new ObjectId(hasher.digest());
|
|
74
|
+
return error;
|
|
75
|
+
}
|
package/lib/logger.js
CHANGED
|
@@ -1,26 +1,28 @@
|
|
|
1
|
-
const mniam = require('mniam');
|
|
2
1
|
const debug = require('debug')('zsyp:logger');
|
|
3
2
|
|
|
4
3
|
module.exports = makeLogger;
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
} = process.env;
|
|
5
|
+
function makeLogger({ db, name }) {
|
|
6
|
+
const cache = Object.create(null);
|
|
9
7
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
function makeLogger({ name }) {
|
|
14
|
-
const collection = db.collection({ name });
|
|
8
|
+
const getCollection = name ?
|
|
9
|
+
() => collectionFromCache(name) :
|
|
10
|
+
({ name }) => collectionFromCache(name);
|
|
15
11
|
|
|
16
12
|
return log;
|
|
17
13
|
|
|
18
|
-
function log(
|
|
19
|
-
debug('saving %j',
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
}
|
|
24
|
-
|
|
14
|
+
function log({ from, item, meta }, res, next) {
|
|
15
|
+
debug('saving %j', item);
|
|
16
|
+
getCollection(meta)
|
|
17
|
+
.insertOne({ ...item, from })
|
|
18
|
+
.then(() => next())
|
|
19
|
+
.catch(err => { console.error(err); next(); });
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function collectionFromCache(name) {
|
|
23
|
+
if (!cache[name]) {
|
|
24
|
+
cache[name] = db.collection({ name });
|
|
25
|
+
}
|
|
26
|
+
return cache[name];
|
|
25
27
|
}
|
|
26
28
|
}
|
package/lib/router.js
CHANGED
|
@@ -1,14 +1,9 @@
|
|
|
1
1
|
const Router = require('router');
|
|
2
|
-
const debug = require('debug')('zsyp:router');
|
|
3
2
|
|
|
4
3
|
const from = require('./from');
|
|
5
4
|
const makeFilter = require('./filter');
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
res.statusCode = 204; // empty
|
|
9
|
-
res.end();
|
|
10
|
-
next();
|
|
11
|
-
}
|
|
5
|
+
const makeConverter = require('./converter');
|
|
6
|
+
const makeLogger = require('./logger');
|
|
12
7
|
|
|
13
8
|
module.exports = function (opts) {
|
|
14
9
|
const router = new Router({
|
|
@@ -17,28 +12,21 @@ module.exports = function (opts) {
|
|
|
17
12
|
});
|
|
18
13
|
|
|
19
14
|
const stack = [
|
|
15
|
+
respond,
|
|
20
16
|
from,
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
stack.push(filter);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const logger = opts.logger(opts);
|
|
17
|
+
makeFilter(opts),
|
|
18
|
+
makeConverter(opts),
|
|
19
|
+
makeLogger(opts),
|
|
20
|
+
opts.finalMiddleware
|
|
21
|
+
].filter(Boolean);
|
|
30
22
|
|
|
31
|
-
router.post('/',
|
|
23
|
+
router.post('/', stack);
|
|
32
24
|
|
|
33
25
|
return router;
|
|
34
|
-
|
|
35
|
-
function log({ from, body }) {
|
|
36
|
-
const report = {
|
|
37
|
-
...body,
|
|
38
|
-
from
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
debug('Logging report %j', report);
|
|
42
|
-
logger(report);
|
|
43
|
-
}
|
|
44
26
|
};
|
|
27
|
+
|
|
28
|
+
function respond(req, res, next) {
|
|
29
|
+
res.statusCode = 204; // empty
|
|
30
|
+
res.end();
|
|
31
|
+
next();
|
|
32
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
const { readFile } = require('fs').promises;
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { SourceMapConsumer } = require('source-map');
|
|
4
|
+
const LRU = require('lru-cache');
|
|
5
|
+
|
|
6
|
+
module.exports = {
|
|
7
|
+
resolve,
|
|
8
|
+
clear
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const {
|
|
12
|
+
ZSYP_SOURCE_MAP_DIR = '/var/lib/zsyp',
|
|
13
|
+
ZSYP_SOURCE_MAP_CACHE_SIZE = 30
|
|
14
|
+
} = process.env;
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
const cache = new LRU({
|
|
18
|
+
max: ZSYP_SOURCE_MAP_CACHE_SIZE,
|
|
19
|
+
fetchMethod: fetchSourceMap,
|
|
20
|
+
dispose: smc => smc && smc.destroy()
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
async function resolve({ an, av }, frame) {
|
|
24
|
+
const [source, line, column ] = frame;
|
|
25
|
+
const smc = await loadSourceMap(an, av, source);
|
|
26
|
+
if (!smc) {
|
|
27
|
+
return frame;
|
|
28
|
+
}
|
|
29
|
+
const pos = smc.originalPositionFor({ line, column });
|
|
30
|
+
if (!pos) {
|
|
31
|
+
return frame;
|
|
32
|
+
}
|
|
33
|
+
const resolved = [pos.source, pos.line, pos.column];
|
|
34
|
+
if (pos.name) {
|
|
35
|
+
resolved.push(pos.name);
|
|
36
|
+
}
|
|
37
|
+
return resolved;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function clear() {
|
|
41
|
+
cache.clear();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function loadSourceMap(app, version, source) {
|
|
45
|
+
const filename = path.resolve(ZSYP_SOURCE_MAP_DIR, app, version, source);
|
|
46
|
+
return cache.fetch(filename);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function fetchSourceMap(filename) {
|
|
50
|
+
try {
|
|
51
|
+
const txt = await readFile(filename + '.map');
|
|
52
|
+
const map = JSON.parse(txt);
|
|
53
|
+
return new SourceMapConsumer(map);
|
|
54
|
+
} catch {
|
|
55
|
+
// ignore errors during map parsing
|
|
56
|
+
}
|
|
57
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zsyp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.2",
|
|
4
4
|
"description": "CSP violation reports logger.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Damian Krzeminski",
|
|
@@ -20,8 +20,11 @@
|
|
|
20
20
|
"body-parser": "~1",
|
|
21
21
|
"debug": "~2 || ~3 || ~4",
|
|
22
22
|
"dotenv": "~16",
|
|
23
|
-
"
|
|
23
|
+
"error-stack-parser": "^2.1.4",
|
|
24
|
+
"lru-cache": "^7.10.1",
|
|
25
|
+
"mniam": "^3.0.0",
|
|
24
26
|
"router": "~1",
|
|
27
|
+
"source-map": "^0.7.4",
|
|
25
28
|
"supertest": "~6",
|
|
26
29
|
"useragent": "^2.3.0"
|
|
27
30
|
},
|