From e27865e53b1e4686b50f19fe8e6db708f189fe1a Mon Sep 17 00:00:00 2001 From: Kirill Pimenov Date: Mon, 3 Aug 2020 20:38:37 +0200 Subject: [PATCH] Fix linter issues Now that the rules are much more pedantic there are quite some places where we are inconsistent with codestyle; all security issues detected were false positives, though. --- .eslintrc.js | 19 + jest.config.js | 28 +- src/App.vue | 8 +- src/components/CanvasText.vue | 43 +- src/components/ForkMe.vue | 111 +- src/components/GeneralInfo.vue | 12 +- src/components/ShardInfo.vue | 55 +- src/components/ShardQrCode.vue | 27 +- src/main.js | 28 +- src/plugins/online.js | 48 +- src/router.js | 26 +- src/util/bipPhrase.js | 2100 +++++++++++++++++++++++++++++++- src/util/crypto.js | 213 ++-- src/views/Combine.vue | 45 +- src/views/Info.vue | 11 +- src/views/Share.vue | 40 +- tests/unit/crypto.spec.js | 200 +-- vue.config.js | 21 +- 18 files changed, 2591 insertions(+), 444 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index f8cdec8..cb77944 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -18,5 +18,24 @@ module.exports = { env: { browser: true, node: true + }, + rules: { + "vue/html-self-closing": [ + "warning", + { + html: "never" + } + ], + "vue/v-on-style": ["warning", "longform"], + "vue/max-attributes-per-line": [ + "warning", + { + singleline: 3, + multiline: { + max: 1, + allowFirstLine: true + } + } + ] } }; diff --git a/jest.config.js b/jest.config.js index b91beaf..8140477 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,24 +1,18 @@ module.exports = { - moduleFileExtensions: [ - 'js', - 'jsx', - 'json', - 'vue' - ], + moduleFileExtensions: ["js", "jsx", "json", "vue"], transform: { - '^.+\\.vue$': 'vue-jest', - '.+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$': 'jest-transform-stub', - '^.+\\.jsx?$': 'babel-jest' + "^.+\\.vue$": "vue-jest", + ".+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$": + "jest-transform-stub", + "^.+\\.jsx?$": "babel-jest" }, moduleNameMapper: { - '^@/(.*)$': '/src/$1' + "^@/(.*)$": "/src/$1" }, - snapshotSerializers: [ - 'jest-serializer-vue' - ], + snapshotSerializers: ["jest-serializer-vue"], testMatch: [ - '**/tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)' + "**/tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)" ], - testURL: 'http://localhost/', - reporters: [ 'default', 'jest-junit' ] -} + testURL: "http://localhost/", + reporters: ["default", "jest-junit"] +}; diff --git a/src/App.vue b/src/App.vue index 4c0a48e..11074d8 100644 --- a/src/App.vue +++ b/src/App.vue @@ -5,8 +5,12 @@
diff --git a/src/components/CanvasText.vue b/src/components/CanvasText.vue index c8c00d8..e50940d 100644 --- a/src/components/CanvasText.vue +++ b/src/components/CanvasText.vue @@ -1,30 +1,33 @@ diff --git a/src/components/ForkMe.vue b/src/components/ForkMe.vue index 5e80311..2b5749f 100644 --- a/src/components/ForkMe.vue +++ b/src/components/ForkMe.vue @@ -1,79 +1,82 @@ > \ No newline at end of file + diff --git a/src/components/GeneralInfo.vue b/src/components/GeneralInfo.vue index a7ccfae..dd1d91a 100644 --- a/src/components/GeneralInfo.vue +++ b/src/components/GeneralInfo.vue @@ -1,7 +1,9 @@ @@ -44,6 +48,11 @@ import crypto from "../util/crypto"; +// It's actually used in the template +// eslint-disable-next-line no-unused-vars +const PLACEHOLDER_QR_DATA = + '{"t":"Very secret info","r":2,"d":"803c12929ba469d720a63fc8ca6f6ef1cc441f8f0b830ea04f8a484169ec800e4a7","n":"29abbb2c509adedb470f6d3fd3d083362b8c1a9283adf987"}'; + export default { name: "Combine", data: function() { @@ -69,6 +78,9 @@ export default { } } }, + mounted: function() { + this.$eventHub.$emit("foldGeneralInfo"); + }, methods: { onDecode: function(result) { var parsed = crypto.parse(result); @@ -111,9 +123,6 @@ export default { var shards = Array.from(this.shards); this.recoveredSecret = crypto.reconstruct(shards, this.passphrase); } - }, - mounted: function() { - this.$eventHub.$emit("foldGeneralInfo"); } }; diff --git a/src/views/Info.vue b/src/views/Info.vue index 19ce7a6..5619e62 100644 --- a/src/views/Info.vue +++ b/src/views/Info.vue @@ -1,14 +1,9 @@ - + diff --git a/src/views/Share.vue b/src/views/Share.vue index 63cd7aa..8a6e2d3 100644 --- a/src/views/Share.vue +++ b/src/views/Share.vue @@ -5,48 +5,52 @@

