mirror of
https://github.com/bitwarden/browser
synced 2026-02-12 22:44:11 +00:00
Add migration
This commit is contained in:
@@ -7,7 +7,69 @@ describe("RemoveUserEncryptedPrivateKey", () => {
|
||||
const sut = new RemoveUserEncryptedPrivateKey(74, 75);
|
||||
|
||||
describe("migrate", () => {
|
||||
it("deletes user encrypted private key, signing key, and signed public key from all users", async () => {
|
||||
it("migrates V1 cryptographic state (privateKey only)", async () => {
|
||||
const output = await runMigrator(sut, {
|
||||
global_account_accounts: {
|
||||
user1: {
|
||||
email: "user1@email.com",
|
||||
name: "User 1",
|
||||
emailVerified: true,
|
||||
},
|
||||
},
|
||||
user_user1_crypto_privateKey: "encryptedPrivateKey",
|
||||
});
|
||||
|
||||
expect(output).toEqual({
|
||||
global_account_accounts: {
|
||||
user1: {
|
||||
email: "user1@email.com",
|
||||
name: "User 1",
|
||||
emailVerified: true,
|
||||
},
|
||||
},
|
||||
user_user1_crypto_accountCryptographicState: {
|
||||
V1: {
|
||||
private_key: "encryptedPrivateKey",
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("migrates V2 cryptographic state (all keys present)", async () => {
|
||||
const output = await runMigrator(sut, {
|
||||
global_account_accounts: {
|
||||
user1: {
|
||||
email: "user1@email.com",
|
||||
name: "User 1",
|
||||
emailVerified: true,
|
||||
},
|
||||
},
|
||||
user_user1_crypto_privateKey: "encryptedPrivateKey",
|
||||
user_user1_crypto_userSigningKey: "signingKey",
|
||||
user_user1_crypto_userSignedPublicKey: "signedPublicKey",
|
||||
user_user1_crypto_accountSecurityState: "securityState",
|
||||
});
|
||||
|
||||
expect(output).toEqual({
|
||||
global_account_accounts: {
|
||||
user1: {
|
||||
email: "user1@email.com",
|
||||
name: "User 1",
|
||||
emailVerified: true,
|
||||
},
|
||||
},
|
||||
user_user1_crypto_accountCryptographicState: {
|
||||
V2: {
|
||||
private_key: "encryptedPrivateKey",
|
||||
signing_key: "signingKey",
|
||||
signed_public_key: "signedPublicKey",
|
||||
security_state: "securityState",
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("migrates multiple users with different cryptographic states", async () => {
|
||||
const output = await runMigrator(sut, {
|
||||
global_account_accounts: {
|
||||
user1: {
|
||||
@@ -20,15 +82,20 @@ describe("RemoveUserEncryptedPrivateKey", () => {
|
||||
name: "User 2",
|
||||
emailVerified: true,
|
||||
},
|
||||
user3: {
|
||||
email: "user3@email.com",
|
||||
name: "User 3",
|
||||
emailVerified: true,
|
||||
},
|
||||
},
|
||||
user_user1_CRYPTO_DISK_privateKey: "abc",
|
||||
user_user2_CRYPTO_DISK_privateKey: "def",
|
||||
user_user1_CRYPTO_DISK_userSigningKey: "sign1",
|
||||
user_user2_CRYPTO_DISK_userSigningKey: "sign2",
|
||||
user_user1_CRYPTO_DISK_userSignedPublicKey: "pub1",
|
||||
user_user2_CRYPTO_DISK_userSignedPublicKey: "pub2",
|
||||
user_user1_CRYPTO_DISK_accountSecurityState: "security1",
|
||||
user_user2_CRYPTO_DISK_accountSecurityState: "security2",
|
||||
// user1: V1 state
|
||||
user_user1_crypto_privateKey: "privateKey1",
|
||||
// user2: V2 state
|
||||
user_user2_crypto_privateKey: "privateKey2",
|
||||
user_user2_crypto_userSigningKey: "signingKey2",
|
||||
user_user2_crypto_userSignedPublicKey: "signedPublicKey2",
|
||||
user_user2_crypto_accountSecurityState: "securityState2",
|
||||
// user3: no cryptographic state
|
||||
});
|
||||
|
||||
expect(output).toEqual({
|
||||
@@ -43,7 +110,56 @@ describe("RemoveUserEncryptedPrivateKey", () => {
|
||||
name: "User 2",
|
||||
emailVerified: true,
|
||||
},
|
||||
user3: {
|
||||
email: "user3@email.com",
|
||||
name: "User 3",
|
||||
emailVerified: true,
|
||||
},
|
||||
},
|
||||
user_user1_crypto_accountCryptographicState: {
|
||||
V1: {
|
||||
private_key: "privateKey1",
|
||||
},
|
||||
},
|
||||
user_user2_crypto_accountCryptographicState: {
|
||||
V2: {
|
||||
private_key: "privateKey2",
|
||||
signing_key: "signingKey2",
|
||||
signed_public_key: "signedPublicKey2",
|
||||
security_state: "securityState2",
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("does not overwrite existing accountCryptographicState", async () => {
|
||||
const existingState = {
|
||||
V1: {
|
||||
private_key: "existingPrivateKey",
|
||||
},
|
||||
};
|
||||
|
||||
const output = await runMigrator(sut, {
|
||||
global_account_accounts: {
|
||||
user1: {
|
||||
email: "user1@email.com",
|
||||
name: "User 1",
|
||||
emailVerified: true,
|
||||
},
|
||||
},
|
||||
user_user1_crypto_accountCryptographicState: existingState,
|
||||
user_user1_crypto_privateKey: "newPrivateKey",
|
||||
});
|
||||
|
||||
expect(output).toEqual({
|
||||
global_account_accounts: {
|
||||
user1: {
|
||||
email: "user1@email.com",
|
||||
name: "User 1",
|
||||
emailVerified: true,
|
||||
},
|
||||
},
|
||||
user_user1_crypto_accountCryptographicState: existingState,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,62 +1,95 @@
|
||||
import { KeyDefinitionLike, MigrationHelper } from "../migration-helper";
|
||||
import { IRREVERSIBLE, Migrator } from "../migrator";
|
||||
import { SignedPublicKey,
|
||||
WrappedAccountCryptographicState, EncString, SignedSecurityState } from "@bitwarden/sdk-internal";
|
||||
|
||||
type ExpectedAccountType = NonNullable<unknown>;
|
||||
|
||||
export const userEncryptedPrivateKey: KeyDefinitionLike = {
|
||||
key: "privateKey",
|
||||
stateDefinition: {
|
||||
name: "CRYPTO_DISK",
|
||||
name: "crypto",
|
||||
},
|
||||
};
|
||||
|
||||
export const userKeyEncryptedSigningKey: KeyDefinitionLike = {
|
||||
key: "userSigningKey",
|
||||
stateDefinition: {
|
||||
name: "CRYPTO_DISK",
|
||||
name: "crypto",
|
||||
},
|
||||
};
|
||||
|
||||
export const userSignedPublicKey: KeyDefinitionLike = {
|
||||
key: "userSignedPublicKey",
|
||||
stateDefinition: {
|
||||
name: "CRYPTO_DISK",
|
||||
name: "crypto",
|
||||
},
|
||||
};
|
||||
|
||||
export const accountSecurityState: KeyDefinitionLike = {
|
||||
key: "accountSecurityState",
|
||||
stateDefinition: {
|
||||
name: "CRYPTO_DISK",
|
||||
name: "crypto",
|
||||
},
|
||||
};
|
||||
|
||||
export const accountCryptographicState: KeyDefinitionLike = {
|
||||
key: "accountCryptographicState",
|
||||
stateDefinition: {
|
||||
name: "crypto",
|
||||
},
|
||||
};
|
||||
|
||||
export class RemoveUserEncryptedPrivateKey extends Migrator<74, 75> {
|
||||
|
||||
async migrate(helper: MigrationHelper): Promise<void> {
|
||||
const accounts = await helper.getAccounts<ExpectedAccountType>();
|
||||
async function migrateAccount(userId: string, account: ExpectedAccountType): Promise<void> {
|
||||
// Remove privateKey
|
||||
const key = await helper.getFromUser(userId, userEncryptedPrivateKey);
|
||||
if (key != null) {
|
||||
for (const { userId } of accounts) {
|
||||
// Check if account cryptographic state already exists
|
||||
const existingAccountCryptoState = await helper.getFromUser(userId, accountCryptographicState);
|
||||
|
||||
// Gather all individual cryptographic key state parts
|
||||
const privateKey = await helper.getFromUser(userId, userEncryptedPrivateKey);
|
||||
const signingKey = await helper.getFromUser(userId, userKeyEncryptedSigningKey);
|
||||
const signedPubKey = await helper.getFromUser(userId, userSignedPublicKey);
|
||||
const accountSecurity = await helper.getFromUser(userId, accountSecurityState);
|
||||
|
||||
// Only migrate if account cryptographic state does not exist
|
||||
if (!existingAccountCryptoState) {
|
||||
// Build the new account cryptographic state object
|
||||
let newAccountCryptographicState: WrappedAccountCryptographicState;
|
||||
if (privateKey != null && signingKey == null && signedPubKey == null && accountSecurity == null) {
|
||||
newAccountCryptographicState = { V1: { private_key: privateKey as EncString } };
|
||||
await helper.setToUser(userId, accountCryptographicState, newAccountCryptographicState);
|
||||
} else if (privateKey != null && signingKey != null && signedPubKey != null && accountSecurity != null) {
|
||||
newAccountCryptographicState = {
|
||||
V2: {
|
||||
private_key: privateKey as EncString,
|
||||
signing_key: signingKey as EncString,
|
||||
signed_public_key: signedPubKey as SignedPublicKey,
|
||||
security_state: accountSecurity as SignedSecurityState,
|
||||
},
|
||||
};
|
||||
await helper.setToUser(userId, accountCryptographicState, newAccountCryptographicState);
|
||||
} else {
|
||||
helper.logService.warning(`Incomplete cryptographic state for user ${userId}, skipping migration of account cryptographic state.`);
|
||||
}
|
||||
}
|
||||
|
||||
// Always remove the old states
|
||||
if (privateKey != null) {
|
||||
await helper.removeFromUser(userId, userEncryptedPrivateKey);
|
||||
}
|
||||
// Remove userSigningKey
|
||||
const signingKey = await helper.getFromUser(userId, userKeyEncryptedSigningKey);
|
||||
if (signingKey != null) {
|
||||
await helper.removeFromUser(userId, userKeyEncryptedSigningKey);
|
||||
}
|
||||
// Remove userSignedPublicKey
|
||||
const signedPubKey = await helper.getFromUser(userId, userSignedPublicKey);
|
||||
if (signedPubKey != null) {
|
||||
await helper.removeFromUser(userId, userSignedPublicKey);
|
||||
}
|
||||
// Remove accountSecurityState
|
||||
const accountSecurity = await helper.getFromUser(userId, accountSecurityState);
|
||||
if (accountSecurity != null) {
|
||||
await helper.removeFromUser(userId, accountSecurityState);
|
||||
}
|
||||
}
|
||||
await Promise.all([...accounts.map(({ userId, account }) => migrateAccount(userId, account))]);
|
||||
}
|
||||
|
||||
async rollback(helper: MigrationHelper): Promise<void> {
|
||||
|
||||
Reference in New Issue
Block a user