mirror of
https://github.com/bitwarden/browser
synced 2025-12-15 15:53:27 +00:00
[PM-5404, PM-3518] Migrate user decryption options to new service (#7344)
* create new user decryption options service * rename new service to user decryption options * add hasMasterPassword to user decryption options service * migrate device trust service to new user decryption options service * add migration for user-decryption-options * migrate sync service and calls to trust-device-service * rename abstraction file * migrate two factor component * migrate two factor spec * migrate sso component * migrate set-password component * migrate base login decryption component * migrate organization options component * fix component imports * add missing imports - remove state service calls - add update user decryption options method * remove acct decryption options from account * lint * fix tests and linting * fix browser * fix desktop * add user decryption options service to cli * remove default value from migration * bump migration number * fix merge conflict * fix vault timeout settings * fix cli * more fixes * add user decryption options service to deps of vault timeout settings service * update login strategy service with user decryption options * remove early return from sync bandaid for user decryption options * move user decryption options service to lib/auth * move user decryption options to libs/auth * fix reference * fix browser * check user decryption options after 2fa check * update migration and revert tsconfig changes * add more documentation * clear user decryption options on logout * fix tests by creating helper for user decryption options * fix tests * pr feedback * fix factory * update migration * add tests * update missed migration num in test
This commit is contained in:
@@ -18,7 +18,7 @@ import { CipherView } from "../../vault/models/view/cipher.view";
|
||||
import { AddEditCipherInfo } from "../../vault/types/add-edit-cipher-info";
|
||||
import { KdfType } from "../enums";
|
||||
import { ServerConfigData } from "../models/data/server-config.data";
|
||||
import { Account, AccountDecryptionOptions } from "../models/domain/account";
|
||||
import { Account } from "../models/domain/account";
|
||||
import { EncString } from "../models/domain/enc-string";
|
||||
import { StorageOptions } from "../models/domain/storage-options";
|
||||
import { SymmetricCryptoKey } from "../models/domain/symmetric-crypto-key";
|
||||
@@ -180,13 +180,6 @@ export abstract class StateService<T extends Account = Account> {
|
||||
) => Promise<void>;
|
||||
getShouldTrustDevice: (options?: StorageOptions) => Promise<boolean | null>;
|
||||
setShouldTrustDevice: (value: boolean, options?: StorageOptions) => Promise<void>;
|
||||
getAccountDecryptionOptions: (
|
||||
options?: StorageOptions,
|
||||
) => Promise<AccountDecryptionOptions | null>;
|
||||
setAccountDecryptionOptions: (
|
||||
value: AccountDecryptionOptions,
|
||||
options?: StorageOptions,
|
||||
) => Promise<void>;
|
||||
getEmail: (options?: StorageOptions) => Promise<string>;
|
||||
setEmail: (value: string, options?: StorageOptions) => Promise<void>;
|
||||
getEmailVerified: (options?: StorageOptions) => Promise<boolean>;
|
||||
|
||||
@@ -2,9 +2,6 @@ import { Jsonify } from "type-fest";
|
||||
|
||||
import { AdminAuthRequestStorable } from "../../../auth/models/domain/admin-auth-req-storable";
|
||||
import { ForceSetPasswordReason } from "../../../auth/models/domain/force-set-password-reason";
|
||||
import { KeyConnectorUserDecryptionOption } from "../../../auth/models/domain/user-decryption-options/key-connector-user-decryption-option";
|
||||
import { TrustedDeviceUserDecryptionOption } from "../../../auth/models/domain/user-decryption-options/trusted-device-user-decryption-option";
|
||||
import { IdentityTokenResponse } from "../../../auth/models/response/identity-token.response";
|
||||
import { UriMatchStrategySetting } from "../../../models/domain/domain-service";
|
||||
import { GeneratorOptions } from "../../../tools/generator/generator-options";
|
||||
import {
|
||||
@@ -235,103 +232,12 @@ export class AccountTokens {
|
||||
}
|
||||
}
|
||||
|
||||
export class AccountDecryptionOptions {
|
||||
hasMasterPassword: boolean;
|
||||
trustedDeviceOption?: TrustedDeviceUserDecryptionOption;
|
||||
keyConnectorOption?: KeyConnectorUserDecryptionOption;
|
||||
|
||||
constructor(init?: Partial<AccountDecryptionOptions>) {
|
||||
if (init) {
|
||||
Object.assign(this, init);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: these nice getters don't work because the Account object is not properly being deserialized out of
|
||||
// JSON (the Account static fromJSON method is not running) so these getters don't exist on the
|
||||
// account decryptions options object when pulled out of state. This is a bug that needs to be fixed later on
|
||||
// get hasTrustedDeviceOption(): boolean {
|
||||
// return this.trustedDeviceOption !== null && this.trustedDeviceOption !== undefined;
|
||||
// }
|
||||
|
||||
// get hasKeyConnectorOption(): boolean {
|
||||
// return this.keyConnectorOption !== null && this.keyConnectorOption !== undefined;
|
||||
// }
|
||||
|
||||
static fromResponse(response: IdentityTokenResponse): AccountDecryptionOptions {
|
||||
if (response == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const accountDecryptionOptions = new AccountDecryptionOptions();
|
||||
|
||||
if (response.userDecryptionOptions) {
|
||||
// If the response has userDecryptionOptions, this means it's on a post-TDE server version and can interrogate
|
||||
// the new decryption options.
|
||||
const responseOptions = response.userDecryptionOptions;
|
||||
accountDecryptionOptions.hasMasterPassword = responseOptions.hasMasterPassword;
|
||||
|
||||
if (responseOptions.trustedDeviceOption) {
|
||||
accountDecryptionOptions.trustedDeviceOption = new TrustedDeviceUserDecryptionOption(
|
||||
responseOptions.trustedDeviceOption.hasAdminApproval,
|
||||
responseOptions.trustedDeviceOption.hasLoginApprovingDevice,
|
||||
responseOptions.trustedDeviceOption.hasManageResetPasswordPermission,
|
||||
);
|
||||
}
|
||||
|
||||
if (responseOptions.keyConnectorOption) {
|
||||
accountDecryptionOptions.keyConnectorOption = new KeyConnectorUserDecryptionOption(
|
||||
responseOptions.keyConnectorOption.keyConnectorUrl,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// If the response does not have userDecryptionOptions, this means it's on a pre-TDE server version and so
|
||||
// we must base our decryption options on the presence of the keyConnectorUrl.
|
||||
// Note that the presence of keyConnectorUrl implies that the user does not have a master password, as in pre-TDE
|
||||
// server versions, a master password short-circuited the addition of the keyConnectorUrl to the response.
|
||||
// TODO: remove this check after 2023.10 release (https://bitwarden.atlassian.net/browse/PM-3537)
|
||||
const usingKeyConnector = response.keyConnectorUrl != null;
|
||||
accountDecryptionOptions.hasMasterPassword = !usingKeyConnector;
|
||||
if (usingKeyConnector) {
|
||||
accountDecryptionOptions.keyConnectorOption = new KeyConnectorUserDecryptionOption(
|
||||
response.keyConnectorUrl,
|
||||
);
|
||||
}
|
||||
}
|
||||
return accountDecryptionOptions;
|
||||
}
|
||||
|
||||
static fromJSON(obj: Jsonify<AccountDecryptionOptions>): AccountDecryptionOptions {
|
||||
if (obj == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const accountDecryptionOptions = Object.assign(new AccountDecryptionOptions(), obj);
|
||||
|
||||
if (obj.trustedDeviceOption) {
|
||||
accountDecryptionOptions.trustedDeviceOption = new TrustedDeviceUserDecryptionOption(
|
||||
obj.trustedDeviceOption.hasAdminApproval,
|
||||
obj.trustedDeviceOption.hasLoginApprovingDevice,
|
||||
obj.trustedDeviceOption.hasManageResetPasswordPermission,
|
||||
);
|
||||
}
|
||||
|
||||
if (obj.keyConnectorOption) {
|
||||
accountDecryptionOptions.keyConnectorOption = new KeyConnectorUserDecryptionOption(
|
||||
obj.keyConnectorOption.keyConnectorUrl,
|
||||
);
|
||||
}
|
||||
|
||||
return accountDecryptionOptions;
|
||||
}
|
||||
}
|
||||
|
||||
export class Account {
|
||||
data?: AccountData = new AccountData();
|
||||
keys?: AccountKeys = new AccountKeys();
|
||||
profile?: AccountProfile = new AccountProfile();
|
||||
settings?: AccountSettings = new AccountSettings();
|
||||
tokens?: AccountTokens = new AccountTokens();
|
||||
decryptionOptions?: AccountDecryptionOptions = new AccountDecryptionOptions();
|
||||
adminAuthRequest?: Jsonify<AdminAuthRequestStorable> = null;
|
||||
|
||||
constructor(init: Partial<Account>) {
|
||||
@@ -356,10 +262,6 @@ export class Account {
|
||||
...new AccountTokens(),
|
||||
...init?.tokens,
|
||||
},
|
||||
decryptionOptions: {
|
||||
...new AccountDecryptionOptions(),
|
||||
...init?.decryptionOptions,
|
||||
},
|
||||
adminAuthRequest: init?.adminAuthRequest,
|
||||
});
|
||||
}
|
||||
@@ -375,7 +277,6 @@ export class Account {
|
||||
profile: AccountProfile.fromJSON(json?.profile),
|
||||
settings: AccountSettings.fromJSON(json?.settings),
|
||||
tokens: AccountTokens.fromJSON(json?.tokens),
|
||||
decryptionOptions: AccountDecryptionOptions.fromJSON(json?.decryptionOptions),
|
||||
adminAuthRequest: AdminAuthRequestStorable.fromJSON(json?.adminAuthRequest),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -34,12 +34,7 @@ import { HtmlStorageLocation, KdfType, StorageLocation } from "../enums";
|
||||
import { StateFactory } from "../factories/state-factory";
|
||||
import { Utils } from "../misc/utils";
|
||||
import { ServerConfigData } from "../models/data/server-config.data";
|
||||
import {
|
||||
Account,
|
||||
AccountData,
|
||||
AccountDecryptionOptions,
|
||||
AccountSettings,
|
||||
} from "../models/domain/account";
|
||||
import { Account, AccountData, AccountSettings } from "../models/domain/account";
|
||||
import { EncString } from "../models/domain/enc-string";
|
||||
import { GlobalState } from "../models/domain/global-state";
|
||||
import { State } from "../models/domain/state";
|
||||
@@ -817,37 +812,6 @@ export class StateService<
|
||||
await this.saveAccount(account, options);
|
||||
}
|
||||
|
||||
async getAccountDecryptionOptions(
|
||||
options?: StorageOptions,
|
||||
): Promise<AccountDecryptionOptions | null> {
|
||||
options = this.reconcileOptions(options, await this.defaultOnDiskLocalOptions());
|
||||
|
||||
if (options?.userId == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const account = await this.getAccount(options);
|
||||
|
||||
return account?.decryptionOptions as AccountDecryptionOptions;
|
||||
}
|
||||
|
||||
async setAccountDecryptionOptions(
|
||||
value: AccountDecryptionOptions,
|
||||
options?: StorageOptions,
|
||||
): Promise<void> {
|
||||
options = this.reconcileOptions(options, await this.defaultOnDiskLocalOptions());
|
||||
|
||||
if (options?.userId == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const account = await this.getAccount(options);
|
||||
|
||||
account.decryptionOptions = value;
|
||||
|
||||
await this.saveAccount(account, options);
|
||||
}
|
||||
|
||||
async getEmail(options?: StorageOptions): Promise<string> {
|
||||
return (
|
||||
await this.getAccount(this.reconcileOptions(options, await this.defaultInMemoryOptions()))
|
||||
|
||||
@@ -44,6 +44,7 @@ export const TOKEN_DISK_LOCAL = new StateDefinition("tokenDiskLocal", "disk", {
|
||||
});
|
||||
export const TOKEN_MEMORY = new StateDefinition("token", "memory");
|
||||
export const LOGIN_STRATEGY_MEMORY = new StateDefinition("loginStrategy", "memory");
|
||||
export const USER_DECRYPTION_OPTIONS_DISK = new StateDefinition("userDecryptionOptions", "disk");
|
||||
|
||||
// Autofill
|
||||
|
||||
|
||||
Reference in New Issue
Block a user