* MM-22292 Don't use redux to store username and password * Set login and password MFA props Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
This commit is contained in:
parent
03d406021f
commit
d100c7d95d
13 changed files with 104 additions and 205 deletions
|
|
@ -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,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -136,10 +136,6 @@ const state = {
|
|||
i18n: {
|
||||
locale: '',
|
||||
},
|
||||
login: {
|
||||
loginId: '',
|
||||
password: '',
|
||||
},
|
||||
root: {
|
||||
deepLinkURL: '',
|
||||
hydrationComplete: false,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
});
|
||||
|
|
@ -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 {
|
|||
<ErrorText error={this.state.error}/>
|
||||
<TextInput
|
||||
ref={this.loginRef}
|
||||
value={this.props.loginId}
|
||||
onChangeText={this.props.actions.handleLoginIdChanged}
|
||||
style={GlobalStyles.inputBox}
|
||||
placeholder={this.createLoginPlaceholder()}
|
||||
placeholderTextColor={changeOpacity('#000', 0.5)}
|
||||
|
|
@ -399,8 +408,6 @@ export default class Login extends PureComponent {
|
|||
/>
|
||||
<TextInput
|
||||
ref={this.passwordRef}
|
||||
value={this.props.password}
|
||||
onChangeText={this.props.actions.handlePasswordChanged}
|
||||
style={GlobalStyles.inputBox}
|
||||
placeholder={this.context.intl.formatMessage({id: 'login.password', defaultMessage: 'Password'})}
|
||||
placeholderTextColor={changeOpacity('#000', 0.5)}
|
||||
|
|
|
|||
|
|
@ -21,12 +21,8 @@ describe('Login', () => {
|
|||
license: {
|
||||
IsLicensed: 'false',
|
||||
},
|
||||
loginId: '',
|
||||
password: '',
|
||||
loginRequest: {},
|
||||
actions: {
|
||||
handleLoginIdChanged: jest.fn(),
|
||||
handlePasswordChanged: jest.fn(),
|
||||
handleSuccessfulLogin: jest.fn(),
|
||||
scheduleExpiredNotification: jest.fn(),
|
||||
login: jest.fn(),
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ const setTransforms = [
|
|||
export default function configureAppStore(initialState) {
|
||||
const viewsBlackListFilter = createBlacklistFilter(
|
||||
'views',
|
||||
['extension', 'login', 'root'],
|
||||
['extension', 'root'],
|
||||
);
|
||||
|
||||
const typingBlackListFilter = createBlacklistFilter(
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
Loading…
Reference in a new issue