[IMPROVEMENT] Add `Change Encryption Password` and `Reset E2E Key` (#2542)
* init * Basic tests passing * Add SecurityPrivacyView * List.Item * section * Start removing theme as prop * Remove StatusBar theme prop * SafeAreaView theme prop * Minor fixes * List.Container * Add translateTitle and translateSubtitle props * Storybook * Show action indicator * Header * Info * Theme stories * FlatList * DisplayName * Fix settings * FlatList tweaks * ThemeView * Screen Lock Config * DefaultBrowserView * PickerView and User Prefs * Notification Prefs * StatusView * Auto Translate * InviteUsersEdit * Visitor * Minor fixes * Remove Separator * Remove iteminfo * Font scale * Legal * Jitsi and e2e * Block * search, star, etc * auto translate and notifications * RoomInfo * Refactor RoomActions * lint * Remove DisclosureIndicator * padding horizontal 12 * Detox * Tests * SecurityPrivacy * E2E encryption sec view * stash * Reset own key * Reset key * Change password * Hide content * Small refactor * Fix tests * Tests passing * Change test order * add pt-br * Address review comments * tests * Missing i18n ptbr Co-authored-by: Djorkaeff Alexandre <djorkaeff.unb@gmail.com>
This commit is contained in:
parent
32a0e9be15
commit
fade17d0de
|
@ -82,6 +82,7 @@ export default class Button extends React.PureComponent {
|
||||||
{ color: textColor },
|
{ color: textColor },
|
||||||
fontSize && { fontSize }
|
fontSize && { fontSize }
|
||||||
]}
|
]}
|
||||||
|
accessibilityLabel={title}
|
||||||
>
|
>
|
||||||
{title}
|
{title}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
|
@ -197,6 +197,7 @@ export default {
|
||||||
Do_you_have_an_account: 'Do you have an account?',
|
Do_you_have_an_account: 'Do you have an account?',
|
||||||
Do_you_have_a_certificate: 'Do you have a certificate?',
|
Do_you_have_a_certificate: 'Do you have a certificate?',
|
||||||
Do_you_really_want_to_key_this_room_question_mark: 'Do you really want to {{key}} this room?',
|
Do_you_really_want_to_key_this_room_question_mark: 'Do you really want to {{key}} this room?',
|
||||||
|
E2E_Encryption: 'E2E Encryption',
|
||||||
E2E_How_It_Works_info1: 'You can now create encrypted private groups and direct messages. You may also change existing private groups or DMs to encrypted.',
|
E2E_How_It_Works_info1: 'You can now create encrypted private groups and direct messages. You may also change existing private groups or DMs to encrypted.',
|
||||||
E2E_How_It_Works_info2: 'This is *end to end encryption* so the key to encode/decode your messages and they will not be saved on the server. For that reason *you need to store this password somewhere safe* which you can access later if you may need.',
|
E2E_How_It_Works_info2: 'This is *end to end encryption* so the key to encode/decode your messages and they will not be saved on the server. For that reason *you need to store this password somewhere safe* which you can access later if you may need.',
|
||||||
E2E_How_It_Works_info3: 'If you proceed, it will be auto generated an E2E password.',
|
E2E_How_It_Works_info3: 'If you proceed, it will be auto generated an E2E password.',
|
||||||
|
@ -462,6 +463,7 @@ export default {
|
||||||
Search_global_users: 'Search for global users',
|
Search_global_users: 'Search for global users',
|
||||||
Search_global_users_description: 'If you turn-on, you can search for any user from others companies or servers.',
|
Search_global_users_description: 'If you turn-on, you can search for any user from others companies or servers.',
|
||||||
Seconds: '{{second}} seconds',
|
Seconds: '{{second}} seconds',
|
||||||
|
Security_and_privacy: 'Security and privacy',
|
||||||
Select_Avatar: 'Select Avatar',
|
Select_Avatar: 'Select Avatar',
|
||||||
Select_Server: 'Select Server',
|
Select_Server: 'Select Server',
|
||||||
Select_Users: 'Select Users',
|
Select_Users: 'Select Users',
|
||||||
|
@ -659,6 +661,18 @@ export default {
|
||||||
Logged_out_of_other_clients_successfully: 'Logged out of other clients successfully',
|
Logged_out_of_other_clients_successfully: 'Logged out of other clients successfully',
|
||||||
Logout_failed: 'Logout failed!',
|
Logout_failed: 'Logout failed!',
|
||||||
Log_analytics_events: 'Log analytics events',
|
Log_analytics_events: 'Log analytics events',
|
||||||
|
E2E_encryption_change_password_title: 'Change Encryption Password',
|
||||||
|
E2E_encryption_change_password_description: 'You can now create encrypted private groups and direct messages. You may also change existing private groups or DMs to encrypted. \nThis is end to end encryption so the key to encode/decode your messages will not be saved on the server. For that reason you need to store your password somewhere safe. You will be required to enter it on other devices you wish to use e2e encryption on.',
|
||||||
|
E2E_encryption_change_password_error: 'Error while changing E2E key password!',
|
||||||
|
E2E_encryption_change_password_success: 'E2E key password changed successfully!',
|
||||||
|
E2E_encryption_change_password_message: 'Make sure you\'ve saved it carefully somewhere else.',
|
||||||
|
E2E_encryption_change_password_confirmation: 'Yes, change it',
|
||||||
|
E2E_encryption_reset_title: 'Reset E2E Key',
|
||||||
|
E2E_encryption_reset_description: 'This option will remove your current E2E key and log you out. \nWhen you login again, Rocket.Chat will generate you a new key and restore your access to any encrypted room that has one or more members online. \nDue to the nature of the E2E encryption, Rocket.Chat will not be able to restore access to any encrypted room that has no member online.',
|
||||||
|
E2E_encryption_reset_button: 'Reset E2E Key',
|
||||||
|
E2E_encryption_reset_error: 'Error while resetting E2E key!',
|
||||||
|
E2E_encryption_reset_message: 'You\'re going to be logged out.',
|
||||||
|
E2E_encryption_reset_confirmation: 'Yes, reset it',
|
||||||
Following: 'Following',
|
Following: 'Following',
|
||||||
Threads_displaying_all: 'Displaying All',
|
Threads_displaying_all: 'Displaying All',
|
||||||
Threads_displaying_following: 'Displaying Following',
|
Threads_displaying_following: 'Displaying Following',
|
||||||
|
|
|
@ -131,6 +131,7 @@ export default {
|
||||||
Channel_Name: 'Nome do Canal',
|
Channel_Name: 'Nome do Canal',
|
||||||
Channels: 'Canais',
|
Channels: 'Canais',
|
||||||
Chats: 'Conversas',
|
Chats: 'Conversas',
|
||||||
|
Change_Language: 'Alterar idioma',
|
||||||
Change_language_loading: 'Alterando idioma.',
|
Change_language_loading: 'Alterando idioma.',
|
||||||
Call_already_ended: 'A chamada já terminou!',
|
Call_already_ended: 'A chamada já terminou!',
|
||||||
Clear_cache_loading: 'Limpando cache.',
|
Clear_cache_loading: 'Limpando cache.',
|
||||||
|
@ -156,6 +157,7 @@ export default {
|
||||||
Conversation: 'Conversação',
|
Conversation: 'Conversação',
|
||||||
connecting_server: 'conectando no servidor',
|
connecting_server: 'conectando no servidor',
|
||||||
Connecting: 'Conectando...',
|
Connecting: 'Conectando...',
|
||||||
|
Contact_us: 'Entre em contato',
|
||||||
Continue_with: 'Entrar com',
|
Continue_with: 'Entrar com',
|
||||||
Contact_your_server_admin: 'Contate o administrador do servidor.',
|
Contact_your_server_admin: 'Contate o administrador do servidor.',
|
||||||
Copied_to_clipboard: 'Copiado para a área de transferência!',
|
Copied_to_clipboard: 'Copiado para a área de transferência!',
|
||||||
|
@ -191,6 +193,7 @@ export default {
|
||||||
Dont_Have_An_Account: 'Não tem uma conta?',
|
Dont_Have_An_Account: 'Não tem uma conta?',
|
||||||
Do_you_have_an_account: 'Você tem uma conta?',
|
Do_you_have_an_account: 'Você tem uma conta?',
|
||||||
Do_you_really_want_to_key_this_room_question_mark: 'Você quer realmente {{key}} esta sala?',
|
Do_you_really_want_to_key_this_room_question_mark: 'Você quer realmente {{key}} esta sala?',
|
||||||
|
E2E_Encryption: 'Encriptação ponta a ponta',
|
||||||
E2E_How_It_Works_info1: 'Agora você pode criar grupos privados criptografados e mensagens diretas. Você também pode alterar grupos privados existentes ou DMs para criptografados.',
|
E2E_How_It_Works_info1: 'Agora você pode criar grupos privados criptografados e mensagens diretas. Você também pode alterar grupos privados existentes ou DMs para criptografados.',
|
||||||
E2E_How_It_Works_info2: 'Esta é a criptografia *ponta a ponta*, portanto, a chave para codificar/decodificar suas mensagens e elas não serão salvas no servidor. Por esse motivo *você precisa armazenar esta senha em algum lugar seguro* que você pode acessar mais tarde se precisar.',
|
E2E_How_It_Works_info2: 'Esta é a criptografia *ponta a ponta*, portanto, a chave para codificar/decodificar suas mensagens e elas não serão salvas no servidor. Por esse motivo *você precisa armazenar esta senha em algum lugar seguro* que você pode acessar mais tarde se precisar.',
|
||||||
E2E_How_It_Works_info3: 'Se você continuar, será gerada automaticamente uma senha E2E.',
|
E2E_How_It_Works_info3: 'Se você continuar, será gerada automaticamente uma senha E2E.',
|
||||||
|
@ -432,6 +435,7 @@ export default {
|
||||||
Search_by: 'Buscar por',
|
Search_by: 'Buscar por',
|
||||||
Search_global_users: 'Busca por usuários globais',
|
Search_global_users: 'Busca por usuários globais',
|
||||||
Search_global_users_description: 'Caso ativado, busca por usuários de outras empresas ou servidores.',
|
Search_global_users_description: 'Caso ativado, busca por usuários de outras empresas ou servidores.',
|
||||||
|
Security_and_privacy: 'Segurança e privacidade',
|
||||||
Select_Avatar: 'Selecionar Avatar',
|
Select_Avatar: 'Selecionar Avatar',
|
||||||
Select_Server: 'Selecionar Servidor',
|
Select_Server: 'Selecionar Servidor',
|
||||||
Select_Users: 'Selecionar Usuários',
|
Select_Users: 'Selecionar Usuários',
|
||||||
|
@ -441,6 +445,7 @@ export default {
|
||||||
Select_a_User: 'Selecione um Usuário',
|
Select_a_User: 'Selecione um Usuário',
|
||||||
Send: 'Enviar',
|
Send: 'Enviar',
|
||||||
Send_audio_message: 'Enviar mensagem de áudio',
|
Send_audio_message: 'Enviar mensagem de áudio',
|
||||||
|
Send_crash_report: 'Enviar relatório de erros',
|
||||||
Send_message: 'Enviar mensagem',
|
Send_message: 'Enviar mensagem',
|
||||||
Send_me_the_code_again: 'Envie-me o código novamente',
|
Send_me_the_code_again: 'Envie-me o código novamente',
|
||||||
Send_to: 'Enviar para...',
|
Send_to: 'Enviar para...',
|
||||||
|
@ -603,6 +608,18 @@ export default {
|
||||||
Logged_out_of_other_clients_successfully: 'Desconectado de outros clientes com sucesso',
|
Logged_out_of_other_clients_successfully: 'Desconectado de outros clientes com sucesso',
|
||||||
Logout_failed: 'Falha ao desconectar!',
|
Logout_failed: 'Falha ao desconectar!',
|
||||||
Log_analytics_events: 'Logar eventos no analytics',
|
Log_analytics_events: 'Logar eventos no analytics',
|
||||||
|
E2E_encryption_change_password_title: 'Alterar Senha de Criptografia',
|
||||||
|
E2E_encryption_change_password_description: 'Agora você pode criar grupos privados criptografados e mensagens diretas. Você também pode alterar os grupos privados ou DMs existentes para criptografados. Esta é uma criptografia de ponta a ponta, logo a chave para codificar / decodificar suas mensagens não será salva no servidor. Por esse motivo, você precisa armazenar sua senha em algum lugar seguro. Será solicitada a inserção de senha em outros dispositivos nos quais deseja usar a criptografia E2E.',
|
||||||
|
E2E_encryption_change_password_error: 'Erro ao alterar senha de criptografia!',
|
||||||
|
E2E_encryption_change_password_success: 'Senha de criptografia alterada com sucesso!',
|
||||||
|
E2E_encryption_change_password_message: 'Certifique-se de tê-la guardado em local seguro.',
|
||||||
|
E2E_encryption_change_password_confirmation: 'Sim, alterar',
|
||||||
|
E2E_encryption_reset_title: 'Redefinir Chave de Criptografia',
|
||||||
|
E2E_encryption_reset_description: 'Essa opção irá remover a chave de criptografia corrente e desconectá-lo. \nQuando você se conectar novamente, uma nova chave será gerada e restaurará acesso a qualquer canal com uma ou mais pessoas online. \nDevico à natureza da criptografia ponta a ponta, não será possível restaurar acesso a canais sem membros online.',
|
||||||
|
E2E_encryption_reset_button: 'Redefinir',
|
||||||
|
E2E_encryption_reset_error: 'Erro ao redefinir chave!',
|
||||||
|
E2E_encryption_reset_message: 'Você será desconectado.',
|
||||||
|
E2E_encryption_reset_confirmation: 'Sim, redefinir',
|
||||||
Following: 'Seguindo',
|
Following: 'Seguindo',
|
||||||
Threads_displaying_all: 'Mostrando Tudo',
|
Threads_displaying_all: 'Mostrando Tudo',
|
||||||
Threads_displaying_following: 'Mostrando Seguindo',
|
Threads_displaying_following: 'Mostrando Seguindo',
|
||||||
|
|
|
@ -68,6 +68,10 @@ 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;
|
||||||
|
@ -192,6 +196,18 @@ class Encryption {
|
||||||
return password;
|
return password;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
changePassword = async(server, password) => {
|
||||||
|
// Cast key to the format server is expecting
|
||||||
|
const privateKey = await SimpleCrypto.RSA.exportKey(this.privateKey);
|
||||||
|
|
||||||
|
// Encode the private key
|
||||||
|
const encodedPrivateKey = await this.encodePrivateKey(EJSON.stringify(privateKey), password, this.userId);
|
||||||
|
const publicKey = await UserPreferences.getStringAsync(`${ server }-${ E2E_PUBLIC_KEY }`);
|
||||||
|
|
||||||
|
// Send the new keys to the server
|
||||||
|
await RocketChat.e2eSetUserPublicAndPrivateKeys(EJSON.stringify(publicKey), encodedPrivateKey);
|
||||||
|
}
|
||||||
|
|
||||||
// get a encryption room instance
|
// get a encryption room instance
|
||||||
getRoomInstance = async(rid) => {
|
getRoomInstance = async(rid) => {
|
||||||
// Prevent handshake again
|
// Prevent handshake again
|
||||||
|
|
|
@ -84,6 +84,12 @@ const RocketChat = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
unsubscribeRooms() {
|
||||||
|
if (this.roomsSub) {
|
||||||
|
this.roomsSub.stop();
|
||||||
|
this.roomsSub = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
canOpenRoom,
|
canOpenRoom,
|
||||||
createChannel({
|
createChannel({
|
||||||
name, users, type, readOnly, broadcast, encrypted
|
name, users, type, readOnly, broadcast, encrypted
|
||||||
|
@ -194,10 +200,7 @@ const RocketChat = {
|
||||||
this.notifyLoggedListener.then(this.stopListener);
|
this.notifyLoggedListener.then(this.stopListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.roomsSub) {
|
this.unsubscribeRooms();
|
||||||
this.roomsSub.stop();
|
|
||||||
this.roomsSub = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
EventEmitter.emit('INQUIRY_UNSUBSCRIBE');
|
EventEmitter.emit('INQUIRY_UNSUBSCRIBE');
|
||||||
|
|
||||||
|
@ -407,6 +410,12 @@ const RocketChat = {
|
||||||
// RC 0.70.0
|
// RC 0.70.0
|
||||||
return this.methodCallWrapper('stream-notify-room-users', `${ rid }/e2ekeyRequest`, rid, e2eKeyId);
|
return this.methodCallWrapper('stream-notify-room-users', `${ rid }/e2ekeyRequest`, rid, e2eKeyId);
|
||||||
},
|
},
|
||||||
|
e2eResetOwnKey() {
|
||||||
|
this.unsubscribeRooms();
|
||||||
|
|
||||||
|
// RC 0.72.0
|
||||||
|
return this.methodCallWrapper('e2e.resetOwnE2EKey');
|
||||||
|
},
|
||||||
|
|
||||||
updateJitsiTimeout(roomId) {
|
updateJitsiTimeout(roomId) {
|
||||||
// RC 0.74.0
|
// RC 0.74.0
|
||||||
|
|
|
@ -31,6 +31,7 @@ import UserPreferences from '../lib/userPreferences';
|
||||||
import { inquiryRequest, inquiryReset } from '../ee/omnichannel/actions/inquiry';
|
import { inquiryRequest, inquiryReset } from '../ee/omnichannel/actions/inquiry';
|
||||||
import { isOmnichannelStatusAvailable } from '../ee/omnichannel/lib';
|
import { isOmnichannelStatusAvailable } from '../ee/omnichannel/lib';
|
||||||
import { E2E_REFRESH_MESSAGES_KEY } from '../lib/encryption/constants';
|
import { E2E_REFRESH_MESSAGES_KEY } from '../lib/encryption/constants';
|
||||||
|
import Navigation from '../lib/Navigation';
|
||||||
|
|
||||||
const getServer = state => state.server.server;
|
const getServer = state => state.server.server;
|
||||||
const loginWithPasswordCall = args => RocketChat.loginWithPassword(args);
|
const loginWithPasswordCall = args => RocketChat.loginWithPassword(args);
|
||||||
|
@ -239,6 +240,9 @@ const handleLogout = function* handleLogout({ forcedByServer }) {
|
||||||
if (forcedByServer) {
|
if (forcedByServer) {
|
||||||
yield put(appStart({ root: ROOT_OUTSIDE }));
|
yield put(appStart({ root: ROOT_OUTSIDE }));
|
||||||
showErrorAlert(I18n.t('Logged_out_by_server'), I18n.t('Oops'));
|
showErrorAlert(I18n.t('Logged_out_by_server'), I18n.t('Oops'));
|
||||||
|
yield delay(300);
|
||||||
|
Navigation.navigate('NewServerView');
|
||||||
|
yield delay(300);
|
||||||
EventEmitter.emit('NewServer', { server });
|
EventEmitter.emit('NewServer', { server });
|
||||||
} else {
|
} else {
|
||||||
const serversDB = database.servers;
|
const serversDB = database.servers;
|
||||||
|
|
|
@ -38,6 +38,8 @@ import UserNotificationPrefView from '../views/UserNotificationPreferencesView';
|
||||||
|
|
||||||
// Settings Stack
|
// Settings Stack
|
||||||
import SettingsView from '../views/SettingsView';
|
import SettingsView from '../views/SettingsView';
|
||||||
|
import SecurityPrivacyView from '../views/SecurityPrivacyView';
|
||||||
|
import E2EEncryptionSecurityView from '../views/E2EEncryptionSecurityView';
|
||||||
import LanguageView from '../views/LanguageView';
|
import LanguageView from '../views/LanguageView';
|
||||||
import ThemeView from '../views/ThemeView';
|
import ThemeView from '../views/ThemeView';
|
||||||
import DefaultBrowserView from '../views/DefaultBrowserView';
|
import DefaultBrowserView from '../views/DefaultBrowserView';
|
||||||
|
@ -225,6 +227,16 @@ const SettingsStackNavigator = () => {
|
||||||
component={SettingsView}
|
component={SettingsView}
|
||||||
options={SettingsView.navigationOptions}
|
options={SettingsView.navigationOptions}
|
||||||
/>
|
/>
|
||||||
|
<SettingsStack.Screen
|
||||||
|
name='SecurityPrivacyView'
|
||||||
|
component={SecurityPrivacyView}
|
||||||
|
options={SecurityPrivacyView.navigationOptions}
|
||||||
|
/>
|
||||||
|
<SettingsStack.Screen
|
||||||
|
name='E2EEncryptionSecurityView'
|
||||||
|
component={E2EEncryptionSecurityView}
|
||||||
|
options={E2EEncryptionSecurityView.navigationOptions}
|
||||||
|
/>
|
||||||
<SettingsStack.Screen
|
<SettingsStack.Screen
|
||||||
name='LanguageView'
|
name='LanguageView'
|
||||||
component={LanguageView}
|
component={LanguageView}
|
||||||
|
|
|
@ -43,6 +43,8 @@ import NewMessageView from '../../views/NewMessageView';
|
||||||
import CreateChannelView from '../../views/CreateChannelView';
|
import CreateChannelView from '../../views/CreateChannelView';
|
||||||
import UserPreferencesView from '../../views/UserPreferencesView';
|
import UserPreferencesView from '../../views/UserPreferencesView';
|
||||||
import UserNotificationPrefView from '../../views/UserNotificationPreferencesView';
|
import UserNotificationPrefView from '../../views/UserNotificationPreferencesView';
|
||||||
|
import SecurityPrivacyView from '../../views/SecurityPrivacyView';
|
||||||
|
import E2EEncryptionSecurityView from '../../views/E2EEncryptionSecurityView';
|
||||||
|
|
||||||
// InsideStackNavigator
|
// InsideStackNavigator
|
||||||
import AttachmentView from '../../views/AttachmentView';
|
import AttachmentView from '../../views/AttachmentView';
|
||||||
|
@ -283,6 +285,16 @@ const ModalStackNavigator = React.memo(({ navigation }) => {
|
||||||
component={UserNotificationPrefView}
|
component={UserNotificationPrefView}
|
||||||
options={UserNotificationPrefView.navigationOptions}
|
options={UserNotificationPrefView.navigationOptions}
|
||||||
/>
|
/>
|
||||||
|
<ModalStack.Screen
|
||||||
|
name='SecurityPrivacyView'
|
||||||
|
component={SecurityPrivacyView}
|
||||||
|
options={SecurityPrivacyView.navigationOptions}
|
||||||
|
/>
|
||||||
|
<ModalStack.Screen
|
||||||
|
name='E2EEncryptionSecurityView'
|
||||||
|
component={E2EEncryptionSecurityView}
|
||||||
|
options={E2EEncryptionSecurityView.navigationOptions}
|
||||||
|
/>
|
||||||
</ModalStack.Navigator>
|
</ModalStack.Navigator>
|
||||||
</ModalContainer>
|
</ModalContainer>
|
||||||
);
|
);
|
||||||
|
|
|
@ -133,16 +133,20 @@ export default {
|
||||||
SE_SHARE_THIS_APP: 'se_share_this_app',
|
SE_SHARE_THIS_APP: 'se_share_this_app',
|
||||||
SE_GO_DEFAULTBROWSER: 'se_go_default_browser',
|
SE_GO_DEFAULTBROWSER: 'se_go_default_browser',
|
||||||
SE_GO_THEME: 'se_go_theme',
|
SE_GO_THEME: 'se_go_theme',
|
||||||
SE_GO_SCREENLOCKCONFIG: 'se_go_screen_lock_cfg',
|
|
||||||
SE_GO_PROFILE: 'se_go_profile',
|
SE_GO_PROFILE: 'se_go_profile',
|
||||||
|
SE_GO_SECURITYPRIVACY: 'se_go_securityprivacy',
|
||||||
SE_READ_LICENSE: 'se_read_license',
|
SE_READ_LICENSE: 'se_read_license',
|
||||||
SE_COPY_APP_VERSION: 'se_copy_app_version',
|
SE_COPY_APP_VERSION: 'se_copy_app_version',
|
||||||
SE_COPY_SERVER_VERSION: 'se_copy_server_version',
|
SE_COPY_SERVER_VERSION: 'se_copy_server_version',
|
||||||
SE_TOGGLE_CRASH_REPORT: 'se_toggle_crash_report',
|
|
||||||
SE_TOGGLE_ANALYTICS_EVENTS: 'se_toggle_analytics_events',
|
|
||||||
SE_CLEAR_LOCAL_SERVER_CACHE: 'se_clear_local_server_cache',
|
SE_CLEAR_LOCAL_SERVER_CACHE: 'se_clear_local_server_cache',
|
||||||
SE_LOG_OUT: 'se_log_out',
|
SE_LOG_OUT: 'se_log_out',
|
||||||
|
|
||||||
|
// SECURITY PRIVACY VIEW
|
||||||
|
SP_GO_E2EENCRYPTIONSECURITY: 'sp_go_e2e_encryption_security',
|
||||||
|
SP_GO_SCREENLOCKCONFIG: 'sp_go_screen_lock_cfg',
|
||||||
|
SP_TOGGLE_CRASH_REPORT: 'sp_toggle_crash_report',
|
||||||
|
SP_TOGGLE_ANALYTICS_EVENTS: 'sp_toggle_analytics_events',
|
||||||
|
|
||||||
// LANGUAGE VIEW
|
// LANGUAGE VIEW
|
||||||
LANG_SET_LANGUAGE: 'lang_set_language',
|
LANG_SET_LANGUAGE: 'lang_set_language',
|
||||||
LANG_SET_LANGUAGE_F: 'lang_set_language_f',
|
LANG_SET_LANGUAGE_F: 'lang_set_language_f',
|
||||||
|
@ -303,5 +307,9 @@ export default {
|
||||||
E2E_SAVE_PW_HOW_IT_WORKS: 'e2e_save_pw_how_it_works',
|
E2E_SAVE_PW_HOW_IT_WORKS: 'e2e_save_pw_how_it_works',
|
||||||
|
|
||||||
// E2E ENTER YOUR PASSWORD VIEW
|
// E2E ENTER YOUR PASSWORD VIEW
|
||||||
E2E_ENTER_PW_SUBMIT: 'e2e_enter_pw_submit'
|
E2E_ENTER_PW_SUBMIT: 'e2e_enter_pw_submit',
|
||||||
|
|
||||||
|
// E2E ENCRYPTION SECURITY VIEW
|
||||||
|
E2E_SEC_CHANGE_PASSWORD: 'e2e_sec_change_password',
|
||||||
|
E2E_SEC_RESET_OWN_KEY: 'e2e_sec_reset_own_key'
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,185 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { View, Text, StyleSheet } from 'react-native';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
|
import StatusBar from '../containers/StatusBar';
|
||||||
|
import * as List from '../containers/List';
|
||||||
|
import I18n from '../i18n';
|
||||||
|
import log, { logEvent, events } from '../utils/log';
|
||||||
|
import { withTheme } from '../theme';
|
||||||
|
import SafeAreaView from '../containers/SafeAreaView';
|
||||||
|
import TextInput from '../containers/TextInput';
|
||||||
|
import Button from '../containers/Button';
|
||||||
|
import { getUserSelector } from '../selectors/login';
|
||||||
|
import { PADDING_HORIZONTAL } from '../containers/List/constants';
|
||||||
|
import sharedStyles from './Styles';
|
||||||
|
import { themes } from '../constants/colors';
|
||||||
|
import { Encryption } from '../lib/encryption';
|
||||||
|
import RocketChat from '../lib/rocketchat';
|
||||||
|
import { logout as logoutAction } from '../actions/login';
|
||||||
|
import { showConfirmationAlert, showErrorAlert } from '../utils/info';
|
||||||
|
import EventEmitter from '../utils/events';
|
||||||
|
import { LISTENER } from '../containers/Toast';
|
||||||
|
import debounce from '../utils/debounce';
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
paddingHorizontal: PADDING_HORIZONTAL
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
fontSize: 16,
|
||||||
|
...sharedStyles.textMedium
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
fontSize: 14,
|
||||||
|
paddingVertical: 10,
|
||||||
|
...sharedStyles.textRegular
|
||||||
|
},
|
||||||
|
changePasswordButton: {
|
||||||
|
marginBottom: 4
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
class E2EEncryptionSecurityView extends React.Component {
|
||||||
|
state = { newPassword: '' }
|
||||||
|
|
||||||
|
newPasswordInputRef = React.createRef();
|
||||||
|
|
||||||
|
onChangePasswordText = debounce(text => this.setState({ newPassword: text }), 300)
|
||||||
|
|
||||||
|
setNewPasswordRef = ref => this.newPasswordInputRef = ref;
|
||||||
|
|
||||||
|
changePassword = () => {
|
||||||
|
const { newPassword } = this.state;
|
||||||
|
if (!newPassword.trim()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
showConfirmationAlert({
|
||||||
|
title: I18n.t('Are_you_sure_question_mark'),
|
||||||
|
message: I18n.t('E2E_encryption_change_password_message'),
|
||||||
|
confirmationText: I18n.t('E2E_encryption_change_password_confirmation'),
|
||||||
|
onPress: async() => {
|
||||||
|
logEvent(events.E2E_SEC_CHANGE_PASSWORD);
|
||||||
|
try {
|
||||||
|
const { server } = this.props;
|
||||||
|
await Encryption.changePassword(server, newPassword);
|
||||||
|
EventEmitter.emit(LISTENER, { message: I18n.t('E2E_encryption_change_password_success') });
|
||||||
|
this.newPasswordInputRef?.clear();
|
||||||
|
this.newPasswordInputRef?.blur();
|
||||||
|
} catch (e) {
|
||||||
|
log(e);
|
||||||
|
showErrorAlert(I18n.t('E2E_encryption_change_password_error'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
resetOwnKey = () => {
|
||||||
|
showConfirmationAlert({
|
||||||
|
title: I18n.t('Are_you_sure_question_mark'),
|
||||||
|
message: I18n.t('E2E_encryption_reset_message'),
|
||||||
|
confirmationText: I18n.t('E2E_encryption_reset_confirmation'),
|
||||||
|
onPress: () => {
|
||||||
|
logEvent(events.E2E_SEC_RESET_OWN_KEY);
|
||||||
|
try {
|
||||||
|
RocketChat.e2eResetOwnKey();
|
||||||
|
const { logout } = this.props;
|
||||||
|
logout();
|
||||||
|
} catch (e) {
|
||||||
|
log(e);
|
||||||
|
showErrorAlert(I18n.t('E2E_encryption_reset_error'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
renderChangePassword = () => {
|
||||||
|
const { newPassword } = this.state;
|
||||||
|
const { theme } = this.props;
|
||||||
|
const { hasPrivateKey } = Encryption;
|
||||||
|
if (!hasPrivateKey) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<List.Section>
|
||||||
|
<Text style={[styles.title, { color: themes[theme].titleColor }]}>{I18n.t('E2E_encryption_change_password_title')}</Text>
|
||||||
|
<Text style={[styles.description, { color: themes[theme].bodyText }]}>{I18n.t('E2E_encryption_change_password_description')}</Text>
|
||||||
|
<TextInput
|
||||||
|
inputRef={this.setNewPasswordRef}
|
||||||
|
placeholder={I18n.t('New_Password')}
|
||||||
|
returnKeyType='send'
|
||||||
|
secureTextEntry
|
||||||
|
onSubmitEditing={this.changePassword}
|
||||||
|
testID='e2e-encryption-security-view-password'
|
||||||
|
theme={theme}
|
||||||
|
onChangeText={this.onChangePasswordText}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
onPress={this.changePassword}
|
||||||
|
title={I18n.t('Save_Changes')}
|
||||||
|
theme={theme}
|
||||||
|
disabled={!newPassword.trim()}
|
||||||
|
style={styles.changePasswordButton}
|
||||||
|
testID='e2e-encryption-security-view-change-password'
|
||||||
|
/>
|
||||||
|
</List.Section>
|
||||||
|
|
||||||
|
<List.Separator />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { theme } = this.props;
|
||||||
|
return (
|
||||||
|
<SafeAreaView testID='e2e-encryption-security-view' style={{ backgroundColor: themes[theme].backgroundColor }}>
|
||||||
|
<StatusBar theme={theme} />
|
||||||
|
<List.Container>
|
||||||
|
<View style={styles.container}>
|
||||||
|
{this.renderChangePassword()}
|
||||||
|
|
||||||
|
<List.Section>
|
||||||
|
<Text style={[styles.title, { color: themes[theme].titleColor }]}>{I18n.t('E2E_encryption_reset_title')}</Text>
|
||||||
|
<Text style={[styles.description, { color: themes[theme].bodyText }]}>{I18n.t('E2E_encryption_reset_description')}</Text>
|
||||||
|
<Button
|
||||||
|
onPress={this.resetOwnKey}
|
||||||
|
title={I18n.t('E2E_encryption_reset_button')}
|
||||||
|
theme={theme}
|
||||||
|
type='secondary'
|
||||||
|
backgroundColor={themes[theme].chatComponentBackground}
|
||||||
|
testID='e2e-encryption-security-view-reset-key'
|
||||||
|
/>
|
||||||
|
</List.Section>
|
||||||
|
</View>
|
||||||
|
</List.Container>
|
||||||
|
</SafeAreaView>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapStateToProps = state => ({
|
||||||
|
server: state.server.server,
|
||||||
|
user: getUserSelector(state)
|
||||||
|
});
|
||||||
|
|
||||||
|
const mapDispatchToProps = dispatch => ({
|
||||||
|
logout: () => dispatch(logoutAction(true))
|
||||||
|
});
|
||||||
|
|
||||||
|
E2EEncryptionSecurityView.navigationOptions = () => ({
|
||||||
|
title: I18n.t('E2E_Encryption')
|
||||||
|
});
|
||||||
|
|
||||||
|
E2EEncryptionSecurityView.propTypes = {
|
||||||
|
theme: PropTypes.string,
|
||||||
|
user: PropTypes.shape({
|
||||||
|
roles: PropTypes.array,
|
||||||
|
id: PropTypes.string
|
||||||
|
}),
|
||||||
|
server: PropTypes.string,
|
||||||
|
logout: PropTypes.func
|
||||||
|
};
|
||||||
|
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(withTheme(E2EEncryptionSecurityView));
|
|
@ -64,7 +64,7 @@ class E2EEnterYourPasswordView extends React.Component {
|
||||||
>
|
>
|
||||||
<StatusBar />
|
<StatusBar />
|
||||||
<ScrollView {...scrollPersistTaps} style={sharedStyles.container} contentContainerStyle={[sharedStyles.containerScrollView, styles.scrollView]}>
|
<ScrollView {...scrollPersistTaps} style={sharedStyles.container} contentContainerStyle={[sharedStyles.containerScrollView, styles.scrollView]}>
|
||||||
<SafeAreaView style={[styles.container, { backgroundColor: themes[theme].backgroundColor }]}>
|
<SafeAreaView style={[styles.container, { backgroundColor: themes[theme].backgroundColor }]} testID='e2e-enter-your-password-view'>
|
||||||
<TextInput
|
<TextInput
|
||||||
inputRef={(e) => { this.passwordInput = e; }}
|
inputRef={(e) => { this.passwordInput = e; }}
|
||||||
placeholder={I18n.t('Password')}
|
placeholder={I18n.t('Password')}
|
||||||
|
@ -82,6 +82,7 @@ class E2EEnterYourPasswordView extends React.Component {
|
||||||
title={I18n.t('Confirm')}
|
title={I18n.t('Confirm')}
|
||||||
disabled={!password}
|
disabled={!password}
|
||||||
theme={theme}
|
theme={theme}
|
||||||
|
testID='e2e-enter-your-password-view-confirm'
|
||||||
/>
|
/>
|
||||||
<Text style={[styles.info, { color: themes[theme].bodyText }]}>{I18n.t('Enter_Your_Encryption_Password_desc1')}</Text>
|
<Text style={[styles.info, { color: themes[theme].bodyText }]}>{I18n.t('Enter_Your_Encryption_Password_desc1')}</Text>
|
||||||
<Text style={[styles.info, { color: themes[theme].bodyText }]}>{I18n.t('Enter_Your_Encryption_Password_desc2')}</Text>
|
<Text style={[styles.info, { color: themes[theme].bodyText }]}>{I18n.t('Enter_Your_Encryption_Password_desc2')}</Text>
|
||||||
|
|
|
@ -126,7 +126,7 @@ class E2ESaveYourPasswordView extends React.Component {
|
||||||
const { theme } = this.props;
|
const { theme } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SafeAreaView style={{ backgroundColor: themes[theme].backgroundColor }}>
|
<SafeAreaView style={{ backgroundColor: themes[theme].backgroundColor }} testID='e2e-save-password-view'>
|
||||||
<StatusBar />
|
<StatusBar />
|
||||||
<ScrollView {...scrollPersistTaps} style={sharedStyles.container} contentContainerStyle={sharedStyles.containerScrollView}>
|
<ScrollView {...scrollPersistTaps} style={sharedStyles.container} contentContainerStyle={sharedStyles.containerScrollView}>
|
||||||
<View style={[styles.container, { backgroundColor: themes[theme].backgroundColor }]}>
|
<View style={[styles.container, { backgroundColor: themes[theme].backgroundColor }]}>
|
||||||
|
@ -150,11 +150,13 @@ class E2ESaveYourPasswordView extends React.Component {
|
||||||
title={I18n.t('How_It_Works')}
|
title={I18n.t('How_It_Works')}
|
||||||
type='secondary'
|
type='secondary'
|
||||||
theme={theme}
|
theme={theme}
|
||||||
|
testID='e2e-save-password-view-how-it-works'
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
onPress={this.onSaved}
|
onPress={this.onSaved}
|
||||||
title={I18n.t('I_Saved_My_E2E_Password')}
|
title={I18n.t('I_Saved_My_E2E_Password')}
|
||||||
theme={theme}
|
theme={theme}
|
||||||
|
testID='e2e-save-password-view-saved-password'
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
|
|
|
@ -417,7 +417,7 @@ class RoomActionsView extends React.Component {
|
||||||
<Avatar
|
<Avatar
|
||||||
text={avatar}
|
text={avatar}
|
||||||
style={styles.avatar}
|
style={styles.avatar}
|
||||||
size={50}
|
size={50 * fontScale}
|
||||||
type={t}
|
type={t}
|
||||||
rid={rid}
|
rid={rid}
|
||||||
>
|
>
|
||||||
|
|
|
@ -26,7 +26,13 @@ const Encryption = React.memo(({
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BorderlessButton style={[styles.encryptionButton, { backgroundColor: themes[theme].actionTintColor }]} theme={theme} onPress={goEncryption}>
|
<BorderlessButton
|
||||||
|
style={[styles.encryptionButton, { backgroundColor: themes[theme].actionTintColor }]}
|
||||||
|
theme={theme}
|
||||||
|
onPress={goEncryption}
|
||||||
|
testID='listheader-encryption'
|
||||||
|
accessibilityLabel={text}
|
||||||
|
>
|
||||||
<CustomIcon name='encrypted' size={24} color={themes[theme].buttonText} style={styles.encryptionIcon} />
|
<CustomIcon name='encrypted' size={24} color={themes[theme].buttonText} style={styles.encryptionIcon} />
|
||||||
<Text style={[styles.encryptionText, { color: themes[theme].buttonText }]}>{text}</Text>
|
<Text style={[styles.encryptionText, { color: themes[theme].buttonText }]}>{text}</Text>
|
||||||
</BorderlessButton>
|
</BorderlessButton>
|
||||||
|
|
|
@ -0,0 +1,158 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { Switch } from 'react-native';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import AsyncStorage from '@react-native-community/async-storage';
|
||||||
|
|
||||||
|
import { toggleCrashReport as toggleCrashReportAction, toggleAnalyticsEvents as toggleAnalyticsEventsAction } from '../actions/crashReport';
|
||||||
|
import { SWITCH_TRACK_COLOR } from '../constants/colors';
|
||||||
|
import StatusBar from '../containers/StatusBar';
|
||||||
|
import * as List from '../containers/List';
|
||||||
|
import I18n from '../i18n';
|
||||||
|
import { CRASH_REPORT_KEY, ANALYTICS_EVENTS_KEY } from '../lib/rocketchat';
|
||||||
|
import {
|
||||||
|
loggerConfig, analytics, logEvent, events
|
||||||
|
} from '../utils/log';
|
||||||
|
import SafeAreaView from '../containers/SafeAreaView';
|
||||||
|
import { isFDroidBuild } from '../constants/environment';
|
||||||
|
import { getUserSelector } from '../selectors/login';
|
||||||
|
|
||||||
|
class SecurityPrivacyView extends React.Component {
|
||||||
|
static navigationOptions = () => ({
|
||||||
|
title: I18n.t('Security_and_privacy')
|
||||||
|
});
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
navigation: PropTypes.object,
|
||||||
|
allowCrashReport: PropTypes.bool,
|
||||||
|
allowAnalyticsEvents: PropTypes.bool,
|
||||||
|
e2eEnabled: PropTypes.bool,
|
||||||
|
toggleCrashReport: PropTypes.func,
|
||||||
|
toggleAnalyticsEvents: PropTypes.func,
|
||||||
|
user: PropTypes.shape({
|
||||||
|
roles: PropTypes.array,
|
||||||
|
id: PropTypes.string
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleCrashReport = (value) => {
|
||||||
|
logEvent(events.SE_TOGGLE_CRASH_REPORT);
|
||||||
|
AsyncStorage.setItem(CRASH_REPORT_KEY, JSON.stringify(value));
|
||||||
|
const { toggleCrashReport } = this.props;
|
||||||
|
toggleCrashReport(value);
|
||||||
|
if (!isFDroidBuild) {
|
||||||
|
loggerConfig.autoNotify = value;
|
||||||
|
if (value) {
|
||||||
|
loggerConfig.clearBeforeSendCallbacks();
|
||||||
|
} else {
|
||||||
|
loggerConfig.registerBeforeSendCallback(() => false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleAnalyticsEvents = (value) => {
|
||||||
|
logEvent(events.SE_TOGGLE_ANALYTICS_EVENTS);
|
||||||
|
const { toggleAnalyticsEvents } = this.props;
|
||||||
|
AsyncStorage.setItem(ANALYTICS_EVENTS_KEY, JSON.stringify(value));
|
||||||
|
toggleAnalyticsEvents(value);
|
||||||
|
analytics().setAnalyticsCollectionEnabled(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
navigateToScreen = (screen) => {
|
||||||
|
logEvent(events[`SP_GO_${ screen.replace('View', '').toUpperCase() }`]);
|
||||||
|
const { navigation } = this.props;
|
||||||
|
navigation.navigate(screen);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderCrashReportSwitch = () => {
|
||||||
|
const { allowCrashReport } = this.props;
|
||||||
|
return (
|
||||||
|
<Switch
|
||||||
|
value={allowCrashReport}
|
||||||
|
trackColor={SWITCH_TRACK_COLOR}
|
||||||
|
onValueChange={this.toggleCrashReport}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderAnalyticsEventsSwitch = () => {
|
||||||
|
const { allowAnalyticsEvents } = this.props;
|
||||||
|
return (
|
||||||
|
<Switch
|
||||||
|
value={allowAnalyticsEvents}
|
||||||
|
trackColor={SWITCH_TRACK_COLOR}
|
||||||
|
onValueChange={this.toggleAnalyticsEvents}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { e2eEnabled } = this.props;
|
||||||
|
return (
|
||||||
|
<SafeAreaView testID='security-privacy-view'>
|
||||||
|
<StatusBar />
|
||||||
|
<List.Container testID='security-privacy-view-list'>
|
||||||
|
<List.Section>
|
||||||
|
<List.Separator />
|
||||||
|
{e2eEnabled
|
||||||
|
? (
|
||||||
|
<>
|
||||||
|
<List.Item
|
||||||
|
title='E2E_Encryption'
|
||||||
|
showActionIndicator
|
||||||
|
onPress={() => this.navigateToScreen('E2EEncryptionSecurityView')}
|
||||||
|
testID='security-privacy-view-e2e-encryption'
|
||||||
|
/>
|
||||||
|
<List.Separator />
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
<List.Item
|
||||||
|
title='Screen_lock'
|
||||||
|
showActionIndicator
|
||||||
|
onPress={() => this.navigateToScreen('ScreenLockConfigView')}
|
||||||
|
testID='security-privacy-view-screen-lock'
|
||||||
|
/>
|
||||||
|
<List.Separator />
|
||||||
|
</List.Section>
|
||||||
|
|
||||||
|
{!isFDroidBuild ? (
|
||||||
|
<>
|
||||||
|
<List.Section>
|
||||||
|
<List.Separator />
|
||||||
|
<List.Item
|
||||||
|
title='Log_analytics_events'
|
||||||
|
testID='security-privacy-view-analytics-events'
|
||||||
|
right={() => this.renderAnalyticsEventsSwitch()}
|
||||||
|
/>
|
||||||
|
<List.Separator />
|
||||||
|
<List.Item
|
||||||
|
title='Send_crash_report'
|
||||||
|
testID='security-privacy-view-crash-report'
|
||||||
|
right={() => this.renderCrashReportSwitch()}
|
||||||
|
/>
|
||||||
|
<List.Separator />
|
||||||
|
<List.Info info='Crash_report_disclaimer' />
|
||||||
|
</List.Section>
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
|
</List.Container>
|
||||||
|
</SafeAreaView>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapStateToProps = state => ({
|
||||||
|
user: getUserSelector(state),
|
||||||
|
allowCrashReport: state.crashReport.allowCrashReport,
|
||||||
|
allowAnalyticsEvents: state.crashReport.allowAnalyticsEvents,
|
||||||
|
e2eEnabled: state.settings.E2E_Enable
|
||||||
|
});
|
||||||
|
|
||||||
|
const mapDispatchToProps = dispatch => ({
|
||||||
|
toggleCrashReport: params => dispatch(toggleCrashReportAction(params)),
|
||||||
|
toggleAnalyticsEvents: params => dispatch(toggleAnalyticsEventsAction(params))
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(SecurityPrivacyView);
|
|
@ -1,30 +1,26 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {
|
import {
|
||||||
Linking, Switch, Share, Clipboard
|
Linking, Share, Clipboard
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import AsyncStorage from '@react-native-community/async-storage';
|
|
||||||
import FastImage from '@rocket.chat/react-native-fast-image';
|
import FastImage from '@rocket.chat/react-native-fast-image';
|
||||||
import CookieManager from '@react-native-community/cookies';
|
import CookieManager from '@react-native-community/cookies';
|
||||||
|
|
||||||
import { logout as logoutAction } from '../../actions/login';
|
import { logout as logoutAction } from '../../actions/login';
|
||||||
import { selectServerRequest as selectServerRequestAction } from '../../actions/server';
|
import { selectServerRequest as selectServerRequestAction } from '../../actions/server';
|
||||||
import { toggleCrashReport as toggleCrashReportAction, toggleAnalyticsEvents as toggleAnalyticsEventsAction } from '../../actions/crashReport';
|
import { themes } from '../../constants/colors';
|
||||||
import { SWITCH_TRACK_COLOR, themes } from '../../constants/colors';
|
|
||||||
import * as HeaderButton from '../../containers/HeaderButton';
|
import * as HeaderButton from '../../containers/HeaderButton';
|
||||||
import StatusBar from '../../containers/StatusBar';
|
import StatusBar from '../../containers/StatusBar';
|
||||||
import * as List from '../../containers/List';
|
import * as List from '../../containers/List';
|
||||||
import I18n from '../../i18n';
|
import I18n from '../../i18n';
|
||||||
import RocketChat, { CRASH_REPORT_KEY, ANALYTICS_EVENTS_KEY } from '../../lib/rocketchat';
|
import RocketChat from '../../lib/rocketchat';
|
||||||
import {
|
import {
|
||||||
getReadableVersion, getDeviceModel, isAndroid
|
getReadableVersion, getDeviceModel, isAndroid
|
||||||
} from '../../utils/deviceInfo';
|
} from '../../utils/deviceInfo';
|
||||||
import openLink from '../../utils/openLink';
|
import openLink from '../../utils/openLink';
|
||||||
import { showErrorAlert, showConfirmationAlert } from '../../utils/info';
|
import { showErrorAlert, showConfirmationAlert } from '../../utils/info';
|
||||||
import {
|
import { logEvent, events } from '../../utils/log';
|
||||||
loggerConfig, analytics, logEvent, events
|
|
||||||
} from '../../utils/log';
|
|
||||||
import {
|
import {
|
||||||
PLAY_MARKET_LINK, FDROID_MARKET_LINK, APP_STORE_LINK, LICENSE_LINK
|
PLAY_MARKET_LINK, FDROID_MARKET_LINK, APP_STORE_LINK, LICENSE_LINK
|
||||||
} from '../../constants/links';
|
} from '../../constants/links';
|
||||||
|
@ -44,7 +40,7 @@ class SettingsView extends React.Component {
|
||||||
headerLeft: () => (isMasterDetail ? (
|
headerLeft: () => (isMasterDetail ? (
|
||||||
<HeaderButton.CloseModal navigation={navigation} testID='settings-view-close' />
|
<HeaderButton.CloseModal navigation={navigation} testID='settings-view-close' />
|
||||||
) : (
|
) : (
|
||||||
<HeaderButton.Drawer navigation={navigation} />
|
<HeaderButton.Drawer navigation={navigation} testID='settings-view-drawer' />
|
||||||
)),
|
)),
|
||||||
title: I18n.t('Settings')
|
title: I18n.t('Settings')
|
||||||
});
|
});
|
||||||
|
@ -52,10 +48,6 @@ class SettingsView extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
navigation: PropTypes.object,
|
navigation: PropTypes.object,
|
||||||
server: PropTypes.object,
|
server: PropTypes.object,
|
||||||
allowCrashReport: PropTypes.bool,
|
|
||||||
allowAnalyticsEvents: PropTypes.bool,
|
|
||||||
toggleCrashReport: PropTypes.func,
|
|
||||||
toggleAnalyticsEvents: PropTypes.func,
|
|
||||||
theme: PropTypes.string,
|
theme: PropTypes.string,
|
||||||
isMasterDetail: PropTypes.bool,
|
isMasterDetail: PropTypes.bool,
|
||||||
logout: PropTypes.func.isRequired,
|
logout: PropTypes.func.isRequired,
|
||||||
|
@ -122,29 +114,6 @@ class SettingsView extends React.Component {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleCrashReport = (value) => {
|
|
||||||
logEvent(events.SE_TOGGLE_CRASH_REPORT);
|
|
||||||
AsyncStorage.setItem(CRASH_REPORT_KEY, JSON.stringify(value));
|
|
||||||
const { toggleCrashReport } = this.props;
|
|
||||||
toggleCrashReport(value);
|
|
||||||
if (!isFDroidBuild) {
|
|
||||||
loggerConfig.autoNotify = value;
|
|
||||||
if (value) {
|
|
||||||
loggerConfig.clearBeforeSendCallbacks();
|
|
||||||
} else {
|
|
||||||
loggerConfig.registerBeforeSendCallback(() => false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleAnalyticsEvents = (value) => {
|
|
||||||
logEvent(events.SE_TOGGLE_ANALYTICS_EVENTS);
|
|
||||||
const { toggleAnalyticsEvents } = this.props;
|
|
||||||
AsyncStorage.setItem(ANALYTICS_EVENTS_KEY, JSON.stringify(value));
|
|
||||||
toggleAnalyticsEvents(value);
|
|
||||||
analytics().setAnalyticsCollectionEnabled(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
navigateToScreen = (screen) => {
|
navigateToScreen = (screen) => {
|
||||||
logEvent(events[`SE_GO_${ screen.replace('View', '').toUpperCase() }`]);
|
logEvent(events[`SE_GO_${ screen.replace('View', '').toUpperCase() }`]);
|
||||||
const { navigation } = this.props;
|
const { navigation } = this.props;
|
||||||
|
@ -202,28 +171,6 @@ class SettingsView extends React.Component {
|
||||||
openLink(LICENSE_LINK, theme);
|
openLink(LICENSE_LINK, theme);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderCrashReportSwitch = () => {
|
|
||||||
const { allowCrashReport } = this.props;
|
|
||||||
return (
|
|
||||||
<Switch
|
|
||||||
value={allowCrashReport}
|
|
||||||
trackColor={SWITCH_TRACK_COLOR}
|
|
||||||
onValueChange={this.toggleCrashReport}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
renderAnalyticsEventsSwitch = () => {
|
|
||||||
const { allowAnalyticsEvents } = this.props;
|
|
||||||
return (
|
|
||||||
<Switch
|
|
||||||
value={allowAnalyticsEvents}
|
|
||||||
trackColor={SWITCH_TRACK_COLOR}
|
|
||||||
onValueChange={this.toggleAnalyticsEvents}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { server, isMasterDetail, theme } = this.props;
|
const { server, isMasterDetail, theme } = this.props;
|
||||||
return (
|
return (
|
||||||
|
@ -299,9 +246,10 @@ class SettingsView extends React.Component {
|
||||||
/>
|
/>
|
||||||
<List.Separator />
|
<List.Separator />
|
||||||
<List.Item
|
<List.Item
|
||||||
title='Screen_lock'
|
title='Security_and_privacy'
|
||||||
showActionIndicator
|
showActionIndicator
|
||||||
onPress={() => this.navigateToScreen('ScreenLockConfigView')}
|
onPress={() => this.navigateToScreen('SecurityPrivacyView')}
|
||||||
|
testID='settings-view-security-privacy'
|
||||||
/>
|
/>
|
||||||
<List.Separator />
|
<List.Separator />
|
||||||
</List.Section>
|
</List.Section>
|
||||||
|
@ -333,27 +281,6 @@ class SettingsView extends React.Component {
|
||||||
<List.Separator />
|
<List.Separator />
|
||||||
</List.Section>
|
</List.Section>
|
||||||
|
|
||||||
{!isFDroidBuild ? (
|
|
||||||
<>
|
|
||||||
<List.Section>
|
|
||||||
<List.Separator />
|
|
||||||
<List.Item
|
|
||||||
title='Log_analytics_events'
|
|
||||||
testID='settings-view-analytics-events'
|
|
||||||
right={() => this.renderAnalyticsEventsSwitch()}
|
|
||||||
/>
|
|
||||||
<List.Separator />
|
|
||||||
<List.Item
|
|
||||||
title='Send_crash_report'
|
|
||||||
testID='settings-view-crash-report'
|
|
||||||
right={() => this.renderCrashReportSwitch()}
|
|
||||||
/>
|
|
||||||
<List.Separator />
|
|
||||||
<List.Info info='Crash_report_disclaimer' />
|
|
||||||
</List.Section>
|
|
||||||
</>
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
<List.Section>
|
<List.Section>
|
||||||
<List.Separator />
|
<List.Separator />
|
||||||
<List.Item
|
<List.Item
|
||||||
|
@ -382,16 +309,12 @@ class SettingsView extends React.Component {
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
server: state.server,
|
server: state.server,
|
||||||
user: getUserSelector(state),
|
user: getUserSelector(state),
|
||||||
allowCrashReport: state.crashReport.allowCrashReport,
|
|
||||||
allowAnalyticsEvents: state.crashReport.allowAnalyticsEvents,
|
|
||||||
isMasterDetail: state.app.isMasterDetail
|
isMasterDetail: state.app.isMasterDetail
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapDispatchToProps = dispatch => ({
|
const mapDispatchToProps = dispatch => ({
|
||||||
logout: () => dispatch(logoutAction()),
|
logout: () => dispatch(logoutAction()),
|
||||||
selectServerRequest: params => dispatch(selectServerRequestAction(params)),
|
selectServerRequest: params => dispatch(selectServerRequestAction(params)),
|
||||||
toggleCrashReport: params => dispatch(toggleCrashReportAction(params)),
|
|
||||||
toggleAnalyticsEvents: params => dispatch(toggleAnalyticsEventsAction(params)),
|
|
||||||
appStart: params => dispatch(appStartAction(params))
|
appStart: params => dispatch(appStartAction(params))
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,198 @@
|
||||||
|
const {
|
||||||
|
expect, element, by, waitFor
|
||||||
|
} = require('detox');
|
||||||
|
const { navigateToLogin, login, sleep, tapBack, mockMessage, searchRoom, logout } = require('../../helpers/app');
|
||||||
|
|
||||||
|
const data = require('../../data');
|
||||||
|
|
||||||
|
const testuser = data.users.regular
|
||||||
|
const otheruser = data.users.alternate
|
||||||
|
|
||||||
|
async function navigateToRoom(roomName) {
|
||||||
|
await searchRoom(`${ roomName }`);
|
||||||
|
await waitFor(element(by.id(`rooms-list-view-item-${ roomName }`))).toExist().withTimeout(60000);
|
||||||
|
await element(by.id(`rooms-list-view-item-${ roomName }`)).tap();
|
||||||
|
await waitFor(element(by.id('room-view'))).toBeVisible().withTimeout(5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function waitForToast() {
|
||||||
|
await sleep(300);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function navigateSecurityPrivacy() {
|
||||||
|
await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(2000);
|
||||||
|
await element(by.id('rooms-list-view-sidebar')).tap();
|
||||||
|
await waitFor(element(by.id('sidebar-view'))).toBeVisible().withTimeout(2000);
|
||||||
|
await waitFor(element(by.id('sidebar-settings'))).toBeVisible().withTimeout(2000);
|
||||||
|
await element(by.id('sidebar-settings')).tap();
|
||||||
|
await waitFor(element(by.id('settings-view'))).toBeVisible().withTimeout(2000);
|
||||||
|
await element(by.id('settings-view-security-privacy')).tap();
|
||||||
|
await waitFor(element(by.id('security-privacy-view'))).toBeVisible().withTimeout(2000);
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('E2E Encryption', () => {
|
||||||
|
const room = `encrypted${ data.random }`;
|
||||||
|
const newPassword = 'abc';
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
await device.launchApp({ permissions: { notifications: 'YES' }, delete: true });
|
||||||
|
await navigateToLogin();
|
||||||
|
await login(testuser.username, testuser.password);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Banner', async() => {
|
||||||
|
describe('Render', async () => {
|
||||||
|
it('should have encryption badge', async () => {
|
||||||
|
await waitFor(element(by.id('listheader-encryption').withDescendant(by.label('Save Your Encryption Password')))).toBeVisible().withTimeout(10000);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Usage', async () => {
|
||||||
|
it('should tap encryption badge and open save password modal', async() => {
|
||||||
|
await element(by.id('listheader-encryption')).tap();
|
||||||
|
await waitFor(element(by.id('e2e-save-password-view'))).toBeVisible().withTimeout(2000);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should tap "How it works" and navigate', async() => {
|
||||||
|
await element(by.id('e2e-save-password-view-how-it-works').and(by.label('How It Works'))).tap();
|
||||||
|
await waitFor(element(by.id('e2e-how-it-works-view'))).toBeVisible().withTimeout(2000);
|
||||||
|
await tapBack();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should tap "Save my password" and close modal', async() => {
|
||||||
|
await element(by.id('e2e-save-password-view-saved-password').and(by.label('I Saved My E2E Password'))).tap();
|
||||||
|
await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(2000);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create encrypted room', async() => {
|
||||||
|
await element(by.id('rooms-list-view-create-channel')).tap();
|
||||||
|
await waitFor(element(by.id('new-message-view'))).toBeVisible().withTimeout(2000);
|
||||||
|
await element(by.id('new-message-view-create-channel')).tap();
|
||||||
|
await waitFor(element(by.id('select-users-view'))).toBeVisible().withTimeout(2000);
|
||||||
|
await element(by.id('select-users-view-search')).replaceText(otheruser.username);
|
||||||
|
await waitFor(element(by.id(`select-users-view-item-${ otheruser.username }`))).toBeVisible().withTimeout(60000);
|
||||||
|
await element(by.id(`select-users-view-item-${ otheruser.username }`)).tap();
|
||||||
|
await waitFor(element(by.id(`selected-user-${ otheruser.username }`))).toBeVisible().withTimeout(5000);
|
||||||
|
await element(by.id('selected-users-view-submit')).tap();
|
||||||
|
await waitFor(element(by.id('create-channel-view'))).toExist().withTimeout(5000);
|
||||||
|
await element(by.id('create-channel-name')).replaceText(room);
|
||||||
|
await element(by.id('create-channel-encrypted')).longPress();
|
||||||
|
await element(by.id('create-channel-submit')).tap();
|
||||||
|
await waitFor(element(by.id('room-view'))).toBeVisible().withTimeout(60000);
|
||||||
|
await waitFor(element(by.id(`room-view-title-${ room }`))).toBeVisible().withTimeout(60000);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should send message and be able to read it', async() => {
|
||||||
|
await mockMessage('message');
|
||||||
|
await tapBack();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('Security and Privacy', async() => {
|
||||||
|
it('should navigate to security privacy', async() => {
|
||||||
|
await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(2000);
|
||||||
|
await element(by.id('rooms-list-view-sidebar')).tap();
|
||||||
|
await waitFor(element(by.id('sidebar-view'))).toBeVisible().withTimeout(2000);
|
||||||
|
await waitFor(element(by.id('sidebar-settings'))).toBeVisible().withTimeout(2000);
|
||||||
|
await element(by.id('sidebar-settings')).tap();
|
||||||
|
await waitFor(element(by.id('settings-view'))).toBeVisible().withTimeout(2000);
|
||||||
|
await element(by.id('settings-view-security-privacy')).tap();
|
||||||
|
await waitFor(element(by.id('security-privacy-view'))).toBeVisible().withTimeout(2000);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('render', async() => {
|
||||||
|
await expect(element(by.id('security-privacy-view-e2e-encryption'))).toExist();
|
||||||
|
await expect(element(by.id('security-privacy-view-screen-lock'))).toExist();
|
||||||
|
await expect(element(by.id('security-privacy-view-analytics-events'))).toExist();
|
||||||
|
await expect(element(by.id('security-privacy-view-crash-report'))).toExist();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('E2E Encryption Security', async() => {
|
||||||
|
it('should navigate to e2e encryption security', async() => {
|
||||||
|
await element(by.id('security-privacy-view-e2e-encryption')).tap();
|
||||||
|
await waitFor(element(by.id('e2e-encryption-security-view'))).toBeVisible().withTimeout(2000);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Render', () => {
|
||||||
|
it('should have items', async() => {
|
||||||
|
await waitFor(element(by.id('e2e-encryption-security-view'))).toBeVisible().withTimeout(2000);
|
||||||
|
await expect(element(by.id('e2e-encryption-security-view-password'))).toExist();
|
||||||
|
await expect(element(by.id('e2e-encryption-security-view-change-password').and(by.label('Save Changes')))).toExist();
|
||||||
|
await expect(element(by.id('e2e-encryption-security-view-reset-key').and(by.label('Reset E2E Key')))).toExist();
|
||||||
|
});
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('Change password', async() => {
|
||||||
|
it('should change password', async() => {
|
||||||
|
await element(by.id('e2e-encryption-security-view-password')).typeText(newPassword);
|
||||||
|
await element(by.id('e2e-encryption-security-view-change-password')).tap();
|
||||||
|
await waitFor(element(by.text('Are you sure?'))).toExist().withTimeout(2000);
|
||||||
|
await expect(element(by.text('Make sure you\'ve saved it carefully somewhere else.'))).toExist();
|
||||||
|
await element(by.label('Yes, change it').and(by.type('_UIAlertControllerActionView'))).tap();
|
||||||
|
await waitForToast();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should navigate to the room and messages should remain decrypted', async() => {
|
||||||
|
await waitFor(element(by.id('e2e-encryption-security-view'))).toBeVisible().withTimeout(2000);
|
||||||
|
await tapBack();
|
||||||
|
await waitFor(element(by.id('security-privacy-view'))).toBeVisible().withTimeout(2000);
|
||||||
|
await tapBack();
|
||||||
|
await waitFor(element(by.id('settings-view'))).toBeVisible().withTimeout(2000);
|
||||||
|
await element(by.id('settings-view-drawer')).tap();
|
||||||
|
await waitFor(element(by.id('sidebar-view'))).toBeVisible().withTimeout(2000);
|
||||||
|
await element(by.id('sidebar-chats')).tap();
|
||||||
|
await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(2000);
|
||||||
|
await navigateToRoom(room);
|
||||||
|
await waitFor(element(by.label(`${ data.random }message`)).atIndex(0)).toExist().withTimeout(2000);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should logout, login and messages should be encrypted', async() => {
|
||||||
|
await tapBack();
|
||||||
|
await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(2000);
|
||||||
|
await logout();
|
||||||
|
await navigateToLogin();
|
||||||
|
await login(testuser.username, testuser.password);
|
||||||
|
await navigateToRoom(room);
|
||||||
|
await waitFor(element(by.label(`${ data.random }message`)).atIndex(0)).toNotExist().withTimeout(2000);
|
||||||
|
await expect(element(by.label('Encrypted message')).atIndex(0)).toExist();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should enter new e2e password and messages should be decrypted', async() => {
|
||||||
|
await tapBack();
|
||||||
|
await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(2000);
|
||||||
|
await waitFor(element(by.id('listheader-encryption').withDescendant(by.label('Enter Your E2E Password')))).toBeVisible().withTimeout(2000);
|
||||||
|
await element(by.id('listheader-encryption').withDescendant(by.label('Enter Your E2E Password'))).tap();
|
||||||
|
await waitFor(element(by.id('e2e-enter-your-password-view'))).toBeVisible().withTimeout(2000);
|
||||||
|
await element(by.id('e2e-enter-your-password-view-password')).typeText(newPassword);
|
||||||
|
await element(by.id('e2e-enter-your-password-view-confirm')).tap();
|
||||||
|
await waitFor(element(by.id('listheader-encryption'))).toNotExist().withTimeout(10000);
|
||||||
|
await navigateToRoom(room);
|
||||||
|
await waitFor(element(by.label(`${ data.random }message`)).atIndex(0)).toExist().withTimeout(2000);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Reset E2E key', async() => {
|
||||||
|
it('should reset e2e key', async() => {
|
||||||
|
await tapBack();
|
||||||
|
await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(2000);
|
||||||
|
await navigateSecurityPrivacy();
|
||||||
|
await element(by.id('security-privacy-view-e2e-encryption')).tap();
|
||||||
|
await waitFor(element(by.id('e2e-encryption-security-view'))).toBeVisible().withTimeout(2000);
|
||||||
|
await element(by.id('e2e-encryption-security-view-reset-key').and(by.label('Reset E2E Key'))).tap();
|
||||||
|
await waitFor(element(by.text('Are you sure?'))).toExist().withTimeout(2000);
|
||||||
|
await expect(element(by.text('You\'re going to be logged out.'))).toExist();
|
||||||
|
await element(by.label('Yes, reset it').and(by.type('UILabel'))).tap();
|
||||||
|
await sleep(2000)
|
||||||
|
await waitFor(element(by.id('workspace-view'))).toBeVisible().withTimeout(10000);
|
||||||
|
await waitFor(element(by.text('You\'ve been logged out by the server. Please log in again.'))).toExist().withTimeout(2000);
|
||||||
|
await element(by.label('OK').and(by.type('_UIAlertControllerActionView'))).tap();
|
||||||
|
await element(by.id('workspace-view-login')).tap();
|
||||||
|
await waitFor(element(by.id('login-view'))).toBeVisible().withTimeout(2000);
|
||||||
|
await login(testuser.username, testuser.password);
|
||||||
|
await waitFor(element(by.id('listheader-encryption').withDescendant(by.label('Save Your Encryption Password')))).toBeVisible().withTimeout(2000);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -30,14 +30,26 @@ describe('Settings screen', () => {
|
||||||
await expect(element(by.id('settings-view-language'))).toExist();
|
await expect(element(by.id('settings-view-language'))).toExist();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should have theme', async() => {
|
it('should have review app', async() => {
|
||||||
await expect(element(by.id('settings-view-theme'))).toExist();
|
await expect(element(by.id('settings-view-review-app'))).toExist();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should have share app', async() => {
|
it('should have share app', async() => {
|
||||||
await expect(element(by.id('settings-view-share-app'))).toExist();
|
await expect(element(by.id('settings-view-share-app'))).toExist();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should have default browser', async() => {
|
||||||
|
await expect(element(by.id('settings-view-default-browser'))).toExist();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have theme', async() => {
|
||||||
|
await expect(element(by.id('settings-view-theme'))).toExist();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have security and privacy', async() => {
|
||||||
|
await expect(element(by.id('settings-view-security-privacy'))).toExist();
|
||||||
|
});
|
||||||
|
|
||||||
it('should have licence', async() => {
|
it('should have licence', async() => {
|
||||||
await expect(element(by.id('settings-view-license'))).toExist();
|
await expect(element(by.id('settings-view-license'))).toExist();
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue