forked from Ivasoft/mattermost-mobile
460 lines
16 KiB
JavaScript
460 lines
16 KiB
JavaScript
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
// See LICENSE.txt for license information.
|
|
|
|
import React, {PureComponent} from 'react';
|
|
import PropTypes from 'prop-types';
|
|
import {intlShape} from 'react-intl';
|
|
import {
|
|
ActivityIndicator,
|
|
Dimensions,
|
|
Image,
|
|
InteractionManager,
|
|
Keyboard,
|
|
StyleSheet,
|
|
Text,
|
|
TextInput,
|
|
TouchableWithoutFeedback,
|
|
View,
|
|
} from 'react-native';
|
|
import Button from 'react-native-button';
|
|
import {KeyboardAwareScrollView} from 'react-native-keyboard-aware-scroll-view';
|
|
|
|
import ErrorText from 'app/components/error_text';
|
|
import FormattedText from 'app/components/formatted_text';
|
|
import StatusBar from 'app/components/status_bar';
|
|
import PushNotifications from 'app/push_notifications';
|
|
import {GlobalStyles} from 'app/styles';
|
|
import {preventDoubleTap} from 'app/utils/tap';
|
|
import tracker from 'app/utils/time_tracker';
|
|
import {t} from 'app/utils/i18n';
|
|
|
|
import {RequestStatus} from 'mattermost-redux/constants';
|
|
|
|
export default class Login extends PureComponent {
|
|
static propTypes = {
|
|
navigator: PropTypes.object,
|
|
theme: PropTypes.object,
|
|
actions: PropTypes.shape({
|
|
handleLoginIdChanged: PropTypes.func.isRequired,
|
|
handlePasswordChanged: PropTypes.func.isRequired,
|
|
handleSuccessfulLogin: PropTypes.func.isRequired,
|
|
getSession: PropTypes.func.isRequired,
|
|
checkMfa: PropTypes.func.isRequired,
|
|
login: PropTypes.func.isRequired,
|
|
}).isRequired,
|
|
config: PropTypes.object.isRequired,
|
|
license: PropTypes.object.isRequired,
|
|
loginId: PropTypes.string.isRequired,
|
|
password: PropTypes.string.isRequired,
|
|
checkMfaRequest: PropTypes.object.isRequired,
|
|
loginRequest: PropTypes.object.isRequired,
|
|
};
|
|
|
|
static contextTypes = {
|
|
intl: intlShape.isRequired,
|
|
};
|
|
|
|
constructor(props) {
|
|
super(props);
|
|
|
|
this.state = {
|
|
error: null,
|
|
};
|
|
}
|
|
|
|
componentWillMount() {
|
|
Dimensions.addEventListener('change', this.orientationDidChange);
|
|
}
|
|
|
|
componentWillReceiveProps(nextProps) {
|
|
if (this.props.loginRequest.status === RequestStatus.STARTED && nextProps.loginRequest.status === RequestStatus.SUCCESS) {
|
|
this.props.actions.handleSuccessfulLogin().then(this.props.actions.getSession).then(this.goToChannel);
|
|
} else if (this.props.loginRequest.status !== nextProps.loginRequest.status && nextProps.loginRequest.status !== RequestStatus.STARTED) {
|
|
this.setState({isLoading: false});
|
|
}
|
|
}
|
|
|
|
componentWillUnmount() {
|
|
Dimensions.removeEventListener('change', this.orientationDidChange);
|
|
}
|
|
|
|
goToChannel = (expiresAt) => {
|
|
const {intl} = this.context;
|
|
const {navigator} = this.props;
|
|
tracker.initialLoad = Date.now();
|
|
|
|
if (expiresAt) {
|
|
PushNotifications.localNotificationSchedule({
|
|
date: new Date(expiresAt),
|
|
message: intl.formatMessage({
|
|
id: 'mobile.session_expired',
|
|
defaultMessage: 'Session Expired: Please log in to continue receiving notifications.',
|
|
}),
|
|
userInfo: {
|
|
localNotification: true,
|
|
},
|
|
});
|
|
}
|
|
|
|
navigator.resetTo({
|
|
screen: 'Channel',
|
|
title: '',
|
|
animated: false,
|
|
backButtonTitle: '',
|
|
navigatorStyle: {
|
|
animated: true,
|
|
animationType: 'fade',
|
|
navBarHidden: true,
|
|
statusBarHidden: false,
|
|
statusBarHideWithNavBar: false,
|
|
screenBackgroundColor: 'transparent',
|
|
},
|
|
});
|
|
};
|
|
|
|
goToMfa = () => {
|
|
const {intl} = this.context;
|
|
const {navigator, theme} = this.props;
|
|
|
|
this.setState({isLoading: false});
|
|
|
|
navigator.push({
|
|
screen: 'MFA',
|
|
title: intl.formatMessage({id: 'mobile.routes.mfa', defaultMessage: 'Multi-factor Authentication'}),
|
|
animated: true,
|
|
backButtonTitle: '',
|
|
navigatorStyle: {
|
|
navBarTextColor: theme.sidebarHeaderTextColor,
|
|
navBarBackgroundColor: theme.sidebarHeaderBg,
|
|
navBarButtonColor: theme.sidebarHeaderTextColor,
|
|
screenBackgroundColor: theme.centerChannelBg,
|
|
},
|
|
});
|
|
};
|
|
|
|
blur = () => {
|
|
this.loginId.blur();
|
|
this.passwd.blur();
|
|
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;
|
|
}
|
|
|
|
if (this.props.config.EnableMultifactorAuthentication === 'true') {
|
|
const result = await this.props.actions.checkMfa(this.props.loginId);
|
|
if (result.data) {
|
|
this.goToMfa();
|
|
} else {
|
|
this.signIn();
|
|
}
|
|
} else {
|
|
this.signIn();
|
|
}
|
|
});
|
|
});
|
|
|
|
signIn = () => {
|
|
const {actions, loginId, loginRequest, password} = this.props;
|
|
if (loginRequest.status !== RequestStatus.STARTED) {
|
|
actions.login(loginId.toLowerCase(), password);
|
|
}
|
|
};
|
|
|
|
createLoginPlaceholder() {
|
|
const {formatMessage} = this.context.intl;
|
|
const license = this.props.license;
|
|
const config = this.props.config;
|
|
|
|
const loginPlaceholders = [];
|
|
if (config.EnableSignInWithEmail === 'true') {
|
|
loginPlaceholders.push(formatMessage({id: 'login.email', defaultMessage: 'Email'}));
|
|
}
|
|
|
|
if (config.EnableSignInWithUsername === 'true') {
|
|
loginPlaceholders.push(formatMessage({id: 'login.username', defaultMessage: 'Username'}));
|
|
}
|
|
|
|
if (license.IsLicensed === 'true' && license.LDAP === 'true' && config.EnableLdap === 'true') {
|
|
if (config.LdapLoginFieldName) {
|
|
loginPlaceholders.push(config.LdapLoginFieldName);
|
|
} else {
|
|
loginPlaceholders.push(formatMessage({id: 'login.ldapUsername', defaultMessage: 'AD/LDAP Username'}));
|
|
}
|
|
}
|
|
|
|
if (loginPlaceholders.length >= 2) {
|
|
return loginPlaceholders.slice(0, loginPlaceholders.length - 1).join(', ') +
|
|
` ${formatMessage({id: 'login.or', defaultMessage: 'or'})} ` +
|
|
loginPlaceholders[loginPlaceholders.length - 1];
|
|
} else if (loginPlaceholders.length === 1) {
|
|
return loginPlaceholders[0];
|
|
}
|
|
|
|
return '';
|
|
}
|
|
|
|
getLoginErrorMessage = () => {
|
|
return (
|
|
this.getServerErrorForLogin() ||
|
|
this.props.checkMfaRequest.error ||
|
|
this.state.error
|
|
);
|
|
};
|
|
|
|
getServerErrorForLogin = () => {
|
|
const {error} = this.props.loginRequest;
|
|
if (!error) {
|
|
return null;
|
|
}
|
|
const errorId = error.server_error_id;
|
|
if (!errorId) {
|
|
return error.message;
|
|
}
|
|
if (
|
|
errorId === 'store.sql_user.get_for_login.app_error' ||
|
|
errorId === 'ent.ldap.do_login.user_not_registered.app_error'
|
|
) {
|
|
return {
|
|
intl: {
|
|
id: t('login.userNotFound'),
|
|
defaultMessage: "We couldn't find an account matching your login credentials.",
|
|
},
|
|
};
|
|
} else if (
|
|
errorId === 'api.user.check_user_password.invalid.app_error' ||
|
|
errorId === 'ent.ldap.do_login.invalid_password.app_error'
|
|
) {
|
|
return {
|
|
intl: {
|
|
id: t('login.invalidPassword'),
|
|
defaultMessage: 'Your password is incorrect.',
|
|
},
|
|
};
|
|
}
|
|
return error.message;
|
|
};
|
|
|
|
loginRef = (ref) => {
|
|
this.loginId = ref;
|
|
};
|
|
|
|
passwordRef = (ref) => {
|
|
this.passwd = ref;
|
|
};
|
|
|
|
passwordFocus = () => {
|
|
this.passwd.focus();
|
|
};
|
|
|
|
orientationDidChange = () => {
|
|
this.scroll.scrollToPosition(0, 0, true);
|
|
};
|
|
|
|
scrollRef = (ref) => {
|
|
this.scroll = ref;
|
|
};
|
|
|
|
forgotPassword = () => {
|
|
const {intl} = this.context;
|
|
const {navigator, theme} = this.props;
|
|
navigator.push({
|
|
screen: 'ForgotPassword',
|
|
title: intl.formatMessage({id: 'password_form.title', defaultMessage: 'Password Reset'}),
|
|
animated: true,
|
|
backButtonTitle: '',
|
|
navigatorStyle: {
|
|
navBarTextColor: theme.sidebarHeaderTextColor,
|
|
navBarBackgroundColor: theme.sidebarHeaderBg,
|
|
navBarButtonColor: theme.sidebarHeaderTextColor,
|
|
screenBackgroundColor: theme.centerChannelBg,
|
|
},
|
|
});
|
|
}
|
|
|
|
render() {
|
|
const isLoading = this.props.loginRequest.status === RequestStatus.STARTED || this.state.isLoading;
|
|
|
|
let proceed;
|
|
if (isLoading) {
|
|
proceed = (
|
|
<ActivityIndicator
|
|
animating={true}
|
|
size='small'
|
|
/>
|
|
);
|
|
} else {
|
|
const additionalStyle = {};
|
|
if (this.props.config.EmailLoginButtonColor) {
|
|
additionalStyle.backgroundColor = this.props.config.EmailLoginButtonColor;
|
|
}
|
|
if (this.props.config.EmailLoginButtonBorderColor) {
|
|
additionalStyle.borderColor = this.props.config.EmailLoginButtonBorderColor;
|
|
}
|
|
|
|
const additionalTextStyle = {};
|
|
if (this.props.config.EmailLoginButtonTextColor) {
|
|
additionalTextStyle.color = this.props.config.EmailLoginButtonTextColor;
|
|
}
|
|
|
|
proceed = (
|
|
<Button
|
|
onPress={this.preSignIn}
|
|
containerStyle={[GlobalStyles.signupButton, additionalStyle]}
|
|
>
|
|
<FormattedText
|
|
id='login.signIn'
|
|
defaultMessage='Sign in'
|
|
style={[GlobalStyles.signupButtonText, additionalTextStyle]}
|
|
/>
|
|
</Button>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<View style={style.container}>
|
|
<StatusBar/>
|
|
<TouchableWithoutFeedback onPress={this.blur}>
|
|
<KeyboardAwareScrollView
|
|
ref={this.scrollRef}
|
|
style={style.container}
|
|
contentContainerStyle={style.innerContainer}
|
|
keyboardShouldPersistTaps='handled'
|
|
enableOnAndroid={true}
|
|
>
|
|
<Image
|
|
source={require('assets/images/logo.png')}
|
|
/>
|
|
<View>
|
|
<Text style={GlobalStyles.header}>
|
|
{this.props.config.SiteName}
|
|
</Text>
|
|
<FormattedText
|
|
style={GlobalStyles.subheader}
|
|
id='web.root.signup_info'
|
|
defaultMessage='All team communication in one place, searchable and accessible anywhere'
|
|
/>
|
|
</View>
|
|
<ErrorText error={this.getLoginErrorMessage()}/>
|
|
<TextInput
|
|
ref={this.loginRef}
|
|
value={this.props.loginId}
|
|
onChangeText={this.props.actions.handleLoginIdChanged}
|
|
style={GlobalStyles.inputBox}
|
|
placeholder={this.createLoginPlaceholder()}
|
|
autoCorrect={false}
|
|
autoCapitalize='none'
|
|
keyboardType='email-address'
|
|
returnKeyType='next'
|
|
underlineColorAndroid='transparent'
|
|
onSubmitEditing={this.passwordFocus}
|
|
blurOnSubmit={false}
|
|
disableFullscreenUI={true}
|
|
/>
|
|
<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'})}
|
|
secureTextEntry={true}
|
|
autoCorrect={false}
|
|
autoCapitalize='none'
|
|
underlineColorAndroid='transparent'
|
|
returnKeyType='go'
|
|
onSubmitEditing={this.preSignIn}
|
|
disableFullscreenUI={true}
|
|
/>
|
|
{proceed}
|
|
<Button
|
|
onPress={this.forgotPassword}
|
|
containerStyle={[style.forgotPasswordBtn]}
|
|
>
|
|
<FormattedText
|
|
id='login.forgot'
|
|
defaultMessage='I forgot my password'
|
|
style={style.forgotPasswordTxt}
|
|
/>
|
|
</Button>
|
|
</KeyboardAwareScrollView>
|
|
</TouchableWithoutFeedback>
|
|
</View>
|
|
);
|
|
}
|
|
}
|
|
|
|
const style = StyleSheet.create({
|
|
container: {
|
|
backgroundColor: '#FFFFFF',
|
|
flex: 1,
|
|
},
|
|
innerContainer: {
|
|
alignItems: 'center',
|
|
flexDirection: 'column',
|
|
justifyContent: 'center',
|
|
paddingHorizontal: 15,
|
|
paddingVertical: 50,
|
|
},
|
|
forgotPasswordBtn: {
|
|
borderColor: 'transparent',
|
|
marginTop: 15,
|
|
},
|
|
forgotPasswordTxt: {
|
|
color: '#2389D7',
|
|
},
|
|
});
|