[IMPROVEMENT] Share credentials with Rocket.Chat.iOS (#982)

*  Create user table

*  Introduce user table

* 🔥 Remove unused table

*  Add userdefaults to storage data

* 💚 Fix android build

*  Get credentials from iOS native client

* 🔥 Remove unused code

*  Revert sign xcode

* 🐛 Fix first login-logout

* 🎨 Use constants to UserDefaults Keys

* 🐛 Fix clear server-user-info on logout

* 🐛 Fix filter null value

* 🚑 Remove user object in logout

*  Fix get servers from native-client

* 🚑 Fix error on change server
This commit is contained in:
Djorkaeff Alexandre 2019-06-26 16:50:03 -03:00 committed by Diego Mello
parent 315d19d37a
commit 255ea84599
14 changed files with 166 additions and 32 deletions

View File

@ -0,0 +1,6 @@
export const SERVERS = 'kServers';
export const TOKEN = 'kAuthToken';
export const USER_ID = 'kUserId';
export const SERVER_URL = 'kAuthServerURL';
export const SERVER_NAME = 'kServerName';
export const SERVER_ICON = 'kServerIconURL';

View File

@ -4,6 +4,20 @@ import Realm from 'realm';
// Realm.clearTestState();
// AsyncStorage.clear();
const userSchema = {
name: 'user',
primaryKey: 'id',
properties: {
id: 'string',
token: { type: 'string', optional: true },
username: { type: 'string', optional: true },
name: { type: 'string', optional: true },
language: { type: 'string', optional: true },
status: { type: 'string', optional: true },
roles: { type: 'string[]', optional: true }
}
};
const serversSchema = {
name: 'servers',
primaryKey: 'id',
@ -370,6 +384,7 @@ class DB {
serversDB: new Realm({
path: 'default.realm',
schema: [
userSchema,
serversSchema
],
schemaVersion: 8,

View File

@ -1,6 +1,7 @@
import { AsyncStorage, InteractionManager } from 'react-native';
import semver from 'semver';
import { Rocketchat as RocketchatClient } from '@rocket.chat/sdk';
import RNUserDefaults from 'rn-user-defaults';
import reduxStore from './createStore';
import defaultSettings from '../constants/settings';
@ -36,6 +37,7 @@ import sendMessage, { getMessage, sendMessageCall } from './methods/sendMessage'
import { sendFileMessage, cancelUpload, isUploadActive } from './methods/sendFileMessage';
import { getDeviceToken } from '../notifications/push';
import { SERVERS, SERVER_URL } from '../constants/userDefaults';
const TOKEN_KEY = 'reactnativemeteor_usertoken';
const SORT_PREFS_KEY = 'RC_SORT_PREFS_KEY';
@ -58,9 +60,9 @@ const RocketChat = {
},
async getUserToken() {
try {
return await AsyncStorage.getItem(TOKEN_KEY);
return await RNUserDefaults.get(TOKEN_KEY);
} catch (error) {
console.warn(`AsyncStorage error: ${ error.message }`);
console.warn(`RNUserDefaults error: ${ error.message }`);
}
},
async getServerInfo(server) {
@ -321,10 +323,26 @@ const RocketChat = {
}
this.sdk = null;
try {
const servers = await RNUserDefaults.objectForKey(SERVERS);
await RNUserDefaults.setObjectForKey(SERVERS, servers && servers.filter(srv => srv[SERVER_URL] !== server));
} catch (error) {
console.log('logout_rn_user_defaults', error);
}
const { serversDB } = database.databases;
const userId = await RNUserDefaults.get(`${ TOKEN_KEY }-${ server }`);
serversDB.write(() => {
const user = serversDB.objectForPrimaryKey('user', userId);
serversDB.delete(user);
});
Promise.all([
AsyncStorage.removeItem('currentServer'),
AsyncStorage.removeItem(TOKEN_KEY),
AsyncStorage.removeItem(`${ TOKEN_KEY }-${ server }`)
RNUserDefaults.clear('currentServer'),
RNUserDefaults.clear(TOKEN_KEY),
RNUserDefaults.clear(`${ TOKEN_KEY }-${ server }`)
]).catch(error => console.log(error));
try {

View File

@ -1,8 +1,8 @@
import { AsyncStorage } from 'react-native';
import { delay } from 'redux-saga';
import {
takeLatest, take, select, put, all
} from 'redux-saga/effects';
import RNUserDefaults from 'rn-user-defaults';
import Navigation from '../lib/Navigation';
import * as types from '../actions/actionsTypes';
@ -43,8 +43,8 @@ const handleOpen = function* handleOpen({ params }) {
}
const [server, user] = yield all([
AsyncStorage.getItem('currentServer'),
AsyncStorage.getItem(`${ RocketChat.TOKEN_KEY }-${ host }`)
RNUserDefaults.get('currentServer'),
RNUserDefaults.get(`${ RocketChat.TOKEN_KEY }-${ host }`)
]);
// TODO: needs better test

View File

@ -1,6 +1,6 @@
import { AsyncStorage } from 'react-native';
import { put, takeLatest, all } from 'redux-saga/effects';
import SplashScreen from 'react-native-splash-screen';
import RNUserDefaults from 'rn-user-defaults';
import * as actions from '../actions';
import { selectServerRequest } from '../actions/server';
@ -11,14 +11,46 @@ import RocketChat from '../lib/rocketchat';
import log from '../utils/log';
import Navigation from '../lib/Navigation';
import database from '../lib/realm';
import {
SERVERS, SERVER_ICON, SERVER_NAME, SERVER_URL, TOKEN, USER_ID
} from '../constants/userDefaults';
const restore = function* restore() {
try {
const { token, server } = yield all({
token: AsyncStorage.getItem(RocketChat.TOKEN_KEY),
server: AsyncStorage.getItem('currentServer')
yield RNUserDefaults.setName('group.ios.chat.rocket');
let { token, server } = yield all({
token: RNUserDefaults.get(RocketChat.TOKEN_KEY),
server: RNUserDefaults.get('currentServer')
});
// get native credentials
const { serversDB } = database.databases;
const servers = yield RNUserDefaults.objectForKey(SERVERS);
if (servers) {
serversDB.write(() => {
servers.forEach(async(serverItem) => {
const serverInfo = {
id: serverItem[SERVER_URL],
name: serverItem[SERVER_NAME],
iconURL: serverItem[SERVER_ICON]
};
try {
serversDB.create('servers', serverInfo, true);
await RNUserDefaults.set(`${ RocketChat.TOKEN_KEY }-${ serverInfo.id }`, serverItem[USER_ID]);
} catch (e) {
log('err_create_servers', e);
}
});
});
}
// if not have current
if (servers.length !== 0 && (!token || !server)) {
server = servers[0][SERVER_URL];
token = servers[0][TOKEN];
}
const sortPreferences = yield RocketChat.getSortPreferences();
yield put(setAllPreferences(sortPreferences));
@ -27,8 +59,8 @@ const restore = function* restore() {
if (!token || !server) {
yield all([
AsyncStorage.removeItem(RocketChat.TOKEN_KEY),
AsyncStorage.removeItem('currentServer')
RNUserDefaults.clear(RocketChat.TOKEN_KEY),
RNUserDefaults.clear('currentServer')
]);
yield put(actions.appStart('outside'));
} else if (server) {

View File

@ -1,7 +1,7 @@
import { AsyncStorage } from 'react-native';
import {
put, call, takeLatest, select, take, fork, cancel
} from 'redux-saga/effects';
import RNUserDefaults from 'rn-user-defaults';
import * as types from '../actions/actionsTypes';
import { appStart } from '../actions';
@ -60,7 +60,7 @@ const fetchUserPresence = function* fetchUserPresence() {
const handleLoginSuccess = function* handleLoginSuccess({ user }) {
try {
const adding = yield select(state => state.server.adding);
yield AsyncStorage.setItem(RocketChat.TOKEN_KEY, user.token);
yield RNUserDefaults.set(RocketChat.TOKEN_KEY, user.token);
const server = yield select(getServer);
yield put(roomsRequest());
@ -72,7 +72,17 @@ const handleLoginSuccess = function* handleLoginSuccess({ user }) {
yield fork(fetchUserPresence);
I18n.locale = user.language;
yield AsyncStorage.setItem(`${ RocketChat.TOKEN_KEY }-${ server }`, JSON.stringify(user));
const { serversDB } = database.databases;
serversDB.write(() => {
try {
serversDB.create('user', user, true);
} catch (e) {
log('err_set_user_token', e);
}
});
yield RNUserDefaults.set(`${ RocketChat.TOKEN_KEY }-${ server }`, user.id);
yield put(setUser(user));
EventEmitter.emit('connected');
@ -105,7 +115,7 @@ const handleLogout = function* handleLogout() {
// see if there's other logged in servers and selects first one
if (servers.length > 0) {
const newServer = servers[0].id;
const token = yield AsyncStorage.getItem(`${ RocketChat.TOKEN_KEY }-${ newServer }`);
const token = yield RNUserDefaults.get(`${ RocketChat.TOKEN_KEY }-${ newServer }`);
if (token) {
return yield put(selectServerRequest(newServer));
}

View File

@ -1,7 +1,8 @@
import {
put, take, takeLatest, fork, cancel, race
} from 'redux-saga/effects';
import { AsyncStorage, Alert } from 'react-native';
import { Alert } from 'react-native';
import RNUserDefaults from 'rn-user-defaults';
import Navigation from '../lib/Navigation';
import { SERVER } from '../actions/actionsTypes';
@ -14,6 +15,7 @@ import RocketChat from '../lib/rocketchat';
import database from '../lib/realm';
import log from '../utils/log';
import I18n from '../i18n';
import { SERVERS, TOKEN, SERVER_URL } from '../constants/userDefaults';
const getServerInfo = function* getServerInfo({ server, raiseError = true }) {
try {
@ -38,13 +40,21 @@ const getServerInfo = function* getServerInfo({ server, raiseError = true }) {
const handleSelectServer = function* handleSelectServer({ server, version, fetchVersion }) {
try {
yield AsyncStorage.setItem('currentServer', server);
const userStringified = yield AsyncStorage.getItem(`${ RocketChat.TOKEN_KEY }-${ server }`);
const { serversDB } = database.databases;
if (userStringified) {
const user = JSON.parse(userStringified);
yield RocketChat.connect({ server, user });
yield put(setUser(user));
yield RNUserDefaults.set('currentServer', server);
const userId = yield RNUserDefaults.get(`${ RocketChat.TOKEN_KEY }-${ server }`);
const user = userId && serversDB.objectForPrimaryKey('user', userId);
const servers = yield RNUserDefaults.objectForKey(SERVERS);
const userCredentials = servers && servers.find(srv => srv[SERVER_URL] === server);
const userLogin = userCredentials && {
token: userCredentials[TOKEN]
};
if (user || userLogin) {
yield RocketChat.connect({ server, user: user || userLogin });
yield put(setUser(user || userLogin));
yield put(actions.appStart('inside'));
} else {
yield RocketChat.connect({ server });

View File

@ -1,11 +1,12 @@
import React, { Component } from 'react';
import {
View, Text, Animated, Easing, TouchableWithoutFeedback, TouchableOpacity, FlatList, Image, AsyncStorage
View, Text, Animated, Easing, TouchableWithoutFeedback, TouchableOpacity, FlatList, Image
} from 'react-native';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import equal from 'deep-equal';
import { withNavigation } from 'react-navigation';
import RNUserDefaults from 'rn-user-defaults';
import { toggleServerDropdown as toggleServerDropdownAction } from '../../actions/rooms';
import { selectServerRequest as selectServerRequestAction } from '../../actions/server';
@ -124,8 +125,8 @@ class ServerDropdown extends Component {
this.close();
if (currentServer !== server) {
const token = await AsyncStorage.getItem(`${ RocketChat.TOKEN_KEY }-${ server }`);
if (!token) {
const userId = await RNUserDefaults.get(`${ RocketChat.TOKEN_KEY }-${ server }`);
if (!userId) {
appStart();
this.newServerTimeout = setTimeout(() => {
EventEmitter.emit('NewServer', { server });

View File

@ -39,6 +39,7 @@ const keyExtractor = item => item.rid;
@connect(state => ({
userId: state.login.user && state.login.user.id,
isAuthenticated: state.login.isAuthenticated,
server: state.server.server,
baseUrl: state.settings.baseUrl || state.server ? state.server.server : '',
searchText: state.rooms.searchText,
@ -111,7 +112,8 @@ export default class RoomsListView extends React.Component {
openSearchHeader: PropTypes.func,
closeSearchHeader: PropTypes.func,
appStart: PropTypes.func,
roomsRequest: PropTypes.func
roomsRequest: PropTypes.func,
isAuthenticated: PropTypes.bool
}
constructor(props) {
@ -187,7 +189,7 @@ export default class RoomsListView extends React.Component {
componentDidUpdate(prevProps) {
const {
sortBy, groupByType, showFavorites, showUnread, appState, roomsRequest
sortBy, groupByType, showFavorites, showUnread, appState, roomsRequest, isAuthenticated
} = this.props;
if (!(
@ -197,7 +199,7 @@ export default class RoomsListView extends React.Component {
&& (prevProps.showUnread === showUnread)
)) {
this.getSubscriptions();
} else if (appState === 'foreground' && appState !== prevProps.appState) {
} else if (appState === 'foreground' && appState !== prevProps.appState && isAuthenticated) {
roomsRequest();
}
}

View File

@ -152,7 +152,7 @@ export default class Sidebar extends Component {
const permissionsFiltered = database.objects('permissions')
.filter(permission => permissions.includes(permission._id));
return permissionsFiltered.reduce((result, permission) => (
result || permission.roles.some(r => roles.includes(r))),
result || permission.roles.some(r => roles.indexOf(r) !== -1)),
false);
}
return false;

View File

@ -20,6 +20,7 @@
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; };
146834051AC3E58100842450 /* libReact.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 146834041AC3E56700842450 /* libReact.a */; };
1E02221122B2F76B00001862 /* libRNUserDefaults.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1E02220D22B2F76400001862 /* libRNUserDefaults.a */; };
24A2AEF2383D44B586D31C01 /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 06BB44DD4855498082A744AD /* libz.tbd */; };
38CEA0ED468E49CFABCD82FD /* libRNFirebase.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A36F9982B71E4662AA8DEB77 /* libRNFirebase.a */; };
50046CB6BDA69B9232CF66D9 /* libPods-RocketChatRN.a in Frameworks */ = {isa = PBXBuildFile; fileRef = C235DC7B31A4D1578EDEF219 /* libPods-RocketChatRN.a */; };
@ -99,6 +100,13 @@
remoteGlobalIDString = 83CBBA2E1A601D0E00E9B192;
remoteInfo = React;
};
1E02220C22B2F76400001862 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 1E0221D522B2F76300001862 /* RNUserDefaults.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 134814201AA4EA6300B7C361;
remoteInfo = RNUserDefaults;
};
3DAD3E831DF850E9000B6D8A /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 00C302BB1ABCB91800DB3ED1 /* RCTImage.xcodeproj */;
@ -452,6 +460,7 @@
1845C223DA364898A8400573 /* FastImage.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = FastImage.xcodeproj; path = "../node_modules/react-native-fast-image/ios/FastImage.xcodeproj"; sourceTree = "<group>"; };
1A34D902CC074FF1BCC7DB48 /* libimageCropPicker.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libimageCropPicker.a; sourceTree = "<group>"; };
1D3BB00B9ABF44EA9BD71318 /* libSafariViewManager.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libSafariViewManager.a; sourceTree = "<group>"; };
1E0221D522B2F76300001862 /* RNUserDefaults.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RNUserDefaults.xcodeproj; path = "../node_modules/rn-user-defaults/ios/RNUserDefaults.xcodeproj"; sourceTree = "<group>"; };
20CE3E407E0D4D9E8C9885F2 /* libRCTVideo.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRCTVideo.a; sourceTree = "<group>"; };
22A8B76C8EBA443BB97CE82D /* RNVectorIcons.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = RNVectorIcons.xcodeproj; path = "../node_modules/react-native-vector-icons/RNVectorIcons.xcodeproj"; sourceTree = "<group>"; };
22D3971EAF2E4660B4FAB3DD /* RNI18n.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = RNI18n.xcodeproj; path = "../node_modules/react-native-i18n/ios/RNI18n.xcodeproj"; sourceTree = "<group>"; };
@ -492,6 +501,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
1E02221122B2F76B00001862 /* libRNUserDefaults.a in Frameworks */,
7ACD4897222860DE00442C55 /* JavaScriptCore.framework in Frameworks */,
7A8DEB5A20ED0BEC00C5DCE4 /* libRNNotifications.a in Frameworks */,
B8971BB2202A093B0000D245 /* libKeyboardTrackingView.a in Frameworks */,
@ -626,6 +636,14 @@
name = Products;
sourceTree = "<group>";
};
1E0221D622B2F76300001862 /* Products */ = {
isa = PBXGroup;
children = (
1E02220D22B2F76400001862 /* libRNUserDefaults.a */,
);
name = Products;
sourceTree = "<group>";
};
22CA7F59107E0C79C2506C7C /* Pods */ = {
isa = PBXGroup;
children = (
@ -731,6 +749,7 @@
832341AE1AAA6A7D00B99B32 /* Libraries */ = {
isa = PBXGroup;
children = (
1E0221D522B2F76300001862 /* RNUserDefaults.xcodeproj */,
7A8DEB1B20ED0BDE00C5DCE4 /* RNNotifications.xcodeproj */,
B8971BAC202A091D0000D245 /* KeyboardTrackingView.xcodeproj */,
7A430E1620238C01008F55BC /* RCTCustomInputController.xcodeproj */,
@ -1016,6 +1035,10 @@
ProductGroup = 7A8DEB1C20ED0BDE00C5DCE4 /* Products */;
ProjectRef = 7A8DEB1B20ED0BDE00C5DCE4 /* RNNotifications.xcodeproj */;
},
{
ProductGroup = 1E0221D622B2F76300001862 /* Products */;
ProjectRef = 1E0221D522B2F76300001862 /* RNUserDefaults.xcodeproj */;
},
{
ProductGroup = B8E79A8A1F3CCC6C005B464F /* Products */;
ProjectRef = 22A8B76C8EBA443BB97CE82D /* RNVectorIcons.xcodeproj */;
@ -1085,6 +1108,13 @@
remoteRef = 146834031AC3E56700842450 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
1E02220D22B2F76400001862 /* libRNUserDefaults.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
path = libRNUserDefaults.a;
remoteRef = 1E02220C22B2F76400001862 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
3DAD3E841DF850E9000B6D8A /* libRCTImage-tvOS.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;

View File

@ -8,5 +8,9 @@
<array>
<string>applinks:go.rocket.chat</string>
</array>
<key>com.apple.security.application-groups</key>
<array>
<string>group.ios.chat.rocket</string>
</array>
</dict>
</plist>

View File

@ -78,6 +78,7 @@
"redux-saga": "^0.16.2",
"remove-markdown": "^0.3.0",
"rn-fetch-blob": "^0.10.15",
"rn-user-defaults": "^1.3.4",
"semver": "6.0.0",
"snyk": "^1.156.0",
"strip-ansi": "5.2.0"

View File

@ -12550,6 +12550,11 @@ rn-fetch-blob@^0.10.15:
base-64 "0.1.0"
glob "7.0.6"
rn-user-defaults@^1.3.4:
version "1.3.4"
resolved "https://registry.yarnpkg.com/rn-user-defaults/-/rn-user-defaults-1.3.4.tgz#1fbdd1bf29d9f853918dca5219e45db54d19fe37"
integrity sha512-CnzZbq3Q1VQUr6wKl9Z48eKmOzu+6dMcl2wN0ty+sBnpUyIqFvv3CMzYzb26v1qK8z7q/PcMr75shFHcrJH9WA==
rsvp@^3.3.3:
version "3.6.2"
resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-3.6.2.tgz#2e96491599a96cde1b515d5674a8f7a91452926a"