1
0
mirror of https://github.com/bitwarden/browser.git synced 2025-02-03 23:21:29 +01:00

[bug] Toggle tokens appropriatly based on timeout action (#661)

This commit is contained in:
Addison Beck 2022-02-09 17:01:43 -05:00 committed by GitHub
parent c282ef8575
commit b7bb16c18a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 84 additions and 80 deletions

View File

@ -14,7 +14,6 @@ export abstract class TokenService {
getClientId: () => Promise<string>; getClientId: () => Promise<string>;
setClientSecret: (clientSecret: string) => Promise<any>; setClientSecret: (clientSecret: string) => Promise<any>;
getClientSecret: () => Promise<string>; getClientSecret: () => Promise<string>;
toggleTokens: () => Promise<any>;
setTwoFactorToken: (tokenResponse: IdentityTokenResponse) => Promise<any>; setTwoFactorToken: (tokenResponse: IdentityTokenResponse) => Promise<any>;
getTwoFactorToken: () => Promise<string>; getTwoFactorToken: () => Promise<string>;
clearTwoFactorToken: () => Promise<any>; clearTwoFactorToken: () => Promise<any>;

View File

@ -147,20 +147,23 @@ export class StateService<
} }
async getAccessToken(options?: StorageOptions): Promise<string> { async getAccessToken(options?: StorageOptions): Promise<string> {
return ( const defaultOptions =
await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())) (await this.getVaultTimeoutAction({ userId: options?.userId })) === "logOut"
)?.tokens?.accessToken; ? this.defaultInMemoryOptions
: await this.defaultOnDiskOptions();
options = this.reconcileOptions(options, defaultOptions);
return (await this.getAccount(options))?.tokens?.accessToken;
} }
async setAccessToken(value: string, options?: StorageOptions): Promise<void> { async setAccessToken(value: string, options?: StorageOptions): Promise<void> {
const account = await this.getAccount( const defaultOptions =
this.reconcileOptions(options, await this.defaultOnDiskOptions()) (await this.getVaultTimeoutAction({ userId: options?.userId })) === "logOut"
); ? this.defaultInMemoryOptions
: await this.defaultOnDiskOptions();
options = this.reconcileOptions(options, defaultOptions);
const account = await this.getAccount(options);
account.tokens.accessToken = value; account.tokens.accessToken = value;
await this.saveAccount( await this.saveAccount(account, options);
account,
this.reconcileOptions(options, await this.defaultOnDiskOptions())
);
} }
async getAddEditCipherInfo(options?: StorageOptions): Promise<any> { async getAddEditCipherInfo(options?: StorageOptions): Promise<any> {
@ -195,37 +198,43 @@ export class StateService<
} }
async getApiKeyClientId(options?: StorageOptions): Promise<string> { async getApiKeyClientId(options?: StorageOptions): Promise<string> {
return ( const defaultOptions =
await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())) (await this.getVaultTimeoutAction({ userId: options?.userId })) === "logOut"
)?.profile?.apiKeyClientId; ? this.defaultInMemoryOptions
: await this.defaultOnDiskOptions();
options = this.reconcileOptions(options, defaultOptions);
return (await this.getAccount(options))?.profile?.apiKeyClientId;
} }
async setApiKeyClientId(value: string, options?: StorageOptions): Promise<void> { async setApiKeyClientId(value: string, options?: StorageOptions): Promise<void> {
const account = await this.getAccount( const defaultOptions =
this.reconcileOptions(options, await this.defaultOnDiskOptions()) (await this.getVaultTimeoutAction({ userId: options?.userId })) === "logOut"
); ? this.defaultInMemoryOptions
: await this.defaultOnDiskOptions();
options = this.reconcileOptions(options, defaultOptions);
const account = await this.getAccount(options);
account.profile.apiKeyClientId = value; account.profile.apiKeyClientId = value;
await this.saveAccount( await this.saveAccount(account, options);
account,
this.reconcileOptions(options, await this.defaultOnDiskOptions())
);
} }
async getApiKeyClientSecret(options?: StorageOptions): Promise<string> { async getApiKeyClientSecret(options?: StorageOptions): Promise<string> {
return ( const defaultOptions =
await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())) (await this.getVaultTimeoutAction({ userId: options?.userId })) === "logOut"
)?.keys?.apiKeyClientSecret; ? this.defaultInMemoryOptions
: await this.defaultOnDiskOptions();
options = this.reconcileOptions(options, defaultOptions);
return (await this.getAccount(options))?.keys?.apiKeyClientSecret;
} }
async setApiKeyClientSecret(value: string, options?: StorageOptions): Promise<void> { async setApiKeyClientSecret(value: string, options?: StorageOptions): Promise<void> {
const account = await this.getAccount( const defaultOptions =
this.reconcileOptions(options, await this.defaultOnDiskOptions()) (await this.getVaultTimeoutAction({ userId: options?.userId })) === "logOut"
); ? this.defaultInMemoryOptions
: await this.defaultOnDiskOptions();
options = this.reconcileOptions(options, defaultOptions);
const account = await this.getAccount(options);
account.keys.apiKeyClientSecret = value; account.keys.apiKeyClientSecret = value;
await this.saveAccount( await this.saveAccount(account, options);
account,
this.reconcileOptions(options, await this.defaultOnDiskOptions())
);
} }
async getAutoConfirmFingerPrints(options?: StorageOptions): Promise<boolean> { async getAutoConfirmFingerPrints(options?: StorageOptions): Promise<boolean> {
@ -1866,20 +1875,23 @@ export class StateService<
} }
async getRefreshToken(options?: StorageOptions): Promise<string> { async getRefreshToken(options?: StorageOptions): Promise<string> {
return ( const defaultOptions =
await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())) (await this.getVaultTimeoutAction({ userId: options?.userId })) === "logOut"
)?.tokens?.refreshToken; ? this.defaultInMemoryOptions
: await this.defaultOnDiskOptions();
options = this.reconcileOptions(options, defaultOptions);
return (await this.getAccount(options))?.tokens?.refreshToken;
} }
async setRefreshToken(value: string, options?: StorageOptions): Promise<void> { async setRefreshToken(value: string, options?: StorageOptions): Promise<void> {
const account = await this.getAccount( const defaultOptions =
this.reconcileOptions(options, await this.defaultOnDiskOptions()) (await this.getVaultTimeoutAction({ userId: options?.userId })) === "logOut"
); ? this.defaultInMemoryOptions
: await this.defaultOnDiskOptions();
options = this.reconcileOptions(options, defaultOptions);
const account = await this.getAccount(options);
account.tokens.refreshToken = value; account.tokens.refreshToken = value;
await this.saveAccount( await this.saveAccount(account, options);
account,
this.reconcileOptions(options, await this.defaultOnDiskOptions())
);
} }
async getRememberedEmail(options?: StorageOptions): Promise<string> { async getRememberedEmail(options?: StorageOptions): Promise<string> {
@ -2225,9 +2237,11 @@ export class StateService<
} }
protected async scaffoldNewAccountStorage(account: TAccount): Promise<void> { protected async scaffoldNewAccountStorage(account: TAccount): Promise<void> {
await this.scaffoldNewAccountLocalStorage(account); // We don't want to manipulate the referenced in memory account
await this.scaffoldNewAccountSessionStorage(account); const deepClone = JSON.parse(JSON.stringify(account));
await this.scaffoldNewAccountMemoryStorage(account); await this.scaffoldNewAccountLocalStorage(deepClone);
await this.scaffoldNewAccountSessionStorage(deepClone);
await this.scaffoldNewAccountMemoryStorage(deepClone);
} }
// TODO: There is a tech debt item for splitting up these methods - only Web uses multiple storage locations in its storageService. // TODO: There is a tech debt item for splitting up these methods - only Web uses multiple storage locations in its storageService.
@ -2246,6 +2260,12 @@ export class StateService<
await this.storageService.remove(keys.tempAccountSettings); await this.storageService.remove(keys.tempAccountSettings);
} }
account.settings.environmentUrls = environmentUrls; account.settings.environmentUrls = environmentUrls;
if (account.settings.vaultTimeoutAction === "logOut") {
account.tokens.accessToken = null;
account.tokens.refreshToken = null;
account.profile.apiKeyClientId = null;
account.keys.apiKeyClientSecret = null;
}
await this.storageService.save( await this.storageService.save(
account.profile.userId, account.profile.userId,
account, account,
@ -2422,7 +2442,7 @@ export class StateService<
protected clearDecryptedDataForActiveUser() { protected clearDecryptedDataForActiveUser() {
const userId = this.state.activeUserId; const userId = this.state.activeUserId;
if (userId == null) { if (userId == null || this.state?.accounts[userId]?.data == null) {
return; return;
} }
this.state.accounts[userId].data = new AccountData(); this.state.accounts[userId].data = new AccountData();

View File

@ -22,9 +22,6 @@ export class TokenService implements TokenServiceAbstraction {
} }
async setClientId(clientId: string): Promise<any> { async setClientId(clientId: string): Promise<any> {
if ((await this.skipTokenStorage()) || clientId == null) {
return;
}
return await this.stateService.setApiKeyClientId(clientId); return await this.stateService.setApiKeyClientId(clientId);
} }
@ -33,9 +30,6 @@ export class TokenService implements TokenServiceAbstraction {
} }
async setClientSecret(clientSecret: string): Promise<any> { async setClientSecret(clientSecret: string): Promise<any> {
if ((await this.skipTokenStorage()) || clientSecret == null) {
return;
}
return await this.stateService.setApiKeyClientSecret(clientSecret); return await this.stateService.setApiKeyClientSecret(clientSecret);
} }
@ -52,9 +46,6 @@ export class TokenService implements TokenServiceAbstraction {
} }
async setRefreshToken(refreshToken: string): Promise<any> { async setRefreshToken(refreshToken: string): Promise<any> {
if (await this.skipTokenStorage()) {
return;
}
return await this.stateService.setRefreshToken(refreshToken); return await this.stateService.setRefreshToken(refreshToken);
} }
@ -62,25 +53,6 @@ export class TokenService implements TokenServiceAbstraction {
return await this.stateService.getRefreshToken(); return await this.stateService.getRefreshToken();
} }
async toggleTokens(): Promise<any> {
const token = await this.getToken();
const refreshToken = await this.getRefreshToken();
const clientId = await this.getClientId();
const clientSecret = await this.getClientSecret();
const timeout = await this.stateService.getVaultTimeout();
const action = await this.stateService.getVaultTimeoutAction();
if ((timeout != null || timeout === 0) && action === "logOut") {
// if we have a vault timeout and the action is log out, reset tokens
await this.clearToken();
}
await this.setToken(token);
await this.setRefreshToken(refreshToken);
await this.setClientId(clientId);
await this.setClientSecret(clientSecret);
}
async setTwoFactorToken(tokenResponse: IdentityTokenResponse): Promise<any> { async setTwoFactorToken(tokenResponse: IdentityTokenResponse): Promise<any> {
return await this.stateService.setTwoFactorToken(tokenResponse.twoFactorToken); return await this.stateService.setTwoFactorToken(tokenResponse.twoFactorToken);
} }
@ -214,10 +186,4 @@ export class TokenService implements TokenServiceAbstraction {
return Array.isArray(decoded.amr) && decoded.amr.includes("external"); return Array.isArray(decoded.amr) && decoded.amr.includes("external");
} }
private async skipTokenStorage(): Promise<boolean> {
const timeout = await this.stateService.getVaultTimeout();
const action = await this.stateService.getVaultTimeoutAction();
return timeout != null && action === "logOut";
}
} }

