import React from 'react'; import PropTypes from 'prop-types'; import { Text, View, ScrollView, Image, StyleSheet, Animated, Easing } from 'react-native'; import { connect } from 'react-redux'; import { Base64 } from 'js-base64'; import { SafeAreaView } from 'react-navigation'; import { RectButton, BorderlessButton } from 'react-native-gesture-handler'; import equal from 'deep-equal'; import sharedStyles from './Styles'; import scrollPersistTaps from '../utils/scrollPersistTaps'; import random from '../utils/random'; import Button from '../containers/Button'; import I18n from '../i18n'; import { LegalButton } from '../containers/HeaderButton'; import StatusBar from '../containers/StatusBar'; import { COLOR_SEPARATOR, COLOR_BORDER } from '../constants/colors'; const styles = StyleSheet.create({ container: { paddingVertical: 30 }, safeArea: { paddingBottom: 30 }, serviceButton: { borderRadius: 2, marginBottom: 10 }, serviceButtonContainer: { borderRadius: 2, borderWidth: 1, borderColor: COLOR_BORDER, width: '100%', height: 48, flexDirection: 'row', alignItems: 'center', justifyContent: 'center', paddingHorizontal: 15 }, serviceIcon: { position: 'absolute', left: 15, top: 12, width: 24, height: 24 }, serviceText: { ...sharedStyles.textRegular, ...sharedStyles.textColorNormal, fontSize: 16 }, serviceName: { ...sharedStyles.textBold }, servicesTogglerContainer: { flexDirection: 'row', alignItems: 'center', marginTop: 5, marginBottom: 30 }, servicesToggler: { width: 32, height: 31 }, separatorContainer: { marginTop: 5, marginBottom: 15 }, separatorLine: { flex: 1, height: 1, backgroundColor: COLOR_SEPARATOR }, separatorLineLeft: { marginRight: 15 }, separatorLineRight: { marginLeft: 15 }, inverted: { transform: [{ scaleY: -1 }] } }); const SERVICE_HEIGHT = 58; const SERVICES_COLLAPSED_HEIGHT = 174; class LoginSignupView extends React.Component { static navigationOptions = ({ navigation }) => { const title = navigation.getParam('title', 'Rocket.Chat'); return { title, headerRight: }; } static propTypes = { navigation: PropTypes.object, server: PropTypes.string, services: PropTypes.object, Site_Name: PropTypes.string, Gitlab_URL: PropTypes.string, CAS_enabled: PropTypes.bool, CAS_login_url: PropTypes.string } constructor(props) { super(props); this.state = { collapsed: true, servicesHeight: new Animated.Value(SERVICES_COLLAPSED_HEIGHT) }; const { Site_Name } = this.props; this.setTitle(Site_Name); } shouldComponentUpdate(nextProps, nextState) { const { collapsed, servicesHeight } = this.state; const { server, Site_Name, services } = this.props; if (nextState.collapsed !== collapsed) { return true; } if (nextState.servicesHeight !== servicesHeight) { return true; } if (nextProps.server !== server) { return true; } if (nextProps.Site_Name !== Site_Name) { return true; } if (!equal(nextProps.services, services)) { return true; } return false; } componentDidUpdate(prevProps) { const { Site_Name } = this.props; if (Site_Name && prevProps.Site_Name !== Site_Name) { this.setTitle(Site_Name); } } setTitle = (title) => { const { navigation } = this.props; navigation.setParams({ title }); } onPressFacebook = () => { const { services, server } = this.props; const { clientId } = services.facebook; const endpoint = 'https://m.facebook.com/v2.9/dialog/oauth'; const redirect_uri = `${ server }/_oauth/facebook?close`; const scope = 'email'; const state = this.getOAuthState(); const params = `?client_id=${ clientId }&redirect_uri=${ redirect_uri }&scope=${ scope }&state=${ state }&display=touch`; this.openOAuth({ url: `${ endpoint }${ params }` }); } onPressGithub = () => { const { services, server } = this.props; const { clientId } = services.github; const endpoint = `https://github.com/login?client_id=${ clientId }&return_to=${ encodeURIComponent('/login/oauth/authorize') }`; const redirect_uri = `${ 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({ url: `${ endpoint }${ encodeURIComponent(params) }` }); } onPressGitlab = () => { const { services, server, Gitlab_URL } = this.props; const { clientId } = services.gitlab; const baseURL = Gitlab_URL ? Gitlab_URL.trim().replace(/\/*$/, '') : 'https://gitlab.com'; const endpoint = `${ baseURL }/oauth/authorize`; const redirect_uri = `${ 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({ url: `${ endpoint }${ params }` }); } onPressGoogle = () => { const { services, server } = this.props; const { clientId } = services.google; const endpoint = 'https://accounts.google.com/o/oauth2/auth'; const redirect_uri = `${ 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({ url: `${ endpoint }${ params }` }); } onPressLinkedin = () => { const { services, server } = this.props; const { clientId } = services.linkedin; const endpoint = 'https://www.linkedin.com/uas/oauth2/authorization'; const redirect_uri = `${ 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({ url: `${ endpoint }${ params }` }); } onPressMeteor = () => { const { services, server } = this.props; const { clientId } = services['meteor-developer']; const endpoint = 'https://www.meteor.com/oauth2/authorize'; const redirect_uri = `${ server }/_oauth/meteor-developer`; const state = this.getOAuthState(); const params = `?client_id=${ clientId }&redirect_uri=${ redirect_uri }&state=${ state }&response_type=code`; this.openOAuth({ url: `${ endpoint }${ params }` }); } onPressTwitter = () => { const { server } = this.props; const state = this.getOAuthState(); const url = `${ server }/_oauth/twitter/?requestTokenAndRedirect=true&state=${ state }`; this.openOAuth({ url }); } onPressCustomOAuth = (loginService) => { const { server } = this.props; const { serverURL, authorizePath, clientId, scope, service } = loginService; const redirectUri = `${ server }/_oauth/${ service }`; const state = this.getOAuthState(); const params = `?client_id=${ clientId }&redirect_uri=${ redirectUri }&response_type=code&state=${ state }&scope=${ scope }`; const domain = `${ serverURL }`; const absolutePath = `${ authorizePath }${ params }`; const url = absolutePath.includes(domain) ? absolutePath : domain + absolutePath; this.openOAuth({ url }); } onPressSaml = (loginService) => { const { server } = this.props; const { clientConfig } = loginService; const { provider } = clientConfig; const ssoToken = random(17); const url = `${ server }/_saml/authorize/${ provider }/${ ssoToken }`; this.openOAuth({ url, ssoToken, authType: 'saml' }); } onPressCas = () => { const { server, CAS_login_url } = this.props; const ssoToken = random(17); const url = `${ CAS_login_url }?service=${ server }/_cas/${ ssoToken }`; this.openOAuth({ url, ssoToken, authType: 'cas' }); } getOAuthState = () => { const credentialToken = random(43); return Base64.encodeURI(JSON.stringify({ loginStyle: 'popup', credentialToken, isCordova: true })); } openOAuth = ({ url, ssoToken, authType = 'oauth' }) => { const { navigation } = this.props; navigation.navigate('AuthenticationWebView', { url, authType, ssoToken }); } login = () => { const { navigation, Site_Name } = this.props; navigation.navigate('LoginView', { title: Site_Name }); } register = () => { const { navigation, Site_Name } = this.props; navigation.navigate('RegisterView', { title: Site_Name }); } transitionServicesTo = (height) => { const { servicesHeight } = this.state; if (this._animation) { this._animation.stop(); } this._animation = Animated.timing(servicesHeight, { toValue: height, duration: 300, easing: Easing.easeOutCubic }).start(); } toggleServices = () => { const { collapsed } = this.state; const { services } = this.props; const { length } = Object.values(services); if (collapsed) { this.transitionServicesTo(SERVICE_HEIGHT * length); } else { this.transitionServicesTo(SERVICES_COLLAPSED_HEIGHT); } this.setState(prevState => ({ collapsed: !prevState.collapsed })); } getSocialOauthProvider = (name) => { const oauthProviders = { facebook: this.onPressFacebook, github: this.onPressGithub, gitlab: this.onPressGitlab, google: this.onPressGoogle, linkedin: this.onPressLinkedin, 'meteor-developer': this.onPressMeteor, twitter: this.onPressTwitter }; return oauthProviders[name]; } renderServicesSeparator = () => { const { collapsed } = this.state; const { services } = this.props; const { length } = Object.values(services); if (length > 3) { return ( ); } return ( ); } renderItem = (service) => { let { name } = service; name = name === 'meteor-developer' ? 'meteor' : name; const icon = `icon_${ name }`; let onPress = () => {}; switch (service.authType) { case 'oauth': { onPress = this.getSocialOauthProvider(service.name); break; } case 'oauth_custom': { onPress = () => this.onPressCustomOAuth(service); break; } case 'saml': { onPress = () => this.onPressSaml(service); break; } case 'cas': { onPress = () => this.onPressCas(); break; } default: break; } name = name.charAt(0).toUpperCase() + name.slice(1); const { CAS_enabled } = this.props; let buttonText; if (service.service === 'saml' || (service.service === 'cas' && CAS_enabled)) { buttonText = {name}; } else { buttonText = ( <> {I18n.t('Continue_with')} {name} ); } return ( {service.authType === 'oauth' ? : null} {buttonText} ); } renderServices = () => { const { servicesHeight } = this.state; const { services } = this.props; const { length } = Object.values(services); const style = { overflow: 'hidden', height: servicesHeight }; if (length > 3) { return ( {Object.values(services).map(service => this.renderItem(service))} ); } return ( {Object.values(services).map(service => this.renderItem(service))} ); } render() { return ( {this.renderServices()} {this.renderServicesSeparator()}