zsyp 0.0.2 → 1.1.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 CHANGED
@@ -1,38 +1,81 @@
1
1
  [![NPM version][npm-image]][npm-url]
2
- [![Build Status][travis-image]][travis-url]
2
+ [![Build Status][build-image]][build-url]
3
3
  [![Dependency Status][deps-image]][deps-url]
4
- [![Dev Dependency Status][deps-dev-image]][deps-dev-url]
5
4
 
6
5
  # zsyp
7
6
 
8
- CSP violation reports logger.
7
+ [CSP] violation reports logger. Zsyp is a simple standalone web service that parses CPS violation reports
8
+ and stores them in MongoDB collection.
9
9
 
10
10
  ## Install
11
11
 
12
12
  ```sh
13
- $ npm install --save zsyp
13
+ npm install --global zsyp
14
14
  ```
15
15
 
16
- ## Usage
16
+ ## Environment
17
17
 
18
- ```js
19
- var zsyp = require('zsyp');
18
+ Zsyp is using [dotenv] and by default reads its environment from `/etc/default/zsyp`
20
19
 
21
- zsyp('Rainbow');
20
+ - `ZSYP_PORT` - port number on which, defaults to 3090
21
+ - `ZSYP_DB` - [mongo URI] connection string, defaults to `mongodb://localhost/zsyp`
22
+ - `ZSYP_DOMAINS` - domain name or a regular expression used to filter CSP violation reports - can be left empty in which case all reports for all domains are logged
23
+
24
+ ## Report format
25
+
26
+ ```json5
27
+ {
28
+ "from": {
29
+ "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS...", // User-Agent string
30
+ "browser": { // browser brand and version
31
+ "name": "Safari",
32
+ "version": "13"
33
+ },
34
+ "os": { // operating system info
35
+ "name": "Mac OS X",
36
+ "version": "10"
37
+ },
38
+ "ip": "1.2.3.4" // originator IP address
39
+ },
40
+ "csp-report": { // original CSP report
41
+ "document-uri": "https://example.com/page",
42
+ "referrer": "https://example.com/",
43
+ "violated-directive": "...",
44
+ "effective-directive": "...",
45
+ "original-policy": "...",
46
+ "blocked-uri": "",
47
+ "status-code": 0,
48
+ "source-file": "..."
49
+ }
50
+ }
22
51
  ```
23
52
 
53
+
54
+ ## Logger
55
+
56
+ Reports are stored in `csp` collection. If you want to use [capped collection] create it
57
+ manually before running zsyp.
58
+
59
+ ```javascript
60
+ db.createCollection( "csp", { capped: true, size: 100000 } );
61
+ ```
62
+
63
+
24
64
  ## License
25
65
 
