Clear stale everBeenUnlocked value from onDisk storage (#682)

* Add StateVersion.Four to remove old everBeenUnlocked key

* Save new state properly

* Add unit tests

* Fix linting
This commit is contained in:
Thomas Rittson 2022-02-14 23:16:07 +10:00 committed by GitHub
parent bcbb52e6ec
commit 609baece05
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 110 additions and 1 deletions

View File

@ -2,5 +2,6 @@ export enum StateVersion {
One = 1, // Original flat key/value pair store One = 1, // Original flat key/value pair store
Two = 2, // Move to a typed State object Two = 2, // Move to a typed State object
Three = 3, // Fix migration of users' premium status Three = 3, // Fix migration of users' premium status
Latest = Three, Four = 4, // Fix 'Never Lock' option by removing stale data
Latest = Four,
} }

View File

@ -158,6 +158,9 @@ export class StateMigrationService<
case StateVersion.Two: case StateVersion.Two:
await this.migrateStateFrom2To3(); await this.migrateStateFrom2To3();
break; break;
case StateVersion.Three:
await this.migrateStateFrom3To4();
break;
} }
currentStateVersion += 1; currentStateVersion += 1;
@ -474,6 +477,23 @@ export class StateMigrationService<
await this.set(keys.global, globals); await this.set(keys.global, globals);
} }
protected async migrateStateFrom3To4(): Promise<void> {
const authenticatedUserIds = await this.get<string[]>(keys.authenticatedAccounts);
await Promise.all(
authenticatedUserIds.map(async (userId) => {
const account = await this.get<TAccount>(userId);
if (account?.profile?.everBeenUnlocked != null) {
delete account.profile.everBeenUnlocked;
return this.set(userId, account);
}
})
);
const globals = await this.getGlobals();
globals.stateVersion = StateVersion.Four;
await this.set(keys.global, globals);
}
protected get options(): StorageOptions { protected get options(): StorageOptions {
return { htmlStorageLocation: HtmlStorageLocation.Local }; return { htmlStorageLocation: HtmlStorageLocation.Local };
} }

View File

@ -0,0 +1,88 @@
import { Arg, Substitute, SubstituteOf } from "@fluffy-spoon/substitute";
import { StorageService } from "jslib-common/abstractions/storage.service";
import { StateMigrationService } from "jslib-common/services/stateMigration.service";
import { StateFactory } from "jslib-common/factories/stateFactory";
import { Account } from "jslib-common/models/domain/account";
import { GlobalState } from "jslib-common/models/domain/globalState";
import { StateVersion } from "jslib-common/enums/stateVersion";
const userId = "USER_ID";
describe("State Migration Service", () => {
let storageService: SubstituteOf<StorageService>;
let secureStorageService: SubstituteOf<StorageService>;
let stateFactory: SubstituteOf<StateFactory>;
let stateMigrationService: StateMigrationService;
beforeEach(() => {
storageService = Substitute.for<StorageService>();
secureStorageService = Substitute.for<StorageService>();
stateFactory = Substitute.for<StateFactory>();
stateMigrationService = new StateMigrationService(
storageService,
secureStorageService,
stateFactory
);
});
describe("StateVersion 3 to 4 migration", async () => {
beforeEach(() => {
const globalVersion3: Partial<GlobalState> = {
stateVersion: StateVersion.Three,
};
storageService.get("global", Arg.any()).resolves(globalVersion3);
storageService.get("authenticatedAccounts", Arg.any()).resolves([userId]);
});
it("clears everBeenUnlocked", async () => {
const accountVersion3: Account = {
profile: {
apiKeyClientId: null,
convertAccountToKeyConnector: null,
email: "EMAIL",
emailVerified: true,
everBeenUnlocked: true,
hasPremiumPersonally: false,
kdfIterations: 100000,
kdfType: 0,
keyHash: "KEY_HASH",
lastSync: "LAST_SYNC",
userId: userId,
usesKeyConnector: false,
forcePasswordReset: false,
},
};
const expectedAccountVersion4: Account = {
profile: {
...accountVersion3.profile,
},
};
delete expectedAccountVersion4.profile.everBeenUnlocked;
storageService.get(userId, Arg.any()).resolves(accountVersion3);
await stateMigrationService.migrate();
storageService.received(1).save(userId, expectedAccountVersion4, Arg.any());
});
it("updates StateVersion number", async () => {
await stateMigrationService.migrate();
storageService.received(1).save(
"global",
Arg.is((globals: GlobalState) => globals.stateVersion === StateVersion.Four),
Arg.any()
);
});
});
});