1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-15 07:43:35 +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:
Jake Fink
2024-03-20 20:33:57 -04:00
committed by GitHub
parent e2fe1e1567
commit 2111b37c32
68 changed files with 1158 additions and 360 deletions

View File

@@ -39,6 +39,7 @@ import { OrganizationMigrator } from "./migrations/40-move-organization-state-to
import { EventCollectionMigrator } from "./migrations/41-move-event-collection-to-state-provider";
import { EnableFaviconMigrator } from "./migrations/42-move-enable-favicon-to-domain-settings-state-provider";
import { AutoConfirmFingerPrintsMigrator } from "./migrations/43-move-auto-confirm-finger-prints-to-state-provider";
import { UserDecryptionOptionsMigrator } from "./migrations/44-move-user-decryption-options-to-state-provider";
import { AddKeyTypeToOrgKeysMigrator } from "./migrations/5-add-key-type-to-org-keys";
import { RemoveLegacyEtmKeyMigrator } from "./migrations/6-remove-legacy-etm-key";
import { MoveBiometricAutoPromptToAccount } from "./migrations/7-move-biometric-auto-prompt-to-account";
@@ -47,7 +48,7 @@ import { MoveBrowserSettingsToGlobal } from "./migrations/9-move-browser-setting
import { MinVersionMigrator } from "./migrations/min-version";
export const MIN_VERSION = 3;
export const CURRENT_VERSION = 43;
export const CURRENT_VERSION = 44;
export type MinVersion = typeof MIN_VERSION;
export function createMigrationBuilder() {
@@ -92,7 +93,8 @@ export function createMigrationBuilder() {
.with(OrganizationMigrator, 39, 40)
.with(EventCollectionMigrator, 40, 41)
.with(EnableFaviconMigrator, 41, 42)
.with(AutoConfirmFingerPrintsMigrator, 42, CURRENT_VERSION);
.with(AutoConfirmFingerPrintsMigrator, 42, 43)
.with(UserDecryptionOptionsMigrator, 43, CURRENT_VERSION);
}
export async function currentVersion(

View File

@@ -0,0 +1,238 @@
import { any, MockProxy } from "jest-mock-extended";
import { MigrationHelper } from "../migration-helper";
import { mockMigrationHelper } from "../migration-helper.spec";
import { UserDecryptionOptionsMigrator } from "./44-move-user-decryption-options-to-state-provider";
function exampleJSON() {
return {
global: {
otherStuff: "otherStuff1",
},
authenticatedAccounts: ["FirstAccount", "SecondAccount", "ThirdAccount"],
FirstAccount: {
decryptionOptions: {
hasMasterPassword: true,
trustedDeviceOption: {
hasAdminApproval: false,
hasLoginApprovingDevice: false,
hasManageResetPasswordPermission: true,
},
keyConnectorOption: {
keyConnectorUrl: "https://keyconnector.bitwarden.com",
},
},
profile: {
otherStuff: "overStuff2",
},
otherStuff: "otherStuff3",
},
SecondAccount: {
decryptionOptions: {
hasMasterPassword: false,
trustedDeviceOption: {
hasAdminApproval: true,
hasLoginApprovingDevice: true,
hasManageResetPasswordPermission: true,
},
keyConnectorOption: {
keyConnectorUrl: "https://selfhosted.bitwarden.com",
},
},
profile: {
otherStuff: "otherStuff4",
},
otherStuff: "otherStuff5",
},
};
}
function rollbackJSON() {
return {
user_FirstAccount_decryptionOptions_userDecryptionOptions: {
hasMasterPassword: true,
trustedDeviceOption: {
hasAdminApproval: false,
hasLoginApprovingDevice: false,
hasManageResetPasswordPermission: true,
},
keyConnectorOption: {
keyConnectorUrl: "https://keyconnector.bitwarden.com",
},
},
user_SecondAccount_decryptionOptions_userDecryptionOptions: {
hasMasterPassword: false,
trustedDeviceOption: {
hasAdminApproval: true,
hasLoginApprovingDevice: true,
hasManageResetPasswordPermission: true,
},
keyConnectorOption: {
keyConnectorUrl: "https://selfhosted.bitwarden.com",
},
},
user_ThirdAccount_decryptionOptions_userDecryptionOptions: {},
global: {
otherStuff: "otherStuff1",
},
authenticatedAccounts: ["FirstAccount", "SecondAccount", "ThirdAccount"],
FirstAccount: {
decryptionOptions: {
hasMasterPassword: true,
trustedDeviceOption: {
hasAdminApproval: false,
hasLoginApprovingDevice: false,
hasManageResetPasswordPermission: true,
},
keyConnectorOption: {
keyConnectorUrl: "https://keyconnector.bitwarden.com",
},
},
profile: {
otherStuff: "overStuff2",
},
otherStuff: "otherStuff3",
},
SecondAccount: {
decryptionOptions: {
hasMasterPassword: false,
trustedDeviceOption: {
hasAdminApproval: true,
hasLoginApprovingDevice: true,
hasManageResetPasswordPermission: true,
},
keyConnectorOption: {
keyConnectorUrl: "https://selfhosted.bitwarden.com",
},
},
profile: {
otherStuff: "otherStuff4",
},
otherStuff: "otherStuff5",
},
};
}
describe("UserDecryptionOptionsMigrator", () => {
let helper: MockProxy<MigrationHelper>;
let sut: UserDecryptionOptionsMigrator;
const keyDefinitionLike = {
key: "decryptionOptions",
stateDefinition: {
name: "userDecryptionOptions",
},
};
describe("migrate", () => {
beforeEach(() => {
helper = mockMigrationHelper(exampleJSON(), 43);
sut = new UserDecryptionOptionsMigrator(43, 44);
});
it("should remove decryptionOptions from all accounts", async () => {
await sut.migrate(helper);
expect(helper.set).toHaveBeenCalledWith("FirstAccount", {
profile: {
otherStuff: "overStuff2",
},
otherStuff: "otherStuff3",
});
expect(helper.set).toHaveBeenCalledWith("SecondAccount", {
profile: {
otherStuff: "otherStuff4",
},
otherStuff: "otherStuff5",
});
});
it("should set decryptionOptions provider value for each account", async () => {
await sut.migrate(helper);
expect(helper.setToUser).toHaveBeenCalledWith("FirstAccount", keyDefinitionLike, {
hasMasterPassword: true,
trustedDeviceOption: {
hasAdminApproval: false,
hasLoginApprovingDevice: false,
hasManageResetPasswordPermission: true,
},
keyConnectorOption: {
keyConnectorUrl: "https://keyconnector.bitwarden.com",
},
});
expect(helper.setToUser).toHaveBeenCalledWith("SecondAccount", keyDefinitionLike, {
hasMasterPassword: false,
trustedDeviceOption: {
hasAdminApproval: true,
hasLoginApprovingDevice: true,
hasManageResetPasswordPermission: true,
},
keyConnectorOption: {
keyConnectorUrl: "https://selfhosted.bitwarden.com",
},
});
});
});
describe("rollback", () => {
beforeEach(() => {
helper = mockMigrationHelper(rollbackJSON(), 44);
sut = new UserDecryptionOptionsMigrator(43, 44);
});
it.each(["FirstAccount", "SecondAccount", "ThirdAccount"])(
"should null out new values",
async (userId) => {
await sut.rollback(helper);
expect(helper.setToUser).toHaveBeenCalledWith(userId, keyDefinitionLike, null);
},
);
it("should add explicit value back to accounts", async () => {
await sut.rollback(helper);
expect(helper.set).toHaveBeenCalledWith("FirstAccount", {
decryptionOptions: {
hasMasterPassword: true,
trustedDeviceOption: {
hasAdminApproval: false,
hasLoginApprovingDevice: false,
hasManageResetPasswordPermission: true,
},
keyConnectorOption: {
keyConnectorUrl: "https://keyconnector.bitwarden.com",
},
},
profile: {
otherStuff: "overStuff2",
},
otherStuff: "otherStuff3",
});
expect(helper.set).toHaveBeenCalledWith("SecondAccount", {
decryptionOptions: {
hasMasterPassword: false,
trustedDeviceOption: {
hasAdminApproval: true,
hasLoginApprovingDevice: true,
hasManageResetPasswordPermission: true,
},
keyConnectorOption: {
keyConnectorUrl: "https://selfhosted.bitwarden.com",
},
},
profile: {
otherStuff: "otherStuff4",
},
otherStuff: "otherStuff5",
});
});
it("should not try to restore values to missing accounts", async () => {
await sut.rollback(helper);
expect(helper.set).not.toHaveBeenCalledWith("ThirdAccount", any());
});
});
});

View File

@@ -0,0 +1,57 @@
import { KeyDefinitionLike, MigrationHelper } from "../migration-helper";
import { Migrator } from "../migrator";
type DecryptionOptionsType = {
hasMasterPassword: boolean;
trustedDeviceOption?: {
hasAdminApproval: boolean;
hasLoginApprovingDevice: boolean;
hasManageResetPasswordPermission: boolean;
};
keyConnectorOption?: {
keyConnectorUrl: string;
};
};
type ExpectedAccountType = {
decryptionOptions?: DecryptionOptionsType;
};
const USER_DECRYPTION_OPTIONS: KeyDefinitionLike = {
key: "decryptionOptions",
stateDefinition: {
name: "userDecryptionOptions",
},
};
export class UserDecryptionOptionsMigrator extends Migrator<43, 44> {
async migrate(helper: MigrationHelper): Promise<void> {
const accounts = await helper.getAccounts<ExpectedAccountType>();
async function migrateAccount(userId: string, account: ExpectedAccountType): Promise<void> {
const value = account?.decryptionOptions;
if (value != null) {
await helper.setToUser(userId, USER_DECRYPTION_OPTIONS, value);
delete account.decryptionOptions;
await helper.set(userId, account);
}
}
await Promise.all([...accounts.map(({ userId, account }) => migrateAccount(userId, account))]);
}
async rollback(helper: MigrationHelper): Promise<void> {
const accounts = await helper.getAccounts<ExpectedAccountType>();
async function rollbackAccount(userId: string, account: ExpectedAccountType): Promise<void> {
const value: DecryptionOptionsType = await helper.getFromUser(
userId,
USER_DECRYPTION_OPTIONS,
);
if (account) {
account.decryptionOptions = Object.assign(account.decryptionOptions, value);
await helper.set(userId, account);
}
await helper.setToUser(userId, USER_DECRYPTION_OPTIONS, null);
}
await Promise.all([...accounts.map(({ userId, account }) => rollbackAccount(userId, account))]);
}
}