[NEW] OAuth (#241)
* Layout * tmp * test iscordova * Webview redirecting * Open and Close login actions * Login services saved on redux * OAuth Github * Server regex fix * OAuth modal style * - Twitter login - Remove services from redux - Open login saga fix * - Facebook login - Fixed user agent - Reactions fix - Message url unique key fix * Google login * Email keyboard removed from messagebox * - Login buttons refactored - RoomList header * Layout improvements * Meteor login redirect_uri changed * fix * Random credentialToken state
This commit is contained in:
parent
dba0e16da7
commit
477609375c
|
@ -20,7 +20,11 @@ export const LOGIN = createRequestTypes('LOGIN', [
|
||||||
'REGISTER_INCOMPLETE',
|
'REGISTER_INCOMPLETE',
|
||||||
'SET_USERNAME_SUBMIT',
|
'SET_USERNAME_SUBMIT',
|
||||||
'SET_USERNAME_REQUEST',
|
'SET_USERNAME_REQUEST',
|
||||||
'SET_USERNAME_SUCCESS'
|
'SET_USERNAME_SUCCESS',
|
||||||
|
'OPEN',
|
||||||
|
'CLOSE',
|
||||||
|
'SET_SERVICES',
|
||||||
|
'REMOVE_SERVICES'
|
||||||
]);
|
]);
|
||||||
export const FORGOT_PASSWORD = createRequestTypes('FORGOT_PASSWORD', [
|
export const FORGOT_PASSWORD = createRequestTypes('FORGOT_PASSWORD', [
|
||||||
...defaultTypes,
|
...defaultTypes,
|
||||||
|
|
|
@ -13,7 +13,6 @@ export function loginRequest(credentials) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export function registerSubmit(credentials) {
|
export function registerSubmit(credentials) {
|
||||||
return {
|
return {
|
||||||
type: types.LOGIN.REGISTER_SUBMIT,
|
type: types.LOGIN.REGISTER_SUBMIT,
|
||||||
|
@ -125,3 +124,28 @@ export function setUser(action) {
|
||||||
...action
|
...action
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function open() {
|
||||||
|
return {
|
||||||
|
type: types.LOGIN.OPEN
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function close() {
|
||||||
|
return {
|
||||||
|
type: types.LOGIN.CLOSE
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setLoginServices(data) {
|
||||||
|
return {
|
||||||
|
type: types.LOGIN.SET_SERVICES,
|
||||||
|
data
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function removeLoginServices() {
|
||||||
|
return {
|
||||||
|
type: types.LOGIN.REMOVE_SERVICES
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -460,7 +460,6 @@ export default class MessageBox extends React.PureComponent {
|
||||||
<TextInput
|
<TextInput
|
||||||
ref={component => this.component = component}
|
ref={component => this.component = component}
|
||||||
style={styles.textBoxInput}
|
style={styles.textBoxInput}
|
||||||
keyboardType='email-address'
|
|
||||||
returnKeyType='default'
|
returnKeyType='default'
|
||||||
blurOnSubmit={false}
|
blurOnSubmit={false}
|
||||||
placeholder='New Message'
|
placeholder='New Message'
|
||||||
|
|
|
@ -165,7 +165,7 @@ export default class Message extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.props.item.urls.map(url => (
|
return this.props.item.urls.map(url => (
|
||||||
<Url url={url} key={url._id} />
|
<Url url={url} key={url.url} />
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ import messagesStatus from '../constants/messagesStatus';
|
||||||
import database from './realm';
|
import database from './realm';
|
||||||
import * as actions from '../actions';
|
import * as actions from '../actions';
|
||||||
import { someoneTyping, roomMessageReceived } from '../actions/room';
|
import { someoneTyping, roomMessageReceived } from '../actions/room';
|
||||||
import { setUser } from '../actions/login';
|
import { setUser, setLoginServices, removeLoginServices } from '../actions/login';
|
||||||
import { disconnect, disconnect_by_user, connectSuccess, connectFailure } from '../actions/connect';
|
import { disconnect, disconnect_by_user, connectSuccess, connectFailure } from '../actions/connect';
|
||||||
import { requestActiveUser } from '../actions/activeUsers';
|
import { requestActiveUser } from '../actions/activeUsers';
|
||||||
import { starredMessageReceived, starredMessageUnstarred } from '../actions/starredMessages';
|
import { starredMessageReceived, starredMessageUnstarred } from '../actions/starredMessages';
|
||||||
|
@ -27,6 +27,8 @@ const SERVER_TIMEOUT = 30000;
|
||||||
const normalizeMessage = (lastMessage) => {
|
const normalizeMessage = (lastMessage) => {
|
||||||
if (lastMessage) {
|
if (lastMessage) {
|
||||||
lastMessage.attachments = lastMessage.attachments || [];
|
lastMessage.attachments = lastMessage.attachments || [];
|
||||||
|
lastMessage.reactions = _.map(lastMessage.reactions, (value, key) =>
|
||||||
|
({ emoji: key, usernames: value.usernames.map(username => ({ value: username })) }));
|
||||||
}
|
}
|
||||||
return lastMessage;
|
return lastMessage;
|
||||||
};
|
};
|
||||||
|
@ -95,10 +97,11 @@ const RocketChat = {
|
||||||
this.ddp.on('disconnected', () => {
|
this.ddp.on('disconnected', () => {
|
||||||
reduxStore.dispatch(disconnect());
|
reduxStore.dispatch(disconnect());
|
||||||
});
|
});
|
||||||
this.ddp.on('open', async() => {
|
// this.ddp.on('open', async() => {
|
||||||
resolve(reduxStore.dispatch(connectSuccess()));
|
// resolve(reduxStore.dispatch(connectSuccess()));
|
||||||
});
|
// });
|
||||||
this.ddp.on('connected', () => {
|
this.ddp.on('connected', () => {
|
||||||
|
resolve(reduxStore.dispatch(connectSuccess()));
|
||||||
RocketChat.getSettings();
|
RocketChat.getSettings();
|
||||||
RocketChat.getPermissions();
|
RocketChat.getPermissions();
|
||||||
RocketChat.getCustomEmoji();
|
RocketChat.getCustomEmoji();
|
||||||
|
@ -171,6 +174,28 @@ const RocketChat = {
|
||||||
return reduxStore.dispatch(pinnedMessageUnpinned(ddpMessage.id));
|
return reduxStore.dispatch(pinnedMessageUnpinned(ddpMessage.id));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.ddp.on('meteor_accounts_loginServiceConfiguration', (ddpMessage) => {
|
||||||
|
if (ddpMessage.msg === 'added') {
|
||||||
|
this.loginServices = this.loginServices || {};
|
||||||
|
if (this.loginServiceTimer) {
|
||||||
|
clearTimeout(this.loginServiceTimer);
|
||||||
|
this.loginServiceTimer = null;
|
||||||
|
}
|
||||||
|
this.loginServiceTimer = setTimeout(() => {
|
||||||
|
reduxStore.dispatch(setLoginServices(this.loginServices));
|
||||||
|
this.loginServiceTimer = null;
|
||||||
|
return this.loginServices = {};
|
||||||
|
}, 1000);
|
||||||
|
this.loginServices[ddpMessage.fields.service] = { ...ddpMessage.fields };
|
||||||
|
delete this.loginServices[ddpMessage.fields.service].service;
|
||||||
|
} else if (ddpMessage.msg === 'removed') {
|
||||||
|
if (this.loginServiceTimer) {
|
||||||
|
clearTimeout(this.loginServiceTimer);
|
||||||
|
}
|
||||||
|
this.loginServiceTimer = setTimeout(() => reduxStore.dispatch(removeLoginServices()), 1000);
|
||||||
|
}
|
||||||
|
});
|
||||||
}).catch(console.log);
|
}).catch(console.log);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -302,8 +327,6 @@ const RocketChat = {
|
||||||
// loadHistory returns message.starred as object
|
// loadHistory returns message.starred as object
|
||||||
// stream-room-messages returns message.starred as an array
|
// stream-room-messages returns message.starred as an array
|
||||||
message.starred = message.starred && (Array.isArray(message.starred) ? message.starred.length > 0 : !!message.starred);
|
message.starred = message.starred && (Array.isArray(message.starred) ? message.starred.length > 0 : !!message.starred);
|
||||||
message.reactions = _.map(message.reactions, (value, key) =>
|
|
||||||
({ emoji: key, usernames: value.usernames.map(username => ({ value: username })) }));
|
|
||||||
return message;
|
return message;
|
||||||
},
|
},
|
||||||
loadMessagesForRoom(rid, end, cb) {
|
loadMessagesForRoom(rid, end, cb) {
|
||||||
|
|
|
@ -6,7 +6,8 @@ const initialState = {
|
||||||
isRegistering: false,
|
isRegistering: false,
|
||||||
token: '',
|
token: '',
|
||||||
user: {},
|
user: {},
|
||||||
error: ''
|
error: '',
|
||||||
|
services: {}
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function login(state = initialState, action) {
|
export default function login(state = initialState, action) {
|
||||||
|
@ -115,6 +116,19 @@ export default function login(state = initialState, action) {
|
||||||
...action
|
...action
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
case types.LOGIN.SET_SERVICES:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
services: {
|
||||||
|
...state.services,
|
||||||
|
...action.data
|
||||||
|
}
|
||||||
|
};
|
||||||
|
case types.LOGIN.REMOVE_SERVICES:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
services: {}
|
||||||
|
};
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { AsyncStorage } from 'react-native';
|
import { AsyncStorage } from 'react-native';
|
||||||
import { put, call, takeLatest, select, all } from 'redux-saga/effects';
|
import { put, call, takeLatest, select, all, take } from 'redux-saga/effects';
|
||||||
import * as types from '../actions/actionsTypes';
|
import * as types from '../actions/actionsTypes';
|
||||||
import {
|
import {
|
||||||
loginRequest,
|
loginRequest,
|
||||||
|
@ -21,7 +21,8 @@ import * as NavigationService from '../containers/routes/NavigationService';
|
||||||
|
|
||||||
const getUser = state => state.login;
|
const getUser = state => state.login;
|
||||||
const getServer = state => state.server.server;
|
const getServer = state => state.server.server;
|
||||||
const loginCall = args => (args.resume ? RocketChat.login(args) : RocketChat.loginWithPassword(args));
|
const getIsConnected = state => state.meteor.connected;
|
||||||
|
const loginCall = args => ((args.resume || args.oauth) ? RocketChat.login(args) : RocketChat.loginWithPassword(args));
|
||||||
const registerCall = args => RocketChat.register(args);
|
const registerCall = args => RocketChat.register(args);
|
||||||
const setUsernameCall = args => RocketChat.setUsername(args);
|
const setUsernameCall = args => RocketChat.setUsername(args);
|
||||||
const logoutCall = args => RocketChat.logout(args);
|
const logoutCall = args => RocketChat.logout(args);
|
||||||
|
@ -148,6 +149,16 @@ const handleForgotPasswordRequest = function* handleForgotPasswordRequest({ emai
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const watchLoginOpen = function* watchLoginOpen() {
|
||||||
|
const isConnected = yield select(getIsConnected);
|
||||||
|
if (!isConnected) {
|
||||||
|
yield take(types.METEOR.SUCCESS);
|
||||||
|
}
|
||||||
|
const sub = yield RocketChat.subscribe('meteor.loginServiceConfiguration');
|
||||||
|
yield take(types.LOGIN.CLOSE);
|
||||||
|
sub.unsubscribe().catch(e => alert(e));
|
||||||
|
};
|
||||||
|
|
||||||
const root = function* root() {
|
const root = function* root() {
|
||||||
yield takeLatest(types.METEOR.SUCCESS, handleLoginWhenServerChanges);
|
yield takeLatest(types.METEOR.SUCCESS, handleLoginWhenServerChanges);
|
||||||
yield takeLatest(types.LOGIN.REQUEST, handleLoginRequest);
|
yield takeLatest(types.LOGIN.REQUEST, handleLoginRequest);
|
||||||
|
@ -161,5 +172,6 @@ const root = function* root() {
|
||||||
yield takeLatest(types.LOGIN.SET_USERNAME_REQUEST, handleSetUsernameRequest);
|
yield takeLatest(types.LOGIN.SET_USERNAME_REQUEST, handleSetUsernameRequest);
|
||||||
yield takeLatest(types.LOGOUT, handleLogout);
|
yield takeLatest(types.LOGOUT, handleLogout);
|
||||||
yield takeLatest(types.FORGOT_PASSWORD.REQUEST, handleForgotPasswordRequest);
|
yield takeLatest(types.FORGOT_PASSWORD.REQUEST, handleForgotPasswordRequest);
|
||||||
|
yield takeLatest(types.LOGIN.OPEN, watchLoginOpen);
|
||||||
};
|
};
|
||||||
export default root;
|
export default root;
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
export default function random(length) {
|
||||||
|
let text = '';
|
||||||
|
const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||||
|
for (let i = 0; i < length; i += 1) {
|
||||||
|
text += possible.charAt(Math.floor(Math.random() * possible.length));
|
||||||
|
}
|
||||||
|
return text;
|
||||||
|
}
|
|
@ -1,23 +1,57 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Spinner from 'react-native-loading-spinner-overlay';
|
import Spinner from 'react-native-loading-spinner-overlay';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { Keyboard, Text, TextInput, View, ScrollView, TouchableOpacity, SafeAreaView } from 'react-native';
|
import { Keyboard, Text, TextInput, View, ScrollView, TouchableOpacity, SafeAreaView, WebView, Platform, LayoutAnimation } from 'react-native';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { bindActionCreators } from 'redux';
|
import Icon from 'react-native-vector-icons/FontAwesome';
|
||||||
import * as loginActions from '../actions/login';
|
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 KeyboardView from '../presentation/KeyboardView';
|
||||||
|
|
||||||
import styles from './Styles';
|
import styles from './Styles';
|
||||||
import scrollPersistTaps from '../utils/scrollPersistTaps';
|
import scrollPersistTaps from '../utils/scrollPersistTaps';
|
||||||
import { showToast } from '../utils/info';
|
import { showToast } from '../utils/info';
|
||||||
|
import random from '../utils/random';
|
||||||
|
|
||||||
class LoginView extends React.Component {
|
@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 = {
|
static propTypes = {
|
||||||
loginSubmit: PropTypes.func.isRequired,
|
loginSubmit: PropTypes.func.isRequired,
|
||||||
Accounts_EmailOrUsernamePlaceholder: PropTypes.string,
|
open: PropTypes.func.isRequired,
|
||||||
Accounts_PasswordPlaceholder: PropTypes.string,
|
close: PropTypes.func.isRequired,
|
||||||
|
navigation: PropTypes.object.isRequired,
|
||||||
login: PropTypes.object,
|
login: PropTypes.object,
|
||||||
navigation: PropTypes.object.isRequired
|
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 = () => ({
|
static navigationOptions = () => ({
|
||||||
|
@ -29,8 +63,99 @@ class LoginView extends React.Component {
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
username: '',
|
username: '',
|
||||||
password: ''
|
password: '',
|
||||||
|
modalVisible: false,
|
||||||
|
oAuthUrl: ''
|
||||||
};
|
};
|
||||||
|
this.redirectRegex = new RegExp(`(?=.*(${ this.props.server }))(?=.*(credentialToken))(?=.*(credentialSecret))`, 'g');
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillMount() {
|
||||||
|
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 = () => {
|
submit = () => {
|
||||||
|
@ -40,10 +165,14 @@ class LoginView extends React.Component {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.props.loginSubmit({ username, password, code });
|
this.props.loginSubmit({ username, password, code });
|
||||||
Keyboard.dismiss();
|
Keyboard.dismiss();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
submitOAuth = (code, credentialToken) => {
|
||||||
|
this.props.loginSubmit({ code, credentialToken });
|
||||||
|
}
|
||||||
|
|
||||||
register = () => {
|
register = () => {
|
||||||
this.props.navigation.navigate('Register');
|
this.props.navigation.navigate('Register');
|
||||||
}
|
}
|
||||||
|
@ -60,6 +189,10 @@ class LoginView extends React.Component {
|
||||||
this.props.navigation.navigate('ForgotPassword');
|
this.props.navigation.navigate('ForgotPassword');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
closeOAuth = () => {
|
||||||
|
this.setState({ modalVisible: false });
|
||||||
|
}
|
||||||
|
|
||||||
renderTOTP = () => {
|
renderTOTP = () => {
|
||||||
if (/totp/ig.test(this.props.login.error.error)) {
|
if (/totp/ig.test(this.props.login.error.error)) {
|
||||||
return (
|
return (
|
||||||
|
@ -82,88 +215,158 @@ class LoginView extends React.Component {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<KeyboardView
|
[
|
||||||
contentContainerStyle={styles.container}
|
<KeyboardView
|
||||||
keyboardVerticalOffset={128}
|
contentContainerStyle={styles.container}
|
||||||
>
|
keyboardVerticalOffset={128}
|
||||||
<ScrollView
|
key='login-view'
|
||||||
style={styles.loginView}
|
|
||||||
{...scrollPersistTaps}
|
|
||||||
>
|
>
|
||||||
<SafeAreaView>
|
<ScrollView
|
||||||
<View style={styles.formContainer}>
|
style={styles.loginView}
|
||||||
<TextInput
|
{...scrollPersistTaps}
|
||||||
style={styles.input_white}
|
>
|
||||||
onChangeText={username => this.setState({ username })}
|
<SafeAreaView>
|
||||||
keyboardType='email-address'
|
<View style={styles.formContainer}>
|
||||||
autoCorrect={false}
|
<TextInput
|
||||||
returnKeyType='next'
|
style={styles.input_white}
|
||||||
autoCapitalize='none'
|
onChangeText={username => this.setState({ username })}
|
||||||
underlineColorAndroid='transparent'
|
keyboardType='email-address'
|
||||||
onSubmitEditing={() => { this.password.focus(); }}
|
autoCorrect={false}
|
||||||
placeholder={this.props.Accounts_EmailOrUsernamePlaceholder || 'Email or username'}
|
returnKeyType='next'
|
||||||
/>
|
autoCapitalize='none'
|
||||||
<TextInput
|
underlineColorAndroid='transparent'
|
||||||
ref={(e) => { this.password = e; }}
|
onSubmitEditing={() => { this.password.focus(); }}
|
||||||
style={styles.input_white}
|
placeholder={this.props.Accounts_EmailOrUsernamePlaceholder || 'Email or username'}
|
||||||
onChangeText={password => this.setState({ password })}
|
/>
|
||||||
secureTextEntry
|
<TextInput
|
||||||
autoCorrect={false}
|
ref={(e) => { this.password = e; }}
|
||||||
returnKeyType='done'
|
style={styles.input_white}
|
||||||
autoCapitalize='none'
|
onChangeText={password => this.setState({ password })}
|
||||||
underlineColorAndroid='transparent'
|
secureTextEntry
|
||||||
onSubmitEditing={this.submit}
|
autoCorrect={false}
|
||||||
placeholder={this.props.Accounts_PasswordPlaceholder || 'Password'}
|
returnKeyType='done'
|
||||||
/>
|
autoCapitalize='none'
|
||||||
|
underlineColorAndroid='transparent'
|
||||||
|
onSubmitEditing={this.submit}
|
||||||
|
placeholder={this.props.Accounts_PasswordPlaceholder || 'Password'}
|
||||||
|
/>
|
||||||
|
|
||||||
{this.renderTOTP()}
|
{this.renderTOTP()}
|
||||||
|
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
style={styles.buttonContainer}
|
style={styles.buttonContainer}
|
||||||
onPress={this.submit}
|
onPress={this.submit}
|
||||||
>
|
>
|
||||||
<Text style={styles.button} accessibilityTraits='button'>LOGIN</Text>
|
<Text style={styles.button} accessibilityTraits='button'>LOGIN</Text>
|
||||||
</TouchableOpacity>
|
|
||||||
|
|
||||||
<View style={styles.loginSecondaryButtons}>
|
|
||||||
<TouchableOpacity style={styles.buttonContainer_inverted} onPress={this.register}>
|
|
||||||
<Text style={styles.button_inverted} accessibilityTraits='button'>REGISTER</Text>
|
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
|
|
||||||
<TouchableOpacity style={styles.buttonContainer_inverted} onPress={this.forgotPassword}>
|
<View style={styles.loginSecondaryButtons}>
|
||||||
<Text style={styles.button_inverted} accessibilityTraits='button'>FORGOT MY PASSWORD</Text>
|
<TouchableOpacity style={styles.buttonContainer_inverted} onPress={this.register}>
|
||||||
|
<Text style={styles.button_inverted} accessibilityTraits='button'>REGISTER</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
|
||||||
|
<TouchableOpacity style={styles.buttonContainer_inverted} onPress={this.forgotPassword}>
|
||||||
|
<Text style={styles.button_inverted} accessibilityTraits='button'>FORGOT MY PASSWORD</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.loginOAuthButtons} key='services'>
|
||||||
|
{this.props.Accounts_OAuth_Facebook && this.props.services.facebook &&
|
||||||
|
<TouchableOpacity
|
||||||
|
style={[styles.oauthButton, styles.facebookButton]}
|
||||||
|
onPress={this.onPressFacebook}
|
||||||
|
>
|
||||||
|
<Icon name='facebook' size={20} color='#ffffff' />
|
||||||
|
</TouchableOpacity>
|
||||||
|
}
|
||||||
|
{this.props.Accounts_OAuth_Github && this.props.services.github &&
|
||||||
|
<TouchableOpacity
|
||||||
|
style={[styles.oauthButton, styles.githubButton]}
|
||||||
|
onPress={this.onPressGithub}
|
||||||
|
>
|
||||||
|
<Icon name='github' size={20} color='#ffffff' />
|
||||||
|
</TouchableOpacity>
|
||||||
|
}
|
||||||
|
{this.props.Accounts_OAuth_Gitlab && this.props.services.gitlab &&
|
||||||
|
<TouchableOpacity
|
||||||
|
style={[styles.oauthButton, styles.gitlabButton]}
|
||||||
|
onPress={this.onPressGitlab}
|
||||||
|
>
|
||||||
|
<Icon name='gitlab' size={20} color='#ffffff' />
|
||||||
|
</TouchableOpacity>
|
||||||
|
}
|
||||||
|
{this.props.Accounts_OAuth_Google && this.props.services.google &&
|
||||||
|
<TouchableOpacity
|
||||||
|
style={[styles.oauthButton, styles.googleButton]}
|
||||||
|
onPress={this.onPressGoogle}
|
||||||
|
>
|
||||||
|
<Icon name='google' size={20} color='#ffffff' />
|
||||||
|
</TouchableOpacity>
|
||||||
|
}
|
||||||
|
{this.props.Accounts_OAuth_Linkedin && this.props.services.linkedin &&
|
||||||
|
<TouchableOpacity
|
||||||
|
style={[styles.oauthButton, styles.linkedinButton]}
|
||||||
|
onPress={this.onPressLinkedin}
|
||||||
|
>
|
||||||
|
<Icon name='linkedin' size={20} color='#ffffff' />
|
||||||
|
</TouchableOpacity>
|
||||||
|
}
|
||||||
|
{this.props.Accounts_OAuth_Meteor && this.props.services['meteor-developer'] &&
|
||||||
|
<TouchableOpacity
|
||||||
|
style={[styles.oauthButton, styles.meteorButton]}
|
||||||
|
onPress={this.onPressMeteor}
|
||||||
|
>
|
||||||
|
<MaterialCommunityIcons name='meteor' size={25} color='#ffffff' />
|
||||||
|
</TouchableOpacity>
|
||||||
|
}
|
||||||
|
{this.props.Accounts_OAuth_Twitter && this.props.services.twitter &&
|
||||||
|
<TouchableOpacity
|
||||||
|
style={[styles.oauthButton, styles.twitterButton]}
|
||||||
|
onPress={this.onPressTwitter}
|
||||||
|
>
|
||||||
|
<Icon name='twitter' size={20} color='#ffffff' />
|
||||||
|
</TouchableOpacity>
|
||||||
|
}
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<TouchableOpacity>
|
||||||
|
<Text style={styles.loginTermsText} accessibilityTraits='button'>
|
||||||
|
By proceeding you are agreeing to our
|
||||||
|
<Text style={styles.link} onPress={this.termsService}> Terms of Service </Text>
|
||||||
|
and
|
||||||
|
<Text style={styles.link} onPress={this.privacyPolicy}> Privacy Policy</Text>
|
||||||
|
</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
|
{this.props.login.failure && <Text style={styles.error}>{this.props.login.error.reason}</Text>}
|
||||||
</View>
|
</View>
|
||||||
|
<Spinner visible={this.props.login.isFetching} textContent='Loading...' textStyle={{ color: '#FFF' }} />
|
||||||
<TouchableOpacity>
|
</SafeAreaView>
|
||||||
<Text style={styles.loginTermsText} accessibilityTraits='button'>
|
</ScrollView>
|
||||||
By proceeding you are agreeing to our
|
</KeyboardView>,
|
||||||
<Text style={styles.link} onPress={this.termsService}> Terms of Service </Text>
|
<Modal
|
||||||
and
|
key='modal-oauth'
|
||||||
<Text style={styles.link} onPress={this.privacyPolicy}> Privacy Policy</Text>
|
visible={this.state.modalVisible}
|
||||||
</Text>
|
animationType='slide'
|
||||||
</TouchableOpacity>
|
style={styles.oAuthModal}
|
||||||
{this.props.login.failure && <Text style={styles.error}>{this.props.login.error.reason}</Text>}
|
onBackButtonPress={this.closeOAuth}
|
||||||
</View>
|
useNativeDriver
|
||||||
<Spinner visible={this.props.login.isFetching} textContent='Loading...' textStyle={{ color: '#FFF' }} />
|
>
|
||||||
</SafeAreaView>
|
<WebView
|
||||||
</ScrollView>
|
source={{ uri: this.state.oAuthUrl }}
|
||||||
</KeyboardView>
|
userAgent={Platform.OS === 'ios' ? 'UserAgent' : 'Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_1 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/10.0 Mobile/14E304 Safari/602.1'}
|
||||||
|
onNavigationStateChange={(webViewState) => {
|
||||||
|
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 });
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Icon name='close' size={30} style={styles.closeOAuth} onPress={this.closeOAuth} />
|
||||||
|
</Modal>
|
||||||
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapStateToProps(state) {
|
|
||||||
return {
|
|
||||||
server: state.server.server,
|
|
||||||
Accounts_EmailOrUsernamePlaceholder: state.settings.Accounts_EmailOrUsernamePlaceholder,
|
|
||||||
Accounts_PasswordPlaceholder: state.settings.Accounts_PasswordPlaceholder,
|
|
||||||
login: state.login
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function mapDispatchToProps(dispatch) {
|
|
||||||
return bindActionCreators(loginActions, dispatch);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(LoginView);
|
|
||||||
|
|
|
@ -241,7 +241,7 @@ export default class RoomsListView extends React.Component {
|
||||||
dataSource={this.state.dataSource}
|
dataSource={this.state.dataSource}
|
||||||
style={styles.list}
|
style={styles.list}
|
||||||
renderItem={this.renderItem}
|
renderItem={this.renderItem}
|
||||||
renderHeader={Platform.OS === 'ios' ? this.renderSearchBar : null}
|
ListHeaderComponent={Platform.OS === 'ios' ? this.renderSearchBar : null}
|
||||||
contentOffset={Platform.OS === 'ios' ? { x: 0, y: 38 } : {}}
|
contentOffset={Platform.OS === 'ios' ? { x: 0, y: 38 } : {}}
|
||||||
enableEmptySections
|
enableEmptySections
|
||||||
keyboardShouldPersistTaps='always'
|
keyboardShouldPersistTaps='always'
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { StyleSheet, Dimensions } from 'react-native';
|
import { StyleSheet, Dimensions, Platform } from 'react-native';
|
||||||
|
|
||||||
export default StyleSheet.create({
|
export default StyleSheet.create({
|
||||||
container: {
|
container: {
|
||||||
|
@ -157,6 +157,11 @@ export default StyleSheet.create({
|
||||||
flexWrap: 'wrap',
|
flexWrap: 'wrap',
|
||||||
justifyContent: 'space-around'
|
justifyContent: 'space-around'
|
||||||
},
|
},
|
||||||
|
loginOAuthButtons: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
justifyContent: 'center'
|
||||||
|
},
|
||||||
validText: {
|
validText: {
|
||||||
color: 'green'
|
color: 'green'
|
||||||
},
|
},
|
||||||
|
@ -165,5 +170,43 @@ export default StyleSheet.create({
|
||||||
},
|
},
|
||||||
validatingText: {
|
validatingText: {
|
||||||
color: '#aaa'
|
color: '#aaa'
|
||||||
|
},
|
||||||
|
oauthButton: {
|
||||||
|
width: 50,
|
||||||
|
height: 50,
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
margin: 4,
|
||||||
|
borderRadius: 4
|
||||||
|
},
|
||||||
|
facebookButton: {
|
||||||
|
backgroundColor: '#3b5998'
|
||||||
|
},
|
||||||
|
githubButton: {
|
||||||
|
backgroundColor: '#4c4c4c'
|
||||||
|
},
|
||||||
|
gitlabButton: {
|
||||||
|
backgroundColor: '#373d47'
|
||||||
|
},
|
||||||
|
googleButton: {
|
||||||
|
backgroundColor: '#dd4b39'
|
||||||
|
},
|
||||||
|
linkedinButton: {
|
||||||
|
backgroundColor: '#1b86bc'
|
||||||
|
},
|
||||||
|
meteorButton: {
|
||||||
|
backgroundColor: '#de4f4f'
|
||||||
|
},
|
||||||
|
twitterButton: {
|
||||||
|
backgroundColor: '#02acec'
|
||||||
|
},
|
||||||
|
closeOAuth: {
|
||||||
|
position: 'absolute',
|
||||||
|
left: 5,
|
||||||
|
top: Platform.OS === 'ios' ? 20 : 0,
|
||||||
|
backgroundColor: 'transparent'
|
||||||
|
},
|
||||||
|
oAuthModal: {
|
||||||
|
margin: 0
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -27,6 +27,10 @@
|
||||||
initialProperties:nil
|
initialProperties:nil
|
||||||
launchOptions:launchOptions];
|
launchOptions:launchOptions];
|
||||||
rootView.backgroundColor = [[UIColor alloc] initWithRed:1.0f green:1.0f blue:1.0f alpha:1];
|
rootView.backgroundColor = [[UIColor alloc] initWithRed:1.0f green:1.0f blue:1.0f alpha:1];
|
||||||
|
|
||||||
|
NSString *newAgent = @"Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_1 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/10.0 Mobile/14E304 Safari/602.1";
|
||||||
|
NSDictionary *dictionary = [[NSDictionary alloc] initWithObjectsAndKeys:newAgent, @"UserAgent", nil];
|
||||||
|
[[NSUserDefaults standardUserDefaults] registerDefaults:dictionary];
|
||||||
|
|
||||||
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
|
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
|
||||||
UIViewController *rootViewController = [UIViewController new];
|
UIViewController *rootViewController = [UIViewController new];
|
||||||
|
|
|
@ -9073,6 +9073,11 @@
|
||||||
"jsdom": "11.6.2"
|
"jsdom": "11.6.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"js-base64": {
|
||||||
|
"version": "2.4.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.4.3.tgz",
|
||||||
|
"integrity": "sha512-H7ErYLM34CvDMto3GbD6xD0JLUGYXR3QTcH6B/tr4Hi/QpSThnCsIp+Sy5FRTw3B0d6py4HcNkW7nO/wdtGWEw=="
|
||||||
|
},
|
||||||
"jest-environment-node": {
|
"jest-environment-node": {
|
||||||
"version": "22.3.0",
|
"version": "22.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-22.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-22.3.0.tgz",
|
||||||
|
|
|
@ -30,6 +30,7 @@
|
||||||
"babel-preset-expo": "^4.0.0",
|
"babel-preset-expo": "^4.0.0",
|
||||||
"deep-equal": "^1.0.1",
|
"deep-equal": "^1.0.1",
|
||||||
"ejson": "^2.1.2",
|
"ejson": "^2.1.2",
|
||||||
|
"js-base64": "^2.4.3",
|
||||||
"lodash": "^4.17.4",
|
"lodash": "^4.17.4",
|
||||||
"moment": "^2.20.1",
|
"moment": "^2.20.1",
|
||||||
"prop-types": "^15.6.0",
|
"prop-types": "^15.6.0",
|
||||||
|
|
Loading…
Reference in New Issue