From d100c7d95dfb749b2e8ca005c65c3b7179a807af Mon Sep 17 00:00:00 2001 From: Mattermost Build Date: Sat, 18 Apr 2020 05:53:48 +0200 Subject: [PATCH] Automated cherry pick of #4072 (#4172) * MM-22292 Don't use redux to store username and password * Set login and password MFA props Co-authored-by: Elias Nahum --- app/actions/views/login.js | 17 --- app/actions/views/login.test.js | 30 +---- app/constants/view.js | 3 - app/init/emm_provider.js | 7 -- app/initial_state.js | 4 - app/reducers/views/index.js | 2 - app/reducers/views/login.js | 33 ------ app/screens/login/login.js | 195 +++++++++++++++++--------------- app/screens/login/login.test.js | 4 - app/screens/mfa/index.js | 10 +- app/store/store.js | 2 +- packager/moduleNames.js | 1 - packager/modulePaths.js | 1 - 13 files changed, 104 insertions(+), 205 deletions(-) delete mode 100644 app/reducers/views/login.js diff --git a/app/actions/views/login.js b/app/actions/views/login.js index 482c9530c..2f40e4685 100644 --- a/app/actions/views/login.js +++ b/app/actions/views/login.js @@ -12,27 +12,12 @@ import {getConfig, getLicense} from 'mattermost-redux/selectors/entities/general import {isTimezoneEnabled} from 'mattermost-redux/selectors/entities/timezone'; import {getCurrentUserId} from 'mattermost-redux/selectors/entities/users'; -import {ViewTypes} from 'app/constants'; import {setAppCredentials} from 'app/init/credentials'; import PushNotifications from 'app/push_notifications'; import {getDeviceTimezoneAsync} from 'app/utils/timezone'; import {setCSRFFromCookie} from 'app/utils/security'; import {loadConfigAndLicense} from 'app/actions/views/root'; -export function handleLoginIdChanged(loginId) { - return { - type: ViewTypes.LOGIN_ID_CHANGED, - loginId, - }; -} - -export function handlePasswordChanged(password) { - return { - type: ViewTypes.PASSWORD_CHANGED, - password, - }; -} - export function handleSuccessfulLogin() { return async (dispatch, getState) => { await dispatch(loadConfigAndLicense()); @@ -122,8 +107,6 @@ export function scheduleExpiredNotification(intl) { } export default { - handleLoginIdChanged, - handlePasswordChanged, handleSuccessfulLogin, scheduleExpiredNotification, }; diff --git a/app/actions/views/login.test.js b/app/actions/views/login.test.js index 7960d67db..77ccf851a 100644 --- a/app/actions/views/login.test.js +++ b/app/actions/views/login.test.js @@ -6,13 +6,7 @@ import thunk from 'redux-thunk'; import * as GeneralActions from 'mattermost-redux/actions/general'; -import {ViewTypes} from 'app/constants'; - -import { - handleLoginIdChanged, - handlePasswordChanged, - handleSuccessfulLogin, -} from 'app/actions/views/login'; +import {handleSuccessfulLogin} from 'app/actions/views/login'; jest.mock('app/init/credentials', () => ({ setAppCredentials: () => jest.fn(), @@ -51,28 +45,6 @@ describe('Actions.Views.Login', () => { }); }); - test('handleLoginIdChanged', () => { - const loginId = 'email@example.com'; - - const action = { - type: ViewTypes.LOGIN_ID_CHANGED, - loginId, - }; - store.dispatch(handleLoginIdChanged(loginId)); - expect(store.getActions()).toEqual([action]); - }); - - test('handlePasswordChanged', () => { - const password = 'password'; - const action = { - type: ViewTypes.PASSWORD_CHANGED, - password, - }; - - store.dispatch(handlePasswordChanged(password)); - expect(store.getActions()).toEqual([action]); - }); - test('handleSuccessfulLogin gets config and license ', async () => { const getClientConfig = jest.spyOn(GeneralActions, 'getClientConfig'); const getLicenseConfig = jest.spyOn(GeneralActions, 'getLicenseConfig'); diff --git a/app/constants/view.js b/app/constants/view.js index 5277b3d07..491415717 100644 --- a/app/constants/view.js +++ b/app/constants/view.js @@ -37,9 +37,6 @@ const ViewTypes = keyMirror({ DATA_CLEANUP: null, SERVER_URL_CHANGED: null, - LOGIN_ID_CHANGED: null, - PASSWORD_CHANGED: null, - POST_DRAFT_CHANGED: null, COMMENT_DRAFT_CHANGED: null, SEARCH_DRAFT_CHANGED: null, diff --git a/app/init/emm_provider.js b/app/init/emm_provider.js index d299558de..5bf258dfc 100644 --- a/app/init/emm_provider.js +++ b/app/init/emm_provider.js @@ -3,7 +3,6 @@ import {Alert, Platform} from 'react-native'; -import {handleLoginIdChanged} from 'app/actions/views/login'; import {setServerUrl} from 'app/actions/views/select_server'; import {getTranslations} from 'app/i18n'; import mattermostBucket from 'app/mattermost_bucket'; @@ -28,7 +27,6 @@ class EMMProvider { this.allowOtherServers = true; this.emmServerUrl = null; - this.emmUsername = null; } checkIfDeviceIsTrusted = (store) => { @@ -110,7 +108,6 @@ class EMMProvider { const credentials = await getAppCredentials(); if (!credentials) { this.emmServerUrl = managedConfig.serverUrl; - this.emmUsername = managedConfig.username; if (managedConfig.allowOtherServers && managedConfig.allowOtherServers === 'false') { this.allowOtherServers = false; @@ -124,10 +121,6 @@ class EMMProvider { if (this.emmServerUrl) { dispatch(setServerUrl(this.emmServerUrl)); } - - if (this.emmUsername) { - dispatch(handleLoginIdChanged(this.emmUsername)); - } } return true; diff --git a/app/initial_state.js b/app/initial_state.js index 6ec7e4b70..62cd3dc58 100644 --- a/app/initial_state.js +++ b/app/initial_state.js @@ -136,10 +136,6 @@ const state = { i18n: { locale: '', }, - login: { - loginId: '', - password: '', - }, root: { deepLinkURL: '', hydrationComplete: false, diff --git a/app/reducers/views/index.js b/app/reducers/views/index.js index 9e34e5ae6..4b15ee1d3 100644 --- a/app/reducers/views/index.js +++ b/app/reducers/views/index.js @@ -8,7 +8,6 @@ import channel from './channel'; import clientUpgrade from './client_upgrade'; import extension from './extension'; import i18n from './i18n'; -import login from './login'; import recentEmojis from './recent_emojis'; import root from './root'; import search from './search'; @@ -25,7 +24,6 @@ export default combineReducers({ clientUpgrade, extension, i18n, - login, recentEmojis, root, search, diff --git a/app/reducers/views/login.js b/app/reducers/views/login.js deleted file mode 100644 index 54c7c9194..000000000 --- a/app/reducers/views/login.js +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import {combineReducers} from 'redux'; -import {UserTypes} from 'mattermost-redux/action_types'; -import {ViewTypes} from 'app/constants'; - -function loginId(state = '', action) { - switch (action.type) { - case ViewTypes.LOGIN_ID_CHANGED: - return action.loginId.trim(); - case UserTypes.LOGOUT_SUCCESS: - return ''; - default: - return state; - } -} - -function password(state = '', action) { - switch (action.type) { - case ViewTypes.PASSWORD_CHANGED: - return action.password; - case UserTypes.LOGOUT_SUCCESS: - return ''; - default: - return state; - } -} - -export default combineReducers({ - loginId, - password, -}); diff --git a/app/screens/login/login.js b/app/screens/login/login.js index 716478a01..890918c36 100644 --- a/app/screens/login/login.js +++ b/app/screens/login/login.js @@ -24,6 +24,7 @@ import ErrorText from 'app/components/error_text'; import FormattedText from 'app/components/formatted_text'; import StatusBar from 'app/components/status_bar'; import {resetToChannel, goToScreen} from 'app/actions/navigation'; +import mattermostManaged from 'app/mattermost_managed'; import {preventDoubleTap} from 'app/utils/tap'; import tracker from 'app/utils/time_tracker'; import {t} from 'app/utils/i18n'; @@ -38,16 +39,12 @@ export const mfaExpectedErrors = ['mfa.validate_token.authenticate.app_error', ' export default class Login extends PureComponent { static propTypes = { actions: PropTypes.shape({ - handleLoginIdChanged: PropTypes.func.isRequired, - handlePasswordChanged: PropTypes.func.isRequired, handleSuccessfulLogin: PropTypes.func.isRequired, scheduleExpiredNotification: PropTypes.func.isRequired, login: PropTypes.func.isRequired, }).isRequired, config: PropTypes.object.isRequired, license: PropTypes.object.isRequired, - loginId: PropTypes.string.isRequired, - password: PropTypes.string.isRequired, isLandscape: PropTypes.bool.isRequired, }; @@ -68,6 +65,7 @@ export default class Login extends PureComponent { Dimensions.addEventListener('change', this.orientationDidChange); setMfaPreflightDone(false); + this.setEmmUsernameIfAvailable(); } componentWillUnmount() { @@ -88,8 +86,10 @@ export default class Login extends PureComponent { const {intl} = this.context; const screen = 'MFA'; const title = intl.formatMessage({id: 'mobile.routes.mfa', defaultMessage: 'Multi-factor Authentication'}); + const loginId = this.loginId?._lastNativeText; //eslint-disable-line no-underscore-dangle + const password = this.passwd?._lastNativeText; //eslint-disable-line no-underscore-dangle - goToScreen(screen, title, {onMfaComplete: this.checkLoginResponse}); + goToScreen(screen, title, {onMfaComplete: this.checkLoginResponse, loginId, password}); }; blur = () => { @@ -98,83 +98,6 @@ export default class Login extends PureComponent { Keyboard.dismiss(); }; - preSignIn = preventDoubleTap(() => { - this.setState({error: null, isLoading: true}); - Keyboard.dismiss(); - InteractionManager.runAfterInteractions(async () => { - if (!this.props.loginId) { - t('login.noEmail'); - t('login.noEmailLdapUsername'); - t('login.noEmailUsername'); - t('login.noEmailUsernameLdapUsername'); - t('login.noLdapUsername'); - t('login.noUsername'); - t('login.noUsernameLdapUsername'); - - // it's slightly weird to be constructing the message ID, but it's a bit nicer than triply nested if statements - let msgId = 'login.no'; - if (this.props.config.EnableSignInWithEmail === 'true') { - msgId += 'Email'; - } - if (this.props.config.EnableSignInWithUsername === 'true') { - msgId += 'Username'; - } - if (this.props.license.IsLicensed === 'true' && this.props.config.EnableLdap === 'true') { - msgId += 'LdapUsername'; - } - - this.setState({ - isLoading: false, - error: { - intl: { - id: msgId, - defaultMessage: '', - values: { - ldapUsername: this.props.config.LdapLoginFieldName || - this.context.intl.formatMessage({ - id: 'login.ldapUsernameLower', - defaultMessage: 'AD/LDAP username', - }), - }, - }, - }, - }); - return; - } - - if (!this.props.password) { - this.setState({ - isLoading: false, - error: { - intl: { - id: t('login.noPassword'), - defaultMessage: 'Please enter your password', - }, - }, - }); - return; - } - - this.signIn(); - }); - }); - - scheduleSessionExpiredNotification = () => { - const {intl} = this.context; - const {actions} = this.props; - - actions.scheduleExpiredNotification(intl); - }; - - signIn = () => { - const {actions, loginId, password} = this.props; - const {isLoading} = this.state; - if (isLoading) { - actions.login(loginId.toLowerCase(), password). - then(this.checkLoginResponse); - } - }; - checkLoginResponse = (data) => { if (mfaExpectedErrors.includes(data?.error?.server_error_id)) { // eslint-disable-line camelcase this.goToMfa(); @@ -228,6 +151,14 @@ export default class Login extends PureComponent { return ''; } + forgotPassword = () => { + const {intl} = this.context; + const screen = 'ForgotPassword'; + const title = intl.formatMessage({id: 'password_form.title', defaultMessage: 'Password Reset'}); + + goToScreen(screen, title); + } + getLoginErrorMessage = (error) => { return ( this.getServerErrorForLogin(error) || @@ -274,6 +205,10 @@ export default class Login extends PureComponent { this.loginId = ref; }; + orientationDidChange = () => { + this.scroll.scrollToPosition(0, 0, true); + }; + passwordRef = (ref) => { this.passwd = ref; }; @@ -282,22 +217,98 @@ export default class Login extends PureComponent { this.passwd.focus(); }; - orientationDidChange = () => { - this.scroll.scrollToPosition(0, 0, true); + preSignIn = preventDoubleTap(() => { + this.setState({error: null, isLoading: true}); + Keyboard.dismiss(); + InteractionManager.runAfterInteractions(async () => { + const loginId = this.loginId?._lastNativeText; //eslint-disable-line no-underscore-dangle + if (!loginId) { + t('login.noEmail'); + t('login.noEmailLdapUsername'); + t('login.noEmailUsername'); + t('login.noEmailUsernameLdapUsername'); + t('login.noLdapUsername'); + t('login.noUsername'); + t('login.noUsernameLdapUsername'); + + // it's slightly weird to be constructing the message ID, but it's a bit nicer than triply nested if statements + let msgId = 'login.no'; + if (this.props.config.EnableSignInWithEmail === 'true') { + msgId += 'Email'; + } + if (this.props.config.EnableSignInWithUsername === 'true') { + msgId += 'Username'; + } + if (this.props.license.IsLicensed === 'true' && this.props.config.EnableLdap === 'true') { + msgId += 'LdapUsername'; + } + + this.setState({ + isLoading: false, + error: { + intl: { + id: msgId, + defaultMessage: '', + values: { + ldapUsername: this.props.config.LdapLoginFieldName || + this.context.intl.formatMessage({ + id: 'login.ldapUsernameLower', + defaultMessage: 'AD/LDAP username', + }), + }, + }, + }, + }); + return; + } + + const password = this.passwd?._lastNativeText; //eslint-disable-line no-underscore-dangle + if (!password) { + this.setState({ + isLoading: false, + error: { + intl: { + id: t('login.noPassword'), + defaultMessage: 'Please enter your password', + }, + }, + }); + return; + } + + this.signIn(); + }); + }); + + scheduleSessionExpiredNotification = () => { + const {intl} = this.context; + const {actions} = this.props; + + actions.scheduleExpiredNotification(intl); }; scrollRef = (ref) => { this.scroll = ref; }; - forgotPassword = () => { - const {intl} = this.context; - const screen = 'ForgotPassword'; - const title = intl.formatMessage({id: 'password_form.title', defaultMessage: 'Password Reset'}); - - goToScreen(screen, title); + setEmmUsernameIfAvailable = async () => { + const managedConfig = await mattermostManaged.getConfig(); + if (managedConfig?.username && this.loginId) { + this.loginId.setNativeProps({text: 'sample'}); + } } + signIn = () => { + const loginId = this.loginId?._lastNativeText; //eslint-disable-line no-underscore-dangle + const password = this.passwd?._lastNativeText; //eslint-disable-line no-underscore-dangle + const {actions} = this.props; + const {isLoading} = this.state; + if (isLoading) { + actions.login(loginId.toLowerCase(), password). + then(this.checkLoginResponse); + } + }; + render() { const {isLoading} = this.state; @@ -383,8 +394,6 @@ export default class Login extends PureComponent { { license: { IsLicensed: 'false', }, - loginId: '', - password: '', loginRequest: {}, actions: { - handleLoginIdChanged: jest.fn(), - handlePasswordChanged: jest.fn(), handleSuccessfulLogin: jest.fn(), scheduleExpiredNotification: jest.fn(), login: jest.fn(), diff --git a/app/screens/mfa/index.js b/app/screens/mfa/index.js index caf034c41..3f2c548a3 100644 --- a/app/screens/mfa/index.js +++ b/app/screens/mfa/index.js @@ -8,14 +8,6 @@ import {login} from 'app/actions/views/user'; import Mfa from './mfa'; -function mapStateToProps(state) { - const {loginId, password} = state.views.login; - return { - loginId, - password, - }; -} - function mapDispatchToProps(dispatch) { return { actions: bindActionCreators({ @@ -24,4 +16,4 @@ function mapDispatchToProps(dispatch) { }; } -export default connect(mapStateToProps, mapDispatchToProps)(Mfa); +export default connect(null, mapDispatchToProps)(Mfa); diff --git a/app/store/store.js b/app/store/store.js index 1470c89f5..a555c5fb4 100644 --- a/app/store/store.js +++ b/app/store/store.js @@ -53,7 +53,7 @@ const setTransforms = [ export default function configureAppStore(initialState) { const viewsBlackListFilter = createBlacklistFilter( 'views', - ['extension', 'login', 'root'], + ['extension', 'root'], ); const typingBlackListFilter = createBlacklistFilter( diff --git a/packager/moduleNames.js b/packager/moduleNames.js index fdfcc2d91..7172ac08b 100644 --- a/packager/moduleNames.js +++ b/packager/moduleNames.js @@ -47,7 +47,6 @@ module.exports = [ 'app/reducers/views/extension.js', 'app/reducers/views/i18n.js', 'app/reducers/views/index.js', - 'app/reducers/views/login.js', 'app/reducers/views/post.js', 'app/reducers/views/recent_emojis.js', 'app/reducers/views/root.js', diff --git a/packager/modulePaths.js b/packager/modulePaths.js index f7ff9dc4e..d12902e5d 100644 --- a/packager/modulePaths.js +++ b/packager/modulePaths.js @@ -47,7 +47,6 @@ module.exports = [ './node_modules/app/reducers/views/extension.js', './node_modules/app/reducers/views/i18n.js', './node_modules/app/reducers/views/index.js', - './node_modules/app/reducers/views/login.js', './node_modules/app/reducers/views/post.js', './node_modules/app/reducers/views/recent_emojis.js', './node_modules/app/reducers/views/root.js',