diff --git a/android/app/build.gradle b/android/app/build.gradle
index cd60135257..689f44677d 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -147,7 +147,7 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode VERSIONCODE as Integer
- versionName "4.49.0"
+ versionName "4.49.1"
vectorDrawables.useSupportLibrary = true
if (!isFoss) {
manifestPlaceholders = [BugsnagAPIKey: BugsnagAPIKey as String]
diff --git a/app/containers/ActionSheet/ActionSheetContentWithInputAndSubmit/index.tsx b/app/containers/ActionSheet/ActionSheetContentWithInputAndSubmit/index.tsx
index 9003827a15..ae0f2c3bb2 100644
--- a/app/containers/ActionSheet/ActionSheetContentWithInputAndSubmit/index.tsx
+++ b/app/containers/ActionSheet/ActionSheetContentWithInputAndSubmit/index.tsx
@@ -1,4 +1,4 @@
-import React, { useState } from 'react';
+import React, { useState, useRef } from 'react';
import { StyleSheet, Text, View } from 'react-native';
import { CustomIcon, TIconsName } from '../../CustomIcon';
@@ -42,22 +42,28 @@ const FooterButtons = ({
confirmTitle = '',
disabled = false,
cancelBackgroundColor = '',
- confirmBackgroundColor = ''
+ confirmBackgroundColor = '',
+ testID = ''
}): React.ReactElement => {
const { colors } = useTheme();
return (
);
@@ -76,62 +82,104 @@ const ActionSheetContentWithInputAndSubmit = ({
iconColor,
customText,
confirmBackgroundColor,
- showInput = true
+ showInput = true,
+ inputs = [],
+ isDisabled
}: {
- onSubmit: (inputValue: string) => void;
+ onSubmit: (inputValue: string | string[]) => void;
onCancel?: () => void;
title: string;
- description: string;
+ description?: string;
testID: string;
secureTextEntry?: boolean;
- placeholder: string;
+ placeholder?: string;
confirmTitle?: string;
iconName?: TIconsName;
iconColor?: string;
customText?: React.ReactElement;
confirmBackgroundColor?: string;
showInput?: boolean;
+ inputs?: { placeholder: string; secureTextEntry?: boolean; key: string }[];
+ isDisabled?: (inputValues: string[]) => boolean;
}): React.ReactElement => {
const { colors } = useTheme();
- const [inputValue, setInputValue] = useState('');
+ const [inputValues, setInputValues] = useState(inputs.map(() => ''));
+ const inputRefs = useRef(inputs.map(() => React.createRef()));
+
+ const handleInputChange = (value: string, index: number) => {
+ const newInputValues = [...inputValues];
+ newInputValues[index] = value;
+ setInputValues(newInputValues);
+ };
+
const { hideActionSheet } = useActionSheet();
+ const renderInputs = () => {
+ if (inputs.length > 0) {
+ return inputs.map((inputConfig, index) => (
+ handleInputChange(value, index)}
+ onSubmitEditing={() => {
+ if (index < inputs.length - 1) {
+ (inputRefs.current[index + 1] as any).current.focus();
+ } else {
+ setTimeout(() => {
+ hideActionSheet();
+ }, 100);
+ if (inputValues.every(value => value)) onSubmit(inputValues);
+ }
+ }}
+ inputRef={inputRefs.current[index] as any}
+ testID={`${testID}-input-${inputConfig.key}`}
+ secureTextEntry={inputConfig.secureTextEntry}
+ bottomSheet={isIOS}
+ />
+ ));
+ }
+
+ return (
+ handleInputChange(value, 0)}
+ onSubmitEditing={() => {
+ setTimeout(() => {
+ hideActionSheet();
+ }, 100);
+ if (inputValues[0]) onSubmit(inputValues[0]);
+ }}
+ testID={`${testID}-input`}
+ secureTextEntry={secureTextEntry}
+ bottomSheet={isIOS}
+ />
+ );
+ };
+
+ const defaultDisabled = showInput && inputValues.some(value => !value);
+ const disabled = isDisabled ? isDisabled(inputValues) : defaultDisabled;
+
return (
<>
{iconName ? : null}
-
- {title}
-
+ {title}
- {description}
+ {description ? {description} : null}
{customText}
>
- {showInput ? (
- setInputValue(value)}
- onSubmitEditing={() => {
- // fix android animation
- setTimeout(() => {
- hideActionSheet();
- }, 100);
- if (inputValue) onSubmit(inputValue);
- }}
- testID={testID}
- secureTextEntry={secureTextEntry}
- bottomSheet={isIOS}
- />
- ) : null}
+ {showInput ? renderInputs() : null}
onSubmit(inputValue)}
+ confirmAction={() => onSubmit(inputs.length > 0 ? inputValues : inputValues[0])}
cancelTitle={i18n.t('Cancel')}
confirmTitle={confirmTitle || i18n.t('Save')}
- disabled={!showInput ? false : !inputValue}
+ disabled={disabled}
+ testID={testID}
/>
);
diff --git a/app/containers/ChangePasswordRequired.tsx b/app/containers/ChangePasswordRequired.tsx
new file mode 100644
index 0000000000..ba3b6f4c70
--- /dev/null
+++ b/app/containers/ChangePasswordRequired.tsx
@@ -0,0 +1,107 @@
+import React, { useState } from 'react';
+import { StyleSheet, Text, View } from 'react-native';
+import { useDispatch } from 'react-redux';
+
+import { logout, setUser } from '../actions/login';
+import I18n from '../i18n';
+import { useSetting } from '../lib/hooks/useSetting';
+import { showErrorAlert } from '../lib/methods/helpers';
+import { Services } from '../lib/services';
+import { useTheme } from '../theme';
+import sharedStyles from '../views/Styles';
+import { useActionSheet } from './ActionSheet';
+import ActionSheetContentWithInputAndSubmit from './ActionSheet/ActionSheetContentWithInputAndSubmit';
+import Button from './Button';
+import { CustomIcon } from './CustomIcon';
+
+export const ChangePasswordRequired = () => {
+ const [loading, setLoading] = useState(false);
+ const { colors } = useTheme();
+ const dispatch = useDispatch();
+ const { showActionSheet, hideActionSheet } = useActionSheet();
+
+ const requiresPasswordConfirmation = useSetting('Accounts_RequirePasswordConfirmation');
+ const passwordPlaceholder = useSetting('Accounts_PasswordPlaceholder') as string;
+ const passwordConfirmationPlaceholder = useSetting('Accounts_ConfirmPasswordPlaceholder') as string;
+
+ const changePassword = async (password: string) => {
+ setLoading(true);
+ try {
+ await Services.setUserPassword(password);
+ dispatch(setUser({ requirePasswordChange: false }));
+ hideActionSheet();
+ } catch (error: any) {
+ showErrorAlert(error?.reason || error?.message, I18n.t('Oops'));
+ }
+ setLoading(false);
+ };
+
+ const showActionSheetPassword = () => {
+ const inputs = [{ placeholder: passwordPlaceholder || I18n.t('Password'), secureTextEntry: true, key: 'password' }];
+ if (requiresPasswordConfirmation) {
+ inputs.push({
+ placeholder: passwordConfirmationPlaceholder || I18n.t('Confirm_your_password'),
+ secureTextEntry: true,
+ key: 'confirm-password'
+ });
+ }
+ showActionSheet({
+ children: (
+ changePassword(input[0])}
+ isDisabled={input => (loading || input[0] === '' || requiresPasswordConfirmation ? input[0] !== input[1] : false)}
+ />
+ )
+ });
+ };
+
+ return (
+
+
+
+
+ {I18n.t('You_need_to_change_your_password')}
+ {I18n.t('To_continue_using_RocketChat')}
+
+
+ );
+};
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ padding: 16,
+ backgroundColor: '#fff'
+ },
+ iconContainer: {
+ alignItems: 'center',
+ padding: 24
+ },
+ title: {
+ fontSize: 20,
+ lineHeight: 30,
+ marginBottom: 24,
+ ...sharedStyles.textBold
+ },
+ description: {
+ fontSize: 16,
+ lineHeight: 24,
+ marginBottom: 24,
+ ...sharedStyles.textRegular
+ }
+});
diff --git a/app/definitions/ILoggedUser.ts b/app/definitions/ILoggedUser.ts
index 92bff8252e..86b84e79f2 100644
--- a/app/definitions/ILoggedUser.ts
+++ b/app/definitions/ILoggedUser.ts
@@ -24,6 +24,7 @@ export interface ILoggedUser {
alsoSendThreadToChannel: 'default' | 'always' | 'never';
bio?: string;
nickname?: string;
+ requirePasswordChange?: boolean;
}
export interface ILoggedUserResultFromServer
diff --git a/app/i18n/locales/en.json b/app/i18n/locales/en.json
index 306c8f9ab9..54b8ce56c7 100644
--- a/app/i18n/locales/en.json
+++ b/app/i18n/locales/en.json
@@ -88,6 +88,7 @@
"Certificate_password": "Certificate password",
"Change_Language": "Change language",
"Change_language_loading": "Changing language.",
+ "Change_password": "Change password",
"changed_room_announcement": "changed room announcement to: {{announcement}}",
"changed_room_description": "changed room description to: {{description}}",
"changing_avatar": "changing avatar",
@@ -127,6 +128,7 @@
"Condensed": "Condensed",
"conference_call": "Conference call",
"Confirm": "Confirm",
+ "Confirm_your_password": "Confirm your password",
"Confirmation": "Confirmation",
"Connect": "Connect",
"Connecting": "Connecting...",
@@ -523,6 +525,7 @@
"Pinned_a_message": "Pinned a message:",
"Place_chat_on_hold": "Place chat on hold",
"Please_add_a_comment": "Please add a comment",
+ "Please_Enter_your_new_password": "Please enter your new password",
"Please_enter_your_password": "Please enter your password",
"Please_wait": "Please wait.",
"Preferences": "Preferences",
@@ -724,6 +727,7 @@
"Threads_displaying_following": "Displaying following",
"Threads_displaying_unread": "Displaying unread",
"Timezone": "Timezone",
+ "To_continue_using_RocketChat": "To continue using the mobile app, you need to change your password.",
"Token_expired": "Your session has expired. Please log in again.",
"Topic": "Topic",
"topic": "topic",
@@ -822,6 +826,7 @@
"You_colon": "You: ",
"You_dont_have_permission_to_perform_this_action": "You don’t have permission to perform this action. Check with a workspace administrator.",
"You_need_to_access_at_least_one_RocketChat_server_to_share_something": "You need to access at least one Rocket.Chat workspace to share something.",
+ "You_need_to_change_your_password": "You need to change your password",
"You_need_to_verifiy_your_email_address_to_get_notications": "You need to verify your email address to get notifications",
"you_were_mentioned": "you were mentioned",
"You_were_removed_from_channel": "You were removed from {{channel}}",
diff --git a/app/i18n/locales/pt-BR.json b/app/i18n/locales/pt-BR.json
index d6f39bbe87..a4a244e8fe 100644
--- a/app/i18n/locales/pt-BR.json
+++ b/app/i18n/locales/pt-BR.json
@@ -86,6 +86,7 @@
"Certificate_password": "Senha do certificado",
"Change_Language": "Alterar idioma",
"Change_language_loading": "Alterando idioma.",
+ "Change_password": "Alterar senha",
"changed_room_announcement": "alterou o anúncio da sala para: {{announcement}}",
"changed_room_description": "alterou a descrição da sala para: {{description}}",
"changing_avatar": "trocando avatar",
@@ -124,6 +125,7 @@
"Condensed": "Condensado",
"conference_call": "Video conferência",
"Confirm": "Confirmar",
+ "Confirm_your_password": "Confirme sua senha",
"Confirmation": "Confirmação",
"Connect": "Conectar",
"Connecting": "Conectando...",
@@ -515,6 +517,7 @@
"Pinned_a_message": "Fixou uma mensagem:",
"Place_chat_on_hold": "Colocar conversa em espera",
"Please_add_a_comment": "Por favor, adicione um comentário",
+ "Please_Enter_your_new_password": "Por favor, insira sua nova senha",
"Please_enter_your_password": "Por favor, digite sua senha",
"Please_wait": "Por favor, aguarde.",
"Preferences": "Preferências",
@@ -713,6 +716,7 @@
"Threads_displaying_following": "Mostrando seguindo",
"Threads_displaying_unread": "Mostrando não lidos",
"Timezone": "Fuso horário",
+ "To_continue_using_RocketChat": "Para continuar usando o aplicativo móvel, você precisa alterar sua senha.",
"Token_expired": "Sua sessão expirou. Por favor entre novamente.",
"Topic": "Tópico",
"topic": "tópico",
@@ -810,6 +814,7 @@
"You_colon": "Você: ",
"You_dont_have_permission_to_perform_this_action": "Você não tem permissão para realizar esta ação. Verifique com um administrador do espaço de trabalho.",
"You_need_to_access_at_least_one_RocketChat_server_to_share_something": "Você precisa acessar pelo menos uma workspace Rocket.Chat para compartilhar.",
+ "You_need_to_change_your_password": "Você precisa alterar sua senha",
"You_need_to_verifiy_your_email_address_to_get_notications": "Você precisa confirmar seu endereço de e-mail para obter notificações",
"you_were_mentioned": "você foi mencionado",
"You_were_removed_from_channel": "Você foi removido de {{channel}}",
diff --git a/app/i18n/locales/ru.json b/app/i18n/locales/ru.json
index f1f5f17417..6c2d619131 100644
--- a/app/i18n/locales/ru.json
+++ b/app/i18n/locales/ru.json
@@ -628,12 +628,12 @@
"Uploading": "Загрузка",
"Use": "Использовать",
"User": "Пользователь",
- "User__username__is_now_a_leader_of__room_name_": "Пользователь {{username}} больше не лидер в чате {{room_name}}",
- "User__username__is_now_a_moderator_of__room_name_": "Пользователь {{username}} больше не модератор в чате {{room_name}}",
- "User__username__is_now_a_owner_of__room_name_": "Пользователь {{username}} больше не владелец в чате {{room_name}}",
- "User__username__removed_from__room_name__leaders": "Пользователь {{username}} удален из {{room_name}} лидеров",
- "User__username__removed_from__room_name__moderators": "Пользователь {{username}} удален из {{room_name}} модераторов",
- "User__username__removed_from__room_name__owners": "Пользователь {{username}} удален из {{room_name}} владельцев",
+ "User__username__is_now_a_leader_of__room_name_": "Пользователь {{username}} теперь лидер в чате {{room_name}}",
+ "User__username__is_now_a_moderator_of__room_name_": "Пользователь {{username}} теперь модератор в чате {{room_name}}",
+ "User__username__is_now_a_owner_of__room_name_": "Пользователь {{username}} теперь владелец в чате {{room_name}}",
+ "User__username__removed_from__room_name__leaders": "Пользователь {{username}} удален из лидеров чата {{room_name}}",
+ "User__username__removed_from__room_name__moderators": "Пользователь {{username}} удален из модераторов чата {{room_name}}",
+ "User__username__removed_from__room_name__owners": "Пользователь {{username}} удален из владельцев чата {{room_name}}",
"User_has_been_ignored": "Пользователь теперь игнорируется",
"User_has_been_key": "Пользователь был {{key}}",
"User_has_been_removed_from_s": "Пользователь удален из {{s}}",
diff --git a/app/lib/constants/defaultSettings.ts b/app/lib/constants/defaultSettings.ts
index 4551f63195..dc51e79c1a 100644
--- a/app/lib/constants/defaultSettings.ts
+++ b/app/lib/constants/defaultSettings.ts
@@ -249,5 +249,11 @@ export const defaultSettings = {
CDN_PREFIX: {
type: 'valueAsString'
},
+ Accounts_RequirePasswordConfirmation:{
+ type: 'valueAsBoolean'
+ },
+ Accounts_ConfirmPasswordPlaceholder:{
+ type: 'valueAsString'
+ },
...deprecatedSettings
} as const;
diff --git a/app/lib/database/model/servers/User.js b/app/lib/database/model/servers/User.js
index 7fbbf4b96d..bf32f82e91 100644
--- a/app/lib/database/model/servers/User.js
+++ b/app/lib/database/model/servers/User.js
@@ -33,4 +33,6 @@ export default class User extends Model {
@field('nickname') nickname;
@field('bio') bio;
+
+ @field('require_password_change') requirePasswordChange;
}
diff --git a/app/lib/database/model/servers/migrations.js b/app/lib/database/model/servers/migrations.js
index 5a3fa8ff96..ba1fc74f56 100644
--- a/app/lib/database/model/servers/migrations.js
+++ b/app/lib/database/model/servers/migrations.js
@@ -146,6 +146,17 @@ export default schemaMigrations({
]
})
]
+ },
+ {
+ toVersion: 16,
+ steps: [
+ addColumns({
+ table: 'users',
+ columns: [
+ { name: 'require_password_change', type: 'string', isOptional: true }
+ ]
+ })
+ ]
}
]
});
diff --git a/app/lib/database/schema/servers.js b/app/lib/database/schema/servers.js
index 4aedcd93f5..5708b01854 100644
--- a/app/lib/database/schema/servers.js
+++ b/app/lib/database/schema/servers.js
@@ -1,7 +1,7 @@
import { appSchema, tableSchema } from '@nozbe/watermelondb';
export default appSchema({
- version: 15,
+ version: 16,
tables: [
tableSchema({
name: 'users',
@@ -19,7 +19,8 @@ export default appSchema({
{ name: 'is_from_webview', type: 'boolean', isOptional: true },
{ name: 'enable_message_parser_early_adoption', type: 'boolean', isOptional: true },
{ name: 'nickname', type: 'string', isOptional: true },
- { name: 'bio', type: 'string', isOptional: true }
+ { name: 'bio', type: 'string', isOptional: true },
+ { name: 'require_password_change', type: 'boolean', isOptional: true }
]
}),
tableSchema({
diff --git a/app/lib/methods/helpers/info.ts b/app/lib/methods/helpers/info.ts
index 287a06b182..d34c7b215e 100644
--- a/app/lib/methods/helpers/info.ts
+++ b/app/lib/methods/helpers/info.ts
@@ -8,7 +8,7 @@ export const showErrorAlert = (message: string, title?: string, onPress = () =>
export const showErrorAlertWithEMessage = (e: any, title?: string): void => {
let errorMessage: string = e?.data?.error;
- if (errorMessage.includes('[error-too-many-requests]')) {
+ if (errorMessage?.includes('[error-too-many-requests]')) {
const seconds = errorMessage.replace(/\D/g, '');
errorMessage = I18n.t('error-too-many-requests', { seconds });
} else {
diff --git a/app/lib/methods/helpers/log/events.ts b/app/lib/methods/helpers/log/events.ts
index ce6156223c..f028d0e41b 100644
--- a/app/lib/methods/helpers/log/events.ts
+++ b/app/lib/methods/helpers/log/events.ts
@@ -6,6 +6,8 @@ export default {
// LOGIN VIEW
LOGIN_DEFAULT_LOGIN: 'login_default_login',
LOGIN_DEFAULT_LOGIN_F: 'login_default_login_f',
+ LOGOUT_BY_SERVER: 'logout_by_server',
+ LOGOUT_TOKEN_EXPIRED: 'logout_token_expired',
// FORGOT PASSWORD VIEW
FP_FORGOT_PASSWORD: 'fp_forgot_password',
diff --git a/app/lib/methods/subscriptions/room.ts b/app/lib/methods/subscriptions/room.ts
index d38a84644c..7810258dbc 100644
--- a/app/lib/methods/subscriptions/room.ts
+++ b/app/lib/methods/subscriptions/room.ts
@@ -74,6 +74,7 @@ export default class RoomSubscription {
unsubscribe = async () => {
console.log(`[RCRN] Unsubscribing from room ${this.rid}`);
+ readMessages(this.rid, new Date(), true).catch(e => console.log(e));
this.isAlive = false;
reduxStore.dispatch(unsubscribeRoom(this.rid));
if (this.promises) {
diff --git a/app/lib/services/connect.ts b/app/lib/services/connect.ts
index 527a9653fa..080375bf58 100644
--- a/app/lib/services/connect.ts
+++ b/app/lib/services/connect.ts
@@ -317,7 +317,8 @@ async function login(credentials: ICredentials, isFromWebView = false): Promise<
enableMessageParserEarlyAdoption,
alsoSendThreadToChannel: result.me.settings?.preferences?.alsoSendThreadToChannel,
bio: result.me.bio,
- nickname: result.me.nickname
+ nickname: result.me.nickname,
+ requirePasswordChange: result.me.requirePasswordChange
};
return user;
}
diff --git a/app/lib/services/getServerTimeSync.ts b/app/lib/services/getServerTimeSync.ts
index 9bce93451c..023b8a2be9 100644
--- a/app/lib/services/getServerTimeSync.ts
+++ b/app/lib/services/getServerTimeSync.ts
@@ -2,9 +2,7 @@ export const getServerTimeSync = async (server: string) => {
try {
const response = await Promise.race([fetch(`${server}/_timesync`), new Promise(res => setTimeout(res, 2000))]);
const data = await response?.json();
- if (data?.data) {
- return parseInt(data.data);
- }
+ if (data) return parseInt(data);
return null;
} catch {
return null;
diff --git a/app/lib/services/restApi.ts b/app/lib/services/restApi.ts
index 94866c268d..ce42c92986 100644
--- a/app/lib/services/restApi.ts
+++ b/app/lib/services/restApi.ts
@@ -1012,3 +1012,5 @@ export const getUsersRoles = (): Promise => sdk.methodCall('getUserRole
export const getSupportedVersionsCloud = (uniqueId?: string, domain?: string) =>
fetch(`https://releases.rocket.chat/v2/server/supportedVersions?uniqueId=${uniqueId}&domain=${domain}&source=mobile`);
+
+export const setUserPassword = (password: string) => sdk.methodCall('setUserPassword', password);
diff --git a/app/sagas/login.js b/app/sagas/login.js
index e2fbd68db9..8477c6fa2e 100644
--- a/app/sagas/login.js
+++ b/app/sagas/login.js
@@ -124,8 +124,10 @@ const handleLoginRequest = function* handleLoginRequest({
}
} catch (e) {
if (e?.data?.message && /you've been logged out by the server/i.test(e.data.message)) {
+ logEvent(events.LOGOUT_BY_SERVER);
yield put(logoutAction(true, 'Logged_out_by_server'));
} else if (e?.data?.message && /your session has expired/i.test(e.data.message)) {
+ logEvent(events.LOGOUT_TOKEN_EXPIRED);
yield put(logoutAction(true, 'Token_expired'));
} else {
logEvent(events.LOGIN_DEFAULT_LOGIN_F);
@@ -211,7 +213,8 @@ const handleLoginSuccess = function* handleLoginSuccess({ user }) {
showMessageInMainThread: user.showMessageInMainThread,
avatarETag: user.avatarETag,
bio: user.bio,
- nickname: user.nickname
+ nickname: user.nickname,
+ requirePasswordChange: user.requirePasswordChange
};
yield serversDB.action(async () => {
try {
@@ -273,15 +276,20 @@ const handleLogout = function* handleLogout({ forcedByServer, message }) {
};
const handleSetUser = function* handleSetUser({ user }) {
- if ('avatarETag' in user) {
+ if ('avatarETag' in user || 'requirePasswordChange' in user) {
const userId = yield select(state => state.login.user.id);
const serversDB = database.servers;
const userCollections = serversDB.get('users');
yield serversDB.write(async () => {
try {
- const userRecord = await userCollections.find(userId);
- await userRecord.update(record => {
- record.avatarETag = user.avatarETag;
+ const record = await userCollections.find(userId);
+ await record.update(userRecord => {
+ if ('avatarETag' in user) {
+ userRecord.avatarETag = user.avatarETag;
+ }
+ if ('requirePasswordChange' in user) {
+ userRecord.requirePasswordChange = user.requirePasswordChange;
+ }
});
} catch {
//
diff --git a/app/sagas/selectServer.ts b/app/sagas/selectServer.ts
index 910a71cfdb..8433e72a64 100644
--- a/app/sagas/selectServer.ts
+++ b/app/sagas/selectServer.ts
@@ -167,7 +167,8 @@ const handleSelectServer = function* handleSelectServer({ server, version, fetch
roles: userRecord.roles,
avatarETag: userRecord.avatarETag,
bio: userRecord.bio,
- nickname: userRecord.nickname
+ nickname: userRecord.nickname,
+ requirePasswordChange: userRecord.requirePasswordChange
};
} else {
const token = UserPreferences.getString(`${TOKEN_KEY}-${userId}`);
diff --git a/app/views/ProfileView/components/DeleteAccountActionSheetContent/index.tsx b/app/views/ProfileView/components/DeleteAccountActionSheetContent/index.tsx
index d6fa7bb4a2..681b1af718 100644
--- a/app/views/ProfileView/components/DeleteAccountActionSheetContent/index.tsx
+++ b/app/views/ProfileView/components/DeleteAccountActionSheetContent/index.tsx
@@ -58,7 +58,7 @@ export function DeleteAccountActionSheetContent(): React.ReactElement {
title={i18n.t('Are_you_sure_you_want_to_delete_your_account')}
description={i18n.t('For_your_security_you_must_enter_your_current_password_to_continue')}
onCancel={hideActionSheet}
- onSubmit={password => handleDeleteAccount(password)}
+ onSubmit={password => handleDeleteAccount(password as string)}
placeholder={i18n.t('Password')}
testID='profile-view-delete-account-sheet'
iconName='warning'
diff --git a/app/views/ProfileView/index.tsx b/app/views/ProfileView/index.tsx
index e9e4e30c83..939f2f12d9 100644
--- a/app/views/ProfileView/index.tsx
+++ b/app/views/ProfileView/index.tsx
@@ -223,9 +223,9 @@ class ProfileView extends React.Component
description={I18n.t('For_your_security_you_must_enter_your_current_password_to_continue')}
testID='profile-view-enter-password-sheet'
placeholder={I18n.t('Password')}
- onSubmit={(p: string) => {
+ onSubmit={p => {
this.props.hideActionSheet();
- this.setState({ currentPassword: p }, () => this.submit());
+ this.setState({ currentPassword: p as string }, () => this.submit());
}}
onCancel={this.props.hideActionSheet}
/>
@@ -321,8 +321,7 @@ class ProfileView extends React.Component
testID={key}
onPress={onPress}
style={[styles.avatarButton, { opacity: disabled ? 0.5 : 1 }, { backgroundColor: themes[theme].strokeLight }]}
- enabled={!disabled}
- >
+ enabled={!disabled}>
{child}
);
@@ -349,8 +348,7 @@ class ProfileView extends React.Component
newValue[key] = value;
this.setState({ customFields: { ...customFields, ...newValue } });
}}
- value={customFields[key]}
- >
+ value={customFields[key]}>
{
// @ts-ignore
@@ -441,8 +439,7 @@ class ProfileView extends React.Component
+ {...scrollPersistTaps}>
,
@@ -315,6 +316,9 @@ class RoomsListView extends React.Component {
const { searching, canCreateRoom } = this.state;
- const { navigation, isMasterDetail, notificationPresenceCap, issuesWithNotifications, supportedVersionsStatus, theme } =
+ const { navigation, isMasterDetail, notificationPresenceCap, issuesWithNotifications, supportedVersionsStatus, theme, user } =
this.props;
if (searching) {
return {
@@ -441,6 +448,8 @@ class RoomsListView extends React.Component navigation.toggleDrawer()
}
badge={() => getBadge()}
- disabled={supportedVersionsStatus === 'expired'}
+ disabled={disabled}
/>
),
headerTitle: () => ,
@@ -475,20 +484,15 @@ class RoomsListView extends React.Component
) : null}
-
+
)
@@ -948,7 +952,7 @@ class RoomsListView extends React.Component {
const { loading, chats, search, searching } = this.state;
- const { theme, refreshing, displayMode, supportedVersionsStatus } = this.props;
+ const { theme, refreshing, displayMode, supportedVersionsStatus, user } = this.props;
const height = displayMode === DisplayMode.Condensed ? ROW_HEIGHT_CONDENSED : ROW_HEIGHT;
@@ -960,6 +964,10 @@ class RoomsListView extends React.Component;
}
+ if (user.requirePasswordChange) {
+ return ;
+ }
+
return (
{
attachments: [],
text: props.route.params?.text ?? '',
room: props.route.params?.room ?? {},
- thread: props.route.params?.thread ?? '',
+ thread: props.route.params?.thread ?? {},
maxFileSize: this.isShareExtension ? this.serverInfo?.FileUpload_MaxFileSize : props.FileUpload_MaxFileSize,
mediaAllowList: this.isShareExtension
? this.serverInfo?.FileUpload_MediaTypeWhiteList
@@ -114,6 +114,16 @@ class ShareView extends Component {
}
};
+ getThreadId = (thread: TThreadModel | string | undefined) => {
+ let threadId = undefined;
+ if (typeof thread === 'object') {
+ threadId = thread?.id;
+ } else if (typeof thread === 'string') {
+ threadId = thread;
+ }
+ return threadId;
+ };
+
setHeader = () => {
const { room, thread, readOnly, attachments } = this.state;
const { navigation, theme } = this.props;
@@ -265,7 +275,7 @@ class ShareView extends Component {
store: 'Uploads',
msg
},
- (thread as TThreadModel)?.id || (thread as string),
+ this.getThreadId(thread),
server,
{ id: user.id, token: user.token }
);
@@ -276,7 +286,7 @@ class ShareView extends Component {
// Send text message
} else if (text.length) {
- await sendMessage(room.rid, text, (thread as TThreadModel)?.id || (thread as string), {
+ await sendMessage(room.rid, text, this.getThreadId(thread), {
id: user.id,
token: user.token
} as IUser);
@@ -347,7 +357,7 @@ class ShareView extends Component {
value={{
rid: room.rid,
t: room.t,
- tmid: (thread as TThreadModel)?.id || (thread as string),
+ tmid: this.getThreadId(thread),
sharing: true,
action: route.params?.action,
selectedMessages,
diff --git a/e2e/helpers/data_setup.ts b/e2e/helpers/data_setup.ts
index de9489ebc8..d963877b43 100644
--- a/e2e/helpers/data_setup.ts
+++ b/e2e/helpers/data_setup.ts
@@ -36,7 +36,7 @@ export interface ITestUser {
email: string;
}
-export const createRandomUser = async (): Promise => {
+export const createRandomUser = async (customProps?: Object): Promise => {
try {
await login(data.adminUser, data.adminPassword);
const user = data.randomUser();
@@ -45,7 +45,8 @@ export const createRandomUser = async (): Promise => {
username: user.username,
name: user.name,
password: user.password,
- email: user.email
+ email: user.email,
+ ...(customProps || {})
});
return user;
} catch (error) {
diff --git a/e2e/tests/assorted/03-profile.spec.ts b/e2e/tests/assorted/03-profile.spec.ts
index 33ce053f8c..fcbd9285a9 100644
--- a/e2e/tests/assorted/03-profile.spec.ts
+++ b/e2e/tests/assorted/03-profile.spec.ts
@@ -97,18 +97,18 @@ describe('Profile screen', () => {
await element(by.id('profile-view-list')).swipe('down');
await waitFor(element(by.id('profile-view-email')))
.toBeVisible()
- .withTimeout(2000);
+ .withTimeout(10000);
await element(by.id('profile-view-email')).replaceText(`mobile+profileChangesNew${random()}@rocket.chat`);
await dismissKeyboardAndScrollUp();
await element(by.id('profile-view-new-password')).replaceText(`${user.password}new`);
await waitFor(element(by.id('profile-view-submit')))
.toExist()
- .withTimeout(2000);
+ .withTimeout(10000);
await element(by.id('profile-view-submit')).tap();
- await waitFor(element(by.id('profile-view-enter-password-sheet')))
+ await waitFor(element(by.id('profile-view-enter-password-sheet-input')))
.toBeVisible()
- .withTimeout(2000);
- await element(by.id('profile-view-enter-password-sheet')).replaceText(`${user.password}`);
+ .withTimeout(10000);
+ await element(by.id('profile-view-enter-password-sheet-input')).replaceText(`${user.password}`);
await element(by[textMatcher]('Save').withAncestor(by.id('action-sheet-content-with-input-and-submit')))
.atIndex(0)
.tap();
diff --git a/e2e/tests/onboarding/08-change-password-required.spec.ts b/e2e/tests/onboarding/08-change-password-required.spec.ts
new file mode 100644
index 0000000000..dd1fa77cea
--- /dev/null
+++ b/e2e/tests/onboarding/08-change-password-required.spec.ts
@@ -0,0 +1,63 @@
+import { by, device, element, waitFor } from 'detox';
+
+import { login, logout, navigateToLogin } from '../../helpers/app';
+import { createRandomUser } from '../../helpers/data_setup';
+
+describe('Change password required', () => {
+ let user = { username: '', password: '' };
+
+ beforeAll(async () => {
+ user = await createRandomUser({ requirePasswordChange: true });
+ await device.launchApp({ permissions: { notifications: 'YES' }, delete: true });
+ await navigateToLogin();
+ await login(user.username, user.password);
+ });
+
+ describe('Usage', () => {
+ it('should have required password info displayed', async () => {
+ await waitFor(element(by.id(`change-password-required-button`)))
+ .toExist()
+ .withTimeout(5000);
+ });
+
+ it('should logout correctly', async () => {
+ await waitFor(element(by.id(`change-password-required-logout`)))
+ .toExist()
+ .withTimeout(5000);
+ await element(by.id('change-password-required-logout')).tap();
+ await waitFor(element(by.id('new-server-view')))
+ .toExist()
+ .withTimeout(10000);
+ });
+
+ it('should change password correctly', async () => {
+ await navigateToLogin();
+ await login(user.username, user.password);
+ await waitFor(element(by.id(`change-password-required-button`)))
+ .toExist()
+ .withTimeout(5000);
+ await element(by.id('change-password-required-button')).tap();
+ await waitFor(element(by.id(`action-sheet-content-with-input-and-submit`)))
+ .toExist()
+ .withTimeout(5000);
+ await element(by.id('change-password-required-sheet-input-password')).replaceText('123456');
+ await element(by.id('change-password-required-sheet-input-confirm-password')).replaceText('123456');
+ await element(by.id('change-password-required-sheet-confirm')).tap();
+ await waitFor(element(by.id(`change-password-required-button`)))
+ .not.toExist()
+ .withTimeout(5000);
+ await waitFor(element(by.id('rooms-list-view-item-general')))
+ .toExist()
+ .withTimeout(10000);
+ await logout();
+ });
+
+ it('should login correctly after password change', async () => {
+ await navigateToLogin();
+ await login(user.username, '123456');
+ await waitFor(element(by.id('rooms-list-view-item-general')))
+ .toExist()
+ .withTimeout(10000);
+ });
+ });
+});
diff --git a/ios/RocketChatRN.xcodeproj/project.pbxproj b/ios/RocketChatRN.xcodeproj/project.pbxproj
index 91dd46d459..fd9aba4608 100644
--- a/ios/RocketChatRN.xcodeproj/project.pbxproj
+++ b/ios/RocketChatRN.xcodeproj/project.pbxproj
@@ -2902,7 +2902,7 @@
INFOPLIST_FILE = NotificationService/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
- MARKETING_VERSION = 4.49.0;
+ MARKETING_VERSION = 4.49.1;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG";
@@ -2942,7 +2942,7 @@
INFOPLIST_FILE = NotificationService/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
- MARKETING_VERSION = 4.49.0;
+ MARKETING_VERSION = 4.49.1;
MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = chat.rocket.reactnative.NotificationService;
diff --git a/ios/RocketChatRN/Info.plist b/ios/RocketChatRN/Info.plist
index e23b4bb69b..454f1fa6e8 100644
--- a/ios/RocketChatRN/Info.plist
+++ b/ios/RocketChatRN/Info.plist
@@ -26,7 +26,7 @@
CFBundlePackageType
APPL
CFBundleShortVersionString
- 4.49.0
+ 4.49.1
CFBundleSignature
????
CFBundleURLTypes
diff --git a/ios/ShareRocketChatRN/Info.plist b/ios/ShareRocketChatRN/Info.plist
index 776ca2a075..ac9590cff2 100644
--- a/ios/ShareRocketChatRN/Info.plist
+++ b/ios/ShareRocketChatRN/Info.plist
@@ -26,7 +26,7 @@
CFBundlePackageType
XPC!
CFBundleShortVersionString
- 4.49.0
+ 4.49.1
CFBundleVersion
1
KeychainGroup
diff --git a/package.json b/package.json
index ae0d66ebf9..a83f939e68 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "rocket-chat-reactnative",
- "version": "4.49.0",
+ "version": "4.49.1",
"private": true,
"scripts": {
"start": "react-native start",