From 4970214324ad7e37608e582589bd54e0ed14fe7a Mon Sep 17 00:00:00 2001 From: Samuel Elliott Date: Fri, 10 Aug 2018 13:37:47 +0100 Subject: [PATCH 01/40] Add an option to disable toasts --- client/src/data/user.settings.default.json | 7 +++++++ client/src/ui/toasts.js | 16 ++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/client/src/data/user.settings.default.json b/client/src/data/user.settings.default.json index 2ca31ad6..7a63c0d5 100644 --- a/client/src/data/user.settings.default.json +++ b/client/src/data/user.settings.default.json @@ -93,6 +93,13 @@ "hint": "When this is enabled you can use Ctrl/Cmd + B to open the BetterDiscord settings menu.", "value": false, "disabled": false + }, + { + "id": "enable-toasts", + "type": "bool", + "text": "Enable Toasts", + "hint": "Allows plugins to show toasts.", + "value": true } ] } diff --git a/client/src/ui/toasts.js b/client/src/ui/toasts.js index 1b572a54..75e3709c 100644 --- a/client/src/ui/toasts.js +++ b/client/src/ui/toasts.js @@ -8,6 +8,8 @@ * LICENSE file in the root directory of this source tree. */ +import { Settings } from 'modules'; + let toasts = 0; export default class Toasts { @@ -24,6 +26,8 @@ export default class Toasts { * @returns {Promise} This promise resolves when the toast is removed from the DOM. */ static async push(message, options = {}) { + if (!this.enabled) return; + const {type = 'basic', icon, additionalClasses, timeout = 3000} = options; const toast = {id: toasts++, message, type, icon, additionalClasses, closing: false}; this.stack.push(toast); @@ -72,4 +76,16 @@ export default class Toasts { return this._stack || (this._stack = []); } + static get setting() { + return Settings.getSetting('ui', 'default', 'enable-toasts'); + } + + static get enabled() { + return this.setting.value; + } + + static set enabled(enabled) { + this.setting.value = enabled; + } + } From a12a3c74f4fe40a073458419c2e13401c605d99a Mon Sep 17 00:00:00 2001 From: Samuel Elliott Date: Fri, 10 Aug 2018 13:57:27 +0100 Subject: [PATCH 02/40] Hide the button properly --- client/src/styles/partials/bdsettings/button.scss | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/client/src/styles/partials/bdsettings/button.scss b/client/src/styles/partials/bdsettings/button.scss index e2fc7fb3..3fb27957 100644 --- a/client/src/styles/partials/bdsettings/button.scss +++ b/client/src/styles/partials/bdsettings/button.scss @@ -44,11 +44,14 @@ } &.bd-hide-button { - transition: opacity 0.4s ease-out; - opacity: 0; + animation: bd-fade-out 0.4s ease-out; &.bd-active { - transition-timing-function: ease-in; + animation: bd-fade-in 0.4s ease-in; + } + + &:not(.bd-active):not(.bd-animating) { + display: none; } } From dc3fed340809c2b55589931919c79b2e092a31b5 Mon Sep 17 00:00:00 2001 From: Samuel Elliott Date: Fri, 10 Aug 2018 14:11:25 +0100 Subject: [PATCH 03/40] Hide collection setting dividers --- client/src/data/user.settings.default.json | 2 +- client/src/styles/partials/bdsettings/index.scss | 1 - .../collection.scss => generic/forms/collections.scss} | 8 ++++++-- client/src/styles/partials/generic/forms/index.scss | 1 + client/src/ui/components/bd/setting/Collection.vue | 2 +- client/src/ui/components/bd/setting/Setting.vue | 6 ++++-- 6 files changed, 13 insertions(+), 7 deletions(-) rename client/src/styles/partials/{bdsettings/collection.scss => generic/forms/collections.scss} (90%) diff --git a/client/src/data/user.settings.default.json b/client/src/data/user.settings.default.json index 7a63c0d5..29db2e30 100644 --- a/client/src/data/user.settings.default.json +++ b/client/src/data/user.settings.default.json @@ -151,7 +151,7 @@ { "id": "security", "text": "Security and Privacy", - "headertext": "Security Settings", + "headertext": "Security and Privacy Settings", "settings": [ { "id": "default", diff --git a/client/src/styles/partials/bdsettings/index.scss b/client/src/styles/partials/bdsettings/index.scss index fbadb580..56081af4 100644 --- a/client/src/styles/partials/bdsettings/index.scss +++ b/client/src/styles/partials/bdsettings/index.scss @@ -8,4 +8,3 @@ @import './updater.scss'; @import './window-preferences'; @import './kvp'; -@import './collection'; diff --git a/client/src/styles/partials/bdsettings/collection.scss b/client/src/styles/partials/generic/forms/collections.scss similarity index 90% rename from client/src/styles/partials/bdsettings/collection.scss rename to client/src/styles/partials/generic/forms/collections.scss index 3f086070..29a52b23 100644 --- a/client/src/styles/partials/bdsettings/collection.scss +++ b/client/src/styles/partials/generic/forms/collections.scss @@ -2,7 +2,7 @@ display: flex; flex-direction: column; - div:first-child { + > :first-child { flex: 1 1 auto; } @@ -11,6 +11,10 @@ flex-grow: 1; margin-top: 5px; + > :first-child { + flex: 1 0 auto; + } + .bd-removeCollectionItem { width: 20px; flex: 0 1 auto; @@ -18,7 +22,6 @@ justify-content: center; align-items: center; cursor: pointer; - margin-bottom: 30px; &:hover { svg { @@ -41,6 +44,7 @@ justify-content: center; align-items: center; margin-right: 2px; + margin-top: 10px; svg { width: 16px; diff --git a/client/src/styles/partials/generic/forms/index.scss b/client/src/styles/partials/generic/forms/index.scss index a8334930..6a4ab7dd 100644 --- a/client/src/styles/partials/generic/forms/index.scss +++ b/client/src/styles/partials/generic/forms/index.scss @@ -9,3 +9,4 @@ @import './files.scss'; @import './guilds.scss'; @import './arrays.scss'; +@import './collections.scss'; diff --git a/client/src/ui/components/bd/setting/Collection.vue b/client/src/ui/components/bd/setting/Collection.vue index 5fa5ebf7..10ba904b 100644 --- a/client/src/ui/components/bd/setting/Collection.vue +++ b/client/src/ui/components/bd/setting/Collection.vue @@ -11,7 +11,7 @@ @@ -51,7 +52,8 @@ export default { props: [ - 'setting' + 'setting', + 'hide-divider' ], components: { BoolSetting, From 0aabc726526452e112bdd0287f39de165c7de68f Mon Sep 17 00:00:00 2001 From: Samuel Elliott Date: Fri, 10 Aug 2018 14:22:37 +0100 Subject: [PATCH 04/40] Indentation --- client/src/modules/patcher.js | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/client/src/modules/patcher.js b/client/src/modules/patcher.js index bf220f2a..2105da9a 100644 --- a/client/src/modules/patcher.js +++ b/client/src/modules/patcher.js @@ -18,9 +18,9 @@ import { ClientLogger as Logger } from 'common'; /** * A callback that modifies method logic. This callback is called on each call of the original method and is provided all data about original call. Any of the data can be modified if necessary, but do so wisely. - * + * * The third argument for the callback will be `undefined` for `before` patches. `originalFunction` for `instead` patches and `returnValue` for `after` patches. - * + * * @callback Patcher~patchCallback * @param {object} thisObject - `this` in the context of the original function. * @param {arguments} arguments - The original arguments of the original function. @@ -42,8 +42,8 @@ export class Patcher { const patches = []; for (const patch of this.patches) { for (const childPatch of patch.children) { - if (childPatch.caller === id) patches.push(childPatch); - } + if (childPatch.caller === id) patches.push(childPatch); + } } return patches; } @@ -135,7 +135,7 @@ export class Patcher { /** * This method patches onto another function, allowing your code to run beforehand. * Using this, you are also able to modify the incoming arguments before the original method is run. - * + * * @param {string} caller - Name of the caller of the patch function. Using this you can undo all patches with the same name using {@link Patcher#unpatchAll}. * @param {object} unresolvedModule - Object with the function to be patched. Can also patch an object's prototype. * @param {string} functionName - Name of the method to be patched @@ -148,7 +148,7 @@ export class Patcher { /** * This method patches onto another function, allowing your code to run afterwards. * Using this, you are also able to modify the return value, using the return of your code instead. - * + * * @param {string} caller - Name of the caller of the patch function. Using this you can undo all patches with the same name using {@link Patcher#unpatchAll}. * @param {object} unresolvedModule - Object with the function to be patched. Can also patch an object's prototype. * @param {string} functionName - Name of the method to be patched @@ -161,7 +161,7 @@ export class Patcher { /** * This method patches onto another function, allowing your code to run instead, preventing the running of the original code. * Using this, you are also able to modify the return value, using the return of your code instead. - * + * * @param {string} caller - Name of the caller of the patch function. Using this you can undo all patches with the same name using {@link Patcher#unpatchAll}. * @param {object} unresolvedModule - Object with the function to be patched. Can also patch an object's prototype. * @param {string} functionName - Name of the method to be patched @@ -175,7 +175,7 @@ export class Patcher { * This method patches onto another function, allowing your code to run before, instead or after the original function. * Using this you are able to modify the incoming arguments before the original function is run as well as the return * value before the original function actually returns. - * + * * @param {string} caller - Name of the caller of the patch function. Using this you can undo all patches with the same name using {@link Patcher#unpatchAll}. * @param {object} unresolvedModule - Object with the function to be patched. Can also patch an object's prototype. * @param {string} functionName - Name of the method to be patched @@ -201,10 +201,10 @@ export class Patcher { unpatch: () => { patch.children.splice(patch.children.findIndex(cpatch => cpatch.id === child.id && cpatch.type === type), 1); if (patch.children.length <= 0) { - const patchNum = this.patches.findIndex(p => p.module == module && p.functionName == functionName); - this.patches[patchNum].revert(); - this.patches.splice(patchNum, 1); - } + const patchNum = this.patches.findIndex(p => p.module == module && p.functionName == functionName); + this.patches[patchNum].revert(); + this.patches.splice(patchNum, 1); + } } }; patch.children.push(child); From c8381eb808fe864bde933fcba7bb795e127dde83 Mon Sep 17 00:00:00 2001 From: Samuel Elliott Date: Fri, 10 Aug 2018 14:23:21 +0100 Subject: [PATCH 05/40] Add disabled, min and max options for collections --- .../partials/generic/forms/collections.scss | 16 ++++++++++------ .../src/ui/components/bd/setting/Collection.vue | 6 ++++-- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/client/src/styles/partials/generic/forms/collections.scss b/client/src/styles/partials/generic/forms/collections.scss index 29a52b23..1d84a8cb 100644 --- a/client/src/styles/partials/generic/forms/collections.scss +++ b/client/src/styles/partials/generic/forms/collections.scss @@ -23,17 +23,21 @@ align-items: center; cursor: pointer; - &:hover { - svg { - fill: #FFF; - } - } - svg { width: 16px; height: 16px; fill: #ccc; } + + &:not(.bd-disabled):hover { + svg { + fill: #fff; + } + } + + &.bd-disabled { + opacity: 0.5; + } } } diff --git a/client/src/ui/components/bd/setting/Collection.vue b/client/src/ui/components/bd/setting/Collection.vue index 10ba904b..9f727eb9 100644 --- a/client/src/ui/components/bd/setting/Collection.vue +++ b/client/src/ui/components/bd/setting/Collection.vue @@ -12,9 +12,9 @@
-
+
-
+
@@ -29,9 +29,11 @@ }, methods: { removeItem(item) { + if (this.setting.disabled || this.setting.min && this.setting.items.length <= this.setting.min) return; this.setting.removeItem(item); }, addItem() { + if (this.setting.disabled || this.setting.max && this.setting.items.length >= this.setting.max) return; this.setting.addItem(); } }, From a4992e905c7fd8b8cc7c9dc2ab5b3d61cfd76dbf Mon Sep 17 00:00:00 2001 From: Samuel Elliott Date: Fri, 10 Aug 2018 14:29:31 +0100 Subject: [PATCH 06/40] Fix class normaliser also normalising classes of elements outside the passed element --- client/src/ui/classnormaliser.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/client/src/ui/classnormaliser.js b/client/src/ui/classnormaliser.js index 52cf0bf7..c2979ab0 100644 --- a/client/src/ui/classnormaliser.js +++ b/client/src/ui/classnormaliser.js @@ -65,8 +65,7 @@ export default class ClassNormaliser extends Module { normalizeElement(element) { if (!(element instanceof Element)) return; - if (element.children && element.children.length) this.normalizeElement(element.children[0]); - if (element.nextElementSibling) this.normalizeElement(element.nextElementSibling); + const classes = element.classList; for (let c = 0, clen = classes.length; c < clen; c++) { if (!randClass.test(classes[c])) continue; @@ -74,6 +73,10 @@ export default class ClassNormaliser extends Module { const newClass = match.split('-').map((s, i) => i ? s[0].toUpperCase() + s.slice(1) : s).join(''); element.classList.add(`${normalizedPrefix}-${newClass}`); } + + for (let child of element.children) { + this.normalizeElement(child); + } } } From 22e78c03e1a62d72fb3a8c3a2003c12cb70fdec9 Mon Sep 17 00:00:00 2001 From: Samuel Elliott Date: Fri, 10 Aug 2018 15:17:57 +0100 Subject: [PATCH 07/40] Add toasts enabled state to the plugin API --- client/src/modules/pluginapi.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/src/modules/pluginapi.js b/client/src/modules/pluginapi.js index 5f7cbfbb..e1b9bfc7 100644 --- a/client/src/modules/pluginapi.js +++ b/client/src/modules/pluginapi.js @@ -330,7 +330,8 @@ export default class PluginApi { success: this.showSuccessToast.bind(this), error: this.showErrorToast.bind(this), info: this.showInfoToast.bind(this), - warning: this.showWarningToast.bind(this) + warning: this.showWarningToast.bind(this), + get enabled() { return Toasts.enabled } }; } From 344a9e6fe54cc7b588db9acf4829904cc3f867d3 Mon Sep 17 00:00:00 2001 From: Samuel Elliott Date: Fri, 10 Aug 2018 15:21:33 +0100 Subject: [PATCH 08/40] Fix escape key to close menu --- client/src/ui/components/BdSettingsWrapper.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/ui/components/BdSettingsWrapper.vue b/client/src/ui/components/BdSettingsWrapper.vue index 96c34a7b..180519e6 100644 --- a/client/src/ui/components/BdSettingsWrapper.vue +++ b/client/src/ui/components/BdSettingsWrapper.vue @@ -47,7 +47,7 @@ methods: { keyupListener(e) { if (Modals.stack.length || !this.active || e.which !== 27) return; - if (this.$refs.settings.activeIndex !== -1) this.$refs.settings.closeContent(); + if (this.$refs.settings.item) this.$refs.settings.closeContent(); else this.active = false; e.stopImmediatePropagation(); }, From 784d8223e8430c26767ee5713e2ff97dd7aeeb5c Mon Sep 17 00:00:00 2001 From: Samuel Elliott Date: Fri, 10 Aug 2018 15:43:28 +0100 Subject: [PATCH 09/40] Add a separate build-release task So you can update the release version without deleting all user data --- gulpfile.babel.js | 4 +++- package.json | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/gulpfile.babel.js b/gulpfile.babel.js index 14086f32..042c796f 100644 --- a/gulpfile.babel.js +++ b/gulpfile.babel.js @@ -81,6 +81,8 @@ gulp.task('node-sass-bindings', function () { ]); }); +gulp.task('build-release', gulp.parallel('release-package', 'client', 'core', 'sparkplug', 'core-modules', 'index', 'css-editor', gulp.series('dependencies', 'node-sass-bindings'))); + gulp.task('release', gulp.series(function () { return del(['release/**/*']); -}, gulp.parallel('release-package', 'client', 'core', 'sparkplug', 'core-modules', 'index', 'css-editor', gulp.series('dependencies', 'node-sass-bindings')))); +}, 'build-release')); diff --git a/package.json b/package.json index 03531f04..4d9249a4 100644 --- a/package.json +++ b/package.json @@ -81,6 +81,7 @@ "build_node-sass": "node scripts/build-node-sass.js", "build_release": "npm run release --prefix client && npm run build --prefix core && npm run release --prefix csseditor", "package_release": "node scripts/package-release.js", - "release": "npm run lint && npm run build_release && gulp release && npm run package_release" + "release": "npm run lint && npm run build_release && gulp release && npm run package_release", + "update_release": "npm run build_release && gulp build-release" } } From 866ad8b13b796e363ca3f8e8d22aecd67b0bb22c Mon Sep 17 00:00:00 2001 From: Jiiks Date: Sat, 11 Aug 2018 15:29:30 +0300 Subject: [PATCH 10/40] Tons of stuff --- client/src/builtin/E2EE.js | 52 ++++++++++++++++++++++++++++ client/src/builtin/E2EEComponent.vue | 29 +++++++++++++--- common/modules/utils.js | 35 +++++++++++++++++++ 3 files changed, 112 insertions(+), 4 deletions(-) diff --git a/client/src/builtin/E2EE.js b/client/src/builtin/E2EE.js index 8be72a91..4c0fe99f 100644 --- a/client/src/builtin/E2EE.js +++ b/client/src/builtin/E2EE.js @@ -13,11 +13,14 @@ import BuiltinModule from './BuiltinModule'; import { WebpackModules, ReactComponents, MonkeyPatch, Patcher, DiscordApi } from 'modules'; import { VueInjector, Reflection } from 'ui'; import { ClientLogger as Logger } from 'common'; +import { request } from 'vendor'; +import { Utils } from 'common'; import E2EEComponent from './E2EEComponent.vue'; import E2EEMessageButton from './E2EEMessageButton.vue'; import aes256 from 'aes256'; let seed = Math.random().toString(36).replace(/[^a-z]+/g, ''); +const decryptCache = []; export default new class E2EE extends BuiltinModule { @@ -70,6 +73,8 @@ export default new class E2EE extends BuiltinModule { const MessageContent = await ReactComponents.getComponent('MessageContent', { selector }); MonkeyPatch('BD:E2EE', MessageContent.component.prototype).before('render', this.beforeRenderMessageContent.bind(this)); MonkeyPatch('BD:E2EE', MessageContent.component.prototype).after('render', this.renderMessageContent.bind(this)); + const ImageWrapper = await ReactComponents.getComponent('ImageWrapper', { selector: '.' + WebpackModules.getClassName('imageWrapper') }); + MonkeyPatch('BD:E2EE', ImageWrapper.component.prototype).before('render', this.beforeRenderImageWrapper.bind(this)); } beforeRenderMessageContent(component, args, retVal) { @@ -107,6 +112,53 @@ export default new class E2EE extends BuiltinModule { })); } + beforeRenderImageWrapper(component, args, retVal) { + if (!component.props || !component.props.src) return; + if (component.props.decrypting) return; + + const src = component.props.src; + if (!src.includes('bde2ee')) return; + + const alreadyDecrypted = decryptCache.find(item => item.src === component.props.src); + if (alreadyDecrypted) { + component.props.className = 'bd-decryptedImage'; + component.props.src = component.props.original = alreadyDecrypted.encodedImage; + component.props.width = alreadyDecrypted.width; + component.props.height = alreadyDecrypted.height; + return; + } + + let resolution = null; + try { + resolution = src.match(/_(.*?)\./)[1].split('x'); + } catch (err) { } + + component.props.className = 'bd-encryptedImage'; + component.props.decrypting = true; + + request.get(component.props.src, { encoding: 'binary' }).then(res => { + const arr = new Uint8Array(new ArrayBuffer(res.length)); + for (let i = 0; i < res.length; i++) arr[i] = res.charCodeAt(i); + const aobindex = Utils.aobscan(arr, [73, 69, 78, 68]) + 8; + + const sliced = arr.slice(aobindex, arr.length - aobindex); + const encoded = Utils.arrayBufferToBase64(sliced); + const base64enc = 'data:image/png;base64,' + encoded; + + if (!component || !component.props) return; + if (resolution && resolution.length >= 2) { + component.props.width = parseInt(resolution[0]); + component.props.height = parseInt(resolution[1]); + } + + decryptCache.push({ src, width: component.props.width, height: component.props.height, encodedImage: base64enc }); + component.props.decrypting = false; + component.forceUpdate(); + }).catch(err => { + console.log('request error', err); + }); + } + patchChannelTextArea(cta) { MonkeyPatch('BD:E2EE', cta.component.prototype).after('render', this.renderChannelTextArea); } diff --git a/client/src/builtin/E2EEComponent.vue b/client/src/builtin/E2EEComponent.vue index cd8fdb38..589d5341 100644 --- a/client/src/builtin/E2EEComponent.vue +++ b/client/src/builtin/E2EEComponent.vue @@ -24,16 +24,23 @@
+
+ +
From 17128a889b7bc8cd20327c5ebfa37ee7b4e0237f Mon Sep 17 00:00:00 2001 From: Jiiks Date: Sun, 12 Aug 2018 17:40:26 +0300 Subject: [PATCH 18/40] add cache module --- client/src/modules/cache.js | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 client/src/modules/cache.js diff --git a/client/src/modules/cache.js b/client/src/modules/cache.js new file mode 100644 index 00000000..1e18a289 --- /dev/null +++ b/client/src/modules/cache.js @@ -0,0 +1,27 @@ +/** + * BetterDiscord Cache Module + * Copyright (c) 2015-present Jiiks/JsSucks - https://github.com/Jiiks / https://github.com/JsSucks + * All rights reserved. + * https://betterdiscord.net + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. +*/ +const CACHE = []; +export default class Cache { + + static get cache() { + return CACHE; + } + + static push(where, data) { + if (!this.cache[where]) this.cache[where] = []; + this.cache[where].push(data); + } + + static find(where, what) { + if (!this.cache[where]) this.cache[where] = []; + return this.cache[where].find(what); + } + +} From 334c9f852ad40897d0af14c57e6babedf73f6e8e Mon Sep 17 00:00:00 2001 From: Jiiks Date: Sun, 12 Aug 2018 18:10:22 +0300 Subject: [PATCH 19/40] Use cache module --- client/src/builtin/E2EE.js | 7 +++---- client/src/index.js | 3 ++- client/src/modules/cache.js | 11 +++++++++++ client/src/modules/modules.js | 1 + 4 files changed, 17 insertions(+), 5 deletions(-) diff --git a/client/src/builtin/E2EE.js b/client/src/builtin/E2EE.js index f390c406..e211c5df 100644 --- a/client/src/builtin/E2EE.js +++ b/client/src/builtin/E2EE.js @@ -8,7 +8,7 @@ * LICENSE file in the root directory of this source tree. */ -import { Settings } from 'modules'; +import { Settings, Cache } from 'modules'; import BuiltinModule from './BuiltinModule'; import { WebpackModules, ReactComponents, MonkeyPatch, Patcher, DiscordApi, Security } from 'modules'; import { VueInjector, Reflection } from 'ui'; @@ -20,7 +20,6 @@ import E2EEMessageButton from './E2EEMessageButton.vue'; import aes256 from 'aes256'; let seed = Math.random().toString(36).replace(/[^a-z]+/g, ''); -const imageCache = []; export default new class E2EE extends BuiltinModule { @@ -128,7 +127,7 @@ export default new class E2EE extends BuiltinModule { const haveKey = this.getKey(DiscordApi.currentChannel.id); if (!haveKey) return; - const cached = imageCache.find(item => item.src === src); + const cached = Cache.find('e2ee:images', item => item.src === src); if (cached) { Logger.info('E2EE', 'Returning encrypted image from cache'); try { @@ -149,7 +148,7 @@ export default new class E2EE extends BuiltinModule { const sliced = arr.slice(aobindex); const image = new TextDecoder().decode(sliced); - imageCache.push({ src, image }); + Cache.push('e2ee:images', { src, image }); if (!component || !component.props) { Logger.warn('E2EE', 'Component seems to be gone'); diff --git a/client/src/index.js b/client/src/index.js index 82bc3972..f919d045 100644 --- a/client/src/index.js +++ b/client/src/index.js @@ -10,7 +10,7 @@ import { DOM, BdUI, BdMenu, Modals, Reflection, Toasts } from 'ui'; import BdCss from './styles/index.scss'; -import { Events, CssEditor, Globals, Settings, Database, Updater, ModuleManager, PluginManager, ThemeManager, ExtModuleManager, Vendor, WebpackModules, Patcher, MonkeyPatch, ReactComponents, ReactHelpers, ReactAutoPatcher, DiscordApi, BdWebApi, Connectivity } from 'modules'; +import { Events, CssEditor, Globals, Settings, Database, Updater, ModuleManager, PluginManager, ThemeManager, ExtModuleManager, Vendor, WebpackModules, Patcher, MonkeyPatch, ReactComponents, ReactHelpers, ReactAutoPatcher, DiscordApi, BdWebApi, Connectivity, Cache } from 'modules'; import { ClientLogger as Logger, ClientIPC, Utils } from 'common'; import { BuiltinManager, EmoteModule, ReactDevtoolsModule, VueDevtoolsModule, TrackingProtection } from 'builtin'; import electron from 'electron'; @@ -37,6 +37,7 @@ class BetterDiscord { EmoteModule, BdWebApi, Connectivity, + Cache, Logger, ClientIPC, Utils, plugins: PluginManager.localContent, diff --git a/client/src/modules/cache.js b/client/src/modules/cache.js index 1e18a289..fb78a123 100644 --- a/client/src/modules/cache.js +++ b/client/src/modules/cache.js @@ -8,17 +8,28 @@ * LICENSE file in the root directory of this source tree. */ const CACHE = []; + export default class Cache { static get cache() { return CACHE; } + /** + * Get something from cache + * @param {String} where Cache identifier + * @param {any} data Data to push + */ static push(where, data) { if (!this.cache[where]) this.cache[where] = []; this.cache[where].push(data); } + /** + * Find something in cache + * @param {String} where Cache identifier + * @param {Function} what Find callback + */ static find(where, what) { if (!this.cache[where]) this.cache[where] = []; return this.cache[where].find(what); diff --git a/client/src/modules/modules.js b/client/src/modules/modules.js index f8a78a41..af7e1c23 100644 --- a/client/src/modules/modules.js +++ b/client/src/modules/modules.js @@ -25,3 +25,4 @@ export { default as DiscordApi, Modules as DiscordApiModules } from './discordap export { default as BdWebApi } from './bdwebapi'; export { default as Connectivity } from './connectivity'; export { default as Security } from './security'; +export { default as Cache } from './cache'; From a1a63f2c359cfdcf825ec75feb4c9a2063ff8611 Mon Sep 17 00:00:00 2001 From: Jiiks Date: Sun, 12 Aug 2018 18:37:11 +0300 Subject: [PATCH 20/40] Proper doc --- client/src/modules/cache.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/modules/cache.js b/client/src/modules/cache.js index fb78a123..76822f15 100644 --- a/client/src/modules/cache.js +++ b/client/src/modules/cache.js @@ -16,7 +16,7 @@ export default class Cache { } /** - * Get something from cache + * Push something to cache * @param {String} where Cache identifier * @param {any} data Data to push */ From 3b8126781d75f670281782457b7687deadc50833 Mon Sep 17 00:00:00 2001 From: Jiiks Date: Sun, 12 Aug 2018 20:31:30 +0300 Subject: [PATCH 21/40] Default to channel id --- client/src/structs/settings/types/securekvp.js | 3 ++- client/src/ui/components/bd/setting/SecureKeyValuePair.vue | 6 ++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/client/src/structs/settings/types/securekvp.js b/client/src/structs/settings/types/securekvp.js index d11a90d3..602eed3b 100644 --- a/client/src/structs/settings/types/securekvp.js +++ b/client/src/structs/settings/types/securekvp.js @@ -11,10 +11,11 @@ import Kvp from './kvp'; export default class SecureKvpSetting extends Kvp { + /** * The value to use when the setting doesn't have a value. */ get defaultValue() { - return { key: 'Key', value: '**********' }; + return { key: 'PlaceholderKey', value: '' }; } } diff --git a/client/src/ui/components/bd/setting/SecureKeyValuePair.vue b/client/src/ui/components/bd/setting/SecureKeyValuePair.vue index 40d145d6..03e4115b 100644 --- a/client/src/ui/components/bd/setting/SecureKeyValuePair.vue +++ b/client/src/ui/components/bd/setting/SecureKeyValuePair.vue @@ -23,6 +23,8 @@ From b24c0ba2f321b999f37e8428ba8a1c765530ce12 Mon Sep 17 00:00:00 2001 From: Jiiks Date: Sun, 12 Aug 2018 20:57:02 +0300 Subject: [PATCH 22/40] Strip base64 prefix to not have static data in encrypted content --- client/src/builtin/E2EE.js | 2 +- client/src/builtin/E2EEComponent.vue | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/client/src/builtin/E2EE.js b/client/src/builtin/E2EE.js index e211c5df..99ce7b54 100644 --- a/client/src/builtin/E2EE.js +++ b/client/src/builtin/E2EE.js @@ -133,7 +133,7 @@ export default new class E2EE extends BuiltinModule { try { const decrypt = this.decrypt(this.decrypt(this.decrypt(seed, this.master), haveKey), cached.image); component.props.className = 'bd-decryptedImage'; - component.props.src = component.props.original = decrypt; + component.props.src = component.props.original = 'data:;base64,' + decrypt; } catch (err) { return } finally { component.props.readyState = 'READY' } return; } diff --git a/client/src/builtin/E2EEComponent.vue b/client/src/builtin/E2EEComponent.vue index 87c709be..5d5186e3 100644 --- a/client/src/builtin/E2EEComponent.vue +++ b/client/src/builtin/E2EEComponent.vue @@ -68,9 +68,8 @@ const canvas = document.createElement('canvas'); canvas.height = img.height; canvas.width = img.width; - const arrBuffer = await Utils.canvasToArrayBuffer(canvas); - const encodedBytes = new TextEncoder().encode(E2EE.encrypt(img.src)); + const encodedBytes = new TextEncoder().encode(E2EE.encrypt(img.src.replace('data:;base64,', ''))); Uploader.upload(DiscordApi.currentChannel.id, FileActions.makeFile(new Uint8Array([...new Uint8Array(arrBuffer), ...encodedBytes]), 'bde2ee.png')); }, From 2b833b514155ab44d706a7eebf5ba2a0a229594c Mon Sep 17 00:00:00 2001 From: Jiiks Date: Sun, 12 Aug 2018 21:43:59 +0300 Subject: [PATCH 23/40] add hmac --- client/src/builtin/E2EE.js | 42 +++++++++++++++++++--------- client/src/builtin/E2EEComponent.vue | 5 ++-- client/src/modules/security.js | 14 ++++++++++ client/webpack.config.js | 3 +- 4 files changed, 48 insertions(+), 16 deletions(-) diff --git a/client/src/builtin/E2EE.js b/client/src/builtin/E2EE.js index 99ce7b54..55ba056f 100644 --- a/client/src/builtin/E2EE.js +++ b/client/src/builtin/E2EE.js @@ -18,6 +18,7 @@ import { Utils } from 'common'; import E2EEComponent from './E2EEComponent.vue'; import E2EEMessageButton from './E2EEMessageButton.vue'; import aes256 from 'aes256'; +import crypto from 'node-crypto'; let seed = Math.random().toString(36).replace(/[^a-z]+/g, ''); @@ -58,6 +59,12 @@ export default new class E2EE extends BuiltinModule { return aes256.decrypt(key, content.replace(prefix, '')); } + async createHmac(data) { + const haveKey = this.getKey(DiscordApi.currentChannel.id); + if (!haveKey) return null; + return Security.createHmac(haveKey, data); + } + getKey(channelId) { const haveKey = this.database.find(kvp => kvp.value.key === channelId); if (!haveKey) return null; @@ -65,7 +72,6 @@ export default new class E2EE extends BuiltinModule { } async enabled(e) { - window.sec = Security; this.patchMessageContent(); const selector = '.' + WebpackModules.getClassName('channelTextArea', 'emojiButton'); const cta = await ReactComponents.getComponent('ChannelTextArea', { selector }); @@ -141,22 +147,32 @@ export default new class E2EE extends BuiltinModule { component.props.readyState = 'LOADING'; Logger.info('E2EE', 'Decrypting image: ' + src); request.get(src, { encoding: 'binary' }).then(res => { - const arr = new Uint8Array(new ArrayBuffer(res.length)); - for (let i = 0; i < res.length; i++) arr[i] = res.charCodeAt(i); + (async () => { + const arr = new Uint8Array(new ArrayBuffer(res.length)); + for (let i = 0; i < res.length; i++) arr[i] = res.charCodeAt(i); - const aobindex = Utils.aobscan(arr, [73, 69, 78, 68]) + 8; - const sliced = arr.slice(aobindex); - const image = new TextDecoder().decode(sliced); + const aobindex = Utils.aobscan(arr, [73, 69, 78, 68]) + 8; + const sliced = arr.slice(aobindex); + const image = new TextDecoder().decode(sliced); - Cache.push('e2ee:images', { src, image }); + const hmac = image.slice(-64); + const data = image.slice(0, -64); + const validateHmac = await this.createHmac(data); + if (hmac !== validateHmac) { + console.log('INVALID HMAC!'); + return; + } - if (!component || !component.props) { - Logger.warn('E2EE', 'Component seems to be gone'); - return; - } + Cache.push('e2ee:images', { src, image: data }); - component.props.decrypting = false; - component.forceUpdate(); + if (!component || !component.props) { + Logger.warn('E2EE', 'Component seems to be gone'); + return; + } + + component.props.decrypting = false; + component.forceUpdate(); + })(); }).catch(err => { console.log('request error', err); }); diff --git a/client/src/builtin/E2EEComponent.vue b/client/src/builtin/E2EEComponent.vue index 5d5186e3..62579c96 100644 --- a/client/src/builtin/E2EEComponent.vue +++ b/client/src/builtin/E2EEComponent.vue @@ -69,8 +69,9 @@ canvas.height = img.height; canvas.width = img.width; const arrBuffer = await Utils.canvasToArrayBuffer(canvas); - const encodedBytes = new TextEncoder().encode(E2EE.encrypt(img.src.replace('data:;base64,', ''))); - + const encrypted = E2EE.encrypt(img.src.replace('data:;base64,', '')); + const hmac = await E2EE.createHmac(encrypted); + const encodedBytes = new TextEncoder().encode(encrypted + hmac); Uploader.upload(DiscordApi.currentChannel.id, FileActions.makeFile(new Uint8Array([...new Uint8Array(arrBuffer), ...encodedBytes]), 'bde2ee.png')); }, toggleEncrypt() { diff --git a/client/src/modules/security.js b/client/src/modules/security.js index f0bf737c..7209cc20 100644 --- a/client/src/modules/security.js +++ b/client/src/modules/security.js @@ -8,6 +8,7 @@ * LICENSE file in the root directory of this source tree. */ +import nodecrypto from 'node-crypto'; import aes256 from 'aes256'; export default class Security { @@ -40,4 +41,17 @@ export default class Security { return decrypt; } + static async createHmac(key, data, algorithm = 'sha256') { + const hmac = nodecrypto.createHmac(algorithm, key); + return new Promise((resolve, reject) => { + hmac.on('readable', () => { + const data = hmac.read(); + if (data) return resolve(data.toString('hex')); + reject(null); + }); + hmac.write(data); + hmac.end(); + }); + } + } diff --git a/client/webpack.config.js b/client/webpack.config.js index 62a2c236..ca202b4d 100644 --- a/client/webpack.config.js +++ b/client/webpack.config.js @@ -38,7 +38,8 @@ module.exports = { process: 'require("process")', net: 'require("net")', request: 'require(require("path").join(require("electron").remote.app.getAppPath(), "node_modules", "request"))', - sparkplug: 'require("../../core/dist/sparkplug")' + sparkplug: 'require("../../core/dist/sparkplug")', + 'node-crypto': 'require("crypto")' }, resolve: { alias: { From cae68947dfad7f9ce091572c5b7b510c50fa565f Mon Sep 17 00:00:00 2001 From: Jiiks Date: Sun, 12 Aug 2018 21:46:59 +0300 Subject: [PATCH 24/40] add bad key handler --- client/src/builtin/E2EE.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/client/src/builtin/E2EE.js b/client/src/builtin/E2EE.js index 55ba056f..d7fbb7dc 100644 --- a/client/src/builtin/E2EE.js +++ b/client/src/builtin/E2EE.js @@ -135,6 +135,11 @@ export default new class E2EE extends BuiltinModule { const cached = Cache.find('e2ee:images', item => item.src === src); if (cached) { + if (cached.invalidKey) { + component.props.className = 'bd-encryptedImageBadKey'; + component.props.readyState = 'READY'; + return; + } Logger.info('E2EE', 'Returning encrypted image from cache'); try { const decrypt = this.decrypt(this.decrypt(this.decrypt(seed, this.master), haveKey), cached.image); @@ -159,6 +164,7 @@ export default new class E2EE extends BuiltinModule { const data = image.slice(0, -64); const validateHmac = await this.createHmac(data); if (hmac !== validateHmac) { + Cache.push('e2ee:images', { src, invalidKey: true }); console.log('INVALID HMAC!'); return; } From 0a751919aabff7902252f91840e82774110edb0d Mon Sep 17 00:00:00 2001 From: Jiiks Date: Sun, 12 Aug 2018 21:48:15 +0300 Subject: [PATCH 25/40] Remove debug --- client/src/builtin/E2EE.js | 1 - 1 file changed, 1 deletion(-) diff --git a/client/src/builtin/E2EE.js b/client/src/builtin/E2EE.js index d7fbb7dc..372933c2 100644 --- a/client/src/builtin/E2EE.js +++ b/client/src/builtin/E2EE.js @@ -165,7 +165,6 @@ export default new class E2EE extends BuiltinModule { const validateHmac = await this.createHmac(data); if (hmac !== validateHmac) { Cache.push('e2ee:images', { src, invalidKey: true }); - console.log('INVALID HMAC!'); return; } From bd0c66466fb82d2654bc2f627d5df9af47b6454e Mon Sep 17 00:00:00 2001 From: Jiiks Date: Sun, 12 Aug 2018 21:54:57 +0300 Subject: [PATCH 26/40] Use randomBytes for master encryption --- client/src/builtin/E2EE.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/src/builtin/E2EE.js b/client/src/builtin/E2EE.js index 372933c2..4aff2c97 100644 --- a/client/src/builtin/E2EE.js +++ b/client/src/builtin/E2EE.js @@ -20,7 +20,7 @@ import E2EEMessageButton from './E2EEMessageButton.vue'; import aes256 from 'aes256'; import crypto from 'node-crypto'; -let seed = Math.random().toString(36).replace(/[^a-z]+/g, ''); +let seed = crypto.randomBytes(64).toString('hex'); export default new class E2EE extends BuiltinModule { @@ -31,7 +31,7 @@ export default new class E2EE extends BuiltinModule { } setMaster(key) { - seed = Math.random().toString(36).replace(/[^a-z]+/g, ''); + seed = crypto.randomBytes(64).toString('hex'); const newMaster = this.encrypt(seed, key); // TODO re-encrypt everything with new master return (this.master = newMaster); From d53a3b4d645c19a556e175d8b8915c00af178615 Mon Sep 17 00:00:00 2001 From: Jiiks Date: Sun, 12 Aug 2018 21:56:57 +0300 Subject: [PATCH 27/40] base classname --- client/src/builtin/E2EE.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/builtin/E2EE.js b/client/src/builtin/E2EE.js index 4aff2c97..1875d829 100644 --- a/client/src/builtin/E2EE.js +++ b/client/src/builtin/E2EE.js @@ -136,7 +136,7 @@ export default new class E2EE extends BuiltinModule { const cached = Cache.find('e2ee:images', item => item.src === src); if (cached) { if (cached.invalidKey) { - component.props.className = 'bd-encryptedImageBadKey'; + component.props.className = 'bd-encryptedImage bd-encryptedImageBadKey'; component.props.readyState = 'READY'; return; } From 71a0ce8fb0c8777b2e25b83a44477637ff91f629 Mon Sep 17 00:00:00 2001 From: Jiiks Date: Sun, 12 Aug 2018 21:58:26 +0300 Subject: [PATCH 28/40] Set readyState on failed authentication --- client/src/builtin/E2EE.js | 1 + 1 file changed, 1 insertion(+) diff --git a/client/src/builtin/E2EE.js b/client/src/builtin/E2EE.js index 1875d829..a72c3072 100644 --- a/client/src/builtin/E2EE.js +++ b/client/src/builtin/E2EE.js @@ -165,6 +165,7 @@ export default new class E2EE extends BuiltinModule { const validateHmac = await this.createHmac(data); if (hmac !== validateHmac) { Cache.push('e2ee:images', { src, invalidKey: true }); + if (component && component.props) component.props.readyState = 'READY'; return; } From ad35c89d7dfb7303683a494500d9cdc527614d1d Mon Sep 17 00:00:00 2001 From: Jiiks Date: Sun, 12 Aug 2018 21:59:54 +0300 Subject: [PATCH 29/40] Force update on failed auth instead --- client/src/builtin/E2EE.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/builtin/E2EE.js b/client/src/builtin/E2EE.js index a72c3072..68ecdb42 100644 --- a/client/src/builtin/E2EE.js +++ b/client/src/builtin/E2EE.js @@ -165,7 +165,7 @@ export default new class E2EE extends BuiltinModule { const validateHmac = await this.createHmac(data); if (hmac !== validateHmac) { Cache.push('e2ee:images', { src, invalidKey: true }); - if (component && component.props) component.props.readyState = 'READY'; + if (component && component.props) component.forceUpdate(); return; } From 3f5b2afbd5559f84ac2a4f229a4b9ed5db5215c0 Mon Sep 17 00:00:00 2001 From: Jiiks Date: Sun, 12 Aug 2018 22:05:44 +0300 Subject: [PATCH 30/40] decrypting should be set to false as well --- client/src/builtin/E2EE.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/client/src/builtin/E2EE.js b/client/src/builtin/E2EE.js index 68ecdb42..992c1f6f 100644 --- a/client/src/builtin/E2EE.js +++ b/client/src/builtin/E2EE.js @@ -165,7 +165,10 @@ export default new class E2EE extends BuiltinModule { const validateHmac = await this.createHmac(data); if (hmac !== validateHmac) { Cache.push('e2ee:images', { src, invalidKey: true }); - if (component && component.props) component.forceUpdate(); + if (component && component.props) { + component.props.decrypting = false; + component.forceUpdate(); + } return; } From 856334cf30394b3230085c4ed7b9f7597ab93278 Mon Sep 17 00:00:00 2001 From: Jiiks Date: Sun, 12 Aug 2018 22:09:34 +0300 Subject: [PATCH 31/40] TODO --- client/src/builtin/E2EE.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/builtin/E2EE.js b/client/src/builtin/E2EE.js index 992c1f6f..7b3e60af 100644 --- a/client/src/builtin/E2EE.js +++ b/client/src/builtin/E2EE.js @@ -135,7 +135,7 @@ export default new class E2EE extends BuiltinModule { const cached = Cache.find('e2ee:images', item => item.src === src); if (cached) { - if (cached.invalidKey) { + if (cached.invalidKey) { // TODO If key has changed we should recheck all with invalid key component.props.className = 'bd-encryptedImage bd-encryptedImageBadKey'; component.props.readyState = 'READY'; return; From a123e20b05d9c4a1cee32483be3d352d4270b2aa Mon Sep 17 00:00:00 2001 From: Jiiks Date: Sun, 12 Aug 2018 22:11:41 +0300 Subject: [PATCH 32/40] Move to Security --- client/src/builtin/E2EE.js | 6 +++--- client/src/modules/security.js | 4 ++++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/client/src/builtin/E2EE.js b/client/src/builtin/E2EE.js index 7b3e60af..df2d907b 100644 --- a/client/src/builtin/E2EE.js +++ b/client/src/builtin/E2EE.js @@ -18,9 +18,9 @@ import { Utils } from 'common'; import E2EEComponent from './E2EEComponent.vue'; import E2EEMessageButton from './E2EEMessageButton.vue'; import aes256 from 'aes256'; -import crypto from 'node-crypto'; +import nodecrypto from 'node-crypto'; -let seed = crypto.randomBytes(64).toString('hex'); +let seed = Security.randomBytes(); export default new class E2EE extends BuiltinModule { @@ -31,7 +31,7 @@ export default new class E2EE extends BuiltinModule { } setMaster(key) { - seed = crypto.randomBytes(64).toString('hex'); + seed = Security.randomBytes(); const newMaster = this.encrypt(seed, key); // TODO re-encrypt everything with new master return (this.master = newMaster); diff --git a/client/src/modules/security.js b/client/src/modules/security.js index 7209cc20..15db8c40 100644 --- a/client/src/modules/security.js +++ b/client/src/modules/security.js @@ -41,6 +41,10 @@ export default class Security { return decrypt; } + static randomBytes(length = 64, to = 'hex') { + return nodecrypto.randomBytes(length).toString(to); + } + static async createHmac(key, data, algorithm = 'sha256') { const hmac = nodecrypto.createHmac(algorithm, key); return new Promise((resolve, reject) => { From b6da7019d0a58421c580df8cebe34761493f4b5c Mon Sep 17 00:00:00 2001 From: Jiiks Date: Sun, 12 Aug 2018 22:12:26 +0300 Subject: [PATCH 33/40] Unused import --- client/src/builtin/E2EE.js | 1 - 1 file changed, 1 deletion(-) diff --git a/client/src/builtin/E2EE.js b/client/src/builtin/E2EE.js index df2d907b..571e7b93 100644 --- a/client/src/builtin/E2EE.js +++ b/client/src/builtin/E2EE.js @@ -18,7 +18,6 @@ import { Utils } from 'common'; import E2EEComponent from './E2EEComponent.vue'; import E2EEMessageButton from './E2EEMessageButton.vue'; import aes256 from 'aes256'; -import nodecrypto from 'node-crypto'; let seed = Security.randomBytes(); From d6d78d1e99b2b3aa424291732b15ac0f8d28179b Mon Sep 17 00:00:00 2001 From: Jiiks Date: Mon, 13 Aug 2018 10:45:52 +0300 Subject: [PATCH 34/40] Move master init to enabled --- client/src/builtin/E2EE.js | 8 ++++++-- client/src/styles/partials/bdsettings/e2ee.scss | 16 ++++++++-------- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/client/src/builtin/E2EE.js b/client/src/builtin/E2EE.js index 571e7b93..68f8128d 100644 --- a/client/src/builtin/E2EE.js +++ b/client/src/builtin/E2EE.js @@ -19,13 +19,13 @@ import E2EEComponent from './E2EEComponent.vue'; import E2EEMessageButton from './E2EEMessageButton.vue'; import aes256 from 'aes256'; -let seed = Security.randomBytes(); +const TEMP_KEY = 'temporarymasterkey'; +let seed; export default new class E2EE extends BuiltinModule { constructor() { super(); - this.master = this.encrypt(seed, 'temporarymasterkey'); this.encryptNewMessages = true; } @@ -71,6 +71,10 @@ export default new class E2EE extends BuiltinModule { } async enabled(e) { + seed = Security.randomBytes(); + // TODO Input modal for key + this.master = this.encrypt(seed, TEMP_KEY); + this.patchMessageContent(); const selector = '.' + WebpackModules.getClassName('channelTextArea', 'emojiButton'); const cta = await ReactComponents.getComponent('ChannelTextArea', { selector }); diff --git a/client/src/styles/partials/bdsettings/e2ee.scss b/client/src/styles/partials/bdsettings/e2ee.scss index 0f8fcd9b..04092b56 100644 --- a/client/src/styles/partials/bdsettings/e2ee.scss +++ b/client/src/styles/partials/bdsettings/e2ee.scss @@ -130,21 +130,21 @@ } .bd-encryptedImage::before { - content: "Encrypted Image"; + // content: "Encrypted Image"; position: absolute; background: $colbdgreen; width: 100%; height: 100%; border-radius: 3px; display: flex; - justify-content: center; - align-items: flex-start; - font-size: 1.2em; - font-weight: 700; - color: #2c2c2c; - line-height: 30px; + // justify-content: center; + // align-items: flex-start; + // font-size: 1.2em; + // font-weight: 700; + // color: #2c2c2c; + // line-height: 30px; background-image: $lockIcon; - background-size: 50% 50%; + background-size: calc(100% / 2); background-repeat: no-repeat; background-position: center; } From 0c0ebb2ebbe49988edfdc7a971784c35615869a3 Mon Sep 17 00:00:00 2001 From: Jiiks Date: Mon, 13 Aug 2018 10:57:32 +0300 Subject: [PATCH 35/40] Pseudo needs content --- client/src/styles/partials/bdsettings/e2ee.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/styles/partials/bdsettings/e2ee.scss b/client/src/styles/partials/bdsettings/e2ee.scss index 04092b56..ccbc47eb 100644 --- a/client/src/styles/partials/bdsettings/e2ee.scss +++ b/client/src/styles/partials/bdsettings/e2ee.scss @@ -130,7 +130,7 @@ } .bd-encryptedImage::before { - // content: "Encrypted Image"; + content: ""; position: absolute; background: $colbdgreen; width: 100%; From 5592a8d37666799c6bc9afe647d2f4aea47d24fc Mon Sep 17 00:00:00 2001 From: Jiiks Date: Mon, 13 Aug 2018 12:33:44 +0300 Subject: [PATCH 36/40] Use security module --- client/src/builtin/E2EE.js | 29 ++++++++++++------- client/src/modules/security.js | 27 +++++++++++++++-- .../bd/setting/SecureKeyValuePair.vue | 5 ++-- 3 files changed, 46 insertions(+), 15 deletions(-) diff --git a/client/src/builtin/E2EE.js b/client/src/builtin/E2EE.js index 68f8128d..a2c93640 100644 --- a/client/src/builtin/E2EE.js +++ b/client/src/builtin/E2EE.js @@ -17,7 +17,6 @@ import { request } from 'vendor'; import { Utils } from 'common'; import E2EEComponent from './E2EEComponent.vue'; import E2EEMessageButton from './E2EEMessageButton.vue'; -import aes256 from 'aes256'; const TEMP_KEY = 'temporarymasterkey'; let seed; @@ -31,7 +30,7 @@ export default new class E2EE extends BuiltinModule { setMaster(key) { seed = Security.randomBytes(); - const newMaster = this.encrypt(seed, key); + const newMaster = Security.encrypt(seed, key); // TODO re-encrypt everything with new master return (this.master = newMaster); } @@ -45,17 +44,22 @@ export default new class E2EE extends BuiltinModule { } encrypt(key, content, prefix = '') { + if (!key) { + // Encrypt something with master + return Security.encrypt(Security.decrypt(seed, this.master), content); + } + if (!content) { // Get key for current channel and encrypt const haveKey = this.getKey(DiscordApi.currentChannel.id); if (!haveKey) return 'nokey'; - return this.encrypt(this.decrypt(this.decrypt(seed, this.master), haveKey), key); + return Security.encrypt(Security.decrypt(seed, [this.master, haveKey]), key); } - return prefix + aes256.encrypt(key, content); + return prefix + Security.encrypt(key, content); } decrypt(key, content, prefix = '') { - return aes256.decrypt(key, content.replace(prefix, '')); + return Security.decrypt(key, content, prefix); } async createHmac(data) { @@ -71,9 +75,10 @@ export default new class E2EE extends BuiltinModule { } async enabled(e) { + window._sec = Security; seed = Security.randomBytes(); // TODO Input modal for key - this.master = this.encrypt(seed, TEMP_KEY); + this.master = Security.encrypt(seed, TEMP_KEY); this.patchMessageContent(); const selector = '.' + WebpackModules.getClassName('channelTextArea', 'emojiButton'); @@ -106,7 +111,7 @@ export default new class E2EE extends BuiltinModule { if (!content.startsWith('$:')) return; // Not an encrypted string let decrypt; try { - decrypt = this.decrypt(this.decrypt(this.decrypt(seed, this.master), key), component.props.message.content); + decrypt = Security.decrypt(seed, [this.master, key, component.props.message.content]); } catch (err) { return } // Ignore errors such as non empty component.props.message.bd_encrypted = true; @@ -121,7 +126,11 @@ export default new class E2EE extends BuiltinModule { renderMessageContent(component, args, retVal) { if (!component.props.message.bd_encrypted) return; - retVal.props.children[0].props.children.props.children.props.children.unshift(VueInjector.createReactElement(E2EEMessageButton)); + try { + retVal.props.children[0].props.children.props.children.props.children.unshift(VueInjector.createReactElement(E2EEMessageButton)); + } catch (err) { + Logger.err('E2EE', err.message); + } } beforeRenderImageWrapper(component, args, retVal) { @@ -145,7 +154,7 @@ export default new class E2EE extends BuiltinModule { } Logger.info('E2EE', 'Returning encrypted image from cache'); try { - const decrypt = this.decrypt(this.decrypt(this.decrypt(seed, this.master), haveKey), cached.image); + const decrypt = Security.decrypt(seed, [this.master, haveKey, cached.image]); component.props.className = 'bd-decryptedImage'; component.props.src = component.props.original = 'data:;base64,' + decrypt; } catch (err) { return } finally { component.props.readyState = 'READY' } @@ -207,7 +216,7 @@ export default new class E2EE extends BuiltinModule { handleChannelTextAreaSubmit(component, args, retVal) { const key = this.getKey(DiscordApi.currentChannel.id); if (!this.encryptNewMessages || !key) return; - component.props.value = this.encrypt(this.decrypt(this.decrypt(seed, this.master), key), component.props.value, '$:'); + component.props.value = Security.encrypt(Security.decrypt(seed, [this.master, key]), component.props.value, '$:'); } async disabled(e) { diff --git a/client/src/modules/security.js b/client/src/modules/security.js index 15db8c40..040be851 100644 --- a/client/src/modules/security.js +++ b/client/src/modules/security.js @@ -14,16 +14,20 @@ import aes256 from 'aes256'; export default class Security { static encrypt(key, content, prefix = '') { - if (key instanceof Array) return this.deepEncrypt(key, content, prefix); + if (key instanceof Array || content instanceof Array) return this.deepEncrypt(key, content, prefix); return `${prefix}${aes256.encrypt(key, content)}`; } static decrypt(key, content, prefix = '') { - if (key instanceof Array) return this.deepDecrypt(key, content, prefix); + if (key instanceof Array || content instanceof Array) { + console.log('deep decrypting'); + return this.deepDecrypt(key, content, prefix); + } return aes256.decrypt(key, content.replace(prefix, '')); } static deepEncrypt(keys, content, prefix = '') { + if (content && content instanceof Array) return this.deepEncryptContent(keys, content, prefix); let encrypt = null; for (const key of keys) { if (encrypt === null) encrypt = this.encrypt(key, content, prefix); @@ -32,7 +36,17 @@ export default class Security { return encrypt; } + static deepEncryptContent(key, contents, prefix = '') { + let encrypt = null; + for (const content of contents) { + if (encrypt === null) encrypt = this.encrypt(key, content, prefix); + else encrypt = this.encrypt(encrypt, content, prefix); + } + return encrypt; + } + static deepDecrypt(keys, content, prefix = '') { + if (content && content instanceof Array) return this.deepDecryptContent(keys, content, prefix); let decrypt = null; for (const key of keys.reverse()) { if (decrypt === null) decrypt = this.decrypt(key, content, prefix); @@ -41,6 +55,15 @@ export default class Security { return decrypt; } + static deepDecryptContent(key, contents, prefix = '') { + let decrypt = null; + for (const content of contents) { + if (decrypt === null) decrypt = this.decrypt(key, content, prefix); + else decrypt = this.decrypt(decrypt, content, prefix); + } + return decrypt; + } + static randomBytes(length = 64, to = 'hex') { return nodecrypto.randomBytes(length).toString(to); } diff --git a/client/src/ui/components/bd/setting/SecureKeyValuePair.vue b/client/src/ui/components/bd/setting/SecureKeyValuePair.vue index 03e4115b..7315fa20 100644 --- a/client/src/ui/components/bd/setting/SecureKeyValuePair.vue +++ b/client/src/ui/components/bd/setting/SecureKeyValuePair.vue @@ -22,13 +22,12 @@