E2EE refactor for new builtin base

This commit is contained in:
Jiiks 2018-08-25 19:19:19 +03:00
parent 1afbcacec3
commit 436c469b1d
1 changed files with 59 additions and 54 deletions

View File

@ -30,6 +30,14 @@ let seed;
export default new class E2EE extends BuiltinModule {
/* Getters */
get moduleName() { return 'E2EE' }
get settingPath() { return ['security', 'default', 'e2ee'] }
get database() { return Settings.getSetting('security', 'e2eedb', 'e2ekvps').value }
constructor() {
super();
this.encryptNewMessages = true;
@ -40,25 +48,18 @@ export default new class E2EE extends BuiltinModule {
async enabled(e) {
await this.fetchMasterKey();
Settings.getSetting('security', 'default', 'use-keytar').on('setting-updated', this.fetchMasterKey);
Events.on('discord:MESSAGE_CREATE', this.handlePublicKey);
this.patchDispatcher();
this.patchMessageContent();
const cta = await ReactComponents.getComponent('ChannelTextArea', { selector: Reflection.resolve('channelTextArea', 'emojiButton').selector });
this.patchChannelTextArea(cta);
this.patchChannelTextAreaSubmit(cta);
cta.forceUpdateAll();
Settings.getSetting('security', 'default', 'use-keytar').on('setting-updated', this.fetchMasterKey);
}
async disabled(e) {
Settings.getSetting('security', 'default', 'use-keytar').off('setting-updated', this.fetchMasterKey);
Events.off('discord:MESSAGE_CREATE', this.handlePublicKey);
for (const patch of Patcher.getPatchesByCaller('BD:E2EE')) patch.unpatch();
const ctaComponent = await ReactComponents.getComponent('ChannelTextArea');
ctaComponent.forceUpdateAll();
}
/* Methods */
async fetchMasterKey() {
try {
if (Settings.get('security', 'default', 'use-keytar')) {
@ -94,14 +95,6 @@ export default new class E2EE extends BuiltinModule {
return (this.master = newMaster);
}
get settingPath() {
return ['security', 'default', 'e2ee'];
}
get database() {
return Settings.getSetting('security', 'e2eedb', 'e2ekvps').value;
}
encrypt(key, content, prefix = '') {
if (!key) {
// Encrypt something with master
@ -171,6 +164,48 @@ export default new class E2EE extends BuiltinModule {
}
}
/* Patches */
async applyPatches() {
if (this.patches.length) return;
const { Dispatcher } = Reflection.modules;
this.patch(Dispatcher, 'dispatch', this.dispatcherPatch, 'before');
this.patchMessageContent();
const ChannelTextArea = await ReactComponents.getComponent('ChannelTextArea', { selector: Reflection.resolve('channelTextArea', 'emojiButton').selector });
this.patchChannelTextArea(ChannelTextArea);
this.patchChannelTextAreaSubmit(ChannelTextArea);
ChannelTextArea.forceUpdateAll();
}
dispatcherPatch(_, [event]) {
if (!event || event.type !== 'MESSAGE_CREATE') return;
const key = this.getKey(event.message.channel_id);
if (!key) return; // We don't have a key for this channel
if (typeof event.message.content !== 'string') return; // Ignore any non string content
if (!event.message.content.startsWith('$:')) return; // Not an encrypted string
let decrypt;
try {
decrypt = this.decrypt(this.decrypt(this.decrypt(seed, this.master), key), event.message.content);
} catch (err) { return } // Ignore errors such as non empty
const { MessageParser, Permissions, DiscordConstants } = Reflection.modules;
const currentChannel = DiscordApi.Channel.fromId(event.message.channel_id).discordObject;
// Create a generic message object to parse mentions with
const parsed = MessageParser.parse(currentChannel, decrypt).content;
if (userMentionPattern.test(parsed))
event.message.mentions = parsed.match(userMentionPattern).map(m => { return { id: m.replace(/[^0-9]/g, '') } });
if (roleMentionPattern.test(parsed))
event.message.mention_roles = parsed.match(roleMentionPattern).map(m => m.replace(/[^0-9]/g, ''));
if (everyoneMentionPattern.test(parsed))
event.message.mention_everyone = Permissions.can(DiscordConstants.Permissions.MENTION_EVERYONE, currentChannel);
}
// TODO Received exchange should also expire if not accepted in time
async handlePublicKey(e) {
if (!DiscordApi.currentChannel) return;
@ -200,43 +235,13 @@ export default new class E2EE extends BuiltinModule {
}
}
patchDispatcher() {
const { Dispatcher } = Reflection.modules;
MonkeyPatch('BD:E2EE', Dispatcher).before('dispatch', (_, [event]) => {
if (event.type !== 'MESSAGE_CREATE') return;
const key = this.getKey(event.message.channel_id);
if (!key) return; // We don't have a key for this channel
if (typeof event.message.content !== 'string') return; // Ignore any non string content
if (!event.message.content.startsWith('$:')) return; // Not an encrypted string
let decrypt;
try {
decrypt = this.decrypt(this.decrypt(this.decrypt(seed, this.master), key), event.message.content);
} catch (err) { return } // Ignore errors such as non empty
const { MessageParser, Permissions, DiscordConstants } = Reflection.modules;
const currentChannel = DiscordApi.Channel.fromId(event.message.channel_id).discordObject;
// Create a generic message object to parse mentions with
const parsed = MessageParser.parse(currentChannel, decrypt).content;
if (userMentionPattern.test(parsed))
event.message.mentions = parsed.match(userMentionPattern).map(m => { return { id: m.replace(/[^0-9]/g, '') } });
if (roleMentionPattern.test(parsed))
event.message.mention_roles = parsed.match(roleMentionPattern).map(m => m.replace(/[^0-9]/g, ''));
if (everyoneMentionPattern.test(parsed))
event.message.mention_everyone = Permissions.can(DiscordConstants.Permissions.MENTION_EVERYONE, currentChannel);
});
}
async patchMessageContent() {
const MessageContent = await ReactComponents.getComponent('MessageContent', { selector: Reflection.resolve('container', 'containerCozy', 'containerCompact', 'edited').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));
this.patch(MessageContent.component.prototype, 'render', this.beforeRenderMessageContent, 'before');
this.patch(MessageContent.component.prototype, 'render', this.afterRenderMessageContent);
const ImageWrapper = await ReactComponents.getComponent('ImageWrapper', { selector: Reflection.resolve('imageWrapper').selector });
MonkeyPatch('BD:E2EE', ImageWrapper.component.prototype).before('render', this.beforeRenderImageWrapper.bind(this));
this.patch(ImageWrapper.component.prototype, 'render', this.beforeRenderImageWrapper, 'before');
}
beforeRenderMessageContent(component) {
@ -280,7 +285,7 @@ export default new class E2EE extends BuiltinModule {
component.props.message.contentParsed = create.contentParsed;
}
renderMessageContent(component, args, retVal) {
afterRenderMessageContent(component, args, retVal) {
if (!component.props.message.bd_encrypted) return;
const buttons = Utils.findInReactTree(retVal, m => Array.isArray(m) && m[1].props && m[1].props.currentUserId);
if (!buttons) return;
@ -361,7 +366,7 @@ export default new class E2EE extends BuiltinModule {
}
patchChannelTextArea(cta) {
MonkeyPatch('BD:E2EE', cta.component.prototype).after('render', this.renderChannelTextArea);
this.patch(cta.component.prototype, 'render', this.renderChannelTextArea);
}
renderChannelTextArea(component, args, retVal) {
@ -371,7 +376,7 @@ export default new class E2EE extends BuiltinModule {
}
patchChannelTextAreaSubmit(cta) {
MonkeyPatch('BD:E2EE', cta.component.prototype).before('handleSubmit', this.handleChannelTextAreaSubmit.bind(this));
this.patch(cta.component.prototype, 'handleSubmit', this.handleChannelTextAreaSubmit, 'before');
}
handleChannelTextAreaSubmit(component, args, retVal) {