Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions src/CONFIG.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// TODO: Figure out how to determine prod/dev on mobile, etc.
const IS_IN_PRODUCTION = false;
import {Platform} from 'react-native';

const IS_IN_PRODUCTION = Platform.OS === 'web' ? process.env.NODE_ENV === 'production' : !__DEV__;

export default {
PUSHER: {
Expand Down
11 changes: 11 additions & 0 deletions src/lib/Str.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
/* globals $, _ */

import Guid from './Guid';

const Str = {
/**
* Returns the proper phrase depending on the count that is passed.
Expand Down Expand Up @@ -49,6 +51,15 @@ const Str = {
nl2br(str) {
return str.replace(/\n/g, '<br />');
},

/**
* Generates a random device login using Guid
*
* @returns {string}
*/
generateDeviceLoginID() {
return `React-Native-Chat-${Guid()}`;
},
};

export default Str;
8 changes: 6 additions & 2 deletions src/page/SignInPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,14 @@ export default class App extends Component {
login: '',
password: '',
// eslint-disable-next-line react/no-unused-state
error: Store.get(STOREKEYS.SESSION, 'error'),
error: null,
};
}

componentDidMount() {
// Listen for changes to our session
Store.subscribe(STOREKEYS.SESSION, this.sessionChanged);
Store.get(STOREKEYS.SESSION, 'error').then(error => this.setState({error}));
}

componentWillUnmount() {
Expand All @@ -54,7 +55,7 @@ export default class App extends Component {
* When the form is submitted, then we trigger our prop callback
*/
submit() {
signIn(this.state.login, this.state.password);
signIn(this.state.login, this.state.password, true);
}

render() {
Expand All @@ -81,6 +82,9 @@ export default class App extends Component {
</View>
<View>
<Button onPress={this.submit} title="Log In" />
{this.state.error && <Text style={{color: 'red'}}>
{this.state.error}
</Text>}
</View>
</SafeAreaView>
</>
Expand Down
93 changes: 60 additions & 33 deletions src/store/actions/SessionActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import {request} from '../../lib/Network';
import ROUTES from '../../ROUTES';
import STOREKEYS from '../STOREKEYS';
import CONFIG from '../../CONFIG';
import Str from '../../lib/Str';
import Guid from '../../lib/Guid';

/**
* Amount of time (in ms) after which an authToken is considered expired.
Expand All @@ -12,23 +14,36 @@ import CONFIG from '../../CONFIG';
* @private
* @type {Number}
*/
const AUTH_TOKEN_EXPIRATION_TIME = 1000 * 60;
const AUTH_TOKEN_EXPIRATION_TIME = 1000 * 60 * 90;

/**
* Create login
* @param {string} authToken
* @param {string} login
* @param {string} password
* @returns {Promise}
*/
function createLogin(authToken, login, password) {
request('CreateLogin', {
return request('CreateLogin', {
authToken,
partnerName: CONFIG.EXPENSIFY.PARTNER_NAME,
partnerPassword: CONFIG.EXPENSIFY.PARTNER_PASSWORD,
partnerUserID: login,
partnerUserSecret: password,
}).catch((err) => {
Store.set(STOREKEYS.SESSION, {error: err});
}).then(() => Store.set(STOREKEYS.CREDENTIALS, {login, password}))
.catch(err => Store.set(STOREKEYS.SESSION, {error: err}));
}

/**
* Sets API data in the store when we make a successful "Authenticate"/"CreateLogin" request
* @param {object} data
* @returns {Promise}
*/
function setSuccessfulSignInData(data) {
return Store.multiSet({
[STOREKEYS.SESSION]: data,
[STOREKEYS.APP_REDIRECT_TO]: ROUTES.HOME,
[STOREKEYS.LAST_AUTHENTICATED]: new Date().getTime(),
});
}

Expand All @@ -42,64 +57,76 @@ function createLogin(authToken, login, password) {
*/
function signIn(login, password, useExpensifyLogin = false) {
let authToken;
return Store.multiSet({
[STOREKEYS.CREDENTIALS]: {login, password},
[STOREKEYS.SESSION]: {},
return request('Authenticate', {
useExpensifyLogin,
partnerName: CONFIG.EXPENSIFY.PARTNER_NAME,
partnerPassword: CONFIG.EXPENSIFY.PARTNER_PASSWORD,
partnerUserID: login,
partnerUserSecret: password,
})
.then(() => request('Authenticate', {
useExpensifyLogin,
partnerName: CONFIG.EXPENSIFY.PARTNER_NAME,
partnerPassword: CONFIG.EXPENSIFY.PARTNER_PASSWORD,
partnerUserID: login,
partnerUserSecret: password,
}))
.then((data) => {
authToken = data && data.authToken;

// 404 We need to create a login
if (data.jsonCode === 404 && !useExpensifyLogin) {
return signIn(login, password, true)
.then((newAuthToken) => {
createLogin(newAuthToken, login, password);
});
}

// If we didn't get a 200 response from authenticate, the user needs to sign in again
if (data.jsonCode !== 200) {
// eslint-disable-next-line no-console
console.warn('Did not get a 200 from authenticate, going back to sign in page');
return Store.set(STOREKEYS.APP_REDIRECT_TO, ROUTES.SIGNIN);
console.debug('Non-200 from authenticate, going back to sign in page');
return Store.multiSet({
[STOREKEYS.CREDENTIALS]: {},
[STOREKEYS.SESSION]: {error: data.message},
[STOREKEYS.APP_REDIRECT_TO]: ROUTES.SIGNIN,
});
}

return Store.multiSet({
[STOREKEYS.SESSION]: data,
[STOREKEYS.APP_REDIRECT_TO]: ROUTES.HOME,
[STOREKEYS.LAST_AUTHENTICATED]: new Date().getTime(),
});
// If Expensify login, it's the users first time logging in and we need to create a login for the user
if (useExpensifyLogin) {
return createLogin(data.authToken, Str.generateDeviceLoginID(), Guid())
.then(() => setSuccessfulSignInData(data));
}

return setSuccessfulSignInData();
})
.then(() => authToken)
.catch((err) => {
console.error(err);
Store.set(STOREKEYS.SESSION, {error: err});
return Store.set(STOREKEYS.SESSION, {error: err.message});
});
}

/**
* Delete login
* @param {string} authToken
* @param {string} login
* @returns {Promise}
*/
function deleteLogin(authToken, login) {
return request('DeleteLogin', {
authToken,
partnerName: CONFIG.EXPENSIFY.PARTNER_NAME,
partnerPassword: CONFIG.EXPENSIFY.PARTNER_PASSWORD,
partnerUserID: login,
}).catch(err => Store.set(STOREKEYS.SESSION, {error: err.message}));
}

/**
* Sign out of our application
*
* @returns {Promise}
*/
async function signOut() {
function signOut() {
return Store.set(STOREKEYS.APP_REDIRECT_TO, ROUTES.SIGNIN)
.then(Store.clear);
.then(() => Store.multiGet([STOREKEYS.SESSION, STOREKEYS.CREDENTIALS]))
.then(data => deleteLogin(data.session.authToken, data.credentials.login))
.then(Store.clear)
.catch(err => Store.set(STOREKEYS.SESSION, {error: err.message}));
}

/**
* Make sure the authToken we have is OK to use
*
* @returns {Promise}
*/
async function verifyAuthToken() {
function verifyAuthToken() {
return Store.multiGet([STOREKEYS.LAST_AUTHENTICATED, STOREKEYS.CREDENTIALS])
.then(({last_authenticated, credentials}) => {
const haveCredentials = !_.isNull(credentials);
Expand Down