import React from 'react'; import Spinner from 'react-native-loading-spinner-overlay'; import PropTypes from 'prop-types'; import { Keyboard, Text, TextInput, View, ScrollView, TouchableOpacity, SafeAreaView, WebView, Platform, LayoutAnimation } from 'react-native'; import { connect } from 'react-redux'; import Icon from 'react-native-vector-icons/FontAwesome'; import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons'; import { Base64 } from 'js-base64'; import Modal from 'react-native-modal'; import { loginSubmit, open, close } from '../actions/login'; import KeyboardView from '../presentation/KeyboardView'; import styles from './Styles'; import scrollPersistTaps from '../utils/scrollPersistTaps'; import { showToast } from '../utils/info'; import random from '../utils/random'; @connect(state => ({ server: state.server.server, login: state.login, Accounts_EmailOrUsernamePlaceholder: state.settings.Accounts_EmailOrUsernamePlaceholder, Accounts_PasswordPlaceholder: state.settings.Accounts_PasswordPlaceholder, Accounts_OAuth_Facebook: state.settings.Accounts_OAuth_Facebook, Accounts_OAuth_Github: state.settings.Accounts_OAuth_Github, Accounts_OAuth_Gitlab: state.settings.Accounts_OAuth_Gitlab, Accounts_OAuth_Google: state.settings.Accounts_OAuth_Google, Accounts_OAuth_Linkedin: state.settings.Accounts_OAuth_Linkedin, Accounts_OAuth_Meteor: state.settings.Accounts_OAuth_Meteor, Accounts_OAuth_Twitter: state.settings.Accounts_OAuth_Twitter, services: state.login.services }), dispatch => ({ loginSubmit: params => dispatch(loginSubmit(params)), open: () => dispatch(open()), close: () => dispatch(close()) })) export default class LoginView extends React.Component { static propTypes = { loginSubmit: PropTypes.func.isRequired, open: PropTypes.func.isRequired, close: PropTypes.func.isRequired, navigation: PropTypes.object.isRequired, login: PropTypes.object, server: PropTypes.string, Accounts_EmailOrUsernamePlaceholder: PropTypes.bool, Accounts_PasswordPlaceholder: PropTypes.string, Accounts_OAuth_Facebook: PropTypes.bool, Accounts_OAuth_Github: PropTypes.bool, Accounts_OAuth_Gitlab: PropTypes.bool, Accounts_OAuth_Google: PropTypes.bool, Accounts_OAuth_Linkedin: PropTypes.bool, Accounts_OAuth_Meteor: PropTypes.bool, Accounts_OAuth_Twitter: PropTypes.bool, services: PropTypes.object } static navigationOptions = () => ({ title: 'Login' }); constructor(props) { super(props); this.state = { username: '', password: '', modalVisible: false, showPassword: false, oAuthUrl: '' }; this.redirectRegex = new RegExp(`(?=.*(${ this.props.server }))(?=.*(credentialToken))(?=.*(credentialSecret))`, 'g'); } componentDidMount() { this.props.open(); } componentWillReceiveProps(nextProps) { if (this.props.services !== nextProps.services) { LayoutAnimation.easeInEaseOut(); } } componentWillUnmount() { this.props.close(); } onPressFacebook = () => { const { appId } = this.props.services.facebook; const endpoint = 'https://m.facebook.com/v2.9/dialog/oauth'; const redirect_uri = `${ this.props.server }/_oauth/facebook?close`; const scope = 'email'; const state = this.getOAuthState(); const params = `?client_id=${ appId }&redirect_uri=${ redirect_uri }&scope=${ scope }&state=${ state }&display=touch`; this.openOAuth(`${ endpoint }${ params }`); } onPressGithub = () => { const { clientId } = this.props.services.github; const endpoint = `https://github.com/login?client_id=${ clientId }&return_to=${ encodeURIComponent('/login/oauth/authorize') }`; const redirect_uri = `${ this.props.server }/_oauth/github?close`; const scope = 'user:email'; const state = this.getOAuthState(); const params = `?client_id=${ clientId }&redirect_uri=${ redirect_uri }&scope=${ scope }&state=${ state }`; this.openOAuth(`${ endpoint }${ encodeURIComponent(params) }`); } onPressGitlab = () => { const { clientId } = this.props.services.gitlab; const endpoint = 'https://gitlab.com/oauth/authorize'; const redirect_uri = `${ this.props.server }/_oauth/gitlab?close`; const scope = 'read_user'; const state = this.getOAuthState(); const params = `?client_id=${ clientId }&redirect_uri=${ redirect_uri }&scope=${ scope }&state=${ state }&response_type=code`; this.openOAuth(`${ endpoint }${ params }`); } onPressGoogle = () => { const { clientId } = this.props.services.google; const endpoint = 'https://accounts.google.com/o/oauth2/auth'; const redirect_uri = `${ this.props.server }/_oauth/google?close`; const scope = 'email'; const state = this.getOAuthState(); const params = `?client_id=${ clientId }&redirect_uri=${ redirect_uri }&scope=${ scope }&state=${ state }&response_type=code`; this.openOAuth(`${ endpoint }${ params }`); } onPressLinkedin = () => { const { clientId } = this.props.services.linkedin; const endpoint = 'https://www.linkedin.com/uas/oauth2/authorization'; const redirect_uri = `${ this.props.server }/_oauth/linkedin?close`; const scope = 'r_emailaddress'; const state = this.getOAuthState(); const params = `?client_id=${ clientId }&redirect_uri=${ redirect_uri }&scope=${ scope }&state=${ state }&response_type=code`; this.openOAuth(`${ endpoint }${ params }`); } onPressMeteor = () => { const { clientId } = this.props.services['meteor-developer']; const endpoint = 'https://www.meteor.com/oauth2/authorize'; const redirect_uri = `${ this.props.server }/_oauth/meteor-developer`; const state = this.getOAuthState(); const params = `?client_id=${ clientId }&redirect_uri=${ redirect_uri }&state=${ state }&response_type=code`; this.openOAuth(`${ endpoint }${ params }`); } onPressTwitter = () => { const state = this.getOAuthState(); const url = `${ this.props.server }/_oauth/twitter/?requestTokenAndRedirect=true&state=${ state }`; this.openOAuth(url); } getOAuthState = () => { const credentialToken = random(43); return Base64.encodeURI(JSON.stringify({ loginStyle: 'popup', credentialToken, isCordova: true })); } openOAuth = (oAuthUrl) => { this.setState({ oAuthUrl, modalVisible: true }); } submit = () => { const { username, password, code } = this.state; if (username.trim() === '' || password.trim() === '') { showToast('Email or password field is empty'); return; } this.props.loginSubmit({ username, password, code }); Keyboard.dismiss(); } submitOAuth = (code, credentialToken) => { this.props.loginSubmit({ code, credentialToken }); } register = () => { this.props.navigation.navigate('Register'); } termsService = () => { this.props.navigation.navigate('TermsService'); } privacyPolicy = () => { this.props.navigation.navigate('PrivacyPolicy'); } forgotPassword = () => { this.props.navigation.navigate('ForgotPassword'); } closeOAuth = () => { this.setState({ modalVisible: false }); } renderTOTP = () => { if (/totp/ig.test(this.props.login.error.error)) { return ( this.codeInput = ref} style={styles.input_white} onChangeText={code => this.setState({ code })} keyboardType='numeric' autoCorrect={false} returnKeyType='done' autoCapitalize='none' onSubmitEditing={this.submit} placeholder='Code' underlineColorAndroid='transparent' /> ); } return null; } render() { return ( [ this.setState({ username })} keyboardType='email-address' autoCorrect={false} returnKeyType='next' autoCapitalize='none' underlineColorAndroid='transparent' onSubmitEditing={() => { this.password.focus(); }} placeholder={this.props.Accounts_EmailOrUsernamePlaceholder || 'Email or username'} /> { this.password = e; }} style={styles.input_white} onChangeText={password => this.setState({ password })} secureTextEntry={!this.state.showPassword} autoCorrect={false} returnKeyType='done' autoCapitalize='none' underlineColorAndroid='transparent' onSubmitEditing={this.submit} placeholder={this.props.Accounts_PasswordPlaceholder || 'Password'} /> { this.setState({ showPassword: !this.state.showPassword }); }}/> {this.renderTOTP()} LOGIN REGISTER FORGOT MY PASSWORD {this.props.Accounts_OAuth_Facebook && this.props.services.facebook && } {this.props.Accounts_OAuth_Github && this.props.services.github && } {this.props.Accounts_OAuth_Gitlab && this.props.services.gitlab && } {this.props.Accounts_OAuth_Google && this.props.services.google && } {this.props.Accounts_OAuth_Linkedin && this.props.services.linkedin && } {this.props.Accounts_OAuth_Meteor && this.props.services['meteor-developer'] && } {this.props.Accounts_OAuth_Twitter && this.props.services.twitter && } By proceeding you are agreeing to our Terms of Service and Privacy Policy {this.props.login.failure && {this.props.login.error.reason}} , { const url = decodeURIComponent(webViewState.url); if (this.redirectRegex.test(url)) { const parts = url.split('#'); const credentials = JSON.parse(parts[1]); this.props.loginSubmit({ oauth: { ...credentials } }); this.setState({ modalVisible: false }); } }} /> ] ); } }