[NEW] Two Factor authentication via email (#1961)
* First api call working * [NEW] REST API Post wrapper 2FA * [NEW] Send 2FA on Email * [I18n] Add translations * [NEW] Translations & Cancel totp * [CHORE] Totp -> TwoFactor * [NEW] Two Factor by email * [NEW] Tablet Support * [FIX] Text colors * [NEW] Password 2fa * [FIX] Encrypt password on 2FA * [NEW] MethodCall2FA * [FIX] Password fallback * [FIX] Wrap all post/methodCall with 2fa * [FIX] Wrap missed function * few fixes * [FIX] Use new TOTP on Login * [improvement] 2fa methodCall Co-authored-by: Djorkaeff Alexandre <djorkaeff.unb@gmail.com>
This commit is contained in:
parent
18afdd843e
commit
6982d7676a
|
@ -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 (
|
||||
<Modal
|
||||
transparent
|
||||
avoidKeyboard
|
||||
useNativeDriver
|
||||
isVisible={visible}
|
||||
hideModalContentWhileAnimating
|
||||
>
|
||||
<View style={styles.container}>
|
||||
<View style={[styles.content, split && [sharedStyles.modal, sharedStyles.modalFormSheet], { backgroundColor: themes[theme].backgroundColor }]}>
|
||||
<Text style={[styles.title, { color }]}>{I18n.t(method?.title || 'Two_Factor_Authentication')}</Text>
|
||||
<Text style={[styles.subtitle, { color }]}>{I18n.t(method?.text)}</Text>
|
||||
<TextInput
|
||||
value={code}
|
||||
theme={theme}
|
||||
returnKeyType='send'
|
||||
autoCapitalize='none'
|
||||
onChangeText={setCode}
|
||||
onSubmitEditing={onSubmit}
|
||||
keyboardType={method?.keyboardType}
|
||||
secureTextEntry={method?.secureTextEntry}
|
||||
error={data.invalid && { error: 'totp-invalid', reason: I18n.t('Code_or_password_invalid') }}
|
||||
/>
|
||||
{isEmail && <Text style={[styles.sendEmail, { color }]} onPress={sendEmail}>{I18n.t('Send_me_the_code_again')}</Text>}
|
||||
<View style={styles.buttonContainer}>
|
||||
<Button
|
||||
title={I18n.t('Cancel')}
|
||||
type='secondary'
|
||||
backgroundColor={themes[theme].chatComponentBackground}
|
||||
style={styles.button}
|
||||
onPress={onCancel}
|
||||
theme={theme}
|
||||
/>
|
||||
<Button
|
||||
title={I18n.t('Send')}
|
||||
type='primary'
|
||||
style={styles.button}
|
||||
onPress={onSubmit}
|
||||
theme={theme}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</Modal>
|
||||
);
|
||||
});
|
||||
TwoFactor.propTypes = {
|
||||
theme: PropTypes.string,
|
||||
split: PropTypes.bool
|
||||
};
|
||||
|
||||
export default withSplit(withTheme(TwoFactor));
|
|
@ -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'
|
||||
}
|
||||
});
|
|
@ -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',
|
||||
|
|
|
@ -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}}',
|
||||
|
|
|
@ -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}
|
||||
<TwoFactor />
|
||||
</ThemeContext.Provider>
|
||||
</Provider>
|
||||
</AppearanceProvider>
|
||||
|
|
|
@ -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 });
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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 });
|
||||
}
|
||||
});
|
||||
});
|
|
@ -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 (
|
||||
<>
|
||||
<Text style={[styles.title, sharedStyles.textBold, { color: themes[theme].titleText }]}>{I18n.t('Two_Factor_Authentication')}</Text>
|
||||
<Text
|
||||
style={[sharedStyles.loginSubtitle, sharedStyles.textRegular, { color: themes[theme].titleText }]}
|
||||
>
|
||||
{I18n.t('Whats_your_2fa')}
|
||||
</Text>
|
||||
<TextInput
|
||||
inputRef={ref => 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}
|
||||
/>
|
||||
<Button
|
||||
title={I18n.t('Confirm')}
|
||||
type='primary'
|
||||
onPress={this.submit}
|
||||
testID='login-view-submit'
|
||||
loading={isFetching}
|
||||
disabled={!this.valid()}
|
||||
theme={theme}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { showTOTP } = this.state;
|
||||
const { Accounts_ShowFormLogin, theme } = this.props;
|
||||
return (
|
||||
<FormContainer theme={theme}>
|
||||
<FormContainerInner>
|
||||
{!showTOTP ? <LoginServices separator={Accounts_ShowFormLogin} /> : null}
|
||||
{!showTOTP ? this.renderUserForm() : null}
|
||||
{showTOTP ? this.renderTOTP() : null}
|
||||
<LoginServices separator={Accounts_ShowFormLogin} />
|
||||
{this.renderUserForm()}
|
||||
</FormContainerInner>
|
||||
</FormContainer>
|
||||
);
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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",
|
||||
|
|
39
yarn.lock
39
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"
|
||||
|
|
Loading…
Reference in New Issue