Skip to content

Commit

Permalink
[NEW] Option to reset e2e key (#12483)
Browse files Browse the repository at this point in the history
* Added option to reset e2e key

* Reset e2e key
  • Loading branch information
Hudell authored and rodrigok committed Nov 21, 2018
1 parent 2882438 commit 9289e5b
Show file tree
Hide file tree
Showing 11 changed files with 135 additions and 3 deletions.
1 change: 1 addition & 0 deletions packages/rocketchat-authorization/server/startup.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ Meteor.startup(function() {
{ _id: 'mention-here', roles : ['admin', 'owner', 'moderator', 'user'] },
{ _id: 'mute-user', roles : ['admin', 'owner', 'moderator'] },
{ _id: 'remove-user', roles : ['admin', 'owner', 'moderator'] },
{ _id: 'reset-other-user-e2e-key', roles : ['admin'] },
{ _id: 'run-import', roles : ['admin'] },
{ _id: 'run-migration', roles : ['admin'] },
{ _id: 'set-moderator', roles : ['admin', 'owner'] },
Expand Down
21 changes: 21 additions & 0 deletions packages/rocketchat-e2e/client/rocketchat.e2e.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ class E2E {
_id: roomId,
});

if (!room) {
return;
}

if (room.encrypted !== true) {
return;
}
Expand Down Expand Up @@ -185,14 +189,24 @@ class E2E {
}

async stopClient() {
console.log('E2E -> Stop Client');
// This flag is used to avoid closing unrelated alerts.
if (showingE2EAlert) {
alerts.close();
}

localStorage.removeItem('public_key');
localStorage.removeItem('private_key');
this.instancesByRoomId = {};
this.privateKey = null;
this.enabled.set(false);
this._ready.set(false);
this.started = false;

this.readyPromise = new Deferred();
this.readyPromise.then(() => {
this._ready.set(true);
});
}

setupListeners() {
Expand Down Expand Up @@ -236,6 +250,7 @@ class E2E {
async loadKeysFromDB() {
try {
const { public_key, private_key } = await call('e2e.fetchMyKeys');

this.db_public_key = public_key;
this.db_private_key = private_key;
} catch (error) {
Expand Down Expand Up @@ -280,6 +295,12 @@ class E2E {
} catch (error) {
return console.error('E2E -> Error exporting private key: ', error);
}

this.requestSubscriptionKeys();
}

async requestSubscriptionKeys() {
call('e2e.requestSubscriptionKeys');
}

createRandomPassword() {
Expand Down
2 changes: 2 additions & 0 deletions packages/rocketchat-e2e/server/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import './methods/getUsersOfRoomWithoutKey';
import './methods/updateGroupKey';
import './methods/setRoomKeyID';
import './methods/fetchMyKeys';
import './methods/resetUserE2EKey';
import './methods/requestSubscriptionKeys';

RocketChat.callbacks.add('afterJoinRoom', (user, room) => {
RocketChat.Notifications.notifyRoom('e2e.keyRequest', room._id, room.e2eKeyId);
Expand Down
33 changes: 33 additions & 0 deletions packages/rocketchat-e2e/server/methods/requestSubscriptionKeys.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { Meteor } from 'meteor/meteor';
import { RocketChat } from 'meteor/rocketchat:lib';

Meteor.methods({
'e2e.requestSubscriptionKeys'() {
if (!Meteor.userId()) {
throw new Meteor.Error('error-invalid-user', 'Invalid user', {
method: 'requestSubscriptionKeys',
});
}

// Get all encrypted rooms that the user is subscribed to

const subscriptions = RocketChat.models.Subscriptions.findByUserId(Meteor.userId());
const roomIds = subscriptions.map((subscription) => subscription.rid);

const query = {
e2eKeyId : {
$exists: true,
},
_id : {
$in: roomIds,
},
};

const rooms = RocketChat.models.Rooms.find(query);
rooms.forEach((room) => {
RocketChat.Notifications.notifyRoom('e2e.keyRequest', room._id, room.e2eKeyId);
});

return true;
},
});
25 changes: 25 additions & 0 deletions packages/rocketchat-e2e/server/methods/resetUserE2EKey.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { Meteor } from 'meteor/meteor';
import { RocketChat } from 'meteor/rocketchat:lib';

Meteor.methods({
'e2e.resetUserE2EKey'(userId) {
if (!Meteor.userId()) {
throw new Meteor.Error('error-invalid-user', 'Invalid user', {
method: 'resetUserE2EKey',
});
}

if (RocketChat.authz.hasPermission(Meteor.userId(), 'reset-other-user-e2e-key') !== true) {
throw new Meteor.Error('error-not-allowed', 'Not allowed', {
method: 'resetUserE2EKey',
});
}

RocketChat.models.Users.resetE2EKey(userId);
RocketChat.models.Subscriptions.resetUserE2EKey(userId);

// Force the user to logout, so that the keys can be generated again
RocketChat.models.Users.removeResumeService(userId);
return true;
},
});
10 changes: 10 additions & 0 deletions packages/rocketchat-e2e/server/models/Subscriptions.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,13 @@ RocketChat.models.Subscriptions.findByRidWithoutE2EKey = function(rid, options)

return this.find(query, options);
};

RocketChat.models.Subscriptions.resetUserE2EKey = function(userId) {
this.update({ 'u._id': userId }, {
$unset: {
E2EKey: '',
},
}, {
multi: true,
});
};
8 changes: 8 additions & 0 deletions packages/rocketchat-e2e/server/models/Users.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,11 @@ RocketChat.models.Users.findByIdsWithPublicE2EKey = function(ids, options) {

return this.find(query, options);
};

RocketChat.models.Users.resetE2EKey = function(userId) {
this.update({ _id: userId }, {
$unset: {
e2e: '',
},
});
};
3 changes: 3 additions & 0 deletions packages/rocketchat-i18n/i18n/en.i18n.json
Original file line number Diff line number Diff line change
Expand Up @@ -2211,6 +2211,8 @@
"Require_password_change": "Require password change",
"Resend_verification_email": "Resend verification email",
"Reset": "Reset",
"Reset_E2E_Key": "Reset E2E Key",
"reset-other-user-e2e-key": "Reset Other User E2E Key",
"Reset_password": "Reset password",
"Reset_section_settings": "Reset Section Settings",
"Reset_Connection": "Reset Connection",
Expand Down Expand Up @@ -2726,6 +2728,7 @@
"User_and_group_mentions_only": "User and group mentions only",
"User_default": "User default",
"User_doesnt_exist": "No user exists by the name of `@%s`.",
"User_e2e_key_was_reset": "User E2E key was reset successfully.",
"User_has_been_activated": "User has been activated",
"User_has_been_deactivated": "User has been deactivated",
"User_has_been_deleted": "User has been deleted",
Expand Down
10 changes: 10 additions & 0 deletions packages/rocketchat-lib/server/models/Users.js
Original file line number Diff line number Diff line change
Expand Up @@ -604,6 +604,16 @@ class ModelUsers extends RocketChat.models._Base {
return this.update({ _id }, update);
}

removeResumeService(_id) {
const update = {
$unset: {
'services.resume': '',
},
};

return this.update({ _id }, update);
}

// INSERT
create(data) {
const user = {
Expand Down
15 changes: 15 additions & 0 deletions packages/rocketchat-ui-flextab/client/tabs/userActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -481,6 +481,21 @@ export const getActions = function({ user, directActions, hideAdminControls }) {
name: t('Activate'),
action: prevent(getUser, ({ _id }) => Meteor.call('setUserActiveStatus', _id, true, success(() => toastr.success(t('User_has_been_activated'))))),
};
}, () => {
if (hideAdminControls || !hasPermission('reset-other-user-e2e-key')) {
return;
}
if (!RocketChat.settings.get('E2E_Enable')) {
return;
}

return {
group: 'admin',
icon: 'key',
id: 'reset-e2e',
name: t('Reset_E2E_Key'),
action: prevent(getUser, ({ _id }) => Meteor.call('e2e.resetUserE2EKey', _id, success(() => toastr.success(t('User_e2e_key_was_reset'))))),
};
}];
return actions;
};
10 changes: 7 additions & 3 deletions packages/rocketchat-ui/client/lib/RoomManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -242,9 +242,13 @@ const loadMissedMessages = function(rid) {
return;
}
const subscription = ChatSubscription.findOne({ rid });
return Meteor.call('loadMissedMessages', rid, lastMessage.ts, (err, result) =>
Array.from(result).map((item) => RocketChat.promises.run('onClientMessageReceived', item).then((msg) => upsertMessage({ msg, subscription })))
);
return Meteor.call('loadMissedMessages', rid, lastMessage.ts, (err, result) => {
if (result) {
return Array.from(result).map((item) => RocketChat.promises.run('onClientMessageReceived', item).then((msg) => upsertMessage({ msg, subscription })));
} else {
return [];
}
});
};

let connectionWasOnline = true;
Expand Down

0 comments on commit 9289e5b

Please sign in to comment.