Skip to content

Commit

Permalink
Merge pull request #7 from AndrewGable/andrew-session
Browse files Browse the repository at this point in the history
Implement infinite sessions
  • Loading branch information
tgolen committed Aug 8, 2020
2 parents fab6e47 + 93491a0 commit fd60e71
Show file tree
Hide file tree
Showing 4 changed files with 80 additions and 37 deletions.
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

0 comments on commit fd60e71

Please sign in to comment.