26
66
  MIT © [Damian Krzeminski](https://pirxpilot.me)
27
67
 
28
- [npm-image]: https://img.shields.io/npm/v/zsyp.svg
29
- [npm-url]: https://npmjs.org/package/zsyp
30
68
 
31
- [travis-url]: https://travis-ci.org/pirxpilot/zsyp
32
- [travis-image]: https://img.shields.io/travis/pirxpilot/zsyp.svg
69
+ [CSP]: https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP
70
+ [mongo URI]: https://docs.mongodb.com/manual/reference/connection-string
71
+ [capped collection]: https://docs.mongodb.com/manual/core/capped-collections/
72
+ [dotenv]: https://www.npmjs.com/package/dotenv
73
+
74
+ [npm-image]: https://img.shields.io/npm/v/zsyp
75
+ [npm-url]: https://npmjs.org/package/zsyp
33
76
 
34
- [deps-image]: https://img.shields.io/david/pirxpilot/zsyp.svg
35
- [deps-url]: https://david-dm.org/pirxpilot/zsyp
77
+ [build-url]: https://github.com/pirxpilot/zsyp/actions/workflows/check.yaml
78
+ [build-image]: https://img.shields.io/github/workflow/status/pirxpilot/zsyp/check
36
79
 
37
- [deps-dev-image]: https://img.shields.io/david/dev/pirxpilot/zsyp.svg
38
- [deps-dev-url]: https://david-dm.org/pirxpilot/zsyp?type=dev
80
+ [deps-image]: https://img.shields.io/librariesio/release/npm/zsyp
81
+ [deps-url]: https://libraries.io/npm/zsyp
package/index.js CHANGED
@@ -1,24 +1,15 @@
1
1
  require('dotenv').config({ path: '/etc/default/zsyp' });
2
2
 
3
- const connect = require('@pirxpilot/connect');
4
- const router = require('./lib/csp');
5
- const makeLogger = require('./lib/logger');
3
+ const logger = require('./lib/logger');
4
+ const makeApp = require('./lib/app');
6
5
 
7
6
  const {
8
7
  ZSYP_PORT: PORT = 3090,
9
- ZSYP_DOMAINS: domains,
10
- ZSYP_DB: database = 'mongodb://localhost/zsyp'
11
8
  } = process.env;
12
9
 
13
- const app = connect();
14
- const log = makeLogger({ database });
15
-
16
- app.use(function (req, res, next) {
17
- req.log = log;
18
- next();
10
+ const app = makeApp({
11
+ logger
19
12
  });
20
- app.use(router({ domains }));
21
-
22
13
 
23
14
  module.exports = app;
24
15
 
package/lib/app.js ADDED
@@ -0,0 +1,21 @@
1
+ const connect = require('@pirxpilot/connect');
2
+ const { json } = require('body-parser');
3
+ const router = require('./router');
4
+
5
+ module.exports = makeApp;
6
+
7
+ const {
8
+ ZSYP_DOMAINS: domains,
9
+ } = process.env;
10
+
11
+
12
+ function makeApp(opts) {
13
+ const app = connect();
14
+
15
+ app.use(json({ limit: 5000, type: ['*/json', 'application/csp-report'] }));
16
+ app.use('/csp', router({ ...opts, name: 'csp', domains }));
17
+ app.use('/event', router({ ...opts, name: 'event' }));
18
+
19
+ return app;
20
+ }
21
+
package/lib/filter.js CHANGED
@@ -4,18 +4,17 @@ module.exports = makeFilter;
4
4
 
5
5
  function makeFilter({ domains }) {
6
6
 
7
- const domainRe = domains && new RegExp(domains);
7
+ if (!domains) {
8
+ return;
9
+ }
8
10
 
11
+ const domainRe = domains && new RegExp(domains);
9
12
  return filter;
10
13
 
11
14
  function filter({ body: csp }, res, next) {
12
- if (!domainRe) {
13
- return next();
14
- }
15
15
  const uri = csp['csp-report']['document-uri'];
16
16
  const { hostname } = new URL(uri);
17
17
  if (domainRe.test(hostname)) {
18
- console.log('OK!!!!!!!!');
19
18
  return next();
20
19
  }
21
20
  }
package/lib/from.js CHANGED
@@ -3,7 +3,8 @@ const { parse } = require('useragent');
3
3
  module.exports = from;
4
4
 
5
5
  function from(req, res, next) {
6
- const ua = req.headers['user-agent'];
6
+ const { headers, body } = req;
7
+ const ua = body?.from?.ua ?? headers['user-agent'];
7
8
  const {
8
9
  family,
9
10
  major,
@@ -20,7 +21,7 @@ function from(req, res, next) {
20
21
  name: os.family,
21
22
  version: os.major
22
23
  },
23
- ip: req.headers['x-forwarded-for'] || req.connection.remoteAddress
24
+ ip: body?.from?.ip ?? headers['x-forwarded-for'] ?? req.connection.remoteAddress
24
25
  };
25
26
  if (device.family !== 'Other') {
26
27
  data.device = device.family;
package/lib/logger.js CHANGED
@@ -1,16 +1,23 @@
1
- const { db } = require('mniam');
1
+ const mniam = require('mniam');
2
2
  const debug = require('debug')('zsyp:logger');
3
3
 
4
4
  module.exports = makeLogger;
5
5
 
6
- function makeLogger({ database }) {
7
- const csp = db(database).collection({ name: 'csp'});
6
+ const {
7
+ ZSYP_DB: database = 'mongodb://localhost/zsyp'
8
+ } = process.env;
9
+
10
+
11
+ const db = mniam.db(database);
12
+
13
+ function makeLogger({ name }) {
14
+ const collection = db.collection({ name });
8
15
 
9
16
  return log;
10
17
 
11
18
  function log(report) {
12
19
  debug('saving %j', report);
13
- csp.insertOne(report, function(err) {
20
+ collection.insertOne(report, function(err) {
14
21
  if (err) {
15
22
  console.error(err);
16
23
  }
package/lib/router.js ADDED
@@ -0,0 +1,44 @@
1
+ const Router = require('router');
2
+ const debug = require('debug')('zsyp:router');
3
+
4
+ const from = require('./from');
5
+ const makeFilter = require('./filter');
6
+
7
+ function respond(req, res, next) {
8
+ res.statusCode = 204; // empty
9
+ res.end();
10
+ next();
11
+ }
12
+
13
+ module.exports = function (opts) {
14
+ const router = new Router({
15
+ strict: true,
16
+ caseSensitive: true
17
+ });
18
+
19
+ const stack = [
20
+ from,
21
+ respond
22
+ ];
23
+
24
+ const filter = makeFilter(opts);
25
+ if (filter) {
26
+ stack.push(filter);
27
+ }
28
+
29
+ const logger = opts.logger(opts);
30
+
31
+ router.post('/', ...stack, log);
32
+
33
+ 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
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zsyp",
3
- "version": "0.0.2",
3
+ "version": "1.1.0",
4
4
  "description": "CSP violation reports logger.",
5
5
  "author": {
6
6
  "name": "Damian Krzeminski",
@@ -19,14 +19,15 @@
19
19
  "@pirxpilot/connect": "^4.0.0",
20
20
  "body-parser": "~1",
21
21
  "debug": "~2 || ~3 || ~4",
22
- "dotenv": "~8",
22
+ "dotenv": "~16",
23
23
  "mniam": "^2.1.0",
24
24
  "router": "~1",
25
+ "supertest": "~6",
25
26
  "useragent": "^2.3.0"
26
27
  },
27
28
  "devDependencies": {
28
29
  "jshint": "~2",
29
- "tape": "~4"
30
+ "tape": "~5"
30
31
  },
31
32
  "scripts": {
32
33
  "test": "make check"
package/History.md DELETED
@@ -1,10 +0,0 @@
1
-
2
- 0.0.2 / 2019-07-17
3
- ==================
4
-
5
- * fix reports domain filtering
6
-
7
- 0.0.1 / 2019-07-15
8
- ==================
9
-
10
- * basic implementation: UA parsing, domain filtering, mongo backend
package/lib/csp.js DELETED
@@ -1,36 +0,0 @@
1
- const Router = require('router');
2
- const { json } = require('body-parser');
3
-
4
- const from = require('./from');
5
- const makeFilter = require('./filter');
6
-
7
- function respond(req, res, next) {
8
- res.statusCode = 204; // empty
9
- res.end();
10
- next();
11
- }
12
-
13
-
14
- function log({ from, body: csp, log }) {
15
- const report = {
16
- from,
17
- ...csp
18
- };
19
-
20
- log(report);
21
- }
22
-
23
- module.exports = function (opts) {
24
- const router = new Router();
25
- const filter = makeFilter(opts);
26
-
27
- router.post('/csp',
28
- from,
29
- json({ limit: 5000, type: [ '*/json', 'application/csp-report' ] }),
30
- respond,
31
- filter,
32
- log
33
- );
34
-
35
- return router;
36
- };