1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-18 09:13:33 +00:00
Files
browser/libs/common/src/state-migrations/migrations/38-migrate-token-svc-to-state-provider.spec.ts
Jared Snider 899172722a Auth/PM-5263 - TokenService State provider migration bug fix to avoid persisting tokens in local storage (#8413)
* PM-5263 - Update token svc state provider migration to avoid persisting secrets that shouldn't exist in local storage to state provider local storage using new migration helper type.

* PM-5263 - TokenSvc migration - tests TODO

* write tests for migration

* fix tests

---------

Co-authored-by: Jake Fink <jfink@bitwarden.com>
2024-03-25 16:26:27 -04:00

301 lines
9.6 KiB
TypeScript

import { MockProxy, any } from "jest-mock-extended";
import { MigrationHelper } from "../migration-helper";
import { mockMigrationHelper } from "../migration-helper.spec";
import {
EMAIL_TWO_FACTOR_TOKEN_RECORD_DISK_LOCAL,
ACCESS_TOKEN_DISK,
REFRESH_TOKEN_DISK,
API_KEY_CLIENT_ID_DISK,
API_KEY_CLIENT_SECRET_DISK,
TokenServiceStateProviderMigrator,
} from "./38-migrate-token-svc-to-state-provider";
// Represents data in state service pre-migration
function preMigrationJson() {
return {
global: {
twoFactorToken: "twoFactorToken",
otherStuff: "otherStuff1",
},
authenticatedAccounts: ["user1", "user2", "user3"],
user1: {
tokens: {
accessToken: "accessToken",
refreshToken: "refreshToken",
otherStuff: "overStuff2",
},
profile: {
apiKeyClientId: "apiKeyClientId",
email: "user1Email",
otherStuff: "overStuff3",
},
keys: {
apiKeyClientSecret: "apiKeyClientSecret",
otherStuff: "overStuff4",
},
otherStuff: "otherStuff5",
},
user2: {
tokens: {
// no tokens to migrate
otherStuff: "overStuff2",
},
profile: {
// no apiKeyClientId to migrate
otherStuff: "overStuff3",
email: "user2Email",
},
keys: {
// no apiKeyClientSecret to migrate
otherStuff: "overStuff4",
},
otherStuff: "otherStuff5",
},
};
}
function rollbackJSON() {
return {
// User specific state provider data
// use pattern user_{userId}_{stateDefinitionName}_{keyDefinitionKey} for user data
// User1 migrated data
user_user1_token_accessToken: "accessToken",
user_user1_token_refreshToken: "refreshToken",
user_user1_token_apiKeyClientId: "apiKeyClientId",
user_user1_token_apiKeyClientSecret: "apiKeyClientSecret",
// User2 migrated data
user_user2_token_accessToken: null as any,
user_user2_token_refreshToken: null as any,
user_user2_token_apiKeyClientId: null as any,
user_user2_token_apiKeyClientSecret: null as any,
// Global state provider data
// use pattern global_{stateDefinitionName}_{keyDefinitionKey} for global data
global_tokenDiskLocal_emailTwoFactorTokenRecord: {
user1Email: "twoFactorToken",
user2Email: "twoFactorToken",
},
global: {
// no longer has twoFactorToken
otherStuff: "otherStuff1",
},
authenticatedAccounts: ["user1", "user2", "user3"],
user1: {
tokens: {
otherStuff: "overStuff2",
},
profile: {
email: "user1Email",
otherStuff: "overStuff3",
},
keys: {
otherStuff: "overStuff4",
},
otherStuff: "otherStuff5",
},
user2: {
tokens: {
otherStuff: "overStuff2",
},
profile: {
email: "user2Email",
otherStuff: "overStuff3",
},
keys: {
otherStuff: "overStuff4",
},
otherStuff: "otherStuff5",
},
};
}
describe("TokenServiceStateProviderMigrator", () => {
let helper: MockProxy<MigrationHelper>;
let sut: TokenServiceStateProviderMigrator;
describe("migrate", () => {
beforeEach(() => {
helper = mockMigrationHelper(preMigrationJson(), 37);
sut = new TokenServiceStateProviderMigrator(37, 38);
});
describe("Session storage", () => {
it("should remove state service data from all accounts that have it", async () => {
await sut.migrate(helper);
expect(helper.set).toHaveBeenCalledWith("user1", {
tokens: {
otherStuff: "overStuff2",
},
profile: {
email: "user1Email",
otherStuff: "overStuff3",
},
keys: {
otherStuff: "overStuff4",
},
otherStuff: "otherStuff5",
});
expect(helper.set).toHaveBeenCalledTimes(2);
expect(helper.set).not.toHaveBeenCalledWith("user2", any());
expect(helper.set).not.toHaveBeenCalledWith("user3", any());
});
it("should migrate data to state providers for defined accounts that have the data", async () => {
await sut.migrate(helper);
// Two factor Token Migration
expect(helper.setToGlobal).toHaveBeenLastCalledWith(
EMAIL_TWO_FACTOR_TOKEN_RECORD_DISK_LOCAL,
{
user1Email: "twoFactorToken",
user2Email: "twoFactorToken",
},
);
expect(helper.setToGlobal).toHaveBeenCalledTimes(1);
expect(helper.setToUser).toHaveBeenCalledWith("user1", ACCESS_TOKEN_DISK, "accessToken");
expect(helper.setToUser).toHaveBeenCalledWith("user1", REFRESH_TOKEN_DISK, "refreshToken");
expect(helper.setToUser).toHaveBeenCalledWith(
"user1",
API_KEY_CLIENT_ID_DISK,
"apiKeyClientId",
);
expect(helper.setToUser).toHaveBeenCalledWith(
"user1",
API_KEY_CLIENT_SECRET_DISK,
"apiKeyClientSecret",
);
expect(helper.setToUser).not.toHaveBeenCalledWith("user2", ACCESS_TOKEN_DISK, any());
expect(helper.setToUser).not.toHaveBeenCalledWith("user2", REFRESH_TOKEN_DISK, any());
expect(helper.setToUser).not.toHaveBeenCalledWith("user2", API_KEY_CLIENT_ID_DISK, any());
expect(helper.setToUser).not.toHaveBeenCalledWith(
"user2",
API_KEY_CLIENT_SECRET_DISK,
any(),
);
// Expect that we didn't migrate anything to user 3
expect(helper.setToUser).not.toHaveBeenCalledWith("user3", ACCESS_TOKEN_DISK, any());
expect(helper.setToUser).not.toHaveBeenCalledWith("user3", REFRESH_TOKEN_DISK, any());
expect(helper.setToUser).not.toHaveBeenCalledWith("user3", API_KEY_CLIENT_ID_DISK, any());
expect(helper.setToUser).not.toHaveBeenCalledWith(
"user3",
API_KEY_CLIENT_SECRET_DISK,
any(),
);
});
});
describe("Local storage", () => {
beforeEach(() => {
helper = mockMigrationHelper(preMigrationJson(), 37, "web-disk-local");
});
it("should remove state service data from all accounts that have it", async () => {
await sut.migrate(helper);
expect(helper.set).toHaveBeenCalledWith("user1", {
tokens: {
otherStuff: "overStuff2",
},
profile: {
email: "user1Email",
otherStuff: "overStuff3",
},
keys: {
otherStuff: "overStuff4",
},
otherStuff: "otherStuff5",
});
expect(helper.set).toHaveBeenCalledTimes(2);
expect(helper.set).not.toHaveBeenCalledWith("user2", any());
expect(helper.set).not.toHaveBeenCalledWith("user3", any());
});
it("should not migrate any data to local storage", async () => {
await sut.migrate(helper);
expect(helper.setToUser).not.toHaveBeenCalled();
});
});
});
describe("rollback", () => {
beforeEach(() => {
helper = mockMigrationHelper(rollbackJSON(), 38);
sut = new TokenServiceStateProviderMigrator(37, 38);
});
it("should null out newly migrated entries in state provider framework", async () => {
await sut.rollback(helper);
expect(helper.setToGlobal).toHaveBeenCalledWith(
EMAIL_TWO_FACTOR_TOKEN_RECORD_DISK_LOCAL,
null,
);
expect(helper.setToUser).toHaveBeenCalledWith("user1", ACCESS_TOKEN_DISK, null);
expect(helper.setToUser).toHaveBeenCalledWith("user1", REFRESH_TOKEN_DISK, null);
expect(helper.setToUser).toHaveBeenCalledWith("user1", API_KEY_CLIENT_ID_DISK, null);
expect(helper.setToUser).toHaveBeenCalledWith("user1", API_KEY_CLIENT_SECRET_DISK, null);
expect(helper.setToUser).toHaveBeenCalledWith("user2", ACCESS_TOKEN_DISK, null);
expect(helper.setToUser).toHaveBeenCalledWith("user2", REFRESH_TOKEN_DISK, null);
expect(helper.setToUser).toHaveBeenCalledWith("user2", API_KEY_CLIENT_ID_DISK, null);
expect(helper.setToUser).toHaveBeenCalledWith("user2", API_KEY_CLIENT_SECRET_DISK, null);
expect(helper.setToUser).toHaveBeenCalledWith("user3", ACCESS_TOKEN_DISK, null);
expect(helper.setToUser).toHaveBeenCalledWith("user3", REFRESH_TOKEN_DISK, null);
expect(helper.setToUser).toHaveBeenCalledWith("user3", API_KEY_CLIENT_ID_DISK, null);
expect(helper.setToUser).toHaveBeenCalledWith("user3", API_KEY_CLIENT_SECRET_DISK, null);
});
it("should add back data to all accounts that had migrated data (only user 1)", async () => {
await sut.rollback(helper);
expect(helper.set).toHaveBeenCalledWith("user1", {
tokens: {
accessToken: "accessToken",
refreshToken: "refreshToken",
otherStuff: "overStuff2",
},
profile: {
apiKeyClientId: "apiKeyClientId",
email: "user1Email",
otherStuff: "overStuff3",
},
keys: {
apiKeyClientSecret: "apiKeyClientSecret",
otherStuff: "overStuff4",
},
otherStuff: "otherStuff5",
});
});
it("should add back the global twoFactorToken", async () => {
await sut.rollback(helper);
expect(helper.set).toHaveBeenCalledWith("global", {
twoFactorToken: "twoFactorToken",
otherStuff: "otherStuff1",
});
});
it("should not add data back if data wasn't migrated or acct doesn't exist", async () => {
await sut.rollback(helper);
// no data to add back for user2 (acct exists but no migrated data) and user3 (no acct)
expect(helper.set).not.toHaveBeenCalledWith("user2", any());
expect(helper.set).not.toHaveBeenCalledWith("user3", any());
});
});
});