[NEW] Encrypted Discussions (#2813)

* I18n key fix

* Add encrypted switch

* Remove unused i18n keys

* Add enabled to encryption reducer

* Show encrypted option on CreateDiscussionView only when e2e encryption is properly set

* Add localSearch and use it on search

* Use encrypted from parent channel

* Fix method calls as rest api with 2fa enabled

* Fix logout after reset keys

* Use encryption reducer instead of lib directly to check render

* Check for room type logic to display encryption option on create discussion

* Check toggle-room-e2e-encryption permission on RoomActionsView

* Check for encryption status instead of setting on server

* Fix

* Disable switch instead of hide it

* Fix spotlight for DMs

* Fix server test
This commit is contained in:
Diego Mello 2021-01-20 14:34:01 -03:00 committed by GitHub
parent a1bd97f658
commit db5074ab70
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 160 additions and 107 deletions

View File

@ -69,4 +69,4 @@ export const INVITE_LINKS = createRequestTypes('INVITE_LINKS', [
export const SETTINGS = createRequestTypes('SETTINGS', ['CLEAR', 'ADD']); export const SETTINGS = createRequestTypes('SETTINGS', ['CLEAR', 'ADD']);
export const APP_STATE = createRequestTypes('APP_STATE', ['FOREGROUND', 'BACKGROUND']); export const APP_STATE = createRequestTypes('APP_STATE', ['FOREGROUND', 'BACKGROUND']);
export const ENTERPRISE_MODULES = createRequestTypes('ENTERPRISE_MODULES', ['CLEAR', 'SET']); export const ENTERPRISE_MODULES = createRequestTypes('ENTERPRISE_MODULES', ['CLEAR', 'SET']);
export const ENCRYPTION = createRequestTypes('ENCRYPTION', ['INIT', 'STOP', 'DECODE_KEY', 'SET_BANNER']); export const ENCRYPTION = createRequestTypes('ENCRYPTION', ['INIT', 'STOP', 'DECODE_KEY', 'SET', 'SET_BANNER']);

View File

@ -12,6 +12,14 @@ export function encryptionStop() {
}; };
} }
export function encryptionSet(enabled = false, banner = null) {
return {
type: types.ENCRYPTION.SET,
enabled,
banner
};
}
export function encryptionSetBanner(banner) { export function encryptionSetBanner(banner) {
return { return {
type: types.ENCRYPTION.SET_BANNER, type: types.ENCRYPTION.SET_BANNER,

View File

@ -562,7 +562,6 @@ export default {
Username: 'اسم المستخدم', Username: 'اسم المستخدم',
Username_or_email: 'اسم المستخدم أو البريد الالكتروني', Username_or_email: 'اسم المستخدم أو البريد الالكتروني',
Uses_server_configuration: 'يستخدم إعداد الخادم', Uses_server_configuration: 'يستخدم إعداد الخادم',
Usually_a_discussion_starts_with_a_question_like_How_do_I_upload_a_picture: 'عادةً، النقاش يبدأ بسؤال، على سبيل المثال: كيف أرفع صورة؟',
Validating: 'يتم التحقق', Validating: 'يتم التحقق',
Registration_Succeeded: 'تم التسجيل بنجاح', Registration_Succeeded: 'تم التسجيل بنجاح',
Verify: 'تحقق', Verify: 'تحقق',
@ -597,7 +596,6 @@ export default {
You_need_to_access_at_least_one_RocketChat_server_to_share_something: 'تحتاج إلى الوصول إلى خادم Rocket.Chat واحد على الأقل لمشاركة شيء ما', You_need_to_access_at_least_one_RocketChat_server_to_share_something: 'تحتاج إلى الوصول إلى خادم Rocket.Chat واحد على الأقل لمشاركة شيء ما',
You_need_to_verifiy_your_email_address_to_get_notications: 'يجب تأكيد البريد الإلكتروني حتى تصلك الإشعارات', You_need_to_verifiy_your_email_address_to_get_notications: 'يجب تأكيد البريد الإلكتروني حتى تصلك الإشعارات',
Your_certificate: 'شهادتك', Your_certificate: 'شهادتك',
Your_message: 'رسالتك',
Your_invite_link_will_expire_after__usesLeft__uses: 'سوف تنتهي صلاحية رابط الدعوة الخاص بك بعد {{usesLeft}} استخدامات', Your_invite_link_will_expire_after__usesLeft__uses: 'سوف تنتهي صلاحية رابط الدعوة الخاص بك بعد {{usesLeft}} استخدامات',
Your_invite_link_will_expire_on__date__or_after__usesLeft__uses: 'ستنتهي صلاحية رابط الدعوة الخاص بك في {{date}} أو بعد {{usesLeft}} استخدامات', Your_invite_link_will_expire_on__date__or_after__usesLeft__uses: 'ستنتهي صلاحية رابط الدعوة الخاص بك في {{date}} أو بعد {{usesLeft}} استخدامات',
Your_invite_link_will_expire_on__date__: 'ستنتهي صلاحية رابط الدعوة الخاص بك في {{date}}', Your_invite_link_will_expire_on__date__: 'ستنتهي صلاحية رابط الدعوة الخاص بك في {{date}}',

View File

@ -565,7 +565,6 @@ export default {
Username: 'Benutzername', Username: 'Benutzername',
Username_or_email: 'Benutzername oder E-Mail-Adresse', Username_or_email: 'Benutzername oder E-Mail-Adresse',
Uses_server_configuration: 'Nutzt Servereinstellungen', Uses_server_configuration: 'Nutzt Servereinstellungen',
Usually_a_discussion_starts_with_a_question_like_How_do_I_upload_a_picture: 'Üblicherweise beginnt eine Diskussion mit einer Frage, beispielsweise: "Wie lade ich ein Bild hoch?"',
Validating: 'Validierung', Validating: 'Validierung',
Registration_Succeeded: 'Registrierung erfolgreich!', Registration_Succeeded: 'Registrierung erfolgreich!',
Verify: 'Überprüfen', Verify: 'Überprüfen',
@ -600,7 +599,6 @@ export default {
You_need_to_access_at_least_one_RocketChat_server_to_share_something: 'Du benötigst Zugang zu mindestens einem Rocket.Chat-Server um etwas zu teilen.', You_need_to_access_at_least_one_RocketChat_server_to_share_something: 'Du benötigst Zugang zu mindestens einem Rocket.Chat-Server um etwas zu teilen.',
You_need_to_verifiy_your_email_address_to_get_notications: 'Du musst deine Email-Adresse bestätigen um Benachrichtigungen zu erhalten.', You_need_to_verifiy_your_email_address_to_get_notications: 'Du musst deine Email-Adresse bestätigen um Benachrichtigungen zu erhalten.',
Your_certificate: 'Dein Zertifikat', Your_certificate: 'Dein Zertifikat',
Your_message: 'Deine Nachricht',
Your_invite_link_will_expire_after__usesLeft__uses: 'Dein Einladungs-Link wird nach {{usesLeft}} Benutzungen ablaufen.', Your_invite_link_will_expire_after__usesLeft__uses: 'Dein Einladungs-Link wird nach {{usesLeft}} Benutzungen ablaufen.',
Your_invite_link_will_expire_on__date__or_after__usesLeft__uses: 'Dein Einladungs-Link wird am {{date}} oder nach {{usesLeft}} Benutzungen ablaufen.', Your_invite_link_will_expire_on__date__or_after__usesLeft__uses: 'Dein Einladungs-Link wird am {{date}} oder nach {{usesLeft}} Benutzungen ablaufen.',
Your_invite_link_will_expire_on__date__: 'Dein Einladungs-Link wird am {{date}} ablaufen.', Your_invite_link_will_expire_on__date__: 'Dein Einladungs-Link wird am {{date}} ablaufen.',

View File

@ -223,7 +223,7 @@ export default {
Enter_Your_Encryption_Password_desc1: 'This will allow you to access your encrypted private groups and direct messages.', Enter_Your_Encryption_Password_desc1: 'This will allow you to access your encrypted private groups and direct messages.',
Enter_Your_Encryption_Password_desc2: 'You need to enter the password to encode/decode messages every place you use the chat.', Enter_Your_Encryption_Password_desc2: 'You need to enter the password to encode/decode messages every place you use the chat.',
Encryption_error_title: 'Your encryption password seems wrong', Encryption_error_title: 'Your encryption password seems wrong',
Encryption_error_desc: 'Wasn\'t possible to decode your encryption key to be imported.', Encryption_error_desc: 'It wasn\'t possible to decode your encryption key to be imported.',
Everyone_can_access_this_channel: 'Everyone can access this channel', Everyone_can_access_this_channel: 'Everyone can access this channel',
Error_uploading: 'Error uploading', Error_uploading: 'Error uploading',
Expiration_Days: 'Expiration (Days)', Expiration_Days: 'Expiration (Days)',
@ -565,7 +565,6 @@ export default {
Username: 'Username', Username: 'Username',
Username_or_email: 'Username or email', Username_or_email: 'Username or email',
Uses_server_configuration: 'Uses server configuration', Uses_server_configuration: 'Uses server configuration',
Usually_a_discussion_starts_with_a_question_like_How_do_I_upload_a_picture: 'Usually, a discussion starts with a question, like "How do I upload a picture?"',
Validating: 'Validating', Validating: 'Validating',
Registration_Succeeded: 'Registration Succeeded!', Registration_Succeeded: 'Registration Succeeded!',
Verify: 'Verify', Verify: 'Verify',
@ -600,7 +599,6 @@ export default {
You_need_to_access_at_least_one_RocketChat_server_to_share_something: 'You need to access at least one Rocket.Chat server to share something.', You_need_to_access_at_least_one_RocketChat_server_to_share_something: 'You need to access at least one Rocket.Chat server to share something.',
You_need_to_verifiy_your_email_address_to_get_notications: 'You need to verify your email address to get notifications', You_need_to_verifiy_your_email_address_to_get_notications: 'You need to verify your email address to get notifications',
Your_certificate: 'Your Certificate', Your_certificate: 'Your Certificate',
Your_message: 'Your message',
Your_invite_link_will_expire_after__usesLeft__uses: 'Your invite link will expire after {{usesLeft}} uses.', Your_invite_link_will_expire_after__usesLeft__uses: 'Your invite link will expire after {{usesLeft}} uses.',
Your_invite_link_will_expire_on__date__or_after__usesLeft__uses: 'Your invite link will expire on {{date}} or after {{usesLeft}} uses.', Your_invite_link_will_expire_on__date__or_after__usesLeft__uses: 'Your invite link will expire on {{date}} or after {{usesLeft}} uses.',
Your_invite_link_will_expire_on__date__: 'Your invite link will expire on {{date}}.', Your_invite_link_will_expire_on__date__: 'Your invite link will expire on {{date}}.',

View File

@ -531,7 +531,6 @@ export default {
Username: 'Nom d\'utilisateur', Username: 'Nom d\'utilisateur',
Username_or_email: 'Nom d\'utilisateur ou address e-mail', Username_or_email: 'Nom d\'utilisateur ou address e-mail',
Uses_server_configuration: 'Utilise la configuration du serveur', Uses_server_configuration: 'Utilise la configuration du serveur',
Usually_a_discussion_starts_with_a_question_like_How_do_I_upload_a_picture: 'Habituellement, une discussion commence par une question, comme "Comment télécharger une image?"',
Validating: 'Validation', Validating: 'Validation',
Registration_Succeeded: 'Inscription réussie!', Registration_Succeeded: 'Inscription réussie!',
Verify: 'Vérifier', Verify: 'Vérifier',
@ -565,7 +564,6 @@ export default {
Logged_out_by_server: 'Vous avez été déconnecté par le serveur. Veuillez vous reconnecter.', Logged_out_by_server: 'Vous avez été déconnecté par le serveur. Veuillez vous reconnecter.',
You_need_to_access_at_least_one_RocketChat_server_to_share_something: 'Vous devez accéder à au moins un serveur Rocket.Chat pour partager quelque chose.', You_need_to_access_at_least_one_RocketChat_server_to_share_something: 'Vous devez accéder à au moins un serveur Rocket.Chat pour partager quelque chose.',
Your_certificate: 'Votre Certificat', Your_certificate: 'Votre Certificat',
Your_message: 'Votre message',
Your_invite_link_will_expire_after__usesLeft__uses: 'Votre lien d\'invitation expirera après {{usesLeft}} utilisations.', Your_invite_link_will_expire_after__usesLeft__uses: 'Votre lien d\'invitation expirera après {{usesLeft}} utilisations.',
Your_invite_link_will_expire_on__date__or_after__usesLeft__uses: 'Votre lien d\'invitation expirera le {{date}} ou après {{usesLeft}} utilisations.', Your_invite_link_will_expire_on__date__or_after__usesLeft__uses: 'Votre lien d\'invitation expirera le {{date}} ou après {{usesLeft}} utilisations.',
Your_invite_link_will_expire_on__date__: 'Votre lien d\'invitation expirera le {{date}}.', Your_invite_link_will_expire_on__date__: 'Votre lien d\'invitation expirera le {{date}}.',

View File

@ -560,7 +560,6 @@ export default {
Username: 'Username', Username: 'Username',
Username_or_email: 'Username o email', Username_or_email: 'Username o email',
Uses_server_configuration: 'Usa la configurazione del server', Uses_server_configuration: 'Usa la configurazione del server',
Usually_a_discussion_starts_with_a_question_like_How_do_I_upload_a_picture: 'Una discussione inizia solitamente con una domanda, ad esempio: "Come posso caricare una foto?"',
Validating: 'Validazione', Validating: 'Validazione',
Registration_Succeeded: 'Registrazione completata!', Registration_Succeeded: 'Registrazione completata!',
Verify: 'Verifica', Verify: 'Verifica',
@ -595,7 +594,6 @@ export default {
You_need_to_access_at_least_one_RocketChat_server_to_share_something: 'Devi accedere ad almeno un server Rocket.Chat prima di condividere qualcosa.', You_need_to_access_at_least_one_RocketChat_server_to_share_something: 'Devi accedere ad almeno un server Rocket.Chat prima di condividere qualcosa.',
You_need_to_verifiy_your_email_address_to_get_notications: 'Devi verificare il tuo indirizzo e-mail per ricevere le notifiche', You_need_to_verifiy_your_email_address_to_get_notications: 'Devi verificare il tuo indirizzo e-mail per ricevere le notifiche',
Your_certificate: 'Il tuo certificato', Your_certificate: 'Il tuo certificato',
Your_message: 'Il tuo messaggio',
Your_invite_link_will_expire_after__usesLeft__uses: 'Il tuo link di invito scadrà dopo {{usesLeft}} utilizzi.', Your_invite_link_will_expire_after__usesLeft__uses: 'Il tuo link di invito scadrà dopo {{usesLeft}} utilizzi.',
Your_invite_link_will_expire_on__date__or_after__usesLeft__uses: 'Il tuo link di invito scadrà il {{date}} oppure dopo {{usesLeft}} utilizzi.', Your_invite_link_will_expire_on__date__or_after__usesLeft__uses: 'Il tuo link di invito scadrà il {{date}} oppure dopo {{usesLeft}} utilizzi.',
Your_invite_link_will_expire_on__date__: 'Il tuo link di invito scadrà il {{date}}.', Your_invite_link_will_expire_on__date__: 'Il tuo link di invito scadrà il {{date}}.',

View File

@ -522,7 +522,6 @@ export default {
Username: 'Usuário', Username: 'Usuário',
Username_or_email: 'Usuário ou email', Username_or_email: 'Usuário ou email',
Uses_server_configuration: 'Usar configuração do servidor', Uses_server_configuration: 'Usar configuração do servidor',
Usually_a_discussion_starts_with_a_question_like_How_do_I_upload_a_picture: 'Normalmente, uma discussão começa com uma pergunta como: Como faço para enviar uma foto?',
Verify: 'Verificar', Verify: 'Verificar',
Verify_email_title: 'Registrado com sucesso!', Verify_email_title: 'Registrado com sucesso!',
Verify_email_desc: 'Nós lhe enviamos um e-mail para confirmar o seu registro. Se você não receber um e-mail em breve, por favor retorne e tente novamente.', Verify_email_desc: 'Nós lhe enviamos um e-mail para confirmar o seu registro. Se você não receber um e-mail em breve, por favor retorne e tente novamente.',
@ -541,7 +540,6 @@ export default {
You_are_in_preview_mode: 'Está é uma prévia do canal', You_are_in_preview_mode: 'Está é uma prévia do canal',
You_are_offline: 'Você está offline', You_are_offline: 'Você está offline',
You_can_search_using_RegExp_eg: 'Você pode usar expressões regulares, por exemplo `/^text$/i`', You_can_search_using_RegExp_eg: 'Você pode usar expressões regulares, por exemplo `/^text$/i`',
Your_message: 'Sua mensagem',
You_need_to_verifiy_your_email_address_to_get_notications: 'Você precisa confirmar seu endereço de e-mail para obter notificações', You_need_to_verifiy_your_email_address_to_get_notications: 'Você precisa confirmar seu endereço de e-mail para obter notificações',
You_colon: 'Você: ', You_colon: 'Você: ',
you_were_mentioned: 'você foi mencionado', you_were_mentioned: 'você foi mencionado',

View File

@ -563,7 +563,6 @@ export default {
Username: 'Имя пользователя', Username: 'Имя пользователя',
Username_or_email: 'Имя пользователя или email', Username_or_email: 'Имя пользователя или email',
Uses_server_configuration: 'Используется конфигурация сервера', Uses_server_configuration: 'Используется конфигурация сервера',
Usually_a_discussion_starts_with_a_question_like_How_do_I_upload_a_picture: 'Обычно Обсуждение начинается с какого-либо вопроса, например, "Как мне загрузить эту картинку?"',
Validating: 'Проверка', Validating: 'Проверка',
Registration_Succeeded: 'Регистрация Успешна!', Registration_Succeeded: 'Регистрация Успешна!',
Verify: 'Проверить', Verify: 'Проверить',
@ -598,7 +597,6 @@ export default {
You_need_to_access_at_least_one_RocketChat_server_to_share_something: 'Вам нужно получить доступ как минимум к одному серверу Rocket.Chat, чтобы поделиться чем-то.', You_need_to_access_at_least_one_RocketChat_server_to_share_something: 'Вам нужно получить доступ как минимум к одному серверу Rocket.Chat, чтобы поделиться чем-то.',
You_need_to_verifiy_your_email_address_to_get_notications: 'Вам необходимо проверить ваш email адрес, чтобы получать уведомления', You_need_to_verifiy_your_email_address_to_get_notications: 'Вам необходимо проверить ваш email адрес, чтобы получать уведомления',
Your_certificate: 'Ваш сертификат', Your_certificate: 'Ваш сертификат',
Your_message: 'Ваше сообщение',
Your_invite_link_will_expire_after__usesLeft__uses: 'Ваша ссылка-приглашение станет не действительной после {{usesLeft}} ее использований.', Your_invite_link_will_expire_after__usesLeft__uses: 'Ваша ссылка-приглашение станет не действительной после {{usesLeft}} ее использований.',
Your_invite_link_will_expire_on__date__or_after__usesLeft__uses: 'Ваша ссылка-приглашение станет не действительной {{date}} или после {{usesLeft}} ее использований.', Your_invite_link_will_expire_on__date__or_after__usesLeft__uses: 'Ваша ссылка-приглашение станет не действительной {{date}} или после {{usesLeft}} ее использований.',
Your_invite_link_will_expire_on__date__: 'Срок действия вашей ссылки-приглашения будет окончен {{date}}.', Your_invite_link_will_expire_on__date__: 'Срок действия вашей ссылки-приглашения будет окончен {{date}}.',

View File

@ -563,7 +563,6 @@ export default {
Username: '用户名', Username: '用户名',
Username_or_email: '用户名或邮箱', Username_or_email: '用户名或邮箱',
Uses_server_configuration: '使用服务器设置', Uses_server_configuration: '使用服务器设置',
Usually_a_discussion_starts_with_a_question_like_How_do_I_upload_a_picture: '通常, 一个讨论会由一个问题开始, 例如 \\"如何上传图片?\\"',
Validating: '正在验证', Validating: '正在验证',
Registration_Succeeded: '注册成功', Registration_Succeeded: '注册成功',
Verify: '验证', Verify: '验证',
@ -598,7 +597,6 @@ export default {
You_need_to_access_at_least_one_RocketChat_server_to_share_something: '您需要访问至少一台Rocket.Chat服务器才能共享某些内容。', You_need_to_access_at_least_one_RocketChat_server_to_share_something: '您需要访问至少一台Rocket.Chat服务器才能共享某些内容。',
You_need_to_verifiy_your_email_address_to_get_notications: '您需要先验证您的邮箱以启用通知', You_need_to_verifiy_your_email_address_to_get_notications: '您需要先验证您的邮箱以启用通知',
Your_certificate: '你的证书', Your_certificate: '你的证书',
Your_message: '你的信息',
Your_invite_link_will_expire_after__usesLeft__uses: '您的邀请链接将在{{usesLeft}}使用后到期。', Your_invite_link_will_expire_after__usesLeft__uses: '您的邀请链接将在{{usesLeft}}使用后到期。',
Your_invite_link_will_expire_on__date__or_after__usesLeft__uses: '您的邀请链接将于{{date}}或{{usesLeft}}使用后到期。', Your_invite_link_will_expire_on__date__or_after__usesLeft__uses: '您的邀请链接将于{{date}}或{{usesLeft}}使用后到期。',
Your_invite_link_will_expire_on__date__: '您的邀请链接将于{{date}}到期。', Your_invite_link_will_expire_on__date__: '您的邀请链接将于{{date}}到期。',

View File

@ -563,7 +563,6 @@ export default {
Username: '使用者名稱', Username: '使用者名稱',
Username_or_email: '使用者名稱或電子郵件', Username_or_email: '使用者名稱或電子郵件',
Uses_server_configuration: '使用伺服器設定', Uses_server_configuration: '使用伺服器設定',
Usually_a_discussion_starts_with_a_question_like_How_do_I_upload_a_picture: '通常,討論會由一個問題開始,像是 \\\'如何上傳一個圖片?\\\'',
Validating: '正在驗證', Validating: '正在驗證',
Registration_Succeeded: '註冊成功', Registration_Succeeded: '註冊成功',
Verify: '驗證', Verify: '驗證',
@ -598,7 +597,6 @@ export default {
You_need_to_access_at_least_one_RocketChat_server_to_share_something: '您需要至少連接一個 Rocket.Chat 伺服器才能共享某些内容。', You_need_to_access_at_least_one_RocketChat_server_to_share_something: '您需要至少連接一個 Rocket.Chat 伺服器才能共享某些内容。',
You_need_to_verifiy_your_email_address_to_get_notications: '您需要先驗證您的電子郵件以啟用通知', You_need_to_verifiy_your_email_address_to_get_notications: '您需要先驗證您的電子郵件以啟用通知',
Your_certificate: '你的證書', Your_certificate: '你的證書',
Your_message: '你的訊息',
Your_invite_link_will_expire_after__usesLeft__uses: '您的邀請連結將在{{usesLeft}}使用後到期。', Your_invite_link_will_expire_after__usesLeft__uses: '您的邀請連結將在{{usesLeft}}使用後到期。',
Your_invite_link_will_expire_on__date__or_after__usesLeft__uses: '您的邀請連結將於{{date}}或{{usesLeft}}使用後到期。', Your_invite_link_will_expire_on__date__or_after__usesLeft__uses: '您的邀請連結將於{{date}}或{{usesLeft}}使用後到期。',
Your_invite_link_will_expire_on__date__: '您的邀請連結將於{{date}}到期。', Your_invite_link_will_expire_on__date__: '您的邀請連結將於{{date}}到期。',

View File

@ -68,10 +68,6 @@ class Encryption {
return this.readyPromise; return this.readyPromise;
} }
get hasPrivateKey() {
return !!this.privateKey;
}
// Stop Encryption client // Stop Encryption client
stop = () => { stop = () => {
this.userId = null; this.userId = null;

View File

@ -596,25 +596,19 @@ const RocketChat = {
readMessages, readMessages,
resendMessage, resendMessage,
async search({ text, filterUsers = true, filterRooms = true }) { async localSearch({ text, filterUsers = true, filterRooms = true }) {
const searchText = text.trim(); const searchText = text.trim();
if (this.oldPromise) {
this.oldPromise('cancel');
}
if (searchText === '') { if (searchText === '') {
delete this.oldPromise;
return []; return [];
} }
const db = database.active; const db = database.active;
const likeString = sanitizeLikeString(searchText); const likeString = sanitizeLikeString(searchText);
let data = await db.collections.get('subscriptions').query( let data = await db.collections.get('subscriptions').query(
Q.or( Q.or(
Q.where('name', Q.like(`%${ likeString }%`)), Q.where('name', Q.like(`%${ likeString }%`)),
Q.where('fname', Q.like(`%${ likeString }%`)) Q.where('fname', Q.like(`%${ likeString }%`))
) ),
Q.experimentalSortBy('room_updated_at', Q.desc)
).fetch(); ).fetch();
if (filterUsers && !filterRooms) { if (filterUsers && !filterRooms) {
@ -627,18 +621,35 @@ const RocketChat = {
data = data.map((sub) => { data = data.map((sub) => {
if (sub.t !== 'd') { if (sub.t !== 'd') {
return ({ return {
rid: sub.rid, rid: sub.rid,
name: sub.name, name: sub.name,
fname: sub.fname, fname: sub.fname,
avatarETag: sub.avatarETag, avatarETag: sub.avatarETag,
t: sub.t, t: sub.t,
encrypted: sub.encrypted,
search: true search: true
}); };
} }
return sub; return sub;
}); });
return data;
},
async search({ text, filterUsers = true, filterRooms = true }) {
const searchText = text.trim();
if (this.oldPromise) {
this.oldPromise('cancel');
}
if (searchText === '') {
return [];
}
let data = await this.localSearch({ text, filterUsers, filterRooms });
const usernames = data.map(sub => sub.name); const usernames = data.map(sub => sub.name);
try { try {
if (data.length < 7) { if (data.length < 7) {
@ -697,11 +708,11 @@ const RocketChat = {
}, },
createDiscussion({ createDiscussion({
prid, pmid, t_name, reply, users prid, pmid, t_name, reply, users, encrypted
}) { }) {
// RC 1.0.0 // RC 1.0.0
return this.post('rooms.createDiscussion', { return this.post('rooms.createDiscussion', {
prid, pmid, t_name, reply, users prid, pmid, t_name, reply, users, encrypted
}); });
}, },
@ -865,14 +876,7 @@ const RocketChat = {
methodCallWrapper(method, ...params) { methodCallWrapper(method, ...params) {
const { API_Use_REST_For_DDP_Calls } = reduxStore.getState().settings; const { API_Use_REST_For_DDP_Calls } = reduxStore.getState().settings;
if (API_Use_REST_For_DDP_Calls) { if (API_Use_REST_For_DDP_Calls) {
return new Promise(async(resolve, reject) => { return this.post(`method.call/${ method }`, { message: JSON.stringify({ method, params }) });
const data = await this.post(`method.call/${ method }`, { message: JSON.stringify({ method, params }) });
const response = JSON.parse(data.message);
if (response?.error) {
return reject(response.error);
}
return resolve(response.result);
});
} }
return this.methodCall(method, ...params); return this.methodCall(method, ...params);
}, },
@ -1067,14 +1071,30 @@ const RocketChat = {
}, },
post(...args) { post(...args) {
return new Promise(async(resolve, reject) => { return new Promise(async(resolve, reject) => {
const isMethodCall = args[0]?.startsWith('method.call/');
try { try {
const result = await this.sdk.post(...args); const result = await this.sdk.post(...args);
/**
* if API_Use_REST_For_DDP_Calls is enabled and it's a method call,
* responses have a different object structure
*/
if (isMethodCall) {
const response = JSON.parse(result.message);
if (response?.error) {
throw response.error;
}
return resolve(response.result);
}
return resolve(result); return resolve(result);
} catch (e) { } catch (e) {
if (e.data && (e.data.errorType === 'totp-required' || e.data.errorType === 'totp-invalid')) { const errorType = isMethodCall ? e?.error : e?.data?.errorType;
const { details } = e.data; const totpInvalid = 'totp-invalid';
const totpRequired = 'totp-required';
if ([totpInvalid, totpRequired].includes(errorType)) {
const { details } = isMethodCall ? e : e?.data;
try { try {
await twoFactor({ method: details?.method, invalid: e.data.errorType === 'totp-invalid' }); await twoFactor({ method: details?.method, invalid: errorType === totpInvalid });
return resolve(this.post(...args)); return resolve(this.post(...args));
} catch { } catch {
// twoFactor was canceled // twoFactor was canceled

View File

@ -1,11 +1,18 @@
import { ENCRYPTION } from '../actions/actionsTypes'; import { ENCRYPTION } from '../actions/actionsTypes';
const initialState = { const initialState = {
enabled: false,
banner: null banner: null
}; };
export default function encryption(state = initialState, action) { export default function encryption(state = initialState, action) {
switch (action.type) { switch (action.type) {
case ENCRYPTION.SET:
return {
...state,
enabled: action.enabled,
banner: action.banner
};
case ENCRYPTION.SET_BANNER: case ENCRYPTION.SET_BANNER:
return { return {
...state, ...state,

View File

@ -2,7 +2,7 @@ import EJSON from 'ejson';
import { takeLatest, select, put } from 'redux-saga/effects'; import { takeLatest, select, put } from 'redux-saga/effects';
import { ENCRYPTION } from '../actions/actionsTypes'; import { ENCRYPTION } from '../actions/actionsTypes';
import { encryptionSetBanner } from '../actions/encryption'; import { encryptionSet } from '../actions/encryption';
import { Encryption } from '../lib/encryption'; import { Encryption } from '../lib/encryption';
import Navigation from '../lib/Navigation'; import Navigation from '../lib/Navigation';
import { import {
@ -52,14 +52,14 @@ const handleEncryptionInit = function* handleEncryptionInit() {
// A private key was received from the server, but it's not saved locally yet // A private key was received from the server, but it's not saved locally yet
// Show the banner asking for the password // Show the banner asking for the password
if (!storedPrivateKey && keys?.privateKey) { if (!storedPrivateKey && keys?.privateKey) {
yield put(encryptionSetBanner(E2E_BANNER_TYPE.REQUEST_PASSWORD)); yield put(encryptionSet(false, E2E_BANNER_TYPE.REQUEST_PASSWORD));
return; return;
} }
// If the user has a private key stored, but never entered the password // If the user has a private key stored, but never entered the password
const storedRandomPassword = yield UserPreferences.getStringAsync(`${ server }-${ E2E_RANDOM_PASSWORD_KEY }`); const storedRandomPassword = yield UserPreferences.getStringAsync(`${ server }-${ E2E_RANDOM_PASSWORD_KEY }`);
if (storedRandomPassword) { if (storedRandomPassword) {
yield put(encryptionSetBanner(E2E_BANNER_TYPE.SAVE_PASSWORD)); yield put(encryptionSet(true, E2E_BANNER_TYPE.SAVE_PASSWORD));
} }
// Fetch stored public e2e key for this server // Fetch stored public e2e key for this server
@ -72,10 +72,11 @@ const handleEncryptionInit = function* handleEncryptionInit() {
if (storedPublicKey && storedPrivateKey) { if (storedPublicKey && storedPrivateKey) {
// Persist these keys // Persist these keys
yield Encryption.persistKeys(server, storedPublicKey, storedPrivateKey); yield Encryption.persistKeys(server, storedPublicKey, storedPrivateKey);
yield put(encryptionSet(true));
} else { } else {
// Create new keys since the user doesn't have any // Create new keys since the user doesn't have any
yield Encryption.createKeys(user.id, server); yield Encryption.createKeys(user.id, server);
yield put(encryptionSetBanner(E2E_BANNER_TYPE.SAVE_PASSWORD)); yield put(encryptionSet(true, E2E_BANNER_TYPE.SAVE_PASSWORD));
} }
// Decrypt all pending messages/subscriptions // Decrypt all pending messages/subscriptions
@ -87,7 +88,7 @@ const handleEncryptionInit = function* handleEncryptionInit() {
const handleEncryptionStop = function* handleEncryptionStop() { const handleEncryptionStop = function* handleEncryptionStop() {
// Hide encryption banner // Hide encryption banner
yield put(encryptionSetBanner()); yield put(encryptionSet());
// Stop Encryption client // Stop Encryption client
Encryption.stop(); Encryption.stop();
}; };
@ -112,7 +113,7 @@ const handleEncryptionDecodeKey = function* handleEncryptionDecodeKey({ password
Encryption.initialize(user.id); Encryption.initialize(user.id);
// Hide encryption banner // Hide encryption banner
yield put(encryptionSetBanner()); yield put(encryptionSet(true));
Navigation.back(); Navigation.back();
} catch { } catch {

View File

@ -112,6 +112,7 @@ export default {
CREATE_DISCUSSION_CREATE_F: 'create_discussion_create_f', CREATE_DISCUSSION_CREATE_F: 'create_discussion_create_f',
CREATE_DISCUSSION_SELECT_CHANNEL: 'create_discussion_select_channel', CREATE_DISCUSSION_SELECT_CHANNEL: 'create_discussion_select_channel',
CREATE_DISCUSSION_SELECT_USERS: 'create_discussion_select_users', CREATE_DISCUSSION_SELECT_USERS: 'create_discussion_select_users',
CREATE_DISCUSSION_TOGGLE_ENCRY: 'create_discussion_toggle_encry',
// PROFILE VIEW // PROFILE VIEW
PROFILE_PICK_AVATAR: 'profile_pick_avatar', PROFILE_PICK_AVATAR: 'profile_pick_avatar',

View File

@ -85,7 +85,7 @@ class CreateChannelView extends React.Component {
error: PropTypes.object, error: PropTypes.object,
failure: PropTypes.bool, failure: PropTypes.bool,
isFetching: PropTypes.bool, isFetching: PropTypes.bool,
e2eEnabled: PropTypes.bool, encryptionEnabled: PropTypes.bool,
users: PropTypes.array.isRequired, users: PropTypes.array.isRequired,
user: PropTypes.shape({ user: PropTypes.shape({
id: PropTypes.string, id: PropTypes.string,
@ -107,7 +107,7 @@ class CreateChannelView extends React.Component {
channelName, type, readOnly, broadcast, encrypted channelName, type, readOnly, broadcast, encrypted
} = this.state; } = this.state;
const { const {
users, isFetching, e2eEnabled, theme users, isFetching, encryptionEnabled, theme
} = this.props; } = this.props;
if (nextProps.theme !== theme) { if (nextProps.theme !== theme) {
return true; return true;
@ -130,7 +130,7 @@ class CreateChannelView extends React.Component {
if (nextProps.isFetching !== isFetching) { if (nextProps.isFetching !== isFetching) {
return true; return true;
} }
if (nextProps.e2eEnabled !== e2eEnabled) { if (nextProps.encryptionEnabled !== encryptionEnabled) {
return true; return true;
} }
if (!equal(nextProps.users, users)) { if (!equal(nextProps.users, users)) {
@ -230,9 +230,9 @@ class CreateChannelView extends React.Component {
renderEncrypted() { renderEncrypted() {
const { type, encrypted } = this.state; const { type, encrypted } = this.state;
const { e2eEnabled } = this.props; const { encryptionEnabled } = this.props;
if (!e2eEnabled) { if (!encryptionEnabled) {
return null; return null;
} }
@ -366,7 +366,7 @@ class CreateChannelView extends React.Component {
const mapStateToProps = state => ({ const mapStateToProps = state => ({
baseUrl: state.server.server, baseUrl: state.server.server,
isFetching: state.createChannel.isFetching, isFetching: state.createChannel.isFetching,
e2eEnabled: state.settings.E2E_Enable, encryptionEnabled: state.encryption.enabled,
users: state.selectedUsers.users, users: state.selectedUsers.users,
user: getUserSelector(state) user: getUserSelector(state)
}); });

View File

@ -18,7 +18,7 @@ const SelectChannel = ({
const getChannels = debounce(async(keyword = '') => { const getChannels = debounce(async(keyword = '') => {
try { try {
const res = await RocketChat.search({ text: keyword, filterUsers: false }); const res = await RocketChat.localSearch({ text: keyword });
setChannels(res); setChannels(res);
} catch { } catch {
// do nothing // do nothing
@ -47,7 +47,7 @@ const SelectChannel = ({
value={initial && [initial]} value={initial && [initial]}
disabled={initial} disabled={initial}
options={channels.map(channel => ({ options={channels.map(channel => ({
value: channel.rid, value: channel,
text: { text: RocketChat.getRoomTitle(channel) }, text: { text: RocketChat.getRoomTitle(channel) },
imageUrl: getAvatar(channel) imageUrl: getAvatar(channel)
}))} }))}

View File

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { ScrollView, Text } from 'react-native'; import { ScrollView, Text, Switch } from 'react-native';
import isEqual from 'lodash/isEqual'; import isEqual from 'lodash/isEqual';
import Loading from '../../containers/Loading'; import Loading from '../../containers/Loading';
@ -10,7 +10,7 @@ import scrollPersistTaps from '../../utils/scrollPersistTaps';
import I18n from '../../i18n'; import I18n from '../../i18n';
import * as HeaderButton from '../../containers/HeaderButton'; import * as HeaderButton from '../../containers/HeaderButton';
import StatusBar from '../../containers/StatusBar'; import StatusBar from '../../containers/StatusBar';
import { themes } from '../../constants/colors'; import { SWITCH_TRACK_COLOR, themes } from '../../constants/colors';
import { withTheme } from '../../theme'; import { withTheme } from '../../theme';
import { getUserSelector } from '../../selectors/login'; import { getUserSelector } from '../../selectors/login';
import TextInput from '../../containers/TextInput'; import TextInput from '../../containers/TextInput';
@ -26,6 +26,7 @@ import styles from './styles';
import SafeAreaView from '../../containers/SafeAreaView'; import SafeAreaView from '../../containers/SafeAreaView';
import { goRoom } from '../../utils/goRoom'; import { goRoom } from '../../utils/goRoom';
import { logEvent, events } from '../../utils/log'; import { logEvent, events } from '../../utils/log';
import { E2E_ROOM_TYPES } from '../../lib/encryption/constants';
class CreateChannelView extends React.Component { class CreateChannelView extends React.Component {
propTypes = { propTypes = {
@ -41,7 +42,8 @@ class CreateChannelView extends React.Component {
theme: PropTypes.string, theme: PropTypes.string,
isMasterDetail: PropTypes.bool, isMasterDetail: PropTypes.bool,
blockUnauthenticatedAccess: PropTypes.bool, blockUnauthenticatedAccess: PropTypes.bool,
serverVersion: PropTypes.string serverVersion: PropTypes.string,
encryptionEnabled: PropTypes.bool
} }
constructor(props) { constructor(props) {
@ -54,7 +56,8 @@ class CreateChannelView extends React.Component {
message, message,
name: message?.msg || '', name: message?.msg || '',
users: [], users: [],
reply: '' reply: '',
encrypted: props.encryptionEnabled
}; };
this.setHeader(); this.setHeader();
} }
@ -109,13 +112,13 @@ class CreateChannelView extends React.Component {
submit = () => { submit = () => {
const { const {
name: t_name, channel: { prid, rid }, message: { id: pmid }, reply, users name: t_name, channel: { prid, rid }, message: { id: pmid }, reply, users, encrypted
} = this.state; } = this.state;
const { create } = this.props; const { create } = this.props;
// create discussion // create discussion
create({ create({
prid: prid || rid, pmid, t_name, reply, users prid: prid || rid, pmid, t_name, reply, users, encrypted
}); });
}; };
@ -134,7 +137,7 @@ class CreateChannelView extends React.Component {
selectChannel = ({ value }) => { selectChannel = ({ value }) => {
logEvent(events.CREATE_DISCUSSION_SELECT_CHANNEL); logEvent(events.CREATE_DISCUSSION_SELECT_CHANNEL);
this.setState({ channel: { rid: value } }); this.setState({ channel: value, encrypted: value?.encrypted });
} }
selectUsers = ({ value }) => { selectUsers = ({ value }) => {
@ -142,10 +145,17 @@ class CreateChannelView extends React.Component {
this.setState({ users: value }); this.setState({ users: value });
} }
onEncryptedChange = (value) => {
logEvent(events.CREATE_DISCUSSION_TOGGLE_ENCRY);
this.setState({ encrypted: value });
}
render() { render() {
const { name, users } = this.state;
const { const {
server, user, loading, blockUnauthenticatedAccess, theme, serverVersion name, users, encrypted, channel
} = this.state;
const {
server, user, loading, blockUnauthenticatedAccess, theme, serverVersion, encryptionEnabled
} = this.props; } = this.props;
return ( return (
<KeyboardView <KeyboardView
@ -185,15 +195,17 @@ class CreateChannelView extends React.Component {
serverVersion={serverVersion} serverVersion={serverVersion}
theme={theme} theme={theme}
/> />
<TextInput {encryptionEnabled && E2E_ROOM_TYPES[channel?.t]
multiline ? (
textAlignVertical='top' <>
label={I18n.t('Your_message')} <Text style={[styles.label, { color: themes[theme].titleText }]}>{I18n.t('Encrypted')}</Text>
inputStyle={styles.multiline} <Switch
theme={theme} value={encrypted}
placeholder={I18n.t('Usually_a_discussion_starts_with_a_question_like_How_do_I_upload_a_picture')} onValueChange={this.onEncryptedChange}
onChangeText={text => this.setState({ reply: text })} trackColor={SWITCH_TRACK_COLOR}
/> />
</>
) : null}
<Loading visible={loading} /> <Loading visible={loading} />
</ScrollView> </ScrollView>
</SafeAreaView> </SafeAreaView>
@ -211,7 +223,8 @@ const mapStateToProps = state => ({
result: state.createDiscussion.result, result: state.createDiscussion.result,
blockUnauthenticatedAccess: state.settings.Accounts_AvatarBlockUnauthenticatedAccess ?? true, blockUnauthenticatedAccess: state.settings.Accounts_AvatarBlockUnauthenticatedAccess ?? true,
serverVersion: state.share.server.version || state.server.version, serverVersion: state.share.server.version || state.server.version,
isMasterDetail: state.app.isMasterDetail isMasterDetail: state.app.isMasterDetail,
encryptionEnabled: state.encryption.enabled
}); });
const mapDispatchToProps = dispatch => ({ const mapDispatchToProps = dispatch => ({

View File

@ -80,12 +80,18 @@ class E2EEncryptionSecurityView extends React.Component {
title: I18n.t('Are_you_sure_question_mark'), title: I18n.t('Are_you_sure_question_mark'),
message: I18n.t('E2E_encryption_reset_message'), message: I18n.t('E2E_encryption_reset_message'),
confirmationText: I18n.t('E2E_encryption_reset_confirmation'), confirmationText: I18n.t('E2E_encryption_reset_confirmation'),
onPress: () => { onPress: async() => {
logEvent(events.E2E_SEC_RESET_OWN_KEY); logEvent(events.E2E_SEC_RESET_OWN_KEY);
try { try {
RocketChat.e2eResetOwnKey(); const res = await RocketChat.e2eResetOwnKey();
const { logout } = this.props; /**
logout(); * It might return an empty object when TOTP is enabled,
* that's why we're using strict equality to boolean
*/
if (res === true) {
const { logout } = this.props;
logout();
}
} catch (e) { } catch (e) {
log(e); log(e);
showErrorAlert(I18n.t('E2E_encryption_reset_error')); showErrorAlert(I18n.t('E2E_encryption_reset_error'));
@ -96,9 +102,8 @@ class E2EEncryptionSecurityView extends React.Component {
renderChangePassword = () => { renderChangePassword = () => {
const { newPassword } = this.state; const { newPassword } = this.state;
const { theme } = this.props; const { theme, encryptionEnabled } = this.props;
const { hasPrivateKey } = Encryption; if (!encryptionEnabled) {
if (!hasPrivateKey) {
return null; return null;
} }
return ( return (
@ -161,7 +166,8 @@ class E2EEncryptionSecurityView extends React.Component {
const mapStateToProps = state => ({ const mapStateToProps = state => ({
server: state.server.server, server: state.server.server,
user: getUserSelector(state) user: getUserSelector(state),
encryptionEnabled: state.encryption.enabled
}); });
const mapDispatchToProps = dispatch => ({ const mapDispatchToProps = dispatch => ({
@ -179,6 +185,7 @@ E2EEncryptionSecurityView.propTypes = {
id: PropTypes.string id: PropTypes.string
}), }),
server: PropTypes.string, server: PropTypes.string,
encryptionEnabled: PropTypes.bool,
logout: PropTypes.func logout: PropTypes.func
}; };

View File

@ -5,6 +5,7 @@ import {
} from 'react-native'; } from 'react-native';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import _ from 'lodash'; import _ from 'lodash';
import semver from 'semver';
import Touch from '../../utils/touch'; import Touch from '../../utils/touch';
import { setLoading as setLoadingAction } from '../../actions/selectedUsers'; import { setLoading as setLoadingAction } from '../../actions/selectedUsers';
@ -46,11 +47,12 @@ class RoomActionsView extends React.Component {
route: PropTypes.object, route: PropTypes.object,
leaveRoom: PropTypes.func, leaveRoom: PropTypes.func,
jitsiEnabled: PropTypes.bool, jitsiEnabled: PropTypes.bool,
e2eEnabled: PropTypes.bool, encryptionEnabled: PropTypes.bool,
setLoadingInvite: PropTypes.func, setLoadingInvite: PropTypes.func,
closeRoom: PropTypes.func, closeRoom: PropTypes.func,
theme: PropTypes.string, theme: PropTypes.string,
fontScale: PropTypes.number fontScale: PropTypes.number,
serverVersion: PropTypes.string
} }
constructor(props) { constructor(props) {
@ -71,7 +73,8 @@ class RoomActionsView extends React.Component {
canInviteUser: false, canInviteUser: false,
canForwardGuest: false, canForwardGuest: false,
canReturnQueue: false, canReturnQueue: false,
canEdit: false canEdit: false,
canToggleEncryption: false
}; };
if (room && room.observe && room.rid) { if (room && room.observe && room.rid) {
this.roomObservable = room.observe(); this.roomObservable = room.observe();
@ -120,6 +123,7 @@ class RoomActionsView extends React.Component {
this.canAddUser(); this.canAddUser();
this.canInviteUser(); this.canInviteUser();
this.canEdit(); this.canEdit();
this.canToggleEncryption();
// livechat permissions // livechat permissions
if (room.t === 'l') { if (room.t === 'l') {
@ -152,7 +156,6 @@ class RoomActionsView extends React.Component {
} }
} }
// eslint-disable-next-line react/sort-comp
canAddUser = async() => { canAddUser = async() => {
const { room, joined } = this.state; const { room, joined } = this.state;
const { rid, t } = room; const { rid, t } = room;
@ -193,6 +196,15 @@ class RoomActionsView extends React.Component {
this.setState({ canEdit }); this.setState({ canEdit });
} }
canToggleEncryption = async() => {
const { room } = this.state;
const { rid } = room;
const permissions = await RocketChat.hasPermission(['toggle-room-e2e-encryption'], rid);
const canToggleEncryption = permissions && permissions['toggle-room-e2e-encryption'];
this.setState({ canToggleEncryption });
}
canViewMembers = async() => { canViewMembers = async() => {
const { room } = this.state; const { room } = this.state;
const { rid, t, broadcast } = room; const { rid, t, broadcast } = room;
@ -235,13 +247,21 @@ class RoomActionsView extends React.Component {
} }
renderEncryptedSwitch = () => { renderEncryptedSwitch = () => {
const { room } = this.state; const { room, canToggleEncryption, canEdit } = this.state;
const { encrypted } = room; const { encrypted } = room;
const { serverVersion } = this.props;
let hasPermission = false;
if (serverVersion && semver.lt(semver.coerce(serverVersion), '3.11.0')) {
hasPermission = canEdit;
} else {
hasPermission = canToggleEncryption;
}
return ( return (
<Switch <Switch
value={encrypted} value={encrypted}
trackColor={SWITCH_TRACK_COLOR} trackColor={SWITCH_TRACK_COLOR}
onValueChange={this.toggleEncrypted} onValueChange={this.toggleEncrypted}
disabled={!hasPermission}
/> />
); );
} }
@ -480,15 +500,12 @@ class RoomActionsView extends React.Component {
} }
renderE2EEncryption = () => { renderE2EEncryption = () => {
const { const { room } = this.state;
room, canEdit const { encryptionEnabled } = this.props;
} = this.state;
const { e2eEnabled } = this.props;
// If can edit this room // If this room type can be encrypted
// If this room type can be Encrypted // If e2e is enabled
// If e2e is enabled for this server if (E2E_ROOM_TYPES[room?.t] && encryptionEnabled) {
if (canEdit && E2E_ROOM_TYPES[room?.t] && e2eEnabled) {
return ( return (
<List.Section> <List.Section>
<List.Separator /> <List.Separator />
@ -847,7 +864,8 @@ class RoomActionsView extends React.Component {
const mapStateToProps = state => ({ const mapStateToProps = state => ({
jitsiEnabled: state.settings.Jitsi_Enabled || false, jitsiEnabled: state.settings.Jitsi_Enabled || false,
e2eEnabled: state.settings.E2E_Enable || false encryptionEnabled: state.encryption.enabled,
serverVersion: state.server.version
}); });
const mapDispatchToProps = dispatch => ({ const mapDispatchToProps = dispatch => ({

View File

@ -60,7 +60,7 @@ class RoomInfoEditView extends React.Component {
route: PropTypes.object, route: PropTypes.object,
deleteRoom: PropTypes.func, deleteRoom: PropTypes.func,
serverVersion: PropTypes.string, serverVersion: PropTypes.string,
e2eEnabled: PropTypes.bool, encryptionEnabled: PropTypes.bool,
theme: PropTypes.string theme: PropTypes.string
}; };
@ -414,7 +414,7 @@ class RoomInfoEditView extends React.Component {
const { const {
name, nameError, description, topic, announcement, t, ro, reactWhenReadOnly, room, joinCode, saving, permissions, archived, enableSysMes, encrypted, avatar name, nameError, description, topic, announcement, t, ro, reactWhenReadOnly, room, joinCode, saving, permissions, archived, enableSysMes, encrypted, avatar
} = this.state; } = this.state;
const { serverVersion, e2eEnabled, theme } = this.props; const { serverVersion, encryptionEnabled, theme } = this.props;
const { dangerColor } = themes[theme]; const { dangerColor } = themes[theme];
return ( return (
@ -561,7 +561,7 @@ class RoomInfoEditView extends React.Component {
{this.renderSystemMessages()} {this.renderSystemMessages()}
</SwitchContainer> </SwitchContainer>
) : null} ) : null}
{e2eEnabled ? ( {encryptionEnabled ? (
<SwitchContainer <SwitchContainer
value={encrypted} value={encrypted}
disabled={!t} disabled={!t}
@ -665,7 +665,7 @@ class RoomInfoEditView extends React.Component {
const mapStateToProps = state => ({ const mapStateToProps = state => ({
serverVersion: state.share.server.version || state.server.version, serverVersion: state.share.server.version || state.server.version,
e2eEnabled: state.settings.E2E_Enable || false encryptionEnabled: state.encryption.enabled
}); });
const mapDispatchToProps = dispatch => ({ const mapDispatchToProps = dispatch => ({