Skip to content

Commit daf4f81

Browse files
authored
store google_token_access in memory (#4238)
1 parent a27e2b7 commit daf4f81

12 files changed

Lines changed: 52 additions & 39 deletions

File tree

extension/chrome/elements/pgp_block.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ export class PgpBlockView extends View {
8686
};
8787

8888
public render = async () => {
89-
const storage = await AcctStore.get(this.acctEmail, ['setup_done', 'google_token_scopes']);
89+
const storage = await AcctStore.get(this.acctEmail, ['setup_done']);
9090
this.orgRules = await OrgRules.newInstance(this.acctEmail);
9191
this.pubLookup = new PubLookup(this.orgRules);
9292
const scopes = await AcctStore.getScopes(this.acctEmail);

extension/chrome/settings/modules/experimental.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import { Url } from '../../../js/common/core/common.js';
1414
import { View } from '../../../js/common/view.js';
1515
import { AcctStore } from '../../../js/common/platform/store/acct-store.js';
1616
import { Api } from '../../../js/common/api/shared/api.js';
17+
import { InMemoryStore } from '../../../js/common/platform/store/in-memory-store.js';
18+
import { InMemoryStoreKeys } from '../../../js/common/core/const.js';
1719

1820
View.run(class ExperimentalView extends View {
1921

@@ -82,7 +84,7 @@ View.run(class ExperimentalView extends View {
8284
};
8385

8486
private makeGoogleAuthTokenUnusableHandler = async () => {
85-
await AcctStore.set(this.acctEmail, { google_token_access: 'flowcrypt_test_bad_access_token' });
87+
await InMemoryStore.set(this.acctEmail, InMemoryStoreKeys.GOOGLE_TOKEN_ACCESS, 'flowcrypt_test_bad_access_token');
8688
BrowserMsg.send.reload(this.parentTabId, {});
8789
};
8890

extension/js/background_page/background_page.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ opgp.initWorker({ path: '/lib/openpgp.worker.js' });
5555

5656
// storage related handlers
5757
BrowserMsg.bgAddListener('db', (r: Bm.Db) => BgHandlers.dbOperationHandler(db, r));
58-
BrowserMsg.bgAddListener('inMemoryStoreSet', async (r: Bm.InMemoryStoreSet) => inMemoryStore.set(emailKeyIndex(r.acctEmail, r.key), r.value));
58+
BrowserMsg.bgAddListener('inMemoryStoreSet', async (r: Bm.InMemoryStoreSet) => inMemoryStore.set(emailKeyIndex(r.acctEmail, r.key), r.value, r.expiration));
5959
BrowserMsg.bgAddListener('inMemoryStoreGet', async (r: Bm.InMemoryStoreGet) => inMemoryStore.get(emailKeyIndex(r.acctEmail, r.key)));
6060
BrowserMsg.bgAddListener('storeGlobalGet', (r: Bm.StoreGlobalGet) => GlobalStore.get(r.keys));
6161
BrowserMsg.bgAddListener('storeGlobalSet', (r: Bm.StoreGlobalSet) => GlobalStore.set(r.values));

extension/js/common/api/account-servers/enterprise-server.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { BackendRes, ProfileUpdate } from './flowcrypt-com-api.js';
1111
import { Dict } from '../../core/common.js';
1212
import { ErrorReport, UnreportableError } from '../../platform/catch.js';
1313
import { ApiErr, BackendAuthErr } from '../shared/api-error.js';
14-
import { FLAVOR } from '../../core/const.js';
14+
import { FLAVOR, InMemoryStoreKeys } from '../../core/const.js';
1515
import { Attachment } from '../../core/attachment.js';
1616
import { Recipients } from '../email-provider/email-provider-api.js';
1717
import { Buf } from '../../core/buf.js';
@@ -141,7 +141,7 @@ export class EnterpriseServer extends Api {
141141
};
142142

143143
private authHdr = async (): Promise<Dict<string>> => {
144-
const idToken = await InMemoryStore.get(this.acctEmail, InMemoryStore.ID_TOKEN_STORAGE_KEY);
144+
const idToken = await InMemoryStore.get(this.acctEmail, InMemoryStoreKeys.ID_TOKEN);
145145
if (idToken) {
146146
return { Authorization: `Bearer ${idToken}` };
147147
}

extension/js/common/api/email-provider/gmail/google-auth.ts

Lines changed: 20 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import { AcctStore, AcctStoreDict } from '../../../platform/store/acct-store.js'
2121
import { AccountServer } from '../../account-server.js';
2222
import { EnterpriseServer } from '../../account-servers/enterprise-server.js';
2323
import { InMemoryStore } from '../../../platform/store/in-memory-store.js';
24+
import { InMemoryStoreKeys } from '../../../core/const.js';
2425

2526
type GoogleAuthTokenInfo = { issued_to: string, audience: string, scope: string, expires_in: number, access_type: 'offline' };
2627
type GoogleAuthTokensResponse = { access_token: string, expires_in: number, refresh_token?: string, id_token: string, token_type: 'Bearer' };
@@ -80,22 +81,27 @@ export class GoogleAuth {
8081
if (!acctEmail) {
8182
throw new Error('missing account_email in api_gmail_call');
8283
}
83-
const storage = await AcctStore.get(acctEmail, ['google_token_access', 'google_token_expires', 'google_token_scopes', 'google_token_refresh']);
84-
if (!storage.google_token_access || !storage.google_token_refresh) {
84+
const storage = await AcctStore.get(acctEmail, ['google_token_scopes', 'google_token_refresh']);
85+
if (!storage.google_token_refresh) {
8586
throw new GoogleAuthErr(`Account ${acctEmail} not connected to FlowCrypt Browser Extension`);
86-
} else if (GoogleAuth.googleApiIsAuthTokenValid(storage) && !forceRefresh) {
87-
return `Bearer ${storage.google_token_access}`;
88-
} else { // refresh token
89-
const refreshTokenRes = await GoogleAuth.googleAuthRefreshToken(storage.google_token_refresh);
87+
}
88+
if (!forceRefresh) {
89+
const googleAccessToken = await InMemoryStore.get(acctEmail, InMemoryStoreKeys.GOOGLE_TOKEN_ACCESS);
90+
if (googleAccessToken) {
91+
return `Bearer ${googleAccessToken}`;
92+
}
93+
}
94+
// refresh token
95+
const refreshTokenRes = await GoogleAuth.googleAuthRefreshToken(storage.google_token_refresh);
96+
if (refreshTokenRes.access_token) {
9097
await GoogleAuth.googleAuthCheckAccessToken(refreshTokenRes.access_token); // https://groups.google.com/forum/#!topic/oauth2-dev/QOFZ4G7Ktzg
9198
await GoogleAuth.googleAuthSaveTokens(acctEmail, refreshTokenRes, storage.google_token_scopes || []);
92-
const auth = await AcctStore.get(acctEmail, ['google_token_access', 'google_token_expires']);
93-
if (GoogleAuth.googleApiIsAuthTokenValid(auth)) { // have a valid gmail_api oauth token
94-
return `Bearer ${auth.google_token_access}`;
95-
} else {
96-
throw new GoogleAuthErr(`Could not refresh google auth token - did not become valid (access:${!!auth.google_token_access},expires:${auth.google_token_expires},now:${Date.now()})`);
99+
const googleAccessToken = await InMemoryStore.get(acctEmail, InMemoryStoreKeys.GOOGLE_TOKEN_ACCESS);
100+
if (googleAccessToken) {
101+
return `Bearer ${googleAccessToken}`;
97102
}
98103
}
104+
throw new GoogleAuthErr(`Could not refresh google auth token - did not become valid (access:${refreshTokenRes.access_token},expires_in:${refreshTokenRes.expires_in},now:${Date.now()})`);
99105
};
100106

101107
public static apiGoogleCallRetryAuthErrorOneTime = async (acctEmail: string, request: JQuery.AjaxSettings): Promise<any> => {
@@ -282,9 +288,8 @@ export class GoogleAuth {
282288
private static googleAuthSaveTokens = async (acctEmail: string, tokensObj: GoogleAuthTokensResponse, scopes: string[]) => {
283289
const parsedOpenId = GoogleAuth.parseIdToken(tokensObj.id_token);
284290
const { full_name, picture } = await AcctStore.get(acctEmail, ['full_name', 'picture']);
291+
const googleTokenExpires = new Date().getTime() + (tokensObj.expires_in as number - 120) * 1000; // let our copy expire 2 minutes beforehand
285292
const toSave: AcctStoreDict = {
286-
google_token_access: tokensObj.access_token,
287-
google_token_expires: new Date().getTime() + (tokensObj.expires_in as number) * 1000,
288293
google_token_scopes: scopes,
289294
full_name: full_name || parsedOpenId.name,
290295
picture: picture || parsedOpenId.picture,
@@ -293,7 +298,8 @@ export class GoogleAuth {
293298
toSave.google_token_refresh = tokensObj.refresh_token;
294299
}
295300
await AcctStore.set(acctEmail, toSave);
296-
await InMemoryStore.set(acctEmail, InMemoryStore.ID_TOKEN_STORAGE_KEY, tokensObj.id_token);
301+
await InMemoryStore.set(acctEmail, InMemoryStoreKeys.ID_TOKEN, tokensObj.id_token);
302+
await InMemoryStore.set(acctEmail, InMemoryStoreKeys.GOOGLE_TOKEN_ACCESS, tokensObj.access_token, googleTokenExpires);
297303
};
298304

299305
private static googleAuthGetTokens = async (code: string) => {
@@ -322,13 +328,6 @@ export class GoogleAuth {
322328
}, Catch.stackTrace()) as any as GoogleAuthTokenInfo;
323329
};
324330

325-
/**
326-
* oauth token will be valid for another 2 min
327-
*/
328-
private static googleApiIsAuthTokenValid = (s: AcctStoreDict) => {
329-
return s.google_token_access && (!s.google_token_expires || s.google_token_expires > Date.now() + (120 * 1000));
330-
};
331-
332331
// todo - would be better to use a TS type guard instead of the type cast when checking OpenId
333332
// check for things we actually use: photo/name/locale
334333
private static parseIdToken = (idToken: string): GmailRes.OpenId => {

extension/js/common/browser/browser-msg.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ export namespace Bm {
5050
export type StripeResult = { token: string };
5151
export type PassphraseEntry = { entered: boolean, initiatorFrameId?: string };
5252
export type Db = { f: string, args: any[] };
53-
export type InMemoryStoreSet = { acctEmail: string, key: string, value: string | undefined };
53+
export type InMemoryStoreSet = { acctEmail: string, key: string, value: string | undefined, expiration: number | undefined };
5454
export type InMemoryStoreGet = { acctEmail: string, key: string };
5555
export type StoreGlobalGet = { keys: GlobalIndex[]; };
5656
export type StoreGlobalSet = { values: GlobalStoreDict; };

extension/js/common/core/const.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,8 @@ export const gmailBackupSearchQuery = (acctEmail: string) => {
3434
'-is:trash'
3535
].join(' ');
3636
};
37+
38+
export class InMemoryStoreKeys {
39+
public static readonly ID_TOKEN = 'idToken';
40+
public static readonly GOOGLE_TOKEN_ACCESS = 'google_token_access';
41+
}

extension/js/common/core/expiration-cache.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ export class ExpirationCache {
99
constructor(public EXPIRATION_TICKS: number) {
1010
}
1111

12-
public set = (key: string, value: string | undefined) => {
12+
public set = (key: string, value?: string, expiration?: number) => {
1313
if (value) {
14-
this.cache[key] = { value, expiration: Date.now() + this.EXPIRATION_TICKS };
14+
this.cache[key] = { value, expiration: expiration || (Date.now() + this.EXPIRATION_TICKS) };
1515
} else {
1616
delete this.cache[key];
1717
}

extension/js/common/platform/store/acct-store.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export type Scopes = {
2626
gmail: boolean;
2727
};
2828

29-
export type AccountIndex = 'keys' | 'notification_setup_needed_dismissed' | 'email_provider' | 'google_token_access' | 'google_token_expires' | 'google_token_scopes' |
29+
export type AccountIndex = 'keys' | 'notification_setup_needed_dismissed' | 'email_provider' | 'google_token_scopes' |
3030
'google_token_refresh' | 'hide_message_password' | 'sendAs' |
3131
'pubkey_sent_to' | 'full_name' | 'cryptup_enabled' | 'setup_done' |
3232
'successfully_received_at_leat_one_message' | 'notification_setup_done_seen' | 'picture' |
@@ -43,8 +43,6 @@ export type AcctStoreDict = {
4343
keys?: KeyInfo[];
4444
notification_setup_needed_dismissed?: boolean;
4545
email_provider?: EmailProvider;
46-
google_token_access?: string;
47-
google_token_expires?: number;
4846
google_token_scopes?: string[]; // these are actuall scope urls the way the provider expects them
4947
google_token_refresh?: string;
5048
hide_message_password?: boolean; // is global?

extension/js/common/platform/store/in-memory-store.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,8 @@ import { BrowserMsg } from '../../browser/browser-msg.js';
99
*/
1010
export class InMemoryStore extends AbstractStore {
1111

12-
public static ID_TOKEN_STORAGE_KEY = 'idToken';
13-
14-
public static set = async (acctEmail: string, key: string, value: string | undefined) => {
15-
return await BrowserMsg.send.bg.await.inMemoryStoreSet({ acctEmail, key, value });
12+
public static set = async (acctEmail: string, key: string, value?: string, expiration?: number) => {
13+
return await BrowserMsg.send.bg.await.inMemoryStoreSet({ acctEmail, key, value, expiration });
1614
};
1715

1816
public static get = async (acctEmail: string, key: string): Promise<string | undefined> => {

0 commit comments

Comments
 (0)