View File

@ -122,9 +122,28 @@ export class VaultTimeoutService implements VaultTimeoutServiceAbstraction {
async setVaultTimeoutOptions(timeout: number, action: string): Promise<void> { async setVaultTimeoutOptions(timeout: number, action: string): Promise<void> {
await this.stateService.setVaultTimeout(timeout); await this.stateService.setVaultTimeout(timeout);
// We swap these tokens from being on disk for lock actions, and in memory for logout actions
// Get them here to set them to their new location after changing the timeout action and clearing if needed
const token = await this.tokenService.getToken();
const refreshToken = await this.tokenService.getRefreshToken();
const clientId = await this.tokenService.getClientId();
const clientSecret = await this.tokenService.getClientSecret();
const currentAction = await this.stateService.getVaultTimeoutAction();
if ((timeout != null || timeout === 0) && action === "logOut" && action !== currentAction) {
// if we have a vault timeout and the action is log out, reset tokens
await this.tokenService.clearToken();
}
await this.stateService.setVaultTimeoutAction(action); await this.stateService.setVaultTimeoutAction(action);
await this.tokenService.setToken(token);
await this.tokenService.setRefreshToken(refreshToken);
await this.tokenService.setClientId(clientId);
await this.tokenService.setClientSecret(clientSecret);
await this.cryptoService.toggleKey(); await this.cryptoService.toggleKey();
await this.tokenService.toggleTokens();
} }
async isPinLockSet(): Promise<[boolean, boolean]> { async isPinLockSet(): Promise<[boolean, boolean]> {