diff --git a/app/containers/TwoFactor/index.js b/app/containers/TwoFactor/index.js
new file mode 100644
index 000000000..9d1d9daab
--- /dev/null
+++ b/app/containers/TwoFactor/index.js
@@ -0,0 +1,137 @@
+import React, { useEffect, useState } from 'react';
+import { View, Text } from 'react-native';
+import _ from 'lodash';
+import PropTypes from 'prop-types';
+import { sha256 } from 'js-sha256';
+import Modal from 'react-native-modal';
+import useDeepCompareEffect from 'use-deep-compare-effect';
+
+import TextInput from '../TextInput';
+import I18n from '../../i18n';
+import EventEmitter from '../../utils/events';
+import { withTheme } from '../../theme';
+import { withSplit } from '../../split';
+import { themes } from '../../constants/colors';
+import Button from '../Button';
+import sharedStyles from '../../views/Styles';
+import RocketChat from '../../lib/rocketchat';
+import styles from './styles';
+
+export const TWO_FACTOR = 'TWO_FACTOR';
+
+const methods = {
+ totp: {
+ text: 'Open_your_authentication_app_and_enter_the_code',
+ keyboardType: 'numeric'
+ },
+ email: {
+ text: 'Verify_your_email_for_the_code_we_sent',
+ keyboardType: 'numeric'
+ },
+ password: {
+ title: 'Please_enter_your_password',
+ text: 'For_your_security_you_must_enter_your_current_password_to_continue',
+ secureTextEntry: true,
+ keyboardType: 'default'
+ }
+};
+
+const TwoFactor = React.memo(({ theme, split }) => {
+ const [visible, setVisible] = useState(false);
+ const [data, setData] = useState({});
+ const [code, setCode] = useState('');
+
+ const method = methods[data.method];
+ const isEmail = data.method === 'email';
+
+ const sendEmail = () => RocketChat.sendEmailCode();
+
+ useDeepCompareEffect(() => {
+ if (!_.isEmpty(data)) {
+ setVisible(true);
+ } else {
+ setVisible(false);
+ }
+ }, [data]);
+
+ const showTwoFactor = args => setData(args);
+
+ useEffect(() => {
+ EventEmitter.addEventListener(TWO_FACTOR, showTwoFactor);
+
+ return () => EventEmitter.removeListener(TWO_FACTOR);
+ }, []);
+
+ const onCancel = () => {
+ const { cancel } = data;
+ if (cancel) {
+ cancel();
+ }
+ setData({});
+ };
+
+ const onSubmit = () => {
+ const { submit } = data;
+ if (submit) {
+ if (data.method === 'password') {
+ submit(sha256(code));
+ } else {
+ submit(code);
+ }
+ }
+ setData({});
+ };
+
+ const color = themes[theme].titleText;
+ return (
+
+
+
+ {I18n.t(method?.title || 'Two_Factor_Authentication')}
+ {I18n.t(method?.text)}
+
+ {isEmail && {I18n.t('Send_me_the_code_again')}}
+
+
+
+
+
+
+
+ );
+});
+TwoFactor.propTypes = {
+ theme: PropTypes.string,
+ split: PropTypes.bool
+};
+
+export default withSplit(withTheme(TwoFactor));
diff --git a/app/containers/TwoFactor/styles.js b/app/containers/TwoFactor/styles.js
new file mode 100644
index 000000000..5e85cc364
--- /dev/null
+++ b/app/containers/TwoFactor/styles.js
@@ -0,0 +1,40 @@
+import { StyleSheet } from 'react-native';
+
+import sharedStyles from '../../views/Styles';
+
+export default StyleSheet.create({
+ container: {
+ flex: 1,
+ justifyContent: 'center',
+ alignItems: 'center'
+ },
+ content: {
+ padding: 16,
+ width: '100%',
+ borderRadius: 4
+ },
+ title: {
+ fontSize: 14,
+ ...sharedStyles.textBold
+ },
+ subtitle: {
+ fontSize: 14,
+ paddingVertical: 8,
+ ...sharedStyles.textRegular,
+ ...sharedStyles.textAlignCenter
+ },
+ sendEmail: {
+ fontSize: 14,
+ paddingBottom: 24,
+ paddingTop: 8,
+ alignSelf: 'center',
+ ...sharedStyles.textRegular
+ },
+ button: {
+ marginBottom: 0
+ },
+ buttonContainer: {
+ flexDirection: 'row',
+ justifyContent: 'space-between'
+ }
+});
diff --git a/app/i18n/locales/en.js b/app/i18n/locales/en.js
index ba77ab0aa..9c0807ed2 100644
--- a/app/i18n/locales/en.js
+++ b/app/i18n/locales/en.js
@@ -138,6 +138,7 @@ export default {
Choose_file: 'Choose file',
Choose_where_you_want_links_be_opened: 'Choose where you want links be opened',
Code: 'Code',
+ Code_or_password_invalid: 'Code or password invalid',
Collaborative: 'Collaborative',
Confirm: 'Confirm',
Connect: 'Connect',
@@ -330,6 +331,7 @@ export default {
Only_authorized_users_can_write_new_messages: 'Only authorized users can write new messages',
Open_emoji_selector: 'Open emoji selector',
Open_Source_Communication: 'Open Source Communication',
+ Open_your_authentication_app_and_enter_the_code: 'Open your authentication app and enter the code.',
OR: 'OR',
Overwrites_the_server_configuration_and_use_room_config: 'Overwrites the server configuration and use room config',
Password: 'Password',
@@ -416,6 +418,7 @@ export default {
Send_audio_message: 'Send audio message',
Send_crash_report: 'Send crash report',
Send_message: 'Send message',
+ Send_me_the_code_again: 'Send me the code again',
Send_to: 'Send to...',
Sent_an_attachment: 'Sent an attachment',
Server: 'Server',
@@ -499,8 +502,10 @@ export default {
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',
+ Verify: 'Verify',
Verify_email_title: 'Registration Succeeded!',
Verify_email_desc: 'We have sent you an email to confirm your registration. If you do not receive an email shortly, please come back and try again.',
+ Verify_your_email_for_the_code_we_sent: 'Verify your email for the code we sent',
Video_call: 'Video call',
View_Original: 'View Original',
Voice_call: 'Voice call',
diff --git a/app/i18n/locales/pt-BR.js b/app/i18n/locales/pt-BR.js
index 768fdbf31..5ba5a46db 100644
--- a/app/i18n/locales/pt-BR.js
+++ b/app/i18n/locales/pt-BR.js
@@ -140,6 +140,7 @@ export default {
Choose_file: 'Enviar arquivo',
Choose_where_you_want_links_be_opened: 'Escolha onde deseja que os links sejam abertos',
Code: 'Código',
+ Code_or_password_invalid: 'Código ou senha inválido',
Collaborative: 'Colaborativo',
Confirm: 'Confirmar',
Connect: 'Conectar',
@@ -302,6 +303,7 @@ export default {
Only_authorized_users_can_write_new_messages: 'Somente usuários autorizados podem escrever novas mensagens',
Open_emoji_selector: 'Abrir seletor de emoji',
Open_Source_Communication: 'Comunicação Open Source',
+ Open_your_authentication_app_and_enter_the_code: 'Abra seu aplicativo de autenticação e digite o código.',
OR: 'OU',
Overwrites_the_server_configuration_and_use_room_config: 'Substituir a configuração do servidor e usar a configuração da sala',
Password: 'Senha',
@@ -378,6 +380,7 @@ export default {
Send: 'Enviar',
Send_audio_message: 'Enviar mensagem de áudio',
Send_message: 'Enviar mensagem',
+ Send_me_the_code_again: 'Envie-me o código novamente',
Send_to: 'Enviar para...',
Sent_an_attachment: 'Enviou um anexo',
Server: 'Servidor',
@@ -446,8 +449,10 @@ export default {
Username_or_email: 'Usuário ou email',
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_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_your_email_for_the_code_we_sent: 'Verifique em seu e-mail o código que enviamos',
Video_call: 'Chamada de vídeo',
Voice_call: 'Chamada de voz',
Websocket_disabled: 'Websocket está desativado para esse servidor.\n{{contact}}',
diff --git a/app/index.js b/app/index.js
index 9efe0e600..9a10476b7 100644
--- a/app/index.js
+++ b/app/index.js
@@ -42,6 +42,7 @@ import { KEY_COMMAND } from './commands';
import Tablet, { initTabletNav } from './tablet';
import sharedStyles from './views/Styles';
import { SplitContext } from './split';
+import TwoFactor from './containers/TwoFactor';
import RoomsListView from './views/RoomsListView';
import RoomView from './views/RoomView';
@@ -721,6 +722,7 @@ export default class Root extends React.Component {
}}
>
{content}
+
diff --git a/app/lib/rocketchat.js b/app/lib/rocketchat.js
index 164d57306..1604880af 100644
--- a/app/lib/rocketchat.js
+++ b/app/lib/rocketchat.js
@@ -48,6 +48,7 @@ import { getDeviceToken } from '../notifications/push';
import { SERVERS, SERVER_URL } from '../constants/userDefaults';
import { setActiveUsers } from '../actions/activeUsers';
import I18n from '../i18n';
+import { twoFactor } from '../utils/twoFactor';
const TOKEN_KEY = 'reactnativemeteor_usertoken';
const SORT_PREFS_KEY = 'RC_SORT_PREFS_KEY';
@@ -76,7 +77,7 @@ const RocketChat = {
name, users, type, readOnly, broadcast
}) {
// RC 0.51.0
- return this.sdk.methodCall(type ? 'createPrivateGroup' : 'createChannel', name, users, readOnly, {}, { broadcast });
+ return this.methodCall(type ? 'createPrivateGroup' : 'createChannel', name, users, readOnly, {}, { broadcast });
},
async getUserToken() {
try {
@@ -195,6 +196,10 @@ const RocketChat = {
this.sdk = null;
}
+ if (this.code) {
+ this.code = null;
+ }
+
// Use useSsl: false only if server url starts with http://
const useSsl = !/http:\/\//.test(server);
@@ -304,25 +309,47 @@ const RocketChat = {
},
updateJitsiTimeout(rid) {
- return this.sdk.methodCall('jitsi:updateTimeout', rid);
+ return this.methodCall('jitsi:updateTimeout', rid);
},
register(credentials) {
// RC 0.50.0
- return this.sdk.post('users.register', credentials, false);
+ return this.post('users.register', credentials, false);
},
setUsername(username) {
// RC 0.51.0
- return this.sdk.methodCall('setUsername', username);
+ return this.methodCall('setUsername', username);
},
forgotPassword(email) {
// RC 0.64.0
- return this.sdk.post('users.forgotPassword', { email }, false);
+ return this.post('users.forgotPassword', { email }, false);
},
- async loginWithPassword({ user, password, code }) {
+ loginTOTP(params) {
+ return new Promise(async(resolve, reject) => {
+ try {
+ const result = await this.login(params);
+ return resolve(result);
+ } catch (e) {
+ if (e.data?.error && (e.data.error === 'totp-required' || e.data.error === 'totp-invalid')) {
+ const { details } = e.data;
+ try {
+ await twoFactor({ method: details?.method, invalid: e.data.error === 'totp-invalid' });
+ return resolve(this.loginTOTP(params));
+ } catch {
+ // twoFactor was canceled
+ return reject();
+ }
+ } else {
+ reject(e);
+ }
+ }
+ });
+ },
+
+ loginWithPassword({ user, password }) {
let params = { user, password };
const state = reduxStore.getState();
@@ -341,16 +368,8 @@ const RocketChat = {
};
}
- if (code) {
- params = {
- user,
- password,
- code
- };
- }
-
try {
- return await this.login(params);
+ return this.loginTOTP(params);
} catch (error) {
throw error;
}
@@ -487,7 +506,7 @@ const RocketChat = {
};
try {
// RC 0.60.0
- await this.sdk.post('push.token', data);
+ await this.post('push.token', data);
} catch (error) {
console.log(error);
}
@@ -599,12 +618,12 @@ const RocketChat = {
spotlight(search, usernames, type) {
// RC 0.51.0
- return this.sdk.methodCall('spotlight', search, usernames, type);
+ return this.methodCall('spotlight', search, usernames, type);
},
createDirectMessage(username) {
// RC 0.59.0
- return this.sdk.post('im.create', { username });
+ return this.post('im.create', { username });
},
createGroupChat() {
@@ -612,14 +631,14 @@ const RocketChat = {
users = users.map(u => u.name);
// RC 3.1.0
- return this.sdk.methodCall('createDirectMessage', ...users);
+ return this.methodCall('createDirectMessage', ...users);
},
createDiscussion({
prid, pmid, t_name, reply, users
}) {
// RC 1.0.0
- return this.sdk.post('rooms.createDiscussion', {
+ return this.post('rooms.createDiscussion', {
prid, pmid, t_name, reply, users
});
},
@@ -628,9 +647,9 @@ const RocketChat = {
// TODO: join code
// RC 0.48.0
if (type === 'p') {
- return this.sdk.methodCall('joinRoom', roomId);
+ return this.methodCall('joinRoom', roomId);
}
- return this.sdk.post('channels.join', { roomId });
+ return this.post('channels.join', { roomId });
},
triggerBlockAction,
triggerSubmitView,
@@ -662,34 +681,34 @@ const RocketChat = {
},
deleteMessage(messageId, rid) {
// RC 0.48.0
- return this.sdk.post('chat.delete', { msgId: messageId, roomId: rid });
+ return this.post('chat.delete', { msgId: messageId, roomId: rid });
},
editMessage(message) {
const { id, msg, rid } = message;
// RC 0.49.0
- return this.sdk.post('chat.update', { roomId: rid, msgId: id, text: msg });
+ return this.post('chat.update', { roomId: rid, msgId: id, text: msg });
},
markAsUnread({ messageId }) {
- return this.sdk.post('subscriptions.unread', { firstUnreadMessage: { _id: messageId } });
+ return this.post('subscriptions.unread', { firstUnreadMessage: { _id: messageId } });
},
toggleStarMessage(messageId, starred) {
if (starred) {
// RC 0.59.0
- return this.sdk.post('chat.unStarMessage', { messageId });
+ return this.post('chat.unStarMessage', { messageId });
}
// RC 0.59.0
- return this.sdk.post('chat.starMessage', { messageId });
+ return this.post('chat.starMessage', { messageId });
},
togglePinMessage(messageId, pinned) {
if (pinned) {
// RC 0.59.0
- return this.sdk.post('chat.unPinMessage', { messageId });
+ return this.post('chat.unPinMessage', { messageId });
}
// RC 0.59.0
- return this.sdk.post('chat.pinMessage', { messageId });
+ return this.post('chat.pinMessage', { messageId });
},
reportMessage(messageId) {
- return this.sdk.post('chat.reportMessage', { messageId, description: 'Message reported by user' });
+ return this.post('chat.reportMessage', { messageId, description: 'Message reported by user' });
},
async getRoom(rid) {
try {
@@ -739,42 +758,42 @@ const RocketChat = {
},
emitTyping(room, t = true) {
const { login } = reduxStore.getState();
- return this.sdk.methodCall('stream-notify-room', `${ room }/typing`, login.user.username, t);
+ return this.methodCall('stream-notify-room', `${ room }/typing`, login.user.username, t);
},
setUserPresenceAway() {
- return this.sdk.methodCall('UserPresence:away');
+ return this.methodCall('UserPresence:away');
},
setUserPresenceOnline() {
- return this.sdk.methodCall('UserPresence:online');
+ return this.methodCall('UserPresence:online');
},
setUserPresenceDefaultStatus(status) {
- return this.sdk.methodCall('UserPresence:setDefaultStatus', status);
+ return this.methodCall('UserPresence:setDefaultStatus', status);
},
setUserStatus(message) {
// RC 1.2.0
- return this.sdk.post('users.setStatus', { message });
+ return this.post('users.setStatus', { message });
},
setReaction(emoji, messageId) {
// RC 0.62.2
- return this.sdk.post('chat.react', { emoji, messageId });
+ return this.post('chat.react', { emoji, messageId });
},
toggleFavorite(roomId, favorite) {
// RC 0.64.0
- return this.sdk.post('rooms.favorite', { roomId, favorite });
+ return this.post('rooms.favorite', { roomId, favorite });
},
toggleRead(read, roomId) {
if (read) {
- return this.sdk.post('subscriptions.unread', { roomId });
+ return this.post('subscriptions.unread', { roomId });
}
- return this.sdk.post('subscriptions.read', { rid: roomId });
+ return this.post('subscriptions.read', { rid: roomId });
},
getRoomMembers(rid, allUsers, skip = 0, limit = 10) {
// RC 0.42.0
- return this.sdk.methodCall('getUsersOfRoom', rid, allUsers, { skip, limit });
+ return this.methodCall('getUsersOfRoom', rid, allUsers, { skip, limit });
},
getUserRoles() {
// RC 0.27.0
- return this.sdk.methodCall('getUserRoles');
+ return this.methodCall('getUserRoles');
},
getRoomCounters(roomId, t) {
// RC 0.65.0
@@ -816,63 +835,110 @@ const RocketChat = {
toggleBlockUser(rid, blocked, block) {
if (block) {
// RC 0.49.0
- return this.sdk.methodCall('blockUser', { rid, blocked });
+ return this.methodCall('blockUser', { rid, blocked });
}
// RC 0.49.0
- return this.sdk.methodCall('unblockUser', { rid, blocked });
+ return this.methodCall('unblockUser', { rid, blocked });
},
leaveRoom(roomId, t) {
// RC 0.48.0
- return this.sdk.post(`${ this.roomTypeToApiType(t) }.leave`, { roomId });
+ return this.post(`${ this.roomTypeToApiType(t) }.leave`, { roomId });
},
deleteRoom(roomId, t) {
// RC 0.49.0
- return this.sdk.post(`${ this.roomTypeToApiType(t) }.delete`, { roomId });
+ return this.post(`${ this.roomTypeToApiType(t) }.delete`, { roomId });
},
toggleMuteUserInRoom(rid, username, mute) {
if (mute) {
// RC 0.51.0
- return this.sdk.methodCall('muteUserInRoom', { rid, username });
+ return this.methodCall('muteUserInRoom', { rid, username });
}
// RC 0.51.0
- return this.sdk.methodCall('unmuteUserInRoom', { rid, username });
+ return this.methodCall('unmuteUserInRoom', { rid, username });
},
toggleArchiveRoom(roomId, t, archive) {
if (archive) {
// RC 0.48.0
- return this.sdk.post(`${ this.roomTypeToApiType(t) }.archive`, { roomId });
+ return this.post(`${ this.roomTypeToApiType(t) }.archive`, { roomId });
}
// RC 0.48.0
- return this.sdk.post(`${ this.roomTypeToApiType(t) }.unarchive`, { roomId });
+ return this.post(`${ this.roomTypeToApiType(t) }.unarchive`, { roomId });
},
hideRoom(roomId, t) {
- return this.sdk.post(`${ this.roomTypeToApiType(t) }.close`, { roomId });
+ return this.post(`${ this.roomTypeToApiType(t) }.close`, { roomId });
},
saveRoomSettings(rid, params) {
// RC 0.55.0
- return this.sdk.methodCall('saveRoomSettings', rid, params);
+ return this.methodCall('saveRoomSettings', rid, params);
+ },
+ post(...args) {
+ return new Promise(async(resolve, reject) => {
+ try {
+ const result = await this.sdk.post(...args);
+ return resolve(result);
+ } catch (e) {
+ if (e.data && (e.data.errorType === 'totp-required' || e.data.errorType === 'totp-invalid')) {
+ const { details } = e.data;
+ try {
+ await twoFactor({ method: details?.method, invalid: e.data.errorType === 'totp-invalid' });
+ return resolve(this.post(...args));
+ } catch {
+ // twoFactor was canceled
+ return resolve({});
+ }
+ } else {
+ reject(e);
+ }
+ }
+ });
+ },
+ methodCall(...args) {
+ return new Promise(async(resolve, reject) => {
+ try {
+ const result = await this.sdk.methodCall(...args, this.code);
+ return resolve(result);
+ } catch (e) {
+ if (e.error && (e.error === 'totp-required' || e.error === 'totp-invalid')) {
+ const { details } = e;
+ try {
+ this.code = await twoFactor({ method: details?.method, invalid: e.error === 'totp-invalid' });
+ return resolve(this.methodCall(...args));
+ } catch {
+ // twoFactor was canceled
+ return resolve({});
+ }
+ } else {
+ reject(e);
+ }
+ }
+ });
+ },
+ sendEmailCode() {
+ const { username } = reduxStore.getState().login.user;
+ // RC 3.1.0
+ return this.post('users.2fa.sendEmailCode', { emailOrUsername: username });
},
saveUserProfile(data, customFields) {
// RC 0.62.2
- return this.sdk.post('users.updateOwnBasicInfo', { data, customFields });
+ return this.post('users.updateOwnBasicInfo', { data, customFields });
},
saveUserPreferences(params) {
// RC 0.51.0
- return this.sdk.methodCall('saveUserPreferences', params);
+ return this.methodCall('saveUserPreferences', params);
},
saveNotificationSettings(roomId, notifications) {
// RC 0.63.0
- return this.sdk.post('rooms.saveNotification', { roomId, notifications });
+ return this.post('rooms.saveNotification', { roomId, notifications });
},
addUsersToRoom(rid) {
let { users } = reduxStore.getState().selectedUsers;
users = users.map(u => u.name);
// RC 0.51.0
- return this.sdk.methodCall('addUsersToRoom', { rid, users });
+ return this.methodCall('addUsersToRoom', { rid, users });
},
getSingleMessage(msgId) {
// RC 0.57.0
- return this.sdk.methodCall('getSingleMessage', msgId);
+ return this.methodCall('getSingleMessage', msgId);
},
async hasPermission(permissions, rid) {
const db = database.active;
@@ -915,15 +981,15 @@ const RocketChat = {
},
getAvatarSuggestion() {
// RC 0.51.0
- return this.sdk.methodCall('getAvatarSuggestion');
+ return this.methodCall('getAvatarSuggestion');
},
resetAvatar(userId) {
// RC 0.55.0
- return this.sdk.post('users.resetAvatar', { userId });
+ return this.post('users.resetAvatar', { userId });
},
setAvatarFromService({ data, contentType = '', service = null }) {
// RC 0.51.0
- return this.sdk.methodCall('setAvatarFromService', data, contentType, service);
+ return this.methodCall('setAvatarFromService', data, contentType, service);
},
async getAllowCrashReport() {
const allowCrashReport = await AsyncStorage.getItem(CRASH_REPORT_KEY);
@@ -1042,9 +1108,9 @@ const RocketChat = {
toggleFollowMessage(mid, follow) {
// RC 1.0
if (follow) {
- return this.sdk.post('chat.followMessage', { mid });
+ return this.post('chat.followMessage', { mid });
}
- return this.sdk.post('chat.unfollowMessage', { mid });
+ return this.post('chat.unfollowMessage', { mid });
},
getThreadsList({ rid, count, offset }) {
// RC 1.0
@@ -1060,7 +1126,7 @@ const RocketChat = {
},
runSlashCommand(command, roomId, params, triggerId, tmid) {
// RC 0.60.2
- return this.sdk.post('commands.run', {
+ return this.post('commands.run', {
command, roomId, params, triggerId, tmid
});
},
@@ -1072,7 +1138,7 @@ const RocketChat = {
},
executeCommandPreview(command, params, roomId, previewItem, triggerId, tmid) {
// RC 0.65.0
- return this.sdk.post('commands.preview', {
+ return this.post('commands.preview', {
command, params, roomId, previewItem, triggerId, tmid
});
},
@@ -1135,13 +1201,13 @@ const RocketChat = {
saveAutoTranslate({
rid, field, value, options
}) {
- return this.sdk.methodCall('autoTranslate.saveSettings', rid, field, value, options);
+ return this.methodCall('autoTranslate.saveSettings', rid, field, value, options);
},
getSupportedLanguagesAutoTranslate() {
- return this.sdk.methodCall('autoTranslate.getSupportedLanguages', 'en');
+ return this.methodCall('autoTranslate.getSupportedLanguages', 'en');
},
translateMessage(message, targetLanguage) {
- return this.sdk.methodCall('autoTranslate.translateMessage', message, targetLanguage);
+ return this.methodCall('autoTranslate.translateMessage', message, targetLanguage);
},
getRoomTitle(room) {
const { UI_Use_Real_Name: useRealName } = reduxStore.getState().settings;
@@ -1160,15 +1226,15 @@ const RocketChat = {
findOrCreateInvite({ rid, days, maxUses }) {
// RC 2.4.0
- return this.sdk.post('findOrCreateInvite', { rid, days, maxUses });
+ return this.post('findOrCreateInvite', { rid, days, maxUses });
},
validateInviteToken(token) {
// RC 2.4.0
- return this.sdk.post('validateInviteToken', { token });
+ return this.post('validateInviteToken', { token });
},
useInviteToken(token) {
// RC 2.4.0
- return this.sdk.post('useInviteToken', { token });
+ return this.post('useInviteToken', { token });
}
};
diff --git a/app/utils/fetch.js b/app/utils/fetch.js
index b29c02fc4..7b0fae135 100644
--- a/app/utils/fetch.js
+++ b/app/utils/fetch.js
@@ -11,7 +11,7 @@ let _basicAuth;
export const setBasicAuth = (basicAuth) => {
_basicAuth = basicAuth;
if (basicAuth) {
- RocketChatSettings.customHeaders = { ...RocketChatSettings.customHeaders, Authorization: `Basic ${ _basicAuth }` };
+ RocketChatSettings.customHeaders = { ...headers, Authorization: `Basic ${ _basicAuth }` };
} else {
RocketChatSettings.customHeaders = headers;
}
diff --git a/app/utils/twoFactor.js b/app/utils/twoFactor.js
new file mode 100644
index 000000000..ae5bc9b07
--- /dev/null
+++ b/app/utils/twoFactor.js
@@ -0,0 +1,20 @@
+import { settings } from '@rocket.chat/sdk';
+
+import EventEmitter from './events';
+import { TWO_FACTOR } from '../containers/TwoFactor';
+
+export const twoFactor = ({ method, invalid }) => new Promise((resolve, reject) => {
+ EventEmitter.emit(TWO_FACTOR, {
+ method,
+ invalid,
+ cancel: () => reject(),
+ submit: (code) => {
+ settings.customHeaders = {
+ ...settings.customHeaders,
+ 'x-2fa-code': code,
+ 'x-2fa-method': method
+ };
+ resolve({ twoFactorCode: code, twoFactorMethod: method });
+ }
+ });
+});
diff --git a/app/views/LoginView.js b/app/views/LoginView.js
index 6697bd7b9..3d81b0f58 100644
--- a/app/views/LoginView.js
+++ b/app/views/LoginView.js
@@ -16,7 +16,6 @@ import { withTheme } from '../theme';
import { themedHeader } from '../utils/navigation';
import FormContainer, { FormContainerInner } from '../containers/FormContainer';
import TextInput from '../containers/TextInput';
-import { animateNextTransition } from '../utils/layoutAnimation';
import { loginRequest as loginRequestAction } from '../actions/login';
import LoginServices from '../containers/LoginServices';
@@ -81,20 +80,13 @@ class LoginView extends React.Component {
super(props);
this.state = {
user: '',
- password: '',
- code: '',
- showTOTP: false
+ password: ''
};
}
componentWillReceiveProps(nextProps) {
const { error } = this.props;
if (nextProps.failure && !equal(error, nextProps.error)) {
- if (nextProps.error && nextProps.error.error === 'totp-required') {
- animateNextTransition();
- this.setState({ showTOTP: true });
- return;
- }
Alert.alert(I18n.t('Oops'), I18n.t('Login_error'));
}
}
@@ -115,12 +107,7 @@ class LoginView extends React.Component {
}
valid = () => {
- const {
- user, password, code, showTOTP
- } = this.state;
- if (showTOTP) {
- return code.trim();
- }
+ const { user, password } = this.state;
return user.trim() && password.trim();
}
@@ -129,10 +116,10 @@ class LoginView extends React.Component {
return;
}
- const { user, password, code } = this.state;
+ const { user, password } = this.state;
const { loginRequest } = this.props;
Keyboard.dismiss();
- loginRequest({ user, password, code });
+ loginRequest({ user, password });
analytics().logEvent('login');
}
@@ -211,50 +198,13 @@ class LoginView extends React.Component {
);
}
- renderTOTP = () => {
- const { isFetching, theme } = this.props;
- return (
- <>
- {I18n.t('Two_Factor_Authentication')}
-
- {I18n.t('Whats_your_2fa')}
-
- this.codeInput = ref}
- autoFocus
- onChangeText={value => this.setState({ code: value })}
- keyboardType='numeric'
- returnKeyType='send'
- autoCapitalize='none'
- onSubmitEditing={this.submit}
- testID='login-view-totp'
- containerStyle={sharedStyles.inputLastChild}
- theme={theme}
- />
-
- >
- );
- }
-
render() {
- const { showTOTP } = this.state;
const { Accounts_ShowFormLogin, theme } = this.props;
return (
- {!showTOTP ? : null}
- {!showTOTP ? this.renderUserForm() : null}
- {showTOTP ? this.renderTOTP() : null}
+
+ {this.renderUserForm()}
);
diff --git a/app/views/ProfileView/index.js b/app/views/ProfileView/index.js
index 1e2c2cc83..b33f15bb2 100644
--- a/app/views/ProfileView/index.js
+++ b/app/views/ProfileView/index.js
@@ -243,10 +243,10 @@ class ProfileView extends React.Component {
} else {
setUser({ ...params });
}
- this.setState({ saving: false });
EventEmitter.emit(LISTENER, { message: I18n.t('Profile_saved_successfully') });
this.init();
}
+ this.setState({ saving: false });
} catch (e) {
this.setState({ saving: false, currentPassword: null });
this.handleError(e, 'saveUserProfile', 'saving_profile');
diff --git a/package.json b/package.json
index 1996e48d6..5894e508b 100644
--- a/package.json
+++ b/package.json
@@ -109,7 +109,8 @@
"semver": "6.3.0",
"snyk": "1.210.0",
"strip-ansi": "5.2.0",
- "url-parse": "^1.4.7"
+ "url-parse": "^1.4.7",
+ "use-deep-compare-effect": "^1.3.1"
},
"devDependencies": {
"@babel/core": "^7.6.2",
diff --git a/yarn.lock b/yarn.lock
index d8b2abd62..504d7f655 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2265,11 +2265,31 @@
resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz#e486d0d97396d79beedd0a6e33f4534ff6b4973e"
integrity sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==
+"@types/prop-types@*":
+ version "15.7.3"
+ resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7"
+ integrity sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==
+
+"@types/react@*":
+ version "16.9.31"
+ resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.31.tgz#6a543529766c8934ec8a89667376c2e73e9e2636"
+ integrity sha512-NpYJpNMWScFXtx3A2BJMeew2G3+9SEslVWMdxNJ6DLvxIuxWjY1bizK9q5Y1ujhln31vtjmhjOAYDr9Xx3k9FQ==
+ dependencies:
+ "@types/prop-types" "*"
+ csstype "^2.2.0"
+
"@types/stack-utils@^1.0.1":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-1.0.1.tgz#0a851d3bd96498fa25c33ab7278ed3bd65f06c3e"
integrity sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw==
+"@types/use-deep-compare-effect@^1.2.0":
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/@types/use-deep-compare-effect/-/use-deep-compare-effect-1.2.0.tgz#d55d9bda6fea5ff7c93038c53052db1b3145f5d9"
+ integrity sha512-2uNqaSobMvUTGR7G72tUHDX+Kx341q25OuM0m2B6VID7eljIvYuDaFTKfmDnbvej67yEhCc35zA6dmIYriwOXA==
+ dependencies:
+ "@types/react" "*"
+
"@types/yargs-parser@*":
version "13.1.0"
resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-13.1.0.tgz#c563aa192f39350a1d18da36c5a8da382bbd8228"
@@ -3910,6 +3930,11 @@ cssstyle@^1.0.0:
dependencies:
cssom "0.3.x"
+csstype@^2.2.0:
+ version "2.6.10"
+ resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.10.tgz#e63af50e66d7c266edb6b32909cfd0aabe03928b"
+ integrity sha512-D34BqZU4cIlMCY93rZHbrq9pjTAQJ3U8S8rfBqjwHxkGPThWFjzZDQpgMJY0QViLxth6ZKYiwFBo14RdN44U/w==
+
csstype@^2.5.7:
version "2.6.6"
resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.6.tgz#c34f8226a94bbb10c32cc0d714afdf942291fc41"
@@ -4107,6 +4132,11 @@ depd@~1.1.2:
resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9"
integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=
+dequal@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/dequal/-/dequal-1.0.0.tgz#41c6065e70de738541c82cdbedea5292277a017e"
+ integrity sha512-/Nd1EQbQbI9UbSHrMiKZjFLrXSnU328iQdZKPQf78XQI6C+gutkFUeoHpG5J08Ioa6HeRbRNFpSIclh1xyG0mw==
+
destroy@~1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80"
@@ -12435,6 +12465,15 @@ urlgrey@^0.4.4:
resolved "https://registry.yarnpkg.com/urlgrey/-/urlgrey-0.4.4.tgz#892fe95960805e85519f1cd4389f2cb4cbb7652f"
integrity sha1-iS/pWWCAXoVRnxzUOJ8stMu3ZS8=
+use-deep-compare-effect@^1.3.1:
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/use-deep-compare-effect/-/use-deep-compare-effect-1.3.1.tgz#90bdbed97e1acb8423f7bb0bf24de58590d021af"
+ integrity sha512-ejL+Al+aeDyC9Sywx56ti4PtSwkf6BH27tEptMWF2cfO41/auG0nRRsArh6Vv5bUyBe3z7IyxmgQCK5nas70hg==
+ dependencies:
+ "@babel/runtime" "^7.7.2"
+ "@types/use-deep-compare-effect" "^1.2.0"
+ dequal "^1.0.0"
+
use-subscription@^1.0.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/use-subscription/-/use-subscription-1.3.0.tgz#3df13a798e826c8d462899423293289a3362e4e6"