Name of your split

-
+ /> +
Inputs longer than 1024 characters make QR codes illegible

Will require any {{ requiredShards }} shards out of - to + to reconstruct

-
+

Your passphrase for the recovery is:

- - + +

- +
@@ -61,6 +65,7 @@ import CanvasText from "../components/CanvasText"; export default { name: "Share", + components: { ShardInfo, CanvasText }, data: function() { return { title: "", @@ -70,7 +75,6 @@ export default { encryptionMode: false }; }, - components: { ShardInfo, CanvasText }, computed: { secretTooLong: function() { return this.secret.length > 1024; @@ -91,6 +95,9 @@ export default { ); } }, + mounted: function() { + this.$eventHub.$emit("foldGeneralInfo"); + }, methods: { regenPassphrase: function() { this.recoveryPassphrase = bipPhrase.generate(4); @@ -101,9 +108,6 @@ export default { toggleMode: function() { this.encryptionMode = !this.encryptionMode; } - }, - mounted: function() { - this.$eventHub.$emit("foldGeneralInfo"); } }; diff --git a/tests/unit/crypto.spec.js b/tests/unit/crypto.spec.js index 11e6c29..32287aa 100644 --- a/tests/unit/crypto.spec.js +++ b/tests/unit/crypto.spec.js @@ -1,98 +1,134 @@ -require('fast-text-encoding'); // NodeJS requires this polyfill +/* eslint-disable security/detect-object-injection */ +require("fast-text-encoding"); // NodeJS requires this polyfill import crypto from "../../src/util/crypto"; -test('encodes-decodes empty values', () => { - var shards = crypto.share('', '', '', 3, 2); - var parsed = shards.map(s => crypto.parse(s)); - var reconstructed = crypto.reconstruct(parsed, ''); - expect(reconstructed).toBe(''); -}) +test("encodes-decodes empty values", () => { + var shards = crypto.share("", "", "", 3, 2); + var parsed = shards.map(s => crypto.parse(s)); + var reconstructed = crypto.reconstruct(parsed, ""); + expect(reconstructed).toBe(""); +}); -test('properly deserializes metadata', () => { - var shards = crypto.share('Secret message', 'Secret title', 'correct-horse-battery-staple', 5, 3); - var parsed = shards.map(s => crypto.parse(s)); - parsed.forEach(message => { - expect(message.title).toBe('Secret title'); - expect(message.requiredShards).toBe(3); - expect(message.nonce).toBe(parsed[0].nonce); - }); -}) +test("properly deserializes metadata", () => { + var shards = crypto.share( + "Secret message", + "Secret title", + "correct-horse-battery-staple", + 5, + 3 + ); + var parsed = shards.map(s => crypto.parse(s)); + parsed.forEach(message => { + expect(message.title).toBe("Secret title"); + expect(message.requiredShards).toBe(3); + expect(message.nonce).toBe(parsed[0].nonce); + }); +}); -test('works with any 2 shards out of 3', () => { - var shards = crypto.share('Secret message', 'Secret title', 'correct-horse-battery-staple', 3, 2); - var parsed = shards.map(s => crypto.parse(s)); - [ +test("works with any 2 shards out of 3", () => { + var shards = crypto.share( + "Secret message", + "Secret title", + "correct-horse-battery-staple", + 3, + 2 + ); + var parsed = shards.map(s => crypto.parse(s)); + [[0, 1], [0, 2], [1, 2], [1, 0], [2, 0], [2, 1]].forEach(([i, j]) => { + expect( + crypto.reconstruct([parsed[i], parsed[j]], "correct-horse-battery-staple") + ).toBe("Secret message"); + }); +}); - [0, 1], [0, 2], [1, 2], - [1, 0], [2, 0], [2, 1] - ].forEach(([i, j]) => { - expect(crypto.reconstruct([parsed[i], parsed[j]], 'correct-horse-battery-staple')).toBe('Secret message') - }); -}) +test("fails with incorrect password", () => { + var shards = crypto.share( + "Secret message", + "Secret title", + "correct-horse-battery-staple", + 5, + 3 + ); + var parsed = shards.map(s => crypto.parse(s)); + var reconstructed = crypto.reconstruct(parsed, ""); + expect(reconstructed).not.toBe("Secret message"); + expect(reconstructed).toBe(""); +}); -test('fails with incorrect password', () => { - var shards = crypto.share('Secret message', 'Secret title', 'correct-horse-battery-staple', 5, 3); - var parsed = shards.map(s => crypto.parse(s)); - var reconstructed = crypto.reconstruct(parsed, ''); - expect(reconstructed).not.toBe('Secret message'); - expect(reconstructed).toBe(''); -}) +test("works with unicode strings", () => { + var shards = crypto.share( + "Текст сообщения", + "Это секрет", + "correct-horse-battery-staple", + 5, + 3 + ); + var parsed = shards.map(s => crypto.parse(s)); + parsed.forEach(message => { + expect(message.title).toBe("Это секрет"); + }); -test('works with unicode strings', () => { - var shards = crypto.share('Текст сообщения', 'Это секрет', 'correct-horse-battery-staple', 5, 3); - var parsed = shards.map(s => crypto.parse(s)); - parsed.forEach(message => { - expect(message.title).toBe('Это секрет') - }); + var reconstructed = crypto.reconstruct( + parsed, + "correct-horse-battery-staple" + ); + expect(reconstructed).toBe("Текст сообщения"); +}); - var reconstructed = crypto.reconstruct(parsed, 'correct-horse-battery-staple'); - expect(reconstructed).toBe('Текст сообщения'); -}) +test("reconstructs the reference example of v0 serialization", () => { + var shards = [ + '{"t":"Very secret info","r":2,"d":"803c12929ba469d720a63fc8ca6f6ef1cc441f8f0b830ea04f8a484169ec800e4a7","n":"29abbb2c509adedb470f6d3fd3d083362b8c1a9283adf987"}', + '{"t":"Very secret info","r":2,"d":"801b4196813bb664141169f939414fec30c7f343326d8df45e557b25b0249fd43e2","n":"29abbb2c509adedb470f6d3fd3d083362b8c1a9283adf987"}', + '{"t":"Very secret info","r":2,"d":"80275318760b66ee5a1d7430dbf8769fda05e9e1ff7447eaa78539fbed006f0390b","n":"29abbb2c509adedb470f6d3fd3d083362b8c1a9283adf987"}' + ].map(s => crypto.parse(s)); -test('reconstructs the reference example of v0 serialization', () => { - var shards = [ - '{"t":"Very secret info","r":2,"d":"803c12929ba469d720a63fc8ca6f6ef1cc441f8f0b830ea04f8a484169ec800e4a7","n":"29abbb2c509adedb470f6d3fd3d083362b8c1a9283adf987"}', - '{"t":"Very secret info","r":2,"d":"801b4196813bb664141169f939414fec30c7f343326d8df45e557b25b0249fd43e2","n":"29abbb2c509adedb470f6d3fd3d083362b8c1a9283adf987"}', - '{"t":"Very secret info","r":2,"d":"80275318760b66ee5a1d7430dbf8769fda05e9e1ff7447eaa78539fbed006f0390b","n":"29abbb2c509adedb470f6d3fd3d083362b8c1a9283adf987"}' - ].map(s => crypto.parse(s)); + [[0, 1], [0, 2], [1, 2], [1, 0], [2, 0], [2, 1]].forEach(([i, j]) => { + expect( + crypto.reconstruct([shards[i], shards[j]], "amazing-daring-panda-horror") + ).toBe("Any questions?"); + }); +}); - [ - [0, 1], [0, 2], [1, 2], - [1, 0], [2, 0], [2, 1] - ].forEach(([i,j]) => { - expect(crypto.reconstruct([shards[i], shards[j]], 'amazing-daring-panda-horror')).toBe('Any questions?') - }); -}) +test("reconstructs the reference example of v1 serialization", () => { + var shards = [ + '{"v":1,"t":"Pssst","r":2,"d":"8AdI3F1Xn4CK9mXEVWLWgg2gzho5WV38E/hn1OYyRMZenL/Jm6dmrZoiji2ZlMSVEW+XN9WW1I/ilDC1yiu4oBa4=","n":"a17TDZHP2iL/sdPHgFJUP3NlAC7bDgrp"}', + '{"v":1,"t":"Pssst","r":2,"d":"8ArluLqrT3URnL+IqsHddG9MpXqvSNt5JBLfTqSJmCg4raIFLg2XhfbnFLCTgCumI4qByThq9bBxnwLy8EgEHYiw=","n":"a17TDZHP2iL/sdPHgFJUP3NlAC7bDgrp"}', + '{"v":1,"t":"Pssst","r":2,"d":"8A2tZOf80PWbatpM/6ML9mLrUFkOu4kpyUiY62bPA6HmkVVtQpfosdF3nuhpo6K3MfmjsJ8ROokDShDgNka/ptFI=","n":"a17TDZHP2iL/sdPHgFJUP3NlAC7bDgrp"}' + ].map(s => crypto.parse(s)); -test('reconstructs the reference example of v1 serialization', () => { - var shards = [ - '{"v":1,"t":"Pssst","r":2,"d":"8AdI3F1Xn4CK9mXEVWLWgg2gzho5WV38E/hn1OYyRMZenL/Jm6dmrZoiji2ZlMSVEW+XN9WW1I/ilDC1yiu4oBa4=","n":"a17TDZHP2iL/sdPHgFJUP3NlAC7bDgrp"}', - '{"v":1,"t":"Pssst","r":2,"d":"8ArluLqrT3URnL+IqsHddG9MpXqvSNt5JBLfTqSJmCg4raIFLg2XhfbnFLCTgCumI4qByThq9bBxnwLy8EgEHYiw=","n":"a17TDZHP2iL/sdPHgFJUP3NlAC7bDgrp"}', - '{"v":1,"t":"Pssst","r":2,"d":"8A2tZOf80PWbatpM/6ML9mLrUFkOu4kpyUiY62bPA6HmkVVtQpfosdF3nuhpo6K3MfmjsJ8ROokDShDgNka/ptFI=","n":"a17TDZHP2iL/sdPHgFJUP3NlAC7bDgrp"}' - ].map(s => crypto.parse(s)); + [[0, 1], [0, 2], [1, 2], [1, 0], [2, 0], [2, 1]].forEach(([i, j]) => { + expect( + crypto.reconstruct([shards[i], shards[j]], "excess-torch-unfold-fix") + ).toBe("Version one is all-around better"); + }); +}); - [ - [0, 1], [0, 2], [1, 2], - [1, 0], [2, 0], [2, 1] - ].forEach(([i,j]) => { - expect(crypto.reconstruct([shards[i], shards[j]], 'excess-torch-unfold-fix')).toBe('Version one is all-around better') - }); -}) +test("throws an error if nonces mismatch", () => { + var shards = crypto + .share("Message", "Title", "correct-horse-battery-staple", 3, 2) + .map(s => crypto.parse(s)); + shards[0].nonce = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; + expect(() => + crypto.reconstruct([shards[0], shards[1]], "correct-horse-battery-staple") + ).toThrow(/Nonces mismatch/); +}); -test('throws an error if nonces mismatch', () => { - var shards = crypto.share('Message', 'Title', 'correct-horse-battery-staple', 3, 2).map(s => crypto.parse(s)); - shards[0].nonce = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" - expect(() => crypto.reconstruct([shards[0], shards[1]], 'correct-horse-battery-staple')).toThrow(/Nonces mismatch/) -}) - -test('throws an error if titles mismatch', () => { - var shards = crypto.share('Message', 'Title', 'correct-horse-battery-staple', 3, 2).map(s => crypto.parse(s)); - shards[0].title = "Completely different title" - expect(() => crypto.reconstruct([shards[0], shards[1]], 'correct-horse-battery-staple')).toThrow(/Titles mismatch/) -}) +test("throws an error if titles mismatch", () => { + var shards = crypto + .share("Message", "Title", "correct-horse-battery-staple", 3, 2) + .map(s => crypto.parse(s)); + shards[0].title = "Completely different title"; + expect(() => + crypto.reconstruct([shards[0], shards[1]], "correct-horse-battery-staple") + ).toThrow(/Titles mismatch/); +}); test("doesn't work if less shards than necessary are supplied", () => { - var shards = crypto.share('Message', 'Title', 'correct-horse-battery-staple', 5, 3).map(s => crypto.parse(s)); - expect(() => crypto.reconstruct([shards[0], shards[1]], 'correct-horse-battery-staple')).toThrow(/Not enough shards/) -}) + var shards = crypto + .share("Message", "Title", "correct-horse-battery-staple", 5, 3) + .map(s => crypto.parse(s)); + expect(() => + crypto.reconstruct([shards[0], shards[1]], "correct-horse-battery-staple") + ).toThrow(/Not enough shards/); +}); diff --git a/vue.config.js b/vue.config.js index e495150..43faf89 100644 --- a/vue.config.js +++ b/vue.config.js @@ -1,9 +1,10 @@ -let HtmlWebpackPlugin = require('html-webpack-plugin'); -let HtmlWebpackInlineSourcePlugin = require('html-webpack-inline-source-plugin'); -let Webpack = require('webpack'); +let HtmlWebpackPlugin = require("html-webpack-plugin"); +let HtmlWebpackInlineSourcePlugin = require("html-webpack-inline-source-plugin"); +let Webpack = require("webpack"); -let childProcess = require('child_process'); -let GIT_REVISION = childProcess.execSync('git rev-parse HEAD').toString(); +// eslint-disable-next-line security/detect-child-process +let childProcess = require("child_process"); +let GIT_REVISION = childProcess.execSync("git rev-parse HEAD").toString(); module.exports = { productionSourceMap: false, @@ -13,15 +14,15 @@ module.exports = { configureWebpack: { plugins: [ new HtmlWebpackPlugin({ - template: 'public/index.html', - inlineSource: '.(js|css)$' + template: "public/index.html", + inlineSource: ".(js|css)$" }), new HtmlWebpackInlineSourcePlugin(HtmlWebpackPlugin), new Webpack.DefinePlugin({ - 'process.env': { - 'GIT_REVISION': JSON.stringify(GIT_REVISION) + "process.env": { + GIT_REVISION: JSON.stringify(GIT_REVISION) } }) ] } -} \ No newline at end of file +};