This reverts commit 770f782a16
.
This commit is contained in:
parent
ac7d80980d
commit
8ee1965832
|
@ -70,9 +70,6 @@ export class FakeAccountService implements AccountService {
|
||||||
}
|
}
|
||||||
|
|
||||||
async switchAccount(userId: UserId): Promise<void> {
|
async switchAccount(userId: UserId): Promise<void> {
|
||||||
const next =
|
|
||||||
userId == null ? null : { id: userId, ...this.accountsSubject["_buffer"]?.[0]?.[userId] };
|
|
||||||
this.activeAccountSubject.next(next);
|
|
||||||
await this.mock.switchAccount(userId);
|
await this.mock.switchAccount(userId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { firstValueFrom } from "rxjs";
|
import { firstValueFrom } from "rxjs";
|
||||||
|
|
||||||
import { makeEncString, trackEmissions } from "../../../spec";
|
import { makeEncString } from "../../../spec";
|
||||||
import { FakeAccountService, mockAccountServiceWith } from "../../../spec/fake-account-service";
|
import { mockAccountServiceWith } from "../../../spec/fake-account-service";
|
||||||
import { FakeSingleUserState } from "../../../spec/fake-state";
|
import { FakeSingleUserState } from "../../../spec/fake-state";
|
||||||
import { FakeStateProvider } from "../../../spec/fake-state-provider";
|
import { FakeStateProvider } from "../../../spec/fake-state-provider";
|
||||||
import { UserId } from "../../types/guid";
|
import { UserId } from "../../types/guid";
|
||||||
|
@ -23,11 +23,10 @@ describe("BiometricStateService", () => {
|
||||||
const userId = "userId" as UserId;
|
const userId = "userId" as UserId;
|
||||||
const encClientKeyHalf = makeEncString();
|
const encClientKeyHalf = makeEncString();
|
||||||
const encryptedClientKeyHalf = encClientKeyHalf.encryptedString;
|
const encryptedClientKeyHalf = encClientKeyHalf.encryptedString;
|
||||||
let accountService: FakeAccountService;
|
const accountService = mockAccountServiceWith(userId);
|
||||||
let stateProvider: FakeStateProvider;
|
let stateProvider: FakeStateProvider;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
accountService = mockAccountServiceWith(userId);
|
|
||||||
stateProvider = new FakeStateProvider(accountService);
|
stateProvider = new FakeStateProvider(accountService);
|
||||||
|
|
||||||
sut = new DefaultBiometricStateService(stateProvider);
|
sut = new DefaultBiometricStateService(stateProvider);
|
||||||
|
@ -146,13 +145,6 @@ describe("BiometricStateService", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("setPromptCancelled", () => {
|
describe("setPromptCancelled", () => {
|
||||||
let existingState: Record<UserId, boolean>;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
existingState = { ["otherUser" as UserId]: false };
|
|
||||||
stateProvider.global.getFake(PROMPT_CANCELLED).stateSubject.next(existingState);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("observable is updated", async () => {
|
test("observable is updated", async () => {
|
||||||
await sut.setPromptCancelled();
|
await sut.setPromptCancelled();
|
||||||
|
|
||||||
|
@ -162,39 +154,10 @@ describe("BiometricStateService", () => {
|
||||||
it("updates state", async () => {
|
it("updates state", async () => {
|
||||||
await sut.setPromptCancelled();
|
await sut.setPromptCancelled();
|
||||||
|
|
||||||
const nextMock = stateProvider.global.getFake(PROMPT_CANCELLED).nextMock;
|
const nextMock = stateProvider.activeUser.getFake(PROMPT_CANCELLED).nextMock;
|
||||||
expect(nextMock).toHaveBeenCalledWith({ ...existingState, [userId]: true });
|
expect(nextMock).toHaveBeenCalledWith([userId, true]);
|
||||||
expect(nextMock).toHaveBeenCalledTimes(1);
|
expect(nextMock).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("throws when called with no active user", async () => {
|
|
||||||
await accountService.switchAccount(null);
|
|
||||||
await expect(sut.setPromptCancelled()).rejects.toThrow(
|
|
||||||
"Cannot update biometric prompt cancelled state without an active user",
|
|
||||||
);
|
|
||||||
const nextMock = stateProvider.global.getFake(PROMPT_CANCELLED).nextMock;
|
|
||||||
expect(nextMock).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("resetPromptCancelled", () => {
|
|
||||||
it("deletes all prompt cancelled state", async () => {
|
|
||||||
await sut.resetPromptCancelled();
|
|
||||||
|
|
||||||
const nextMock = stateProvider.global.getFake(PROMPT_CANCELLED).nextMock;
|
|
||||||
expect(nextMock).toHaveBeenCalledWith(null);
|
|
||||||
expect(nextMock).toHaveBeenCalledTimes(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("updates observable to false", async () => {
|
|
||||||
const emissions = trackEmissions(sut.promptCancelled$);
|
|
||||||
|
|
||||||
await sut.setPromptCancelled();
|
|
||||||
|
|
||||||
await sut.resetPromptCancelled();
|
|
||||||
|
|
||||||
expect(emissions).toEqual([false, true, false]);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("setPromptAutomatically", () => {
|
describe("setPromptAutomatically", () => {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { Observable, firstValueFrom, map, combineLatest } from "rxjs";
|
import { Observable, firstValueFrom, map } from "rxjs";
|
||||||
|
|
||||||
import { UserId } from "../../types/guid";
|
import { UserId } from "../../types/guid";
|
||||||
import { EncryptedString, EncString } from "../models/domain/enc-string";
|
import { EncryptedString, EncString } from "../models/domain/enc-string";
|
||||||
|
@ -107,7 +107,7 @@ export class DefaultBiometricStateService implements BiometricStateService {
|
||||||
private requirePasswordOnStartState: ActiveUserState<boolean>;
|
private requirePasswordOnStartState: ActiveUserState<boolean>;
|
||||||
private encryptedClientKeyHalfState: ActiveUserState<EncryptedString | undefined>;
|
private encryptedClientKeyHalfState: ActiveUserState<EncryptedString | undefined>;
|
||||||
private dismissedRequirePasswordOnStartCalloutState: ActiveUserState<boolean>;
|
private dismissedRequirePasswordOnStartCalloutState: ActiveUserState<boolean>;
|
||||||
private promptCancelledState: GlobalState<Record<UserId, boolean>>;
|
private promptCancelledState: ActiveUserState<boolean>;
|
||||||
private promptAutomaticallyState: ActiveUserState<boolean>;
|
private promptAutomaticallyState: ActiveUserState<boolean>;
|
||||||
private fingerprintValidatedState: GlobalState<boolean>;
|
private fingerprintValidatedState: GlobalState<boolean>;
|
||||||
biometricUnlockEnabled$: Observable<boolean>;
|
biometricUnlockEnabled$: Observable<boolean>;
|
||||||
|
@ -138,15 +138,8 @@ export class DefaultBiometricStateService implements BiometricStateService {
|
||||||
this.dismissedRequirePasswordOnStartCallout$ =
|
this.dismissedRequirePasswordOnStartCallout$ =
|
||||||
this.dismissedRequirePasswordOnStartCalloutState.state$.pipe(map(Boolean));
|
this.dismissedRequirePasswordOnStartCalloutState.state$.pipe(map(Boolean));
|
||||||
|
|
||||||
this.promptCancelledState = this.stateProvider.getGlobal(PROMPT_CANCELLED);
|
this.promptCancelledState = this.stateProvider.getActive(PROMPT_CANCELLED);
|
||||||
this.promptCancelled$ = combineLatest([
|
this.promptCancelled$ = this.promptCancelledState.state$.pipe(map(Boolean));
|
||||||
this.stateProvider.activeUserId$,
|
|
||||||
this.promptCancelledState.state$,
|
|
||||||
]).pipe(
|
|
||||||
map(([userId, record]) => {
|
|
||||||
return record?.[userId] ?? false;
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
this.promptAutomaticallyState = this.stateProvider.getActive(PROMPT_AUTOMATICALLY);
|
this.promptAutomaticallyState = this.stateProvider.getActive(PROMPT_AUTOMATICALLY);
|
||||||
this.promptAutomatically$ = this.promptAutomaticallyState.state$.pipe(map(Boolean));
|
this.promptAutomatically$ = this.promptAutomaticallyState.state$.pipe(map(Boolean));
|
||||||
|
|
||||||
|
@ -209,15 +202,6 @@ export class DefaultBiometricStateService implements BiometricStateService {
|
||||||
|
|
||||||
async logout(userId: UserId): Promise<void> {
|
async logout(userId: UserId): Promise<void> {
|
||||||
await this.stateProvider.getUser(userId, ENCRYPTED_CLIENT_KEY_HALF).update(() => null);
|
await this.stateProvider.getUser(userId, ENCRYPTED_CLIENT_KEY_HALF).update(() => null);
|
||||||
await this.promptCancelledState.update(
|
|
||||||
(record) => {
|
|
||||||
delete record[userId];
|
|
||||||
return record;
|
|
||||||
},
|
|
||||||
{
|
|
||||||
shouldUpdate: (record) => record[userId] == true,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
await this.stateProvider.getUser(userId, PROMPT_CANCELLED).update(() => null);
|
await this.stateProvider.getUser(userId, PROMPT_CANCELLED).update(() => null);
|
||||||
// Persist auto prompt setting through logout
|
// Persist auto prompt setting through logout
|
||||||
// Persist dismissed require password on start callout through logout
|
// Persist dismissed require password on start callout through logout
|
||||||
|
@ -228,24 +212,7 @@ export class DefaultBiometricStateService implements BiometricStateService {
|
||||||
}
|
}
|
||||||
|
|
||||||
async setPromptCancelled(): Promise<void> {
|
async setPromptCancelled(): Promise<void> {
|
||||||
await this.promptCancelledState.update(
|
await this.promptCancelledState.update(() => true);
|
||||||
(record, userId) => {
|
|
||||||
record ??= {};
|
|
||||||
record[userId] = true;
|
|
||||||
return record;
|
|
||||||
},
|
|
||||||
{
|
|
||||||
combineLatestWith: this.stateProvider.activeUserId$,
|
|
||||||
shouldUpdate: (_, userId) => {
|
|
||||||
if (userId == null) {
|
|
||||||
throw new Error(
|
|
||||||
"Cannot update biometric prompt cancelled state without an active user",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async resetPromptCancelled(): Promise<void> {
|
async resetPromptCancelled(): Promise<void> {
|
||||||
|
|
|
@ -14,7 +14,7 @@ import {
|
||||||
describe.each([
|
describe.each([
|
||||||
[ENCRYPTED_CLIENT_KEY_HALF, "encryptedClientKeyHalf"],
|
[ENCRYPTED_CLIENT_KEY_HALF, "encryptedClientKeyHalf"],
|
||||||
[DISMISSED_REQUIRE_PASSWORD_ON_START_CALLOUT, true],
|
[DISMISSED_REQUIRE_PASSWORD_ON_START_CALLOUT, true],
|
||||||
[PROMPT_CANCELLED, { userId1: true, userId2: false }],
|
[PROMPT_CANCELLED, true],
|
||||||
[PROMPT_AUTOMATICALLY, true],
|
[PROMPT_AUTOMATICALLY, true],
|
||||||
[REQUIRE_PASSWORD_ON_START, true],
|
[REQUIRE_PASSWORD_ON_START, true],
|
||||||
[BIOMETRIC_UNLOCK_ENABLED, true],
|
[BIOMETRIC_UNLOCK_ENABLED, true],
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import { UserId } from "../../types/guid";
|
|
||||||
import { EncryptedString } from "../models/domain/enc-string";
|
import { EncryptedString } from "../models/domain/enc-string";
|
||||||
import { KeyDefinition, BIOMETRIC_SETTINGS_DISK } from "../state";
|
import { KeyDefinition, BIOMETRIC_SETTINGS_DISK } from "../state";
|
||||||
|
|
||||||
|
@ -57,7 +56,7 @@ export const DISMISSED_REQUIRE_PASSWORD_ON_START_CALLOUT = new KeyDefinition<boo
|
||||||
* Stores whether the user has elected to cancel the biometric prompt. This is stored on disk due to process-reload
|
* Stores whether the user has elected to cancel the biometric prompt. This is stored on disk due to process-reload
|
||||||
* wiping memory state. We don't want to prompt the user again if they've elected to cancel.
|
* wiping memory state. We don't want to prompt the user again if they've elected to cancel.
|
||||||
*/
|
*/
|
||||||
export const PROMPT_CANCELLED = KeyDefinition.record<boolean, UserId>(
|
export const PROMPT_CANCELLED = new KeyDefinition<boolean>(
|
||||||
BIOMETRIC_SETTINGS_DISK,
|
BIOMETRIC_SETTINGS_DISK,
|
||||||
"promptCancelled",
|
"promptCancelled",
|
||||||
{
|
{
|
||||||
|
|
|
@ -33,7 +33,6 @@ import { DomainSettingsMigrator } from "./migrations/34-move-domain-settings-to-
|
||||||
import { MoveThemeToStateProviderMigrator } from "./migrations/35-move-theme-to-state-providers";
|
import { MoveThemeToStateProviderMigrator } from "./migrations/35-move-theme-to-state-providers";
|
||||||
import { VaultSettingsKeyMigrator } from "./migrations/36-move-show-card-and-identity-to-state-provider";
|
import { VaultSettingsKeyMigrator } from "./migrations/36-move-show-card-and-identity-to-state-provider";
|
||||||
import { AvatarColorMigrator } from "./migrations/37-move-avatar-color-to-state-providers";
|
import { AvatarColorMigrator } from "./migrations/37-move-avatar-color-to-state-providers";
|
||||||
import { DeleteBiometricPromptCancelledData } from "./migrations/38-delete-orphaned-biometric-prompt-data";
|
|
||||||
import { RemoveEverBeenUnlockedMigrator } from "./migrations/4-remove-ever-been-unlocked";
|
import { RemoveEverBeenUnlockedMigrator } from "./migrations/4-remove-ever-been-unlocked";
|
||||||
import { AddKeyTypeToOrgKeysMigrator } from "./migrations/5-add-key-type-to-org-keys";
|
import { AddKeyTypeToOrgKeysMigrator } from "./migrations/5-add-key-type-to-org-keys";
|
||||||
import { RemoveLegacyEtmKeyMigrator } from "./migrations/6-remove-legacy-etm-key";
|
import { RemoveLegacyEtmKeyMigrator } from "./migrations/6-remove-legacy-etm-key";
|
||||||
|
@ -43,7 +42,7 @@ import { MoveBrowserSettingsToGlobal } from "./migrations/9-move-browser-setting
|
||||||
import { MinVersionMigrator } from "./migrations/min-version";
|
import { MinVersionMigrator } from "./migrations/min-version";
|
||||||
|
|
||||||
export const MIN_VERSION = 2;
|
export const MIN_VERSION = 2;
|
||||||
export const CURRENT_VERSION = 38;
|
export const CURRENT_VERSION = 37;
|
||||||
export type MinVersion = typeof MIN_VERSION;
|
export type MinVersion = typeof MIN_VERSION;
|
||||||
|
|
||||||
export function createMigrationBuilder() {
|
export function createMigrationBuilder() {
|
||||||
|
@ -83,8 +82,7 @@ export function createMigrationBuilder() {
|
||||||
.with(DomainSettingsMigrator, 33, 34)
|
.with(DomainSettingsMigrator, 33, 34)
|
||||||
.with(MoveThemeToStateProviderMigrator, 34, 35)
|
.with(MoveThemeToStateProviderMigrator, 34, 35)
|
||||||
.with(VaultSettingsKeyMigrator, 35, 36)
|
.with(VaultSettingsKeyMigrator, 35, 36)
|
||||||
.with(AvatarColorMigrator, 36, 37)
|
.with(AvatarColorMigrator, 36, CURRENT_VERSION);
|
||||||
.with(DeleteBiometricPromptCancelledData, 37, CURRENT_VERSION);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function currentVersion(
|
export async function currentVersion(
|
||||||
|
|
|
@ -1,28 +0,0 @@
|
||||||
import { runMigrator } from "../migration-helper.spec";
|
|
||||||
import { IRREVERSIBLE } from "../migrator";
|
|
||||||
|
|
||||||
import { DeleteBiometricPromptCancelledData } from "./38-delete-orphaned-biometric-prompt-data";
|
|
||||||
|
|
||||||
describe("MoveThemeToStateProviders", () => {
|
|
||||||
const sut = new DeleteBiometricPromptCancelledData(37, 38);
|
|
||||||
|
|
||||||
describe("migrate", () => {
|
|
||||||
it("deletes promptCancelled from all users", async () => {
|
|
||||||
const output = await runMigrator(sut, {
|
|
||||||
authenticatedAccounts: ["user-1", "user-2"],
|
|
||||||
"user_user-1_biometricSettings_promptCancelled": true,
|
|
||||||
"user_user-2_biometricSettings_promptCancelled": false,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(output).toEqual({
|
|
||||||
authenticatedAccounts: ["user-1", "user-2"],
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("rollback", () => {
|
|
||||||
it("is irreversible", async () => {
|
|
||||||
await expect(runMigrator(sut, {}, "rollback")).rejects.toThrow(IRREVERSIBLE);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,23 +0,0 @@
|
||||||
import { KeyDefinitionLike, MigrationHelper } from "../migration-helper";
|
|
||||||
import { IRREVERSIBLE, Migrator } from "../migrator";
|
|
||||||
|
|
||||||
export const PROMPT_CANCELLED: KeyDefinitionLike = {
|
|
||||||
key: "promptCancelled",
|
|
||||||
stateDefinition: { name: "biometricSettings" },
|
|
||||||
};
|
|
||||||
|
|
||||||
export class DeleteBiometricPromptCancelledData extends Migrator<37, 38> {
|
|
||||||
async migrate(helper: MigrationHelper): Promise<void> {
|
|
||||||
await Promise.all(
|
|
||||||
(await helper.getAccounts()).map(async ({ userId }) => {
|
|
||||||
if (helper.getFromUser(userId, PROMPT_CANCELLED) != null) {
|
|
||||||
await helper.removeFromUser(userId, PROMPT_CANCELLED);
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async rollback(helper: MigrationHelper): Promise<void> {
|
|
||||||
throw IRREVERSIBLE;
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue