From ab2f8c6000c9df03d30364d5f21eec67c3b808f6 Mon Sep 17 00:00:00 2001 From: andrew Date: Sat, 8 Aug 2020 07:13:42 -0700 Subject: [PATCH 1/2] Create a new login when needed --- js/store/actions/SessionActions.js | 46 ++++++++++++++++++++++++++---- 1 file changed, 41 insertions(+), 5 deletions(-) diff --git a/js/store/actions/SessionActions.js b/js/store/actions/SessionActions.js index 4fad555feaf0..1f705226a21d 100644 --- a/js/store/actions/SessionActions.js +++ b/js/store/actions/SessionActions.js @@ -4,25 +4,43 @@ import ROUTES from '../../ROUTES.js'; import STOREKEYS from '../STOREKEYS.js'; import * as PersistentStorage from '../../lib/PersistentStorage.js'; +// TODO: Figure out how to determine prod/dev on mobile, etc. +const IS_IN_PRODUCTION = false; +const partnerName = IS_IN_PRODUCTION ? 'chat-expensify-com' : 'android'; +const partnerPassword = IS_IN_PRODUCTION + ? 'e21965746fd75f82bb66' + : 'c3a9ac418ea3f152aae2'; + /** * Sign in with the API * @param {string} login * @param {string} password + * @param {boolean} useExpensifyLogin */ -function signIn(login, password) { +function signIn(login, password, useExpensifyLogin = false) { Store.set(STOREKEYS.CREDENTIALS, {login, password}); Store.set(STOREKEYS.SESSION, {}); - request('Authenticate', { - useExpensifyLogin: true, - partnerName: 'expensify.com', - partnerPassword: 'MkgLvVAyaTlmw', + return request('Authenticate', { + useExpensifyLogin: useExpensifyLogin, + partnerName: partnerName, + partnerPassword: partnerPassword, partnerUserID: login, partnerUserSecret: password, }) .then((data) => { + // 404 We need to create a login + if (data.jsonCode === 404 && !useExpensifyLogin) { + signIn(login, password, true).then((expensifyLoginData) => { + createLogin(expensifyLoginData.authToken, login, password); + }); + return; + } + Store.set(STOREKEYS.SESSION, data); Store.set(STOREKEYS.APP_REDIRECT_TO, ROUTES.HOME); + + return data; }) .catch((err) => { console.warn(err); @@ -30,6 +48,24 @@ function signIn(login, password) { }); } +/** + * Create login + * @param {string} authToken + * @param {string} login + * @param {string} password + */ +function createLogin(authToken, login, password) { + request('CreateLogin', { + authToken: authToken, + partnerName, + partnerPassword, + partnerUserID: login, + partnerUserSecret: password, + }).catch((err) => { + Store.set(STOREKEYS.SESSION, {error: err}); + }); +} + /** * Sign out of our application */ From c859882940998717f9aa0c35a6607cb789d6f734 Mon Sep 17 00:00:00 2001 From: andrew Date: Sat, 8 Aug 2020 08:01:15 -0700 Subject: [PATCH 2/2] Add V1 of infinite sessions --- js/lib/DateUtils.js | 2 +- js/lib/Network.js | 5 ++-- js/store/STOREKEYS.js | 1 + js/store/actions/SessionActions.js | 40 +++++++++++++++++++++++++----- package-lock.json | 5 ++++ package.json | 1 + 6 files changed, 45 insertions(+), 9 deletions(-) diff --git a/js/lib/DateUtils.js b/js/lib/DateUtils.js index ef332128572f..f9f9b57c8400 100644 --- a/js/lib/DateUtils.js +++ b/js/lib/DateUtils.js @@ -1,4 +1,4 @@ -/* globals moment */ +import moment from 'moment'; import Str from './Str.js'; // Non-Deprecated Methods diff --git a/js/lib/Network.js b/js/lib/Network.js index bbe048a6407a..fc3697765e1c 100644 --- a/js/lib/Network.js +++ b/js/lib/Network.js @@ -11,7 +11,8 @@ let isAppOffline = false; * @param {string} [type] * @returns {$.Deferred} */ -async function request(command, data, type) { +async function request(command, data, type = 'post') { + console.debug(`Making "${command}" ${type} request`); const formData = new FormData(); formData.append('authToken', await Store.get('session', 'authToken')); for (const property in data) { @@ -21,7 +22,7 @@ async function request(command, data, type) { let response = await fetch( `https://expensify.com.dev/api?command=${command}`, { - method: 'post', + method: type, body: formData, }, ); diff --git a/js/store/STOREKEYS.js b/js/store/STOREKEYS.js index 705f26a1d8bd..ac06965f41f4 100644 --- a/js/store/STOREKEYS.js +++ b/js/store/STOREKEYS.js @@ -9,4 +9,5 @@ export default { ACTIVE_REPORT: 'active_report', REPORTS: 'reports', SESSION: 'session', + LAST_AUTHENTICATED: 'last_authenticated', }; diff --git a/js/store/actions/SessionActions.js b/js/store/actions/SessionActions.js index 1f705226a21d..f45f213e3d29 100644 --- a/js/store/actions/SessionActions.js +++ b/js/store/actions/SessionActions.js @@ -3,6 +3,7 @@ import {request} from '../../lib/Network.js'; import ROUTES from '../../ROUTES.js'; import STOREKEYS from '../STOREKEYS.js'; import * as PersistentStorage from '../../lib/PersistentStorage.js'; +import * as _ from 'lodash'; // TODO: Figure out how to determine prod/dev on mobile, etc. const IS_IN_PRODUCTION = false; @@ -11,6 +12,15 @@ const partnerPassword = IS_IN_PRODUCTION ? 'e21965746fd75f82bb66' : 'c3a9ac418ea3f152aae2'; +/** + * Amount of time (in ms) after which an authToken is considered expired. + * Currently set to 90min + * + * @private + * @type {Number} + */ +const AUTH_TOKEN_EXPIRATION_TIME = 1000 * 60; + /** * Sign in with the API * @param {string} login @@ -20,7 +30,6 @@ const partnerPassword = IS_IN_PRODUCTION function signIn(login, password, useExpensifyLogin = false) { Store.set(STOREKEYS.CREDENTIALS, {login, password}); Store.set(STOREKEYS.SESSION, {}); - return request('Authenticate', { useExpensifyLogin: useExpensifyLogin, partnerName: partnerName, @@ -37,8 +46,18 @@ function signIn(login, password, useExpensifyLogin = false) { return; } + // If we didn't get a 200 response from authenticate, the user needs to sign in again + if (data.jsonCode !== 200) { + console.warn( + 'Did not get a 200 from authenticate, going back to sign in page', + ); + Store.set(STOREKEYS.APP_REDIRECT_TO, ROUTES.SIGNIN); + return; + } + Store.set(STOREKEYS.SESSION, data); Store.set(STOREKEYS.APP_REDIRECT_TO, ROUTES.HOME); + Store.set(STOREKEYS.LAST_AUTHENTICATED, new Date().getTime()); return data; }) @@ -77,18 +96,27 @@ async function signOut() { /** * Make sure the authToken we have is OK to use */ -function verifyAuthToken() { +async function verifyAuthToken() { + const lastAuthenticated = await Store.get(STOREKEYS.LAST_AUTHENTICATED); + const credentials = await Store.get(STOREKEYS.CREDENTIALS); + const haveCredentials = !_.isNull(credentials); + const haveExpiredAuthToken = + lastAuthenticated < new Date().getTime() - AUTH_TOKEN_EXPIRATION_TIME; + + if (haveExpiredAuthToken && haveCredentials) { + console.debug('Invalid auth token: Token has expired.'); + signIn(credentials.login, credentials.password); + return; + } + request('Get', {returnValueList: 'account'}).then((data) => { if (data.jsonCode === 200) { console.debug('We have valid auth token'); Store.set(STOREKEYS.SESSION, data); return; - } else if (data.jsonCode === 407) { - console.warn('We need to re-auth'); - return; } - // If the auth token is bad, we want them to go to the sign in page + // If the auth token is bad and we didn't have credentials saved, we want them to go to the sign in page Store.set(STOREKEYS.APP_REDIRECT_TO, ROUTES.SIGNIN); }); } diff --git a/package-lock.json b/package-lock.json index 973d10e555cd..8f19e279b73f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9666,6 +9666,11 @@ "minimist": "^1.2.5" } }, + "moment": { + "version": "2.27.0", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.27.0.tgz", + "integrity": "sha512-al0MUK7cpIcglMv3YF13qSgdAIqxHTO7brRtaz3DlSULbqfazqkc5kEjNrLDOM7fsjshoFIihnU8snrP7zUvhQ==" + }, "move-concurrently": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", diff --git a/package.json b/package.json index 5edd7088c104..602944600000 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "@react-native-community/async-storage": "^1.11.0", "jquery": "^3.5.1", "lodash": "^4.17.19", + "moment": "^2.27.0", "react": "^16.13.1", "react-dom": "^16.13.1", "react-native": "0.63.2",