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",