mirror of
https://github.com/bitwarden/browser
synced 2025-12-10 13:23:34 +00:00
[PM-24677] Slim StateService down so it can be moved to state lib (#16021)
* Slim StateService down so it can be moved to state lib * Fix accidental import changes * Add `switchAccount` assertion * Needs to use mock
This commit is contained in:
@@ -2,6 +2,7 @@ import { mock, MockProxy } from "jest-mock-extended";
|
|||||||
import { BehaviorSubject, of } from "rxjs";
|
import { BehaviorSubject, of } from "rxjs";
|
||||||
|
|
||||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
|
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
|
||||||
import {
|
import {
|
||||||
AUTOFILL_CARD_ID,
|
AUTOFILL_CARD_ID,
|
||||||
AUTOFILL_ID,
|
AUTOFILL_ID,
|
||||||
@@ -17,7 +18,6 @@ import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/s
|
|||||||
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
|
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
|
||||||
import { UserId } from "@bitwarden/common/types/guid";
|
import { UserId } from "@bitwarden/common/types/guid";
|
||||||
import { CipherType } from "@bitwarden/common/vault/enums";
|
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||||
import { Cipher } from "@bitwarden/common/vault/models/domain/cipher";
|
import { Cipher } from "@bitwarden/common/vault/models/domain/cipher";
|
||||||
@@ -67,7 +67,7 @@ const createCipher = (data?: {
|
|||||||
};
|
};
|
||||||
|
|
||||||
describe("context-menu", () => {
|
describe("context-menu", () => {
|
||||||
let stateService: MockProxy<StateService>;
|
let tokenService: MockProxy<TokenService>;
|
||||||
let autofillSettingsService: MockProxy<AutofillSettingsServiceAbstraction>;
|
let autofillSettingsService: MockProxy<AutofillSettingsServiceAbstraction>;
|
||||||
let i18nService: MockProxy<I18nService>;
|
let i18nService: MockProxy<I18nService>;
|
||||||
let logService: MockProxy<LogService>;
|
let logService: MockProxy<LogService>;
|
||||||
@@ -85,7 +85,7 @@ describe("context-menu", () => {
|
|||||||
let sut: MainContextMenuHandler;
|
let sut: MainContextMenuHandler;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
stateService = mock();
|
tokenService = mock();
|
||||||
autofillSettingsService = mock();
|
autofillSettingsService = mock();
|
||||||
i18nService = mock();
|
i18nService = mock();
|
||||||
logService = mock();
|
logService = mock();
|
||||||
@@ -109,7 +109,7 @@ describe("context-menu", () => {
|
|||||||
|
|
||||||
i18nService.t.mockImplementation((key) => key);
|
i18nService.t.mockImplementation((key) => key);
|
||||||
sut = new MainContextMenuHandler(
|
sut = new MainContextMenuHandler(
|
||||||
stateService,
|
tokenService,
|
||||||
autofillSettingsService,
|
autofillSettingsService,
|
||||||
i18nService,
|
i18nService,
|
||||||
logService,
|
logService,
|
||||||
@@ -276,7 +276,7 @@ describe("context-menu", () => {
|
|||||||
it("removes menu items that require code injection", async () => {
|
it("removes menu items that require code injection", async () => {
|
||||||
billingAccountProfileStateService.hasPremiumFromAnySource$.mockReturnValue(of(true));
|
billingAccountProfileStateService.hasPremiumFromAnySource$.mockReturnValue(of(true));
|
||||||
autofillSettingsService.enableContextMenu$ = of(true);
|
autofillSettingsService.enableContextMenu$ = of(true);
|
||||||
stateService.getIsAuthenticated.mockResolvedValue(true);
|
tokenService.hasAccessToken$.mockReturnValue(of(true));
|
||||||
|
|
||||||
const optionId = "1";
|
const optionId = "1";
|
||||||
await sut.loadOptions("TEST_TITLE", optionId, createCipher());
|
await sut.loadOptions("TEST_TITLE", optionId, createCipher());
|
||||||
@@ -317,7 +317,7 @@ describe("context-menu", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("Loads context menu items that ask the user to unlock their vault if they are authed", async () => {
|
it("Loads context menu items that ask the user to unlock their vault if they are authed", async () => {
|
||||||
stateService.getIsAuthenticated.mockResolvedValue(true);
|
tokenService.hasAccessToken$.mockReturnValue(of(true));
|
||||||
|
|
||||||
await sut.noAccess();
|
await sut.noAccess();
|
||||||
|
|
||||||
@@ -325,7 +325,7 @@ describe("context-menu", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("Loads context menu items that ask the user to login to their vault if they are not authed", async () => {
|
it("Loads context menu items that ask the user to login to their vault if they are not authed", async () => {
|
||||||
stateService.getIsAuthenticated.mockResolvedValue(false);
|
tokenService.hasAccessToken$.mockReturnValue(of(false));
|
||||||
|
|
||||||
await sut.noAccess();
|
await sut.noAccess();
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
// FIXME: Update this file to be type safe and remove this and next line
|
// FIXME: Update this file to be type safe and remove this and next line
|
||||||
// @ts-strict-ignore
|
// @ts-strict-ignore
|
||||||
import { firstValueFrom } from "rxjs";
|
import { firstValueFrom, map } from "rxjs";
|
||||||
|
|
||||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
|
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
|
||||||
import {
|
import {
|
||||||
AUTOFILL_CARD_ID,
|
AUTOFILL_CARD_ID,
|
||||||
AUTOFILL_ID,
|
AUTOFILL_ID,
|
||||||
@@ -23,7 +24,6 @@ import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/s
|
|||||||
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
|
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
|
||||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||||
import { CipherType } from "@bitwarden/common/vault/enums/cipher-type";
|
import { CipherType } from "@bitwarden/common/vault/enums/cipher-type";
|
||||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||||
@@ -152,7 +152,7 @@ export class MainContextMenuHandler {
|
|||||||
];
|
];
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private stateService: StateService,
|
private tokenService: TokenService,
|
||||||
private autofillSettingsService: AutofillSettingsServiceAbstraction,
|
private autofillSettingsService: AutofillSettingsServiceAbstraction,
|
||||||
private i18nService: I18nService,
|
private i18nService: I18nService,
|
||||||
private logService: LogService,
|
private logService: LogService,
|
||||||
@@ -343,7 +343,11 @@ export class MainContextMenuHandler {
|
|||||||
|
|
||||||
async noAccess() {
|
async noAccess() {
|
||||||
if (await this.init()) {
|
if (await this.init()) {
|
||||||
const authed = await this.stateService.getIsAuthenticated();
|
const userId = await firstValueFrom(
|
||||||
|
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
|
||||||
|
);
|
||||||
|
const authed =
|
||||||
|
userId != null && (await firstValueFrom(this.tokenService.hasAccessToken$(userId)));
|
||||||
this.loadOptions(
|
this.loadOptions(
|
||||||
this.i18nService.t(authed ? "unlockVaultMenu" : "loginToVaultMenu"),
|
this.i18nService.t(authed ? "unlockVaultMenu" : "loginToVaultMenu"),
|
||||||
NOOP_COMMAND_SUFFIX,
|
NOOP_COMMAND_SUFFIX,
|
||||||
|
|||||||
@@ -111,14 +111,11 @@ import {
|
|||||||
ObservableStorageService,
|
ObservableStorageService,
|
||||||
} from "@bitwarden/common/platform/abstractions/storage.service";
|
} from "@bitwarden/common/platform/abstractions/storage.service";
|
||||||
import { SystemService as SystemServiceAbstraction } from "@bitwarden/common/platform/abstractions/system.service";
|
import { SystemService as SystemServiceAbstraction } from "@bitwarden/common/platform/abstractions/system.service";
|
||||||
import { StateFactory } from "@bitwarden/common/platform/factories/state-factory";
|
|
||||||
import { IpcService } from "@bitwarden/common/platform/ipc";
|
import { IpcService } from "@bitwarden/common/platform/ipc";
|
||||||
import { Message, MessageListener, MessageSender } from "@bitwarden/common/platform/messaging";
|
import { Message, MessageListener, MessageSender } from "@bitwarden/common/platform/messaging";
|
||||||
// eslint-disable-next-line no-restricted-imports -- Used for dependency creation
|
// eslint-disable-next-line no-restricted-imports -- Used for dependency creation
|
||||||
import { SubjectMessageSender } from "@bitwarden/common/platform/messaging/internal";
|
import { SubjectMessageSender } from "@bitwarden/common/platform/messaging/internal";
|
||||||
import { Lazy } from "@bitwarden/common/platform/misc/lazy";
|
import { Lazy } from "@bitwarden/common/platform/misc/lazy";
|
||||||
import { Account } from "@bitwarden/common/platform/models/domain/account";
|
|
||||||
import { GlobalState } from "@bitwarden/common/platform/models/domain/global-state";
|
|
||||||
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
||||||
import { NotificationsService } from "@bitwarden/common/platform/notifications";
|
import { NotificationsService } from "@bitwarden/common/platform/notifications";
|
||||||
// eslint-disable-next-line no-restricted-imports -- Needed for service creation
|
// eslint-disable-next-line no-restricted-imports -- Needed for service creation
|
||||||
@@ -143,11 +140,11 @@ import { MigrationRunner } from "@bitwarden/common/platform/services/migration-r
|
|||||||
import { DefaultSdkClientFactory } from "@bitwarden/common/platform/services/sdk/default-sdk-client-factory";
|
import { DefaultSdkClientFactory } from "@bitwarden/common/platform/services/sdk/default-sdk-client-factory";
|
||||||
import { DefaultSdkService } from "@bitwarden/common/platform/services/sdk/default-sdk.service";
|
import { DefaultSdkService } from "@bitwarden/common/platform/services/sdk/default-sdk.service";
|
||||||
import { NoopSdkClientFactory } from "@bitwarden/common/platform/services/sdk/noop-sdk-client-factory";
|
import { NoopSdkClientFactory } from "@bitwarden/common/platform/services/sdk/noop-sdk-client-factory";
|
||||||
import { StateService } from "@bitwarden/common/platform/services/state.service";
|
|
||||||
import { SystemService } from "@bitwarden/common/platform/services/system.service";
|
import { SystemService } from "@bitwarden/common/platform/services/system.service";
|
||||||
import { UserAutoUnlockKeyService } from "@bitwarden/common/platform/services/user-auto-unlock-key.service";
|
import { UserAutoUnlockKeyService } from "@bitwarden/common/platform/services/user-auto-unlock-key.service";
|
||||||
import {
|
import {
|
||||||
ActiveUserStateProvider,
|
ActiveUserStateProvider,
|
||||||
|
DefaultStateService,
|
||||||
DerivedStateProvider,
|
DerivedStateProvider,
|
||||||
GlobalStateProvider,
|
GlobalStateProvider,
|
||||||
SingleUserStateProvider,
|
SingleUserStateProvider,
|
||||||
@@ -387,6 +384,7 @@ export default class MainBackground {
|
|||||||
activeUserStateProvider: ActiveUserStateProvider;
|
activeUserStateProvider: ActiveUserStateProvider;
|
||||||
derivedStateProvider: DerivedStateProvider;
|
derivedStateProvider: DerivedStateProvider;
|
||||||
stateProvider: StateProvider;
|
stateProvider: StateProvider;
|
||||||
|
migrationRunner: MigrationRunner;
|
||||||
taskSchedulerService: BrowserTaskSchedulerService;
|
taskSchedulerService: BrowserTaskSchedulerService;
|
||||||
fido2Background: Fido2BackgroundAbstraction;
|
fido2Background: Fido2BackgroundAbstraction;
|
||||||
individualVaultExportService: IndividualVaultExportServiceAbstraction;
|
individualVaultExportService: IndividualVaultExportServiceAbstraction;
|
||||||
@@ -592,8 +590,9 @@ export default class MainBackground {
|
|||||||
this.globalStateProvider,
|
this.globalStateProvider,
|
||||||
this.singleUserStateProvider,
|
this.singleUserStateProvider,
|
||||||
);
|
);
|
||||||
|
const activeUserAccessor = new DefaultActiveUserAccessor(this.accountService);
|
||||||
this.activeUserStateProvider = new DefaultActiveUserStateProvider(
|
this.activeUserStateProvider = new DefaultActiveUserStateProvider(
|
||||||
new DefaultActiveUserAccessor(this.accountService),
|
activeUserAccessor,
|
||||||
this.singleUserStateProvider,
|
this.singleUserStateProvider,
|
||||||
);
|
);
|
||||||
this.derivedStateProvider = new InlineDerivedStateProvider();
|
this.derivedStateProvider = new InlineDerivedStateProvider();
|
||||||
@@ -639,23 +638,17 @@ export default class MainBackground {
|
|||||||
this.taskSchedulerService,
|
this.taskSchedulerService,
|
||||||
);
|
);
|
||||||
|
|
||||||
const migrationRunner = new MigrationRunner(
|
this.migrationRunner = new MigrationRunner(
|
||||||
this.storageService,
|
this.storageService,
|
||||||
this.logService,
|
this.logService,
|
||||||
new MigrationBuilderService(),
|
new MigrationBuilderService(),
|
||||||
ClientType.Browser,
|
ClientType.Browser,
|
||||||
);
|
);
|
||||||
|
|
||||||
this.stateService = new StateService(
|
this.stateService = new DefaultStateService(
|
||||||
this.storageService,
|
this.storageService,
|
||||||
this.secureStorageService,
|
this.secureStorageService,
|
||||||
this.memoryStorageService,
|
activeUserAccessor,
|
||||||
this.logService,
|
|
||||||
new StateFactory(GlobalState, Account),
|
|
||||||
this.accountService,
|
|
||||||
this.environmentService,
|
|
||||||
this.tokenService,
|
|
||||||
migrationRunner,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
this.masterPasswordService = new MasterPasswordService(
|
this.masterPasswordService = new MasterPasswordService(
|
||||||
@@ -887,7 +880,6 @@ export default class MainBackground {
|
|||||||
this.apiService,
|
this.apiService,
|
||||||
this.i18nService,
|
this.i18nService,
|
||||||
this.searchService,
|
this.searchService,
|
||||||
this.stateService,
|
|
||||||
this.autofillSettingsService,
|
this.autofillSettingsService,
|
||||||
this.encryptService,
|
this.encryptService,
|
||||||
this.cipherFileUploadService,
|
this.cipherFileUploadService,
|
||||||
@@ -946,6 +938,7 @@ export default class MainBackground {
|
|||||||
this.messagingService,
|
this.messagingService,
|
||||||
this.searchService,
|
this.searchService,
|
||||||
this.stateService,
|
this.stateService,
|
||||||
|
this.tokenService,
|
||||||
this.authService,
|
this.authService,
|
||||||
this.vaultTimeoutSettingsService,
|
this.vaultTimeoutSettingsService,
|
||||||
this.stateEventRunnerService,
|
this.stateEventRunnerService,
|
||||||
@@ -989,7 +982,6 @@ export default class MainBackground {
|
|||||||
this.sendService,
|
this.sendService,
|
||||||
this.logService,
|
this.logService,
|
||||||
this.keyConnectorService,
|
this.keyConnectorService,
|
||||||
this.stateService,
|
|
||||||
this.providerService,
|
this.providerService,
|
||||||
this.folderApiService,
|
this.folderApiService,
|
||||||
this.organizationService,
|
this.organizationService,
|
||||||
@@ -1320,7 +1312,7 @@ export default class MainBackground {
|
|||||||
);
|
);
|
||||||
|
|
||||||
this.mainContextMenuHandler = new MainContextMenuHandler(
|
this.mainContextMenuHandler = new MainContextMenuHandler(
|
||||||
this.stateService,
|
this.tokenService,
|
||||||
this.autofillSettingsService,
|
this.autofillSettingsService,
|
||||||
this.i18nService,
|
this.i18nService,
|
||||||
this.logService,
|
this.logService,
|
||||||
@@ -1387,7 +1379,7 @@ export default class MainBackground {
|
|||||||
|
|
||||||
await this.sdkLoadService.loadAndInit();
|
await this.sdkLoadService.loadAndInit();
|
||||||
// Only the "true" background should run migrations
|
// Only the "true" background should run migrations
|
||||||
await this.stateService.init({ runMigrations: true });
|
await this.migrationRunner.run();
|
||||||
|
|
||||||
// This is here instead of in in the InitService b/c we don't plan for
|
// This is here instead of in in the InitService b/c we don't plan for
|
||||||
// side effects to run in the Browser InitService.
|
// side effects to run in the Browser InitService.
|
||||||
@@ -1607,6 +1599,7 @@ export default class MainBackground {
|
|||||||
const needStorageReseed = await this.needsStorageReseed(userBeingLoggedOut);
|
const needStorageReseed = await this.needsStorageReseed(userBeingLoggedOut);
|
||||||
|
|
||||||
await this.stateService.clean({ userId: userBeingLoggedOut });
|
await this.stateService.clean({ userId: userBeingLoggedOut });
|
||||||
|
await this.tokenService.clearAccessToken(userBeingLoggedOut);
|
||||||
await this.accountService.clean(userBeingLoggedOut);
|
await this.accountService.clean(userBeingLoggedOut);
|
||||||
|
|
||||||
await this.stateEventRunnerService.handleEvent("logout", userBeingLoggedOut);
|
await this.stateEventRunnerService.handleEvent("logout", userBeingLoggedOut);
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import { Subject } from "rxjs";
|
|||||||
import { CollectionService } from "@bitwarden/admin-console/common";
|
import { CollectionService } from "@bitwarden/admin-console/common";
|
||||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||||
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
||||||
|
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
|
||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
|
||||||
import { MessageListener, MessageSender } from "@bitwarden/common/platform/messaging";
|
import { MessageListener, MessageSender } from "@bitwarden/common/platform/messaging";
|
||||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||||
import { SyncOptions } from "@bitwarden/common/platform/sync/sync.service";
|
import { SyncOptions } from "@bitwarden/common/platform/sync/sync.service";
|
||||||
@@ -22,7 +22,7 @@ import { FullSyncFinishedMessage } from "./sync-service.listener";
|
|||||||
|
|
||||||
describe("ForegroundSyncService", () => {
|
describe("ForegroundSyncService", () => {
|
||||||
const userId = Utils.newGuid() as UserId;
|
const userId = Utils.newGuid() as UserId;
|
||||||
const stateService = mock<StateService>();
|
const tokenService = mock<TokenService>();
|
||||||
const folderService = mock<InternalFolderService>();
|
const folderService = mock<InternalFolderService>();
|
||||||
const folderApiService = mock<FolderApiServiceAbstraction>();
|
const folderApiService = mock<FolderApiServiceAbstraction>();
|
||||||
const messageSender = mock<MessageSender>();
|
const messageSender = mock<MessageSender>();
|
||||||
@@ -38,7 +38,7 @@ describe("ForegroundSyncService", () => {
|
|||||||
const stateProvider = new FakeStateProvider(accountService);
|
const stateProvider = new FakeStateProvider(accountService);
|
||||||
|
|
||||||
const sut = new ForegroundSyncService(
|
const sut = new ForegroundSyncService(
|
||||||
stateService,
|
tokenService,
|
||||||
folderService,
|
folderService,
|
||||||
folderApiService,
|
folderApiService,
|
||||||
messageSender,
|
messageSender,
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import { CollectionService } from "@bitwarden/admin-console/common";
|
|||||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
||||||
|
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
|
||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
|
||||||
import {
|
import {
|
||||||
CommandDefinition,
|
CommandDefinition,
|
||||||
MessageListener,
|
MessageListener,
|
||||||
@@ -31,7 +31,7 @@ export const DO_FULL_SYNC = new CommandDefinition<FullSyncMessage>("doFullSync")
|
|||||||
|
|
||||||
export class ForegroundSyncService extends CoreSyncService {
|
export class ForegroundSyncService extends CoreSyncService {
|
||||||
constructor(
|
constructor(
|
||||||
stateService: StateService,
|
tokenService: TokenService,
|
||||||
folderService: InternalFolderService,
|
folderService: InternalFolderService,
|
||||||
folderApiService: FolderApiServiceAbstraction,
|
folderApiService: FolderApiServiceAbstraction,
|
||||||
messageSender: MessageSender,
|
messageSender: MessageSender,
|
||||||
@@ -47,7 +47,7 @@ export class ForegroundSyncService extends CoreSyncService {
|
|||||||
stateProvider: StateProvider,
|
stateProvider: StateProvider,
|
||||||
) {
|
) {
|
||||||
super(
|
super(
|
||||||
stateService,
|
tokenService,
|
||||||
folderService,
|
folderService,
|
||||||
folderApiService,
|
folderApiService,
|
||||||
messageSender,
|
messageSender,
|
||||||
|
|||||||
@@ -28,13 +28,13 @@ import { DocumentLangSetter } from "@bitwarden/angular/platform/i18n";
|
|||||||
import { LogoutReason, UserDecryptionOptionsServiceAbstraction } from "@bitwarden/auth/common";
|
import { LogoutReason, UserDecryptionOptionsServiceAbstraction } from "@bitwarden/auth/common";
|
||||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
||||||
|
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
|
||||||
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
||||||
import { AnimationControlService } from "@bitwarden/common/platform/abstractions/animation-control.service";
|
import { AnimationControlService } from "@bitwarden/common/platform/abstractions/animation-control.service";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
import { SdkService } from "@bitwarden/common/platform/abstractions/sdk/sdk.service";
|
import { SdkService } from "@bitwarden/common/platform/abstractions/sdk/sdk.service";
|
||||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
|
||||||
import { MessageListener } from "@bitwarden/common/platform/messaging";
|
import { MessageListener } from "@bitwarden/common/platform/messaging";
|
||||||
import { UserId } from "@bitwarden/common/types/guid";
|
import { UserId } from "@bitwarden/common/types/guid";
|
||||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||||
@@ -102,7 +102,7 @@ export class AppComponent implements OnInit, OnDestroy {
|
|||||||
private authService: AuthService,
|
private authService: AuthService,
|
||||||
private i18nService: I18nService,
|
private i18nService: I18nService,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
private stateService: StateService,
|
private readonly tokenService: TokenService,
|
||||||
private vaultBrowserStateService: VaultBrowserStateService,
|
private vaultBrowserStateService: VaultBrowserStateService,
|
||||||
private cipherService: CipherService,
|
private cipherService: CipherService,
|
||||||
private changeDetectorRef: ChangeDetectorRef,
|
private changeDetectorRef: ChangeDetectorRef,
|
||||||
@@ -321,7 +321,7 @@ export class AppComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async clearComponentStates() {
|
private async clearComponentStates() {
|
||||||
if (!(await this.stateService.getIsAuthenticated())) {
|
if (!(await firstValueFrom(this.tokenService.hasAccessToken$(this.activeUserId)))) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { LogService as LogServiceAbstraction } from "@bitwarden/common/platform/
|
|||||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
import { SdkLoadService } from "@bitwarden/common/platform/abstractions/sdk/sdk-load.service";
|
import { SdkLoadService } from "@bitwarden/common/platform/abstractions/sdk/sdk-load.service";
|
||||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
||||||
|
import { MigrationRunner } from "@bitwarden/common/platform/services/migration-runner";
|
||||||
|
|
||||||
import { BrowserApi } from "../../platform/browser/browser-api";
|
import { BrowserApi } from "../../platform/browser/browser-api";
|
||||||
import BrowserPopupUtils from "../../platform/browser/browser-popup-utils";
|
import BrowserPopupUtils from "../../platform/browser/browser-popup-utils";
|
||||||
@@ -27,13 +28,14 @@ export class InitService {
|
|||||||
private themingService: AbstractThemingService,
|
private themingService: AbstractThemingService,
|
||||||
private sdkLoadService: SdkLoadService,
|
private sdkLoadService: SdkLoadService,
|
||||||
private viewCacheService: PopupViewCacheService,
|
private viewCacheService: PopupViewCacheService,
|
||||||
|
private readonly migrationRunner: MigrationRunner,
|
||||||
@Inject(DOCUMENT) private document: Document,
|
@Inject(DOCUMENT) private document: Document,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
return async () => {
|
return async () => {
|
||||||
await this.sdkLoadService.loadAndInit();
|
await this.sdkLoadService.loadAndInit();
|
||||||
await this.stateService.init({ runMigrations: false }); // Browser background is responsible for migrations
|
await this.migrationRunner.waitForCompletion(); // Browser background is responsible for migrations
|
||||||
await this.i18nService.init();
|
await this.i18nService.init();
|
||||||
this.twoFactorService.init();
|
this.twoFactorService.init();
|
||||||
await this.viewCacheService.init();
|
await this.viewCacheService.init();
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ import {
|
|||||||
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
||||||
import { MasterPasswordApiService } from "@bitwarden/common/auth/abstractions/master-password-api.service.abstraction";
|
import { MasterPasswordApiService } from "@bitwarden/common/auth/abstractions/master-password-api.service.abstraction";
|
||||||
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
|
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
|
||||||
|
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
|
||||||
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
||||||
import {
|
import {
|
||||||
AutofillSettingsService,
|
AutofillSettingsService,
|
||||||
@@ -333,7 +334,7 @@ const safeProviders: SafeProvider[] = [
|
|||||||
provide: SyncService,
|
provide: SyncService,
|
||||||
useClass: ForegroundSyncService,
|
useClass: ForegroundSyncService,
|
||||||
deps: [
|
deps: [
|
||||||
StateService,
|
TokenService,
|
||||||
InternalFolderService,
|
InternalFolderService,
|
||||||
FolderApiServiceAbstraction,
|
FolderApiServiceAbstraction,
|
||||||
MessageSender,
|
MessageSender,
|
||||||
|
|||||||
@@ -129,7 +129,7 @@ export abstract class BaseProgram {
|
|||||||
if (!userId) {
|
if (!userId) {
|
||||||
fail();
|
fail();
|
||||||
}
|
}
|
||||||
const authed = await this.serviceContainer.stateService.getIsAuthenticated({ userId });
|
const authed = await firstValueFrom(this.serviceContainer.tokenService.hasAccessToken$(userId));
|
||||||
if (!authed) {
|
if (!authed) {
|
||||||
fail();
|
fail();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -107,7 +107,8 @@ export class OssServeConfigurator {
|
|||||||
);
|
);
|
||||||
this.generateCommand = new GenerateCommand(
|
this.generateCommand = new GenerateCommand(
|
||||||
this.serviceContainer.passwordGenerationService,
|
this.serviceContainer.passwordGenerationService,
|
||||||
this.serviceContainer.stateService,
|
this.serviceContainer.tokenService,
|
||||||
|
this.serviceContainer.accountService,
|
||||||
);
|
);
|
||||||
this.syncCommand = new SyncCommand(this.serviceContainer.syncService);
|
this.syncCommand = new SyncCommand(this.serviceContainer.syncService);
|
||||||
this.statusCommand = new StatusCommand(
|
this.statusCommand = new StatusCommand(
|
||||||
@@ -417,14 +418,18 @@ export class OssServeConfigurator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected async errorIfLocked(res: koa.Response) {
|
protected async errorIfLocked(res: koa.Response) {
|
||||||
const authed = await this.serviceContainer.stateService.getIsAuthenticated();
|
const userId = await firstValueFrom(
|
||||||
|
this.serviceContainer.accountService.activeAccount$.pipe(map((account) => account?.id)),
|
||||||
|
);
|
||||||
|
|
||||||
|
const authed =
|
||||||
|
userId != null ||
|
||||||
|
(await firstValueFrom(this.serviceContainer.tokenService.hasAccessToken$(userId)));
|
||||||
|
|
||||||
if (!authed) {
|
if (!authed) {
|
||||||
this.processResponse(res, Response.error("You are not logged in."));
|
this.processResponse(res, Response.error("You are not logged in."));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
const userId = await firstValueFrom(
|
|
||||||
this.serviceContainer.accountService.activeAccount$.pipe(map((account) => account?.id)),
|
|
||||||
);
|
|
||||||
if (await this.serviceContainer.keyService.hasUserKey(userId)) {
|
if (await this.serviceContainer.keyService.hasUserKey(userId)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
// @ts-strict-ignore
|
// @ts-strict-ignore
|
||||||
import * as chalk from "chalk";
|
import * as chalk from "chalk";
|
||||||
import { program, Command, OptionValues } from "commander";
|
import { program, Command, OptionValues } from "commander";
|
||||||
import { firstValueFrom } from "rxjs";
|
import { firstValueFrom, of, switchMap } from "rxjs";
|
||||||
|
|
||||||
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
||||||
|
|
||||||
@@ -129,7 +129,17 @@ export class Program extends BaseProgram {
|
|||||||
"Path to a file containing your password as its first line",
|
"Path to a file containing your password as its first line",
|
||||||
)
|
)
|
||||||
.option("--check", "Check login status.", async () => {
|
.option("--check", "Check login status.", async () => {
|
||||||
const authed = await this.serviceContainer.stateService.getIsAuthenticated();
|
const authed = await firstValueFrom(
|
||||||
|
this.serviceContainer.accountService.activeAccount$.pipe(
|
||||||
|
switchMap((account) => {
|
||||||
|
if (account == null) {
|
||||||
|
return of(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.serviceContainer.tokenService.hasAccessToken$(account.id);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
if (authed) {
|
if (authed) {
|
||||||
const res = new MessageResponse("You are logged in!", null);
|
const res = new MessageResponse("You are logged in!", null);
|
||||||
this.processResponse(Response.success(res), true);
|
this.processResponse(Response.success(res), true);
|
||||||
@@ -350,7 +360,8 @@ export class Program extends BaseProgram {
|
|||||||
.action(async (options) => {
|
.action(async (options) => {
|
||||||
const command = new GenerateCommand(
|
const command = new GenerateCommand(
|
||||||
this.serviceContainer.passwordGenerationService,
|
this.serviceContainer.passwordGenerationService,
|
||||||
this.serviceContainer.stateService,
|
this.serviceContainer.tokenService,
|
||||||
|
this.serviceContainer.accountService,
|
||||||
);
|
);
|
||||||
const response = await command.run(options);
|
const response = await command.run(options);
|
||||||
this.processResponse(response);
|
this.processResponse(response);
|
||||||
|
|||||||
@@ -88,10 +88,7 @@ import {
|
|||||||
import { SdkLoadService } from "@bitwarden/common/platform/abstractions/sdk/sdk-load.service";
|
import { SdkLoadService } from "@bitwarden/common/platform/abstractions/sdk/sdk-load.service";
|
||||||
import { SdkService } from "@bitwarden/common/platform/abstractions/sdk/sdk.service";
|
import { SdkService } from "@bitwarden/common/platform/abstractions/sdk/sdk.service";
|
||||||
import { KeySuffixOptions, LogLevelType } from "@bitwarden/common/platform/enums";
|
import { KeySuffixOptions, LogLevelType } from "@bitwarden/common/platform/enums";
|
||||||
import { StateFactory } from "@bitwarden/common/platform/factories/state-factory";
|
|
||||||
import { MessageSender } from "@bitwarden/common/platform/messaging";
|
import { MessageSender } from "@bitwarden/common/platform/messaging";
|
||||||
import { Account } from "@bitwarden/common/platform/models/domain/account";
|
|
||||||
import { GlobalState } from "@bitwarden/common/platform/models/domain/global-state";
|
|
||||||
import {
|
import {
|
||||||
TaskSchedulerService,
|
TaskSchedulerService,
|
||||||
DefaultTaskSchedulerService,
|
DefaultTaskSchedulerService,
|
||||||
@@ -108,16 +105,17 @@ import { MigrationRunner } from "@bitwarden/common/platform/services/migration-r
|
|||||||
import { DefaultSdkClientFactory } from "@bitwarden/common/platform/services/sdk/default-sdk-client-factory";
|
import { DefaultSdkClientFactory } from "@bitwarden/common/platform/services/sdk/default-sdk-client-factory";
|
||||||
import { DefaultSdkService } from "@bitwarden/common/platform/services/sdk/default-sdk.service";
|
import { DefaultSdkService } from "@bitwarden/common/platform/services/sdk/default-sdk.service";
|
||||||
import { NoopSdkClientFactory } from "@bitwarden/common/platform/services/sdk/noop-sdk-client-factory";
|
import { NoopSdkClientFactory } from "@bitwarden/common/platform/services/sdk/noop-sdk-client-factory";
|
||||||
import { StateService } from "@bitwarden/common/platform/services/state.service";
|
|
||||||
import { StorageServiceProvider } from "@bitwarden/common/platform/services/storage-service.provider";
|
import { StorageServiceProvider } from "@bitwarden/common/platform/services/storage-service.provider";
|
||||||
import { UserAutoUnlockKeyService } from "@bitwarden/common/platform/services/user-auto-unlock-key.service";
|
import { UserAutoUnlockKeyService } from "@bitwarden/common/platform/services/user-auto-unlock-key.service";
|
||||||
import {
|
import {
|
||||||
ActiveUserStateProvider,
|
ActiveUserStateProvider,
|
||||||
|
DefaultStateService,
|
||||||
DerivedStateProvider,
|
DerivedStateProvider,
|
||||||
GlobalStateProvider,
|
GlobalStateProvider,
|
||||||
SingleUserStateProvider,
|
SingleUserStateProvider,
|
||||||
StateEventRunnerService,
|
StateEventRunnerService,
|
||||||
StateProvider,
|
StateProvider,
|
||||||
|
StateService,
|
||||||
} from "@bitwarden/common/platform/state";
|
} from "@bitwarden/common/platform/state";
|
||||||
/* eslint-disable import/no-restricted-paths -- We need the implementation to inject, but generally these should not be accessed */
|
/* eslint-disable import/no-restricted-paths -- We need the implementation to inject, but generally these should not be accessed */
|
||||||
import { DefaultActiveUserStateProvider } from "@bitwarden/common/platform/state/implementations/default-active-user-state.provider";
|
import { DefaultActiveUserStateProvider } from "@bitwarden/common/platform/state/implementations/default-active-user-state.provider";
|
||||||
@@ -212,6 +210,7 @@ export class ServiceContainer {
|
|||||||
secureStorageService: NodeEnvSecureStorageService;
|
secureStorageService: NodeEnvSecureStorageService;
|
||||||
memoryStorageService: MemoryStorageService;
|
memoryStorageService: MemoryStorageService;
|
||||||
memoryStorageForStateProviders: MemoryStorageServiceForStateProviders;
|
memoryStorageForStateProviders: MemoryStorageServiceForStateProviders;
|
||||||
|
migrationRunner: MigrationRunner;
|
||||||
i18nService: I18nService;
|
i18nService: I18nService;
|
||||||
platformUtilsService: CliPlatformUtilsService;
|
platformUtilsService: CliPlatformUtilsService;
|
||||||
keyService: KeyService;
|
keyService: KeyService;
|
||||||
@@ -379,8 +378,10 @@ export class ServiceContainer {
|
|||||||
this.singleUserStateProvider,
|
this.singleUserStateProvider,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const activeUserAccessor = new DefaultActiveUserAccessor(this.accountService);
|
||||||
|
|
||||||
this.activeUserStateProvider = new DefaultActiveUserStateProvider(
|
this.activeUserStateProvider = new DefaultActiveUserStateProvider(
|
||||||
new DefaultActiveUserAccessor(this.accountService),
|
activeUserAccessor,
|
||||||
this.singleUserStateProvider,
|
this.singleUserStateProvider,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -412,23 +413,17 @@ export class ServiceContainer {
|
|||||||
logoutCallback,
|
logoutCallback,
|
||||||
);
|
);
|
||||||
|
|
||||||
const migrationRunner = new MigrationRunner(
|
this.migrationRunner = new MigrationRunner(
|
||||||
this.storageService,
|
this.storageService,
|
||||||
this.logService,
|
this.logService,
|
||||||
new MigrationBuilderService(),
|
new MigrationBuilderService(),
|
||||||
ClientType.Cli,
|
ClientType.Cli,
|
||||||
);
|
);
|
||||||
|
|
||||||
this.stateService = new StateService(
|
this.stateService = new DefaultStateService(
|
||||||
this.storageService,
|
this.storageService,
|
||||||
this.secureStorageService,
|
this.secureStorageService,
|
||||||
this.memoryStorageService,
|
activeUserAccessor,
|
||||||
this.logService,
|
|
||||||
new StateFactory(GlobalState, Account),
|
|
||||||
this.accountService,
|
|
||||||
this.environmentService,
|
|
||||||
this.tokenService,
|
|
||||||
migrationRunner,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
this.kdfConfigService = new DefaultKdfConfigService(this.stateProvider);
|
this.kdfConfigService = new DefaultKdfConfigService(this.stateProvider);
|
||||||
@@ -713,7 +708,6 @@ export class ServiceContainer {
|
|||||||
this.apiService,
|
this.apiService,
|
||||||
this.i18nService,
|
this.i18nService,
|
||||||
this.searchService,
|
this.searchService,
|
||||||
this.stateService,
|
|
||||||
this.autofillSettingsService,
|
this.autofillSettingsService,
|
||||||
this.encryptService,
|
this.encryptService,
|
||||||
this.cipherFileUploadService,
|
this.cipherFileUploadService,
|
||||||
@@ -764,6 +758,7 @@ export class ServiceContainer {
|
|||||||
this.messagingService,
|
this.messagingService,
|
||||||
this.searchService,
|
this.searchService,
|
||||||
this.stateService,
|
this.stateService,
|
||||||
|
this.tokenService,
|
||||||
this.authService,
|
this.authService,
|
||||||
this.vaultTimeoutSettingsService,
|
this.vaultTimeoutSettingsService,
|
||||||
this.stateEventRunnerService,
|
this.stateEventRunnerService,
|
||||||
@@ -790,7 +785,6 @@ export class ServiceContainer {
|
|||||||
this.sendService,
|
this.sendService,
|
||||||
this.logService,
|
this.logService,
|
||||||
this.keyConnectorService,
|
this.keyConnectorService,
|
||||||
this.stateService,
|
|
||||||
this.providerService,
|
this.providerService,
|
||||||
this.folderApiService,
|
this.folderApiService,
|
||||||
this.organizationService,
|
this.organizationService,
|
||||||
@@ -903,7 +897,8 @@ export class ServiceContainer {
|
|||||||
|
|
||||||
await this.stateEventRunnerService.handleEvent("logout", userId as UserId);
|
await this.stateEventRunnerService.handleEvent("logout", userId as UserId);
|
||||||
|
|
||||||
await this.stateService.clean();
|
await this.stateService.clean({ userId: userId });
|
||||||
|
await this.tokenService.clearAccessToken(userId);
|
||||||
await this.accountService.clean(userId as UserId);
|
await this.accountService.clean(userId as UserId);
|
||||||
await this.accountService.switchAccount(null);
|
await this.accountService.switchAccount(null);
|
||||||
process.env.BW_SESSION = undefined;
|
process.env.BW_SESSION = undefined;
|
||||||
@@ -917,7 +912,8 @@ export class ServiceContainer {
|
|||||||
|
|
||||||
await this.sdkLoadService.loadAndInit();
|
await this.sdkLoadService.loadAndInit();
|
||||||
await this.storageService.init();
|
await this.storageService.init();
|
||||||
await this.stateService.init();
|
|
||||||
|
await this.migrationRunner.run();
|
||||||
this.containerService.attachToGlobal(global);
|
this.containerService.attachToGlobal(global);
|
||||||
await this.i18nService.init();
|
await this.i18nService.init();
|
||||||
this.twoFactorService.init();
|
this.twoFactorService.init();
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
// FIXME: Update this file to be type safe and remove this and next line
|
// FIXME: Update this file to be type safe and remove this and next line
|
||||||
// @ts-strict-ignore
|
// @ts-strict-ignore
|
||||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
import { firstValueFrom, of, switchMap } from "rxjs";
|
||||||
|
|
||||||
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
|
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
|
||||||
import {
|
import {
|
||||||
DefaultPasswordGenerationOptions,
|
DefaultPasswordGenerationOptions,
|
||||||
DefaultPassphraseGenerationOptions,
|
DefaultPassphraseGenerationOptions,
|
||||||
@@ -17,7 +20,8 @@ import { CliUtils } from "../utils";
|
|||||||
export class GenerateCommand {
|
export class GenerateCommand {
|
||||||
constructor(
|
constructor(
|
||||||
private passwordGenerationService: PasswordGenerationServiceAbstraction,
|
private passwordGenerationService: PasswordGenerationServiceAbstraction,
|
||||||
private stateService: StateService,
|
private tokenService: TokenService,
|
||||||
|
private accountService: AccountService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async run(cmdOptions: Record<string, any>): Promise<Response> {
|
async run(cmdOptions: Record<string, any>): Promise<Response> {
|
||||||
@@ -38,7 +42,18 @@ export class GenerateCommand {
|
|||||||
ambiguous: !normalizedOptions.ambiguous,
|
ambiguous: !normalizedOptions.ambiguous,
|
||||||
};
|
};
|
||||||
|
|
||||||
const enforcedOptions = (await this.stateService.getIsAuthenticated())
|
const shouldEnforceOptions = await firstValueFrom(
|
||||||
|
this.accountService.activeAccount$.pipe(
|
||||||
|
switchMap((account) => {
|
||||||
|
if (account == null) {
|
||||||
|
return of(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.tokenService.hasAccessToken$(account.id);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
const enforcedOptions = shouldEnforceOptions
|
||||||
? (await this.passwordGenerationService.enforcePasswordGeneratorPoliciesOnOptions(options))[0]
|
? (await this.passwordGenerationService.enforcePasswordGeneratorPoliciesOnOptions(options))[0]
|
||||||
: options;
|
: options;
|
||||||
|
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ import { OrganizationService } from "@bitwarden/common/admin-console/abstraction
|
|||||||
import { InternalPolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
import { InternalPolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
||||||
|
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
|
||||||
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
||||||
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
||||||
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||||
@@ -175,6 +176,7 @@ export class AppComponent implements OnInit, OnDestroy {
|
|||||||
private readonly destroyRef: DestroyRef,
|
private readonly destroyRef: DestroyRef,
|
||||||
private readonly documentLangSetter: DocumentLangSetter,
|
private readonly documentLangSetter: DocumentLangSetter,
|
||||||
private restrictedItemTypesService: RestrictedItemTypesService,
|
private restrictedItemTypesService: RestrictedItemTypesService,
|
||||||
|
private readonly tokenService: TokenService,
|
||||||
) {
|
) {
|
||||||
this.deviceTrustToastService.setupListeners$.pipe(takeUntilDestroyed()).subscribe();
|
this.deviceTrustToastService.setupListeners$.pipe(takeUntilDestroyed()).subscribe();
|
||||||
|
|
||||||
@@ -684,6 +686,7 @@ export class AppComponent implements OnInit, OnDestroy {
|
|||||||
await this.stateEventRunnerService.handleEvent("logout", userBeingLoggedOut);
|
await this.stateEventRunnerService.handleEvent("logout", userBeingLoggedOut);
|
||||||
|
|
||||||
await this.stateService.clean({ userId: userBeingLoggedOut });
|
await this.stateService.clean({ userId: userBeingLoggedOut });
|
||||||
|
await this.tokenService.clearAccessToken(userBeingLoggedOut);
|
||||||
await this.accountService.clean(userBeingLoggedOut);
|
await this.accountService.clean(userBeingLoggedOut);
|
||||||
|
|
||||||
// HACK: Wait for the user logging outs authentication status to transition to LoggedOut
|
// HACK: Wait for the user logging outs authentication status to transition to LoggedOut
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import { SdkLoadService } from "@bitwarden/common/platform/abstractions/sdk/sdk-
|
|||||||
import { StateService as StateServiceAbstraction } from "@bitwarden/common/platform/abstractions/state.service";
|
import { StateService as StateServiceAbstraction } from "@bitwarden/common/platform/abstractions/state.service";
|
||||||
import { NotificationsService } from "@bitwarden/common/platform/notifications";
|
import { NotificationsService } from "@bitwarden/common/platform/notifications";
|
||||||
import { ContainerService } from "@bitwarden/common/platform/services/container.service";
|
import { ContainerService } from "@bitwarden/common/platform/services/container.service";
|
||||||
|
import { MigrationRunner } from "@bitwarden/common/platform/services/migration-runner";
|
||||||
import { UserAutoUnlockKeyService } from "@bitwarden/common/platform/services/user-auto-unlock-key.service";
|
import { UserAutoUnlockKeyService } from "@bitwarden/common/platform/services/user-auto-unlock-key.service";
|
||||||
import { SyncService as SyncServiceAbstraction } from "@bitwarden/common/platform/sync";
|
import { SyncService as SyncServiceAbstraction } from "@bitwarden/common/platform/sync";
|
||||||
import { EventUploadService } from "@bitwarden/common/services/event/event-upload.service";
|
import { EventUploadService } from "@bitwarden/common/services/event/event-upload.service";
|
||||||
@@ -52,6 +53,7 @@ export class InitService {
|
|||||||
private autotypeService: DesktopAutotypeService,
|
private autotypeService: DesktopAutotypeService,
|
||||||
private sdkLoadService: SdkLoadService,
|
private sdkLoadService: SdkLoadService,
|
||||||
@Inject(DOCUMENT) private document: Document,
|
@Inject(DOCUMENT) private document: Document,
|
||||||
|
private readonly migrationRunner: MigrationRunner,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
@@ -59,7 +61,7 @@ export class InitService {
|
|||||||
await this.sdkLoadService.loadAndInit();
|
await this.sdkLoadService.loadAndInit();
|
||||||
await this.sshAgentService.init();
|
await this.sshAgentService.init();
|
||||||
this.nativeMessagingService.init();
|
this.nativeMessagingService.init();
|
||||||
await this.stateService.init({ runMigrations: false }); // Desktop will run them in main process
|
await this.migrationRunner.waitForCompletion(); // Desktop will run migrations in the main process
|
||||||
|
|
||||||
const accounts = await firstValueFrom(this.accountService.accounts$);
|
const accounts = await firstValueFrom(this.accountService.accounts$);
|
||||||
const setUserKeyInMemoryPromises = [];
|
const setUserKeyInMemoryPromises = [];
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ export class ElectronKeyService extends DefaultKeyService {
|
|||||||
|
|
||||||
protected override async getKeyFromStorage(
|
protected override async getKeyFromStorage(
|
||||||
keySuffix: KeySuffixOptions,
|
keySuffix: KeySuffixOptions,
|
||||||
userId?: UserId,
|
userId: UserId,
|
||||||
): Promise<UserKey | null> {
|
): Promise<UserKey | null> {
|
||||||
return await super.getKeyFromStorage(keySuffix, userId);
|
return await super.getKeyFromStorage(keySuffix, userId);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import { EventUploadService } from "@bitwarden/common/abstractions/event/event-u
|
|||||||
import { InternalOrganizationServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
import { InternalOrganizationServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
||||||
|
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
|
||||||
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
||||||
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||||
import { ProcessReloadServiceAbstraction } from "@bitwarden/common/key-management/abstractions/process-reload.service";
|
import { ProcessReloadServiceAbstraction } from "@bitwarden/common/key-management/abstractions/process-reload.service";
|
||||||
@@ -89,6 +90,7 @@ export class AppComponent implements OnDestroy, OnInit {
|
|||||||
private deviceTrustToastService: DeviceTrustToastService,
|
private deviceTrustToastService: DeviceTrustToastService,
|
||||||
private readonly destoryRef: DestroyRef,
|
private readonly destoryRef: DestroyRef,
|
||||||
private readonly documentLangSetter: DocumentLangSetter,
|
private readonly documentLangSetter: DocumentLangSetter,
|
||||||
|
private readonly tokenService: TokenService,
|
||||||
) {
|
) {
|
||||||
this.deviceTrustToastService.setupListeners$.pipe(takeUntilDestroyed()).subscribe();
|
this.deviceTrustToastService.setupListeners$.pipe(takeUntilDestroyed()).subscribe();
|
||||||
|
|
||||||
@@ -297,6 +299,7 @@ export class AppComponent implements OnDestroy, OnInit {
|
|||||||
await this.searchService.clearIndex(userId);
|
await this.searchService.clearIndex(userId);
|
||||||
this.authService.logOut(async () => {
|
this.authService.logOut(async () => {
|
||||||
await this.stateService.clean({ userId: userId });
|
await this.stateService.clean({ userId: userId });
|
||||||
|
await this.tokenService.clearAccessToken(userId);
|
||||||
await this.accountService.clean(userId);
|
await this.accountService.clean(userId);
|
||||||
await this.accountService.switchAccount(null);
|
await this.accountService.switchAccount(null);
|
||||||
|
|
||||||
|
|||||||
@@ -2,14 +2,15 @@
|
|||||||
// @ts-strict-ignore
|
// @ts-strict-ignore
|
||||||
import { Component, OnInit } from "@angular/core";
|
import { Component, OnInit } from "@angular/core";
|
||||||
import { ActivatedRoute, Router } from "@angular/router";
|
import { ActivatedRoute, Router } from "@angular/router";
|
||||||
|
import { firstValueFrom } from "rxjs";
|
||||||
import { first } from "rxjs/operators";
|
import { first } from "rxjs/operators";
|
||||||
|
|
||||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||||
|
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
|
||||||
import { VerifyEmailRequest } from "@bitwarden/common/models/request/verify-email.request";
|
import { VerifyEmailRequest } from "@bitwarden/common/models/request/verify-email.request";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
|
||||||
import { ToastService } from "@bitwarden/components";
|
import { ToastService } from "@bitwarden/components";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@@ -25,7 +26,7 @@ export class VerifyEmailTokenComponent implements OnInit {
|
|||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
private apiService: ApiService,
|
private apiService: ApiService,
|
||||||
private logService: LogService,
|
private logService: LogService,
|
||||||
private stateService: StateService,
|
private tokenService: TokenService,
|
||||||
private toastService: ToastService,
|
private toastService: ToastService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@@ -37,7 +38,7 @@ export class VerifyEmailTokenComponent implements OnInit {
|
|||||||
await this.apiService.postAccountVerifyEmailToken(
|
await this.apiService.postAccountVerifyEmailToken(
|
||||||
new VerifyEmailRequest(qParams.userId, qParams.token),
|
new VerifyEmailRequest(qParams.userId, qParams.token),
|
||||||
);
|
);
|
||||||
if (await this.stateService.getIsAuthenticated()) {
|
if (await firstValueFrom(this.tokenService.hasAccessToken$(qParams.userId))) {
|
||||||
await this.apiService.refreshIdentityToken();
|
await this.apiService.refreshIdentityToken();
|
||||||
}
|
}
|
||||||
this.toastService.showToast({
|
this.toastService.showToast({
|
||||||
|
|||||||
@@ -11,10 +11,10 @@ import { EncryptService } from "@bitwarden/common/key-management/crypto/abstract
|
|||||||
import { DefaultVaultTimeoutService } from "@bitwarden/common/key-management/vault-timeout";
|
import { DefaultVaultTimeoutService } from "@bitwarden/common/key-management/vault-timeout";
|
||||||
import { I18nService as I18nServiceAbstraction } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService as I18nServiceAbstraction } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { SdkLoadService } from "@bitwarden/common/platform/abstractions/sdk/sdk-load.service";
|
import { SdkLoadService } from "@bitwarden/common/platform/abstractions/sdk/sdk-load.service";
|
||||||
import { StateService as StateServiceAbstraction } from "@bitwarden/common/platform/abstractions/state.service";
|
|
||||||
import { IpcService } from "@bitwarden/common/platform/ipc";
|
import { IpcService } from "@bitwarden/common/platform/ipc";
|
||||||
import { NotificationsService } from "@bitwarden/common/platform/notifications";
|
import { NotificationsService } from "@bitwarden/common/platform/notifications";
|
||||||
import { ContainerService } from "@bitwarden/common/platform/services/container.service";
|
import { ContainerService } from "@bitwarden/common/platform/services/container.service";
|
||||||
|
import { MigrationRunner } from "@bitwarden/common/platform/services/migration-runner";
|
||||||
import { UserAutoUnlockKeyService } from "@bitwarden/common/platform/services/user-auto-unlock-key.service";
|
import { UserAutoUnlockKeyService } from "@bitwarden/common/platform/services/user-auto-unlock-key.service";
|
||||||
import { EventUploadService } from "@bitwarden/common/services/event/event-upload.service";
|
import { EventUploadService } from "@bitwarden/common/services/event/event-upload.service";
|
||||||
import { TaskService } from "@bitwarden/common/vault/tasks";
|
import { TaskService } from "@bitwarden/common/vault/tasks";
|
||||||
@@ -31,7 +31,6 @@ export class InitService {
|
|||||||
private i18nService: I18nServiceAbstraction,
|
private i18nService: I18nServiceAbstraction,
|
||||||
private eventUploadService: EventUploadServiceAbstraction,
|
private eventUploadService: EventUploadServiceAbstraction,
|
||||||
private twoFactorService: TwoFactorServiceAbstraction,
|
private twoFactorService: TwoFactorServiceAbstraction,
|
||||||
private stateService: StateServiceAbstraction,
|
|
||||||
private keyService: KeyServiceAbstraction,
|
private keyService: KeyServiceAbstraction,
|
||||||
private themingService: AbstractThemingService,
|
private themingService: AbstractThemingService,
|
||||||
private encryptService: EncryptService,
|
private encryptService: EncryptService,
|
||||||
@@ -41,13 +40,14 @@ export class InitService {
|
|||||||
private ipcService: IpcService,
|
private ipcService: IpcService,
|
||||||
private sdkLoadService: SdkLoadService,
|
private sdkLoadService: SdkLoadService,
|
||||||
private taskService: TaskService,
|
private taskService: TaskService,
|
||||||
|
private readonly migrationRunner: MigrationRunner,
|
||||||
@Inject(DOCUMENT) private document: Document,
|
@Inject(DOCUMENT) private document: Document,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
return async () => {
|
return async () => {
|
||||||
await this.sdkLoadService.loadAndInit();
|
await this.sdkLoadService.loadAndInit();
|
||||||
await this.stateService.init();
|
await this.migrationRunner.run();
|
||||||
|
|
||||||
const activeAccount = await firstValueFrom(this.accountService.activeAccount$);
|
const activeAccount = await firstValueFrom(this.accountService.activeAccount$);
|
||||||
if (activeAccount) {
|
if (activeAccount) {
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import {
|
|||||||
ObservableStorageService,
|
ObservableStorageService,
|
||||||
} from "@bitwarden/common/platform/abstractions/storage.service";
|
} from "@bitwarden/common/platform/abstractions/storage.service";
|
||||||
import { Theme } from "@bitwarden/common/platform/enums";
|
import { Theme } from "@bitwarden/common/platform/enums";
|
||||||
import { StateFactory } from "@bitwarden/common/platform/factories/state-factory";
|
|
||||||
import { Message } from "@bitwarden/common/platform/messaging";
|
import { Message } from "@bitwarden/common/platform/messaging";
|
||||||
import { HttpOperations } from "@bitwarden/common/services/api.service";
|
import { HttpOperations } from "@bitwarden/common/services/api.service";
|
||||||
import { SafeInjectionToken } from "@bitwarden/ui-common";
|
import { SafeInjectionToken } from "@bitwarden/ui-common";
|
||||||
@@ -33,7 +32,6 @@ export const OBSERVABLE_DISK_LOCAL_STORAGE = new SafeInjectionToken<
|
|||||||
>("OBSERVABLE_DISK_LOCAL_STORAGE");
|
>("OBSERVABLE_DISK_LOCAL_STORAGE");
|
||||||
export const MEMORY_STORAGE = new SafeInjectionToken<AbstractStorageService>("MEMORY_STORAGE");
|
export const MEMORY_STORAGE = new SafeInjectionToken<AbstractStorageService>("MEMORY_STORAGE");
|
||||||
export const SECURE_STORAGE = new SafeInjectionToken<AbstractStorageService>("SECURE_STORAGE");
|
export const SECURE_STORAGE = new SafeInjectionToken<AbstractStorageService>("SECURE_STORAGE");
|
||||||
export const STATE_FACTORY = new SafeInjectionToken<StateFactory>("STATE_FACTORY");
|
|
||||||
export const LOGOUT_CALLBACK = new SafeInjectionToken<
|
export const LOGOUT_CALLBACK = new SafeInjectionToken<
|
||||||
(logoutReason: LogoutReason, userId?: string) => Promise<void>
|
(logoutReason: LogoutReason, userId?: string) => Promise<void>
|
||||||
>("LOGOUT_CALLBACK");
|
>("LOGOUT_CALLBACK");
|
||||||
|
|||||||
@@ -196,13 +196,10 @@ import { SdkService } from "@bitwarden/common/platform/abstractions/sdk/sdk.serv
|
|||||||
import { StateService as StateServiceAbstraction } from "@bitwarden/common/platform/abstractions/state.service";
|
import { StateService as StateServiceAbstraction } from "@bitwarden/common/platform/abstractions/state.service";
|
||||||
import { AbstractStorageService } from "@bitwarden/common/platform/abstractions/storage.service";
|
import { AbstractStorageService } from "@bitwarden/common/platform/abstractions/storage.service";
|
||||||
import { ValidationService as ValidationServiceAbstraction } from "@bitwarden/common/platform/abstractions/validation.service";
|
import { ValidationService as ValidationServiceAbstraction } from "@bitwarden/common/platform/abstractions/validation.service";
|
||||||
import { StateFactory } from "@bitwarden/common/platform/factories/state-factory";
|
|
||||||
import { Message, MessageListener, MessageSender } from "@bitwarden/common/platform/messaging";
|
import { Message, MessageListener, MessageSender } from "@bitwarden/common/platform/messaging";
|
||||||
// eslint-disable-next-line no-restricted-imports -- Used for dependency injection
|
// eslint-disable-next-line no-restricted-imports -- Used for dependency injection
|
||||||
import { SubjectMessageSender } from "@bitwarden/common/platform/messaging/internal";
|
import { SubjectMessageSender } from "@bitwarden/common/platform/messaging/internal";
|
||||||
import { devFlagEnabled } from "@bitwarden/common/platform/misc/flags";
|
import { devFlagEnabled } from "@bitwarden/common/platform/misc/flags";
|
||||||
import { Account } from "@bitwarden/common/platform/models/domain/account";
|
|
||||||
import { GlobalState } from "@bitwarden/common/platform/models/domain/global-state";
|
|
||||||
import { NotificationsService } from "@bitwarden/common/platform/notifications";
|
import { NotificationsService } from "@bitwarden/common/platform/notifications";
|
||||||
// eslint-disable-next-line no-restricted-imports -- Needed for service creation
|
// eslint-disable-next-line no-restricted-imports -- Needed for service creation
|
||||||
import {
|
import {
|
||||||
@@ -228,13 +225,13 @@ import { FileUploadService } from "@bitwarden/common/platform/services/file-uplo
|
|||||||
import { MigrationBuilderService } from "@bitwarden/common/platform/services/migration-builder.service";
|
import { MigrationBuilderService } from "@bitwarden/common/platform/services/migration-builder.service";
|
||||||
import { MigrationRunner } from "@bitwarden/common/platform/services/migration-runner";
|
import { MigrationRunner } from "@bitwarden/common/platform/services/migration-runner";
|
||||||
import { DefaultSdkService } from "@bitwarden/common/platform/services/sdk/default-sdk.service";
|
import { DefaultSdkService } from "@bitwarden/common/platform/services/sdk/default-sdk.service";
|
||||||
import { StateService } from "@bitwarden/common/platform/services/state.service";
|
|
||||||
import { StorageServiceProvider } from "@bitwarden/common/platform/services/storage-service.provider";
|
import { StorageServiceProvider } from "@bitwarden/common/platform/services/storage-service.provider";
|
||||||
import { UserAutoUnlockKeyService } from "@bitwarden/common/platform/services/user-auto-unlock-key.service";
|
import { UserAutoUnlockKeyService } from "@bitwarden/common/platform/services/user-auto-unlock-key.service";
|
||||||
import { ValidationService } from "@bitwarden/common/platform/services/validation.service";
|
import { ValidationService } from "@bitwarden/common/platform/services/validation.service";
|
||||||
import {
|
import {
|
||||||
ActiveUserAccessor,
|
ActiveUserAccessor,
|
||||||
ActiveUserStateProvider,
|
ActiveUserStateProvider,
|
||||||
|
DefaultStateService,
|
||||||
DerivedStateProvider,
|
DerivedStateProvider,
|
||||||
GlobalStateProvider,
|
GlobalStateProvider,
|
||||||
SingleUserStateProvider,
|
SingleUserStateProvider,
|
||||||
@@ -371,12 +368,10 @@ import {
|
|||||||
LOCKED_CALLBACK,
|
LOCKED_CALLBACK,
|
||||||
LOG_MAC_FAILURES,
|
LOG_MAC_FAILURES,
|
||||||
LOGOUT_CALLBACK,
|
LOGOUT_CALLBACK,
|
||||||
MEMORY_STORAGE,
|
|
||||||
OBSERVABLE_DISK_STORAGE,
|
OBSERVABLE_DISK_STORAGE,
|
||||||
OBSERVABLE_MEMORY_STORAGE,
|
OBSERVABLE_MEMORY_STORAGE,
|
||||||
REFRESH_ACCESS_TOKEN_ERROR_CALLBACK,
|
REFRESH_ACCESS_TOKEN_ERROR_CALLBACK,
|
||||||
SECURE_STORAGE,
|
SECURE_STORAGE,
|
||||||
STATE_FACTORY,
|
|
||||||
SUPPORTS_SECURE_STORAGE,
|
SUPPORTS_SECURE_STORAGE,
|
||||||
SYSTEM_LANGUAGE,
|
SYSTEM_LANGUAGE,
|
||||||
SYSTEM_THEME_OBSERVABLE,
|
SYSTEM_THEME_OBSERVABLE,
|
||||||
@@ -414,10 +409,6 @@ const safeProviders: SafeProvider[] = [
|
|||||||
useFactory: (window: Window) => window.navigator.language,
|
useFactory: (window: Window) => window.navigator.language,
|
||||||
deps: [WINDOW],
|
deps: [WINDOW],
|
||||||
}),
|
}),
|
||||||
safeProvider({
|
|
||||||
provide: STATE_FACTORY,
|
|
||||||
useValue: new StateFactory(GlobalState, Account),
|
|
||||||
}),
|
|
||||||
// TODO: PM-21212 - Deprecate LogoutCallback in favor of LogoutService
|
// TODO: PM-21212 - Deprecate LogoutCallback in favor of LogoutService
|
||||||
safeProvider({
|
safeProvider({
|
||||||
provide: LOGOUT_CALLBACK,
|
provide: LOGOUT_CALLBACK,
|
||||||
@@ -530,7 +521,6 @@ const safeProviders: SafeProvider[] = [
|
|||||||
apiService: ApiServiceAbstraction,
|
apiService: ApiServiceAbstraction,
|
||||||
i18nService: I18nServiceAbstraction,
|
i18nService: I18nServiceAbstraction,
|
||||||
searchService: SearchServiceAbstraction,
|
searchService: SearchServiceAbstraction,
|
||||||
stateService: StateServiceAbstraction,
|
|
||||||
autofillSettingsService: AutofillSettingsServiceAbstraction,
|
autofillSettingsService: AutofillSettingsServiceAbstraction,
|
||||||
encryptService: EncryptService,
|
encryptService: EncryptService,
|
||||||
fileUploadService: CipherFileUploadServiceAbstraction,
|
fileUploadService: CipherFileUploadServiceAbstraction,
|
||||||
@@ -547,7 +537,6 @@ const safeProviders: SafeProvider[] = [
|
|||||||
apiService,
|
apiService,
|
||||||
i18nService,
|
i18nService,
|
||||||
searchService,
|
searchService,
|
||||||
stateService,
|
|
||||||
autofillSettingsService,
|
autofillSettingsService,
|
||||||
encryptService,
|
encryptService,
|
||||||
fileUploadService,
|
fileUploadService,
|
||||||
@@ -564,7 +553,6 @@ const safeProviders: SafeProvider[] = [
|
|||||||
ApiServiceAbstraction,
|
ApiServiceAbstraction,
|
||||||
I18nServiceAbstraction,
|
I18nServiceAbstraction,
|
||||||
SearchServiceAbstraction,
|
SearchServiceAbstraction,
|
||||||
StateServiceAbstraction,
|
|
||||||
AutofillSettingsServiceAbstraction,
|
AutofillSettingsServiceAbstraction,
|
||||||
EncryptService,
|
EncryptService,
|
||||||
CipherFileUploadServiceAbstraction,
|
CipherFileUploadServiceAbstraction,
|
||||||
@@ -801,7 +789,6 @@ const safeProviders: SafeProvider[] = [
|
|||||||
InternalSendService,
|
InternalSendService,
|
||||||
LogService,
|
LogService,
|
||||||
KeyConnectorServiceAbstraction,
|
KeyConnectorServiceAbstraction,
|
||||||
StateServiceAbstraction,
|
|
||||||
ProviderServiceAbstraction,
|
ProviderServiceAbstraction,
|
||||||
FolderApiServiceAbstraction,
|
FolderApiServiceAbstraction,
|
||||||
InternalOrganizationServiceAbstraction,
|
InternalOrganizationServiceAbstraction,
|
||||||
@@ -849,6 +836,7 @@ const safeProviders: SafeProvider[] = [
|
|||||||
MessagingServiceAbstraction,
|
MessagingServiceAbstraction,
|
||||||
SearchServiceAbstraction,
|
SearchServiceAbstraction,
|
||||||
StateServiceAbstraction,
|
StateServiceAbstraction,
|
||||||
|
TokenServiceAbstraction,
|
||||||
AuthServiceAbstraction,
|
AuthServiceAbstraction,
|
||||||
VaultTimeoutSettingsService,
|
VaultTimeoutSettingsService,
|
||||||
StateEventRunnerService,
|
StateEventRunnerService,
|
||||||
@@ -868,24 +856,10 @@ const safeProviders: SafeProvider[] = [
|
|||||||
useClass: SsoLoginService,
|
useClass: SsoLoginService,
|
||||||
deps: [StateProvider, LogService],
|
deps: [StateProvider, LogService],
|
||||||
}),
|
}),
|
||||||
safeProvider({
|
|
||||||
provide: STATE_FACTORY,
|
|
||||||
useValue: new StateFactory(GlobalState, Account),
|
|
||||||
}),
|
|
||||||
safeProvider({
|
safeProvider({
|
||||||
provide: StateServiceAbstraction,
|
provide: StateServiceAbstraction,
|
||||||
useClass: StateService,
|
useClass: DefaultStateService,
|
||||||
deps: [
|
deps: [AbstractStorageService, SECURE_STORAGE, ActiveUserAccessor],
|
||||||
AbstractStorageService,
|
|
||||||
SECURE_STORAGE,
|
|
||||||
MEMORY_STORAGE,
|
|
||||||
LogService,
|
|
||||||
STATE_FACTORY,
|
|
||||||
AccountServiceAbstraction,
|
|
||||||
EnvironmentService,
|
|
||||||
TokenServiceAbstraction,
|
|
||||||
MigrationRunner,
|
|
||||||
],
|
|
||||||
}),
|
}),
|
||||||
safeProvider({
|
safeProvider({
|
||||||
provide: IndividualVaultExportServiceAbstraction,
|
provide: IndividualVaultExportServiceAbstraction,
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ import { MessagingService } from "@bitwarden/common/platform/abstractions/messag
|
|||||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
||||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||||
import { Account, AccountProfile } from "@bitwarden/common/platform/models/domain/account";
|
|
||||||
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
||||||
import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec";
|
import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec";
|
||||||
import {
|
import {
|
||||||
@@ -243,18 +242,8 @@ describe("LoginStrategy", () => {
|
|||||||
refreshToken,
|
refreshToken,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(stateService.addAccount).toHaveBeenCalledWith(
|
expect(environmentService.seedUserEnvironment).toHaveBeenCalled();
|
||||||
new Account({
|
|
||||||
profile: {
|
|
||||||
...new AccountProfile(),
|
|
||||||
...{
|
|
||||||
userId: userId,
|
|
||||||
name: name,
|
|
||||||
email: email,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
expect(userDecryptionOptionsService.setUserDecryptionOptions).toHaveBeenCalledWith(
|
expect(userDecryptionOptionsService.setUserDecryptionOptions).toHaveBeenCalledWith(
|
||||||
UserDecryptionOptions.fromResponse(idTokenResponse),
|
UserDecryptionOptions.fromResponse(idTokenResponse),
|
||||||
);
|
);
|
||||||
@@ -388,7 +377,8 @@ describe("LoginStrategy", () => {
|
|||||||
|
|
||||||
const result = await passwordLoginStrategy.logIn(credentials);
|
const result = await passwordLoginStrategy.logIn(credentials);
|
||||||
|
|
||||||
expect(stateService.addAccount).not.toHaveBeenCalled();
|
expect(environmentService.seedUserEnvironment).not.toHaveBeenCalled();
|
||||||
|
expect(accountService.mock.switchAccount).not.toHaveBeenCalled();
|
||||||
expect(messagingService.send).not.toHaveBeenCalled();
|
expect(messagingService.send).not.toHaveBeenCalled();
|
||||||
expect(tokenService.clearTwoFactorToken).toHaveBeenCalled();
|
expect(tokenService.clearTwoFactorToken).toHaveBeenCalled();
|
||||||
|
|
||||||
@@ -422,7 +412,7 @@ describe("LoginStrategy", () => {
|
|||||||
|
|
||||||
const result = await passwordLoginStrategy.logIn(credentials);
|
const result = await passwordLoginStrategy.logIn(credentials);
|
||||||
|
|
||||||
expect(stateService.addAccount).not.toHaveBeenCalled();
|
expect(environmentService.seedUserEnvironment).not.toHaveBeenCalled();
|
||||||
expect(messagingService.send).not.toHaveBeenCalled();
|
expect(messagingService.send).not.toHaveBeenCalled();
|
||||||
|
|
||||||
const expected = new AuthResult();
|
const expected = new AuthResult();
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ import { MessagingService } from "@bitwarden/common/platform/abstractions/messag
|
|||||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
||||||
import { EncryptionType } from "@bitwarden/common/platform/enums";
|
import { EncryptionType } from "@bitwarden/common/platform/enums";
|
||||||
import { Account, AccountProfile } from "@bitwarden/common/platform/models/domain/account";
|
|
||||||
import { UserId } from "@bitwarden/common/types/guid";
|
import { UserId } from "@bitwarden/common/types/guid";
|
||||||
import {
|
import {
|
||||||
KeyService,
|
KeyService,
|
||||||
@@ -198,19 +197,6 @@ export abstract class LoginStrategy {
|
|||||||
|
|
||||||
await this.accountService.switchAccount(userId);
|
await this.accountService.switchAccount(userId);
|
||||||
|
|
||||||
await this.stateService.addAccount(
|
|
||||||
new Account({
|
|
||||||
profile: {
|
|
||||||
...new AccountProfile(),
|
|
||||||
...{
|
|
||||||
userId,
|
|
||||||
name: accountInformation.name,
|
|
||||||
email: accountInformation.email,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
await this.verifyAccountAdded(userId);
|
await this.verifyAccountAdded(userId);
|
||||||
|
|
||||||
// We must set user decryption options before retrieving vault timeout settings
|
// We must set user decryption options before retrieving vault timeout settings
|
||||||
|
|||||||
@@ -170,7 +170,7 @@ describe("UserApiLoginStrategy", () => {
|
|||||||
mockVaultTimeoutAction,
|
mockVaultTimeoutAction,
|
||||||
mockVaultTimeout,
|
mockVaultTimeout,
|
||||||
);
|
);
|
||||||
expect(stateService.addAccount).toHaveBeenCalled();
|
expect(environmentService.seedUserEnvironment).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("sets the encrypted user key and private key from the identity token response", async () => {
|
it("sets the encrypted user key and private key from the identity token response", async () => {
|
||||||
|
|||||||
@@ -12,15 +12,16 @@ import { LogoutReason } from "@bitwarden/auth/common";
|
|||||||
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
|
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
|
||||||
// eslint-disable-next-line no-restricted-imports
|
// eslint-disable-next-line no-restricted-imports
|
||||||
import { BiometricsService } from "@bitwarden/key-management";
|
import { BiometricsService } from "@bitwarden/key-management";
|
||||||
|
import { StateService } from "@bitwarden/state";
|
||||||
|
|
||||||
import { FakeAccountService, mockAccountServiceWith } from "../../../../spec";
|
import { FakeAccountService, mockAccountServiceWith } from "../../../../spec";
|
||||||
import { AccountInfo } from "../../../auth/abstractions/account.service";
|
import { AccountInfo } from "../../../auth/abstractions/account.service";
|
||||||
import { AuthService } from "../../../auth/abstractions/auth.service";
|
import { AuthService } from "../../../auth/abstractions/auth.service";
|
||||||
|
import { TokenService } from "../../../auth/abstractions/token.service";
|
||||||
import { AuthenticationStatus } from "../../../auth/enums/authentication-status";
|
import { AuthenticationStatus } from "../../../auth/enums/authentication-status";
|
||||||
import { LogService } from "../../../platform/abstractions/log.service";
|
import { LogService } from "../../../platform/abstractions/log.service";
|
||||||
import { MessagingService } from "../../../platform/abstractions/messaging.service";
|
import { MessagingService } from "../../../platform/abstractions/messaging.service";
|
||||||
import { PlatformUtilsService } from "../../../platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "../../../platform/abstractions/platform-utils.service";
|
||||||
import { StateService } from "../../../platform/abstractions/state.service";
|
|
||||||
import { Utils } from "../../../platform/misc/utils";
|
import { Utils } from "../../../platform/misc/utils";
|
||||||
import { TaskSchedulerService } from "../../../platform/scheduling";
|
import { TaskSchedulerService } from "../../../platform/scheduling";
|
||||||
import { StateEventRunnerService } from "../../../platform/state";
|
import { StateEventRunnerService } from "../../../platform/state";
|
||||||
@@ -45,6 +46,7 @@ describe("VaultTimeoutService", () => {
|
|||||||
let messagingService: MockProxy<MessagingService>;
|
let messagingService: MockProxy<MessagingService>;
|
||||||
let searchService: MockProxy<SearchService>;
|
let searchService: MockProxy<SearchService>;
|
||||||
let stateService: MockProxy<StateService>;
|
let stateService: MockProxy<StateService>;
|
||||||
|
let tokenService: MockProxy<TokenService>;
|
||||||
let authService: MockProxy<AuthService>;
|
let authService: MockProxy<AuthService>;
|
||||||
let vaultTimeoutSettingsService: MockProxy<VaultTimeoutSettingsService>;
|
let vaultTimeoutSettingsService: MockProxy<VaultTimeoutSettingsService>;
|
||||||
let stateEventRunnerService: MockProxy<StateEventRunnerService>;
|
let stateEventRunnerService: MockProxy<StateEventRunnerService>;
|
||||||
@@ -71,6 +73,7 @@ describe("VaultTimeoutService", () => {
|
|||||||
messagingService = mock();
|
messagingService = mock();
|
||||||
searchService = mock();
|
searchService = mock();
|
||||||
stateService = mock();
|
stateService = mock();
|
||||||
|
tokenService = mock();
|
||||||
authService = mock();
|
authService = mock();
|
||||||
vaultTimeoutSettingsService = mock();
|
vaultTimeoutSettingsService = mock();
|
||||||
stateEventRunnerService = mock();
|
stateEventRunnerService = mock();
|
||||||
@@ -99,6 +102,7 @@ describe("VaultTimeoutService", () => {
|
|||||||
messagingService,
|
messagingService,
|
||||||
searchService,
|
searchService,
|
||||||
stateService,
|
stateService,
|
||||||
|
tokenService,
|
||||||
authService,
|
authService,
|
||||||
vaultTimeoutSettingsService,
|
vaultTimeoutSettingsService,
|
||||||
stateEventRunnerService,
|
stateEventRunnerService,
|
||||||
@@ -141,9 +145,8 @@ describe("VaultTimeoutService", () => {
|
|||||||
authService.getAuthStatus.mockImplementation((userId) => {
|
authService.getAuthStatus.mockImplementation((userId) => {
|
||||||
return Promise.resolve(accounts[userId]?.authStatus);
|
return Promise.resolve(accounts[userId]?.authStatus);
|
||||||
});
|
});
|
||||||
stateService.getIsAuthenticated.mockImplementation((options) => {
|
tokenService.hasAccessToken$.mockImplementation((userId) => {
|
||||||
// Just like actual state service, if no userId is given fallback to active userId
|
return of(accounts[userId]?.isAuthenticated ?? false);
|
||||||
return Promise.resolve(accounts[options.userId ?? globalSetups?.userId]?.isAuthenticated);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
vaultTimeoutSettingsService.getVaultTimeoutByUserId$.mockImplementation((userId) => {
|
vaultTimeoutSettingsService.getVaultTimeoutByUserId$.mockImplementation((userId) => {
|
||||||
@@ -201,7 +204,7 @@ describe("VaultTimeoutService", () => {
|
|||||||
|
|
||||||
const expectUserToHaveLocked = (userId: string) => {
|
const expectUserToHaveLocked = (userId: string) => {
|
||||||
// This does NOT assert all the things that the lock process does
|
// This does NOT assert all the things that the lock process does
|
||||||
expect(stateService.getIsAuthenticated).toHaveBeenCalledWith({ userId: userId });
|
expect(tokenService.hasAccessToken$).toHaveBeenCalledWith(userId);
|
||||||
expect(vaultTimeoutSettingsService.availableVaultTimeoutActions$).toHaveBeenCalledWith(userId);
|
expect(vaultTimeoutSettingsService.availableVaultTimeoutActions$).toHaveBeenCalledWith(userId);
|
||||||
expect(stateService.setUserKeyAutoUnlock).toHaveBeenCalledWith(null, { userId: userId });
|
expect(stateService.setUserKeyAutoUnlock).toHaveBeenCalledWith(null, { userId: userId });
|
||||||
expect(masterPasswordService.mock.clearMasterKey).toHaveBeenCalledWith(userId);
|
expect(masterPasswordService.mock.clearMasterKey).toHaveBeenCalledWith(userId);
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import { BiometricsService } from "@bitwarden/key-management";
|
|||||||
|
|
||||||
import { AccountService } from "../../../auth/abstractions/account.service";
|
import { AccountService } from "../../../auth/abstractions/account.service";
|
||||||
import { AuthService } from "../../../auth/abstractions/auth.service";
|
import { AuthService } from "../../../auth/abstractions/auth.service";
|
||||||
|
import { TokenService } from "../../../auth/abstractions/token.service";
|
||||||
import { AuthenticationStatus } from "../../../auth/enums/authentication-status";
|
import { AuthenticationStatus } from "../../../auth/enums/authentication-status";
|
||||||
import { LogService } from "../../../platform/abstractions/log.service";
|
import { LogService } from "../../../platform/abstractions/log.service";
|
||||||
import { MessagingService } from "../../../platform/abstractions/messaging.service";
|
import { MessagingService } from "../../../platform/abstractions/messaging.service";
|
||||||
@@ -43,6 +44,7 @@ export class VaultTimeoutService implements VaultTimeoutServiceAbstraction {
|
|||||||
private messagingService: MessagingService,
|
private messagingService: MessagingService,
|
||||||
private searchService: SearchService,
|
private searchService: SearchService,
|
||||||
private stateService: StateService,
|
private stateService: StateService,
|
||||||
|
private tokenService: TokenService,
|
||||||
private authService: AuthService,
|
private authService: AuthService,
|
||||||
private vaultTimeoutSettingsService: VaultTimeoutSettingsService,
|
private vaultTimeoutSettingsService: VaultTimeoutSettingsService,
|
||||||
private stateEventRunnerService: StateEventRunnerService,
|
private stateEventRunnerService: StateEventRunnerService,
|
||||||
@@ -108,7 +110,10 @@ export class VaultTimeoutService implements VaultTimeoutServiceAbstraction {
|
|||||||
async lock(userId?: UserId): Promise<void> {
|
async lock(userId?: UserId): Promise<void> {
|
||||||
await this.biometricService.setShouldAutopromptNow(false);
|
await this.biometricService.setShouldAutopromptNow(false);
|
||||||
|
|
||||||
const authed = await this.stateService.getIsAuthenticated({ userId: userId });
|
const lockingUserId =
|
||||||
|
userId ?? (await firstValueFrom(this.accountService.activeAccount$.pipe(map((a) => a?.id))));
|
||||||
|
|
||||||
|
const authed = await firstValueFrom(this.tokenService.hasAccessToken$(lockingUserId));
|
||||||
if (!authed) {
|
if (!authed) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -121,12 +126,6 @@ export class VaultTimeoutService implements VaultTimeoutServiceAbstraction {
|
|||||||
await this.logOut(userId);
|
await this.logOut(userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentUserId = await firstValueFrom(
|
|
||||||
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
|
|
||||||
);
|
|
||||||
|
|
||||||
const lockingUserId = userId ?? currentUserId;
|
|
||||||
|
|
||||||
// HACK: Start listening for the transition of the locking user from something to the locked state.
|
// HACK: Start listening for the transition of the locking user from something to the locked state.
|
||||||
// This is very much a hack to ensure that the authentication status to retrievable right after
|
// This is very much a hack to ensure that the authentication status to retrievable right after
|
||||||
// it does its work. Particularly the `lockedCallback` and `"locked"` message. Instead
|
// it does its work. Particularly the `lockedCallback` and `"locked"` message. Instead
|
||||||
|
|||||||
@@ -1,63 +1 @@
|
|||||||
import { BiometricKey } from "../../auth/types/biometric-key";
|
export { StateService } from "@bitwarden/state";
|
||||||
import { Account } from "../models/domain/account";
|
|
||||||
import { StorageOptions } from "../models/domain/storage-options";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Options for customizing the initiation behavior.
|
|
||||||
*/
|
|
||||||
export type InitOptions = {
|
|
||||||
/**
|
|
||||||
* Whether or not to run state migrations as part of the init process. Defaults to true.
|
|
||||||
*
|
|
||||||
* If false, the init method will instead wait for migrations to complete before doing its
|
|
||||||
* other init operations. Make sure migrations have either already completed, or will complete
|
|
||||||
* before calling {@link StateService.init} with `runMigrations: false`.
|
|
||||||
*/
|
|
||||||
runMigrations?: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
export abstract class StateService<T extends Account = Account> {
|
|
||||||
abstract addAccount(account: T): Promise<void>;
|
|
||||||
abstract clean(options?: StorageOptions): Promise<void>;
|
|
||||||
abstract init(initOptions?: InitOptions): Promise<void>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the user's auto key
|
|
||||||
*/
|
|
||||||
abstract getUserKeyAutoUnlock(options?: StorageOptions): Promise<string>;
|
|
||||||
/**
|
|
||||||
* Sets the user's auto key
|
|
||||||
*/
|
|
||||||
abstract setUserKeyAutoUnlock(value: string | null, options?: StorageOptions): Promise<void>;
|
|
||||||
/**
|
|
||||||
* Gets the user's biometric key
|
|
||||||
*/
|
|
||||||
abstract getUserKeyBiometric(options?: StorageOptions): Promise<string>;
|
|
||||||
/**
|
|
||||||
* Checks if the user has a biometric key available
|
|
||||||
*/
|
|
||||||
abstract hasUserKeyBiometric(options?: StorageOptions): Promise<boolean>;
|
|
||||||
/**
|
|
||||||
* Sets the user's biometric key
|
|
||||||
*/
|
|
||||||
abstract setUserKeyBiometric(value: BiometricKey, options?: StorageOptions): Promise<void>;
|
|
||||||
/**
|
|
||||||
* @deprecated For backwards compatible purposes only, use DesktopAutofillSettingsService
|
|
||||||
*/
|
|
||||||
abstract setEnableDuckDuckGoBrowserIntegration(
|
|
||||||
value: boolean,
|
|
||||||
options?: StorageOptions,
|
|
||||||
): Promise<void>;
|
|
||||||
abstract getDuckDuckGoSharedKey(options?: StorageOptions): Promise<string>;
|
|
||||||
abstract setDuckDuckGoSharedKey(value: string, options?: StorageOptions): Promise<void>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated Use `TokenService.hasAccessToken$()` or `AuthService.authStatusFor$` instead.
|
|
||||||
*/
|
|
||||||
abstract getIsAuthenticated(options?: StorageOptions): Promise<boolean>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated Use `AccountService.activeAccount$` instead.
|
|
||||||
*/
|
|
||||||
abstract getUserId(options?: StorageOptions): Promise<string>;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
import { Account } from "../models/domain/account";
|
|
||||||
|
|
||||||
export class AccountFactory<T extends Account = Account> {
|
|
||||||
private accountConstructor: new (init: Partial<T>) => T;
|
|
||||||
|
|
||||||
constructor(accountConstructor: new (init: Partial<T>) => T) {
|
|
||||||
this.accountConstructor = accountConstructor;
|
|
||||||
}
|
|
||||||
|
|
||||||
create(args: Partial<T>) {
|
|
||||||
return new this.accountConstructor(args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
// FIXME: Update this file to be type safe and remove this and next line
|
|
||||||
// @ts-strict-ignore
|
|
||||||
import { GlobalState } from "../models/domain/global-state";
|
|
||||||
|
|
||||||
export class GlobalStateFactory<T extends GlobalState = GlobalState> {
|
|
||||||
private globalStateConstructor: new (init: Partial<T>) => T;
|
|
||||||
|
|
||||||
constructor(globalStateConstructor: new (init: Partial<T>) => T) {
|
|
||||||
this.globalStateConstructor = globalStateConstructor;
|
|
||||||
}
|
|
||||||
|
|
||||||
create(args?: Partial<T>) {
|
|
||||||
return new this.globalStateConstructor(args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
import { Account } from "../models/domain/account";
|
|
||||||
import { GlobalState } from "../models/domain/global-state";
|
|
||||||
|
|
||||||
import { AccountFactory } from "./account-factory";
|
|
||||||
import { GlobalStateFactory } from "./global-state-factory";
|
|
||||||
|
|
||||||
export class StateFactory<
|
|
||||||
TGlobal extends GlobalState = GlobalState,
|
|
||||||
TAccount extends Account = Account,
|
|
||||||
> {
|
|
||||||
private globalStateFactory: GlobalStateFactory<TGlobal>;
|
|
||||||
private accountFactory: AccountFactory<TAccount>;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
globalStateConstructor: new (init: Partial<TGlobal>) => TGlobal,
|
|
||||||
accountConstructor: new (init: Partial<TAccount>) => TAccount,
|
|
||||||
) {
|
|
||||||
this.globalStateFactory = new GlobalStateFactory(globalStateConstructor);
|
|
||||||
this.accountFactory = new AccountFactory(accountConstructor);
|
|
||||||
}
|
|
||||||
|
|
||||||
createGlobal(args: Partial<TGlobal>): TGlobal {
|
|
||||||
return this.globalStateFactory.create(args);
|
|
||||||
}
|
|
||||||
|
|
||||||
createAccount(args: Partial<TAccount>): TAccount {
|
|
||||||
return this.accountFactory.create(args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
import { makeStaticByteArray } from "../../../../spec";
|
|
||||||
import { Utils } from "../../misc/utils";
|
|
||||||
|
|
||||||
import { AccountKeys, EncryptionPair } from "./account";
|
|
||||||
|
|
||||||
describe("AccountKeys", () => {
|
|
||||||
describe("toJSON", () => {
|
|
||||||
it("should serialize itself", () => {
|
|
||||||
const keys = new AccountKeys();
|
|
||||||
const buffer = makeStaticByteArray(64);
|
|
||||||
keys.publicKey = buffer;
|
|
||||||
|
|
||||||
const bufferSpy = jest.spyOn(Utils, "fromBufferToByteString");
|
|
||||||
keys.toJSON();
|
|
||||||
expect(bufferSpy).toHaveBeenCalledWith(buffer);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should serialize public key as a string", () => {
|
|
||||||
const keys = new AccountKeys();
|
|
||||||
keys.publicKey = Utils.fromByteStringToArray("hello");
|
|
||||||
const json = JSON.stringify(keys);
|
|
||||||
expect(json).toContain('"publicKey":"hello"');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("fromJSON", () => {
|
|
||||||
it("should deserialize public key to a buffer", () => {
|
|
||||||
const keys = AccountKeys.fromJSON({
|
|
||||||
publicKey: "hello",
|
|
||||||
});
|
|
||||||
expect(keys.publicKey).toEqual(Utils.fromByteStringToArray("hello"));
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should deserialize privateKey", () => {
|
|
||||||
const spy = jest.spyOn(EncryptionPair, "fromJSON");
|
|
||||||
AccountKeys.fromJSON({
|
|
||||||
privateKey: { encrypted: "encrypted", decrypted: "decrypted" },
|
|
||||||
} as any);
|
|
||||||
expect(spy).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
import { AccountProfile } from "./account";
|
|
||||||
|
|
||||||
describe("AccountProfile", () => {
|
|
||||||
describe("fromJSON", () => {
|
|
||||||
it("should deserialize to an instance of itself", () => {
|
|
||||||
expect(AccountProfile.fromJSON({})).toBeInstanceOf(AccountProfile);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
import { Account, AccountKeys, AccountProfile } from "./account";
|
|
||||||
|
|
||||||
describe("Account", () => {
|
|
||||||
describe("fromJSON", () => {
|
|
||||||
it("should deserialize to an instance of itself", () => {
|
|
||||||
expect(Account.fromJSON({})).toBeInstanceOf(Account);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should call all the sub-fromJSONs", () => {
|
|
||||||
const keysSpy = jest.spyOn(AccountKeys, "fromJSON");
|
|
||||||
const profileSpy = jest.spyOn(AccountProfile, "fromJSON");
|
|
||||||
|
|
||||||
Account.fromJSON({});
|
|
||||||
|
|
||||||
expect(keysSpy).toHaveBeenCalled();
|
|
||||||
expect(profileSpy).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,136 +0,0 @@
|
|||||||
// FIXME: Update this file to be type safe and remove this and next line
|
|
||||||
// @ts-strict-ignore
|
|
||||||
import { Jsonify } from "type-fest";
|
|
||||||
|
|
||||||
import { DeepJsonify } from "../../../types/deep-jsonify";
|
|
||||||
import { Utils } from "../../misc/utils";
|
|
||||||
|
|
||||||
import { SymmetricCryptoKey } from "./symmetric-crypto-key";
|
|
||||||
|
|
||||||
export class EncryptionPair<TEncrypted, TDecrypted> {
|
|
||||||
encrypted?: TEncrypted;
|
|
||||||
decrypted?: TDecrypted;
|
|
||||||
|
|
||||||
toJSON() {
|
|
||||||
return {
|
|
||||||
encrypted: this.encrypted,
|
|
||||||
decrypted:
|
|
||||||
this.decrypted instanceof ArrayBuffer
|
|
||||||
? Utils.fromBufferToByteString(this.decrypted)
|
|
||||||
: this.decrypted,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
static fromJSON<TEncrypted, TDecrypted>(
|
|
||||||
obj: { encrypted?: Jsonify<TEncrypted>; decrypted?: string | Jsonify<TDecrypted> },
|
|
||||||
decryptedFromJson?: (decObj: Jsonify<TDecrypted> | string) => TDecrypted,
|
|
||||||
encryptedFromJson?: (encObj: Jsonify<TEncrypted>) => TEncrypted,
|
|
||||||
) {
|
|
||||||
if (obj == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const pair = new EncryptionPair<TEncrypted, TDecrypted>();
|
|
||||||
if (obj?.encrypted != null) {
|
|
||||||
pair.encrypted = encryptedFromJson
|
|
||||||
? encryptedFromJson(obj.encrypted)
|
|
||||||
: (obj.encrypted as TEncrypted);
|
|
||||||
}
|
|
||||||
if (obj?.decrypted != null) {
|
|
||||||
pair.decrypted = decryptedFromJson
|
|
||||||
? decryptedFromJson(obj.decrypted)
|
|
||||||
: (obj.decrypted as TDecrypted);
|
|
||||||
}
|
|
||||||
return pair;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class AccountKeys {
|
|
||||||
publicKey?: Uint8Array;
|
|
||||||
|
|
||||||
/** @deprecated July 2023, left for migration purposes*/
|
|
||||||
cryptoSymmetricKey?: EncryptionPair<string, SymmetricCryptoKey> = new EncryptionPair<
|
|
||||||
string,
|
|
||||||
SymmetricCryptoKey
|
|
||||||
>();
|
|
||||||
|
|
||||||
toJSON() {
|
|
||||||
// If you pass undefined into fromBufferToByteString, you will get an empty string back
|
|
||||||
// which will cause all sorts of headaches down the line when you try to getPublicKey
|
|
||||||
// and expect a Uint8Array and get an empty string instead.
|
|
||||||
return Utils.merge(this, {
|
|
||||||
publicKey: this.publicKey ? Utils.fromBufferToByteString(this.publicKey) : undefined,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
static fromJSON(obj: DeepJsonify<AccountKeys>): AccountKeys {
|
|
||||||
if (obj == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return Object.assign(new AccountKeys(), obj, {
|
|
||||||
cryptoSymmetricKey: EncryptionPair.fromJSON(
|
|
||||||
obj?.cryptoSymmetricKey,
|
|
||||||
SymmetricCryptoKey.fromJSON,
|
|
||||||
),
|
|
||||||
publicKey: Utils.fromByteStringToArray(obj?.publicKey),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
static initRecordEncryptionPairsFromJSON(obj: any) {
|
|
||||||
return EncryptionPair.fromJSON(obj, (decObj: any) => {
|
|
||||||
if (obj == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const record: Record<string, SymmetricCryptoKey> = {};
|
|
||||||
for (const id in decObj) {
|
|
||||||
record[id] = SymmetricCryptoKey.fromJSON(decObj[id]);
|
|
||||||
}
|
|
||||||
return record;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class AccountProfile {
|
|
||||||
name?: string;
|
|
||||||
email?: string;
|
|
||||||
emailVerified?: boolean;
|
|
||||||
userId?: string;
|
|
||||||
|
|
||||||
static fromJSON(obj: Jsonify<AccountProfile>): AccountProfile {
|
|
||||||
if (obj == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Object.assign(new AccountProfile(), obj);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Account {
|
|
||||||
keys?: AccountKeys = new AccountKeys();
|
|
||||||
profile?: AccountProfile = new AccountProfile();
|
|
||||||
|
|
||||||
constructor(init: Partial<Account>) {
|
|
||||||
Object.assign(this, {
|
|
||||||
keys: {
|
|
||||||
...new AccountKeys(),
|
|
||||||
...init?.keys,
|
|
||||||
},
|
|
||||||
profile: {
|
|
||||||
...new AccountProfile(),
|
|
||||||
...init?.profile,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
static fromJSON(json: Jsonify<Account>): Account {
|
|
||||||
if (json == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Object.assign(new Account({}), json, {
|
|
||||||
keys: AccountKeys.fromJSON(json?.keys),
|
|
||||||
profile: AccountProfile.fromJSON(json?.profile),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
import { Utils } from "../../misc/utils";
|
|
||||||
|
|
||||||
import { EncryptionPair } from "./account";
|
|
||||||
|
|
||||||
describe("EncryptionPair", () => {
|
|
||||||
describe("toJSON", () => {
|
|
||||||
it("should populate decryptedSerialized for buffer arrays", () => {
|
|
||||||
const pair = new EncryptionPair<string, ArrayBuffer>();
|
|
||||||
pair.decrypted = Utils.fromByteStringToArray("hello").buffer;
|
|
||||||
const json = pair.toJSON();
|
|
||||||
expect(json.decrypted).toEqual("hello");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should populate decryptedSerialized for TypesArrays", () => {
|
|
||||||
const pair = new EncryptionPair<string, Uint8Array>();
|
|
||||||
pair.decrypted = Utils.fromByteStringToArray("hello");
|
|
||||||
const json = pair.toJSON();
|
|
||||||
expect(json.decrypted).toEqual(new Uint8Array([104, 101, 108, 108, 111]));
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should serialize encrypted and decrypted", () => {
|
|
||||||
const pair = new EncryptionPair<string, string>();
|
|
||||||
pair.encrypted = "hello";
|
|
||||||
pair.decrypted = "world";
|
|
||||||
const json = pair.toJSON();
|
|
||||||
expect(json.encrypted).toEqual("hello");
|
|
||||||
expect(json.decrypted).toEqual("world");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("fromJSON", () => {
|
|
||||||
it("should deserialize encrypted and decrypted", () => {
|
|
||||||
const pair = EncryptionPair.fromJSON({
|
|
||||||
encrypted: "hello",
|
|
||||||
decrypted: "world",
|
|
||||||
});
|
|
||||||
expect(pair.encrypted).toEqual("hello");
|
|
||||||
expect(pair.decrypted).toEqual("world");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
import { Account } from "./account";
|
|
||||||
import { State } from "./state";
|
|
||||||
|
|
||||||
describe("state", () => {
|
|
||||||
describe("fromJSON", () => {
|
|
||||||
it("should deserialize to an instance of itself", () => {
|
|
||||||
expect(State.fromJSON({}, () => new Account({}))).toBeInstanceOf(State);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should always assign an object to accounts", () => {
|
|
||||||
const state = State.fromJSON({}, () => new Account({}));
|
|
||||||
expect(state.accounts).not.toBeNull();
|
|
||||||
expect(state.accounts).toEqual({});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should build an account map", () => {
|
|
||||||
const accountsSpy = jest.spyOn(Account, "fromJSON");
|
|
||||||
const state = State.fromJSON(
|
|
||||||
{
|
|
||||||
accounts: {
|
|
||||||
userId: {},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Account.fromJSON,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(state.accounts["userId"]).toBeInstanceOf(Account);
|
|
||||||
expect(accountsSpy).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
// FIXME: Update this file to be type safe and remove this and next line
|
|
||||||
// @ts-strict-ignore
|
|
||||||
import { Jsonify } from "type-fest";
|
|
||||||
|
|
||||||
import { Account } from "./account";
|
|
||||||
import { GlobalState } from "./global-state";
|
|
||||||
|
|
||||||
export class State<
|
|
||||||
TGlobalState extends GlobalState = GlobalState,
|
|
||||||
TAccount extends Account = Account,
|
|
||||||
> {
|
|
||||||
accounts: { [userId: string]: TAccount } = {};
|
|
||||||
globals: TGlobalState;
|
|
||||||
|
|
||||||
constructor(globals: TGlobalState) {
|
|
||||||
this.globals = globals;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO, make Jsonify<State,TGlobalState,TAccount> work. It currently doesn't because Globals doesn't implement Jsonify.
|
|
||||||
static fromJSON<TGlobalState extends GlobalState, TAccount extends Account>(
|
|
||||||
obj: any,
|
|
||||||
accountDeserializer: (json: Jsonify<TAccount>) => TAccount,
|
|
||||||
): State<TGlobalState, TAccount> {
|
|
||||||
if (obj == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Object.assign(new State(null), obj, {
|
|
||||||
accounts: State.buildAccountMapFromJSON(obj?.accounts, accountDeserializer),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private static buildAccountMapFromJSON<TAccount extends Account>(
|
|
||||||
jsonAccounts: { [userId: string]: Jsonify<TAccount> },
|
|
||||||
accountDeserializer: (json: Jsonify<TAccount>) => TAccount,
|
|
||||||
) {
|
|
||||||
if (!jsonAccounts) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
const accounts: { [userId: string]: TAccount } = {};
|
|
||||||
for (const userId in jsonAccounts) {
|
|
||||||
accounts[userId] = accountDeserializer(jsonAccounts[userId]);
|
|
||||||
}
|
|
||||||
return accounts;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,659 +0,0 @@
|
|||||||
// FIXME: Update this file to be type safe and remove this and next line
|
|
||||||
// @ts-strict-ignore
|
|
||||||
import { firstValueFrom, map } from "rxjs";
|
|
||||||
import { Jsonify, JsonValue } from "type-fest";
|
|
||||||
|
|
||||||
import { AccountService } from "../../auth/abstractions/account.service";
|
|
||||||
import { TokenService } from "../../auth/abstractions/token.service";
|
|
||||||
import { BiometricKey } from "../../auth/types/biometric-key";
|
|
||||||
import { UserId } from "../../types/guid";
|
|
||||||
import { EnvironmentService } from "../abstractions/environment.service";
|
|
||||||
import { LogService } from "../abstractions/log.service";
|
|
||||||
import {
|
|
||||||
InitOptions,
|
|
||||||
StateService as StateServiceAbstraction,
|
|
||||||
} from "../abstractions/state.service";
|
|
||||||
import { AbstractStorageService } from "../abstractions/storage.service";
|
|
||||||
import { HtmlStorageLocation, StorageLocation } from "../enums";
|
|
||||||
import { StateFactory } from "../factories/state-factory";
|
|
||||||
import { Account } from "../models/domain/account";
|
|
||||||
import { GlobalState } from "../models/domain/global-state";
|
|
||||||
import { State } from "../models/domain/state";
|
|
||||||
import { StorageOptions } from "../models/domain/storage-options";
|
|
||||||
|
|
||||||
import { MigrationRunner } from "./migration-runner";
|
|
||||||
|
|
||||||
const keys = {
|
|
||||||
state: "state",
|
|
||||||
stateVersion: "stateVersion",
|
|
||||||
global: "global",
|
|
||||||
tempAccountSettings: "tempAccountSettings", // used to hold account specific settings (i.e clear clipboard) between initial migration and first account authentication
|
|
||||||
};
|
|
||||||
|
|
||||||
const partialKeys = {
|
|
||||||
userAutoKey: "_user_auto",
|
|
||||||
userBiometricKey: "_user_biometric",
|
|
||||||
|
|
||||||
autoKey: "_masterkey_auto",
|
|
||||||
masterKey: "_masterkey",
|
|
||||||
};
|
|
||||||
|
|
||||||
const DDG_SHARED_KEY = "DuckDuckGoSharedKey";
|
|
||||||
|
|
||||||
export class StateService<
|
|
||||||
TGlobalState extends GlobalState = GlobalState,
|
|
||||||
TAccount extends Account = Account,
|
|
||||||
> implements StateServiceAbstraction<TAccount>
|
|
||||||
{
|
|
||||||
private hasBeenInited = false;
|
|
||||||
protected isRecoveredSession = false;
|
|
||||||
|
|
||||||
// default account serializer, must be overridden by child class
|
|
||||||
protected accountDeserializer = Account.fromJSON as (json: Jsonify<TAccount>) => TAccount;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
protected storageService: AbstractStorageService,
|
|
||||||
protected secureStorageService: AbstractStorageService,
|
|
||||||
protected memoryStorageService: AbstractStorageService,
|
|
||||||
protected logService: LogService,
|
|
||||||
protected stateFactory: StateFactory<TGlobalState, TAccount>,
|
|
||||||
protected accountService: AccountService,
|
|
||||||
protected environmentService: EnvironmentService,
|
|
||||||
protected tokenService: TokenService,
|
|
||||||
private migrationRunner: MigrationRunner,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
async init(initOptions: InitOptions = {}): Promise<void> {
|
|
||||||
// Deconstruct and apply defaults
|
|
||||||
const { runMigrations = true } = initOptions;
|
|
||||||
if (this.hasBeenInited) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (runMigrations) {
|
|
||||||
await this.migrationRunner.run();
|
|
||||||
} else {
|
|
||||||
// It may have been requested to not run the migrations but we should defensively not
|
|
||||||
// continue this method until migrations have a chance to be completed elsewhere.
|
|
||||||
await this.migrationRunner.waitForCompletion();
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.state().then(async (state) => {
|
|
||||||
if (state == null) {
|
|
||||||
await this.setState(new State<TGlobalState, TAccount>(this.createGlobals()));
|
|
||||||
} else {
|
|
||||||
this.isRecoveredSession = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
await this.initAccountState();
|
|
||||||
|
|
||||||
this.hasBeenInited = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
async initAccountState() {
|
|
||||||
if (this.isRecoveredSession) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get all likely authenticated accounts
|
|
||||||
const authenticatedAccounts = await firstValueFrom(
|
|
||||||
this.accountService.accounts$.pipe(map((accounts) => Object.keys(accounts))),
|
|
||||||
);
|
|
||||||
|
|
||||||
await this.updateState(async (state) => {
|
|
||||||
for (const i in authenticatedAccounts) {
|
|
||||||
state = await this.syncAccountFromDisk(authenticatedAccounts[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return state;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async syncAccountFromDisk(userId: string): Promise<State<TGlobalState, TAccount>> {
|
|
||||||
if (userId == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const diskAccount = await this.getAccountFromDisk({ userId: userId });
|
|
||||||
const state = await this.updateState(async (state) => {
|
|
||||||
if (state.accounts == null) {
|
|
||||||
state.accounts = {};
|
|
||||||
}
|
|
||||||
state.accounts[userId] = this.createAccount();
|
|
||||||
|
|
||||||
if (diskAccount == null) {
|
|
||||||
// Return early because we can't set the diskAccount.profile
|
|
||||||
// if diskAccount itself is null
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
|
|
||||||
state.accounts[userId].profile = diskAccount.profile;
|
|
||||||
return state;
|
|
||||||
});
|
|
||||||
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
|
|
||||||
async addAccount(account: TAccount) {
|
|
||||||
await this.updateState(async (state) => {
|
|
||||||
state.accounts[account.profile.userId] = account;
|
|
||||||
return state;
|
|
||||||
});
|
|
||||||
await this.scaffoldNewAccountStorage(account);
|
|
||||||
}
|
|
||||||
|
|
||||||
async clean(options?: StorageOptions): Promise<void> {
|
|
||||||
options = this.reconcileOptions(options, await this.defaultInMemoryOptions());
|
|
||||||
await this.deAuthenticateAccount(options.userId);
|
|
||||||
|
|
||||||
await this.removeAccountFromDisk(options?.userId);
|
|
||||||
await this.removeAccountFromMemory(options?.userId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* user key when using the "never" option of vault timeout
|
|
||||||
*/
|
|
||||||
async getUserKeyAutoUnlock(options?: StorageOptions): Promise<string> {
|
|
||||||
options = this.reconcileOptions(
|
|
||||||
this.reconcileOptions(options, { keySuffix: "auto" }),
|
|
||||||
await this.defaultSecureStorageOptions(),
|
|
||||||
);
|
|
||||||
if (options?.userId == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return await this.secureStorageService.get<string>(
|
|
||||||
`${options.userId}${partialKeys.userAutoKey}`,
|
|
||||||
options,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* user key when using the "never" option of vault timeout
|
|
||||||
*/
|
|
||||||
async setUserKeyAutoUnlock(value: string | null, options?: StorageOptions): Promise<void> {
|
|
||||||
options = this.reconcileOptions(
|
|
||||||
this.reconcileOptions(options, { keySuffix: "auto" }),
|
|
||||||
await this.defaultSecureStorageOptions(),
|
|
||||||
);
|
|
||||||
if (options?.userId == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await this.saveSecureStorageKey(partialKeys.userAutoKey, value, options);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* User's encrypted symmetric key when using biometrics
|
|
||||||
*/
|
|
||||||
async getUserKeyBiometric(options?: StorageOptions): Promise<string> {
|
|
||||||
options = this.reconcileOptions(
|
|
||||||
this.reconcileOptions(options, { keySuffix: "biometric" }),
|
|
||||||
await this.defaultSecureStorageOptions(),
|
|
||||||
);
|
|
||||||
if (options?.userId == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return await this.secureStorageService.get<string>(
|
|
||||||
`${options.userId}${partialKeys.userBiometricKey}`,
|
|
||||||
options,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async hasUserKeyBiometric(options?: StorageOptions): Promise<boolean> {
|
|
||||||
options = this.reconcileOptions(
|
|
||||||
this.reconcileOptions(options, { keySuffix: "biometric" }),
|
|
||||||
await this.defaultSecureStorageOptions(),
|
|
||||||
);
|
|
||||||
if (options?.userId == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return await this.secureStorageService.has(
|
|
||||||
`${options.userId}${partialKeys.userBiometricKey}`,
|
|
||||||
options,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async setUserKeyBiometric(value: BiometricKey, options?: StorageOptions): Promise<void> {
|
|
||||||
options = this.reconcileOptions(
|
|
||||||
this.reconcileOptions(options, { keySuffix: "biometric" }),
|
|
||||||
await this.defaultSecureStorageOptions(),
|
|
||||||
);
|
|
||||||
if (options?.userId == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await this.saveSecureStorageKey(partialKeys.userBiometricKey, value, options);
|
|
||||||
}
|
|
||||||
|
|
||||||
async getDuckDuckGoSharedKey(options?: StorageOptions): Promise<string> {
|
|
||||||
options = this.reconcileOptions(options, await this.defaultSecureStorageOptions());
|
|
||||||
if (options?.userId == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return await this.secureStorageService.get<string>(DDG_SHARED_KEY, options);
|
|
||||||
}
|
|
||||||
|
|
||||||
async setDuckDuckGoSharedKey(value: string, options?: StorageOptions): Promise<void> {
|
|
||||||
options = this.reconcileOptions(options, await this.defaultSecureStorageOptions());
|
|
||||||
if (options?.userId == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
value == null
|
|
||||||
? await this.secureStorageService.remove(DDG_SHARED_KEY, options)
|
|
||||||
: await this.secureStorageService.save(DDG_SHARED_KEY, value, options);
|
|
||||||
}
|
|
||||||
|
|
||||||
async setEnableDuckDuckGoBrowserIntegration(
|
|
||||||
value: boolean,
|
|
||||||
options?: StorageOptions,
|
|
||||||
): Promise<void> {
|
|
||||||
const globals = await this.getGlobals(
|
|
||||||
this.reconcileOptions(options, await this.defaultOnDiskOptions()),
|
|
||||||
);
|
|
||||||
globals.enableDuckDuckGoBrowserIntegration = value;
|
|
||||||
await this.saveGlobals(
|
|
||||||
globals,
|
|
||||||
this.reconcileOptions(options, await this.defaultOnDiskOptions()),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated Use UserKey instead
|
|
||||||
*/
|
|
||||||
async getEncryptedCryptoSymmetricKey(options?: StorageOptions): Promise<string> {
|
|
||||||
return (
|
|
||||||
await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions()))
|
|
||||||
)?.keys.cryptoSymmetricKey.encrypted;
|
|
||||||
}
|
|
||||||
|
|
||||||
async getIsAuthenticated(options?: StorageOptions): Promise<boolean> {
|
|
||||||
return (
|
|
||||||
(await this.tokenService.getAccessToken(options?.userId as UserId)) != null &&
|
|
||||||
(await this.getUserId(options)) != null
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async getUserId(options?: StorageOptions): Promise<string> {
|
|
||||||
return (
|
|
||||||
await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions()))
|
|
||||||
)?.profile?.userId;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async getGlobals(options: StorageOptions): Promise<TGlobalState> {
|
|
||||||
let globals: TGlobalState;
|
|
||||||
if (this.useMemory(options.storageLocation)) {
|
|
||||||
globals = await this.getGlobalsFromMemory();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.useDisk && globals == null) {
|
|
||||||
globals = await this.getGlobalsFromDisk(options);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (globals == null) {
|
|
||||||
globals = this.createGlobals();
|
|
||||||
}
|
|
||||||
|
|
||||||
return globals;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async saveGlobals(globals: TGlobalState, options: StorageOptions) {
|
|
||||||
return this.useMemory(options.storageLocation)
|
|
||||||
? this.saveGlobalsToMemory(globals)
|
|
||||||
: await this.saveGlobalsToDisk(globals, options);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async getGlobalsFromMemory(): Promise<TGlobalState> {
|
|
||||||
return (await this.state()).globals;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async getGlobalsFromDisk(options: StorageOptions): Promise<TGlobalState> {
|
|
||||||
return await this.storageService.get<TGlobalState>(keys.global, options);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async saveGlobalsToMemory(globals: TGlobalState): Promise<void> {
|
|
||||||
await this.updateState(async (state) => {
|
|
||||||
state.globals = globals;
|
|
||||||
return state;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async saveGlobalsToDisk(globals: TGlobalState, options: StorageOptions): Promise<void> {
|
|
||||||
if (options.useSecureStorage) {
|
|
||||||
await this.secureStorageService.save(keys.global, globals, options);
|
|
||||||
} else {
|
|
||||||
await this.storageService.save(keys.global, globals, options);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async getAccount(options: StorageOptions): Promise<TAccount> {
|
|
||||||
try {
|
|
||||||
let account: TAccount;
|
|
||||||
if (this.useMemory(options.storageLocation)) {
|
|
||||||
account = await this.getAccountFromMemory(options);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.useDisk(options.storageLocation) && account == null) {
|
|
||||||
account = await this.getAccountFromDisk(options);
|
|
||||||
}
|
|
||||||
|
|
||||||
return account;
|
|
||||||
} catch (e) {
|
|
||||||
this.logService.error(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async getAccountFromMemory(options: StorageOptions): Promise<TAccount> {
|
|
||||||
const userId =
|
|
||||||
options.userId ??
|
|
||||||
(await firstValueFrom(
|
|
||||||
this.accountService.activeAccount$.pipe(map((account) => account?.id)),
|
|
||||||
));
|
|
||||||
|
|
||||||
return await this.state().then(async (state) => {
|
|
||||||
if (state.accounts == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return state.accounts[userId];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async getAccountFromDisk(options: StorageOptions): Promise<TAccount> {
|
|
||||||
const userId =
|
|
||||||
options.userId ??
|
|
||||||
(await firstValueFrom(
|
|
||||||
this.accountService.activeAccount$.pipe(map((account) => account?.id)),
|
|
||||||
));
|
|
||||||
|
|
||||||
if (userId == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const account = options?.useSecureStorage
|
|
||||||
? ((await this.secureStorageService.get<TAccount>(options.userId, options)) ??
|
|
||||||
(await this.storageService.get<TAccount>(
|
|
||||||
options.userId,
|
|
||||||
this.reconcileOptions(options, { htmlStorageLocation: HtmlStorageLocation.Local }),
|
|
||||||
)))
|
|
||||||
: await this.storageService.get<TAccount>(options.userId, options);
|
|
||||||
return account;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected useMemory(storageLocation: StorageLocation) {
|
|
||||||
return storageLocation === StorageLocation.Memory || storageLocation === StorageLocation.Both;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected useDisk(storageLocation: StorageLocation) {
|
|
||||||
return storageLocation === StorageLocation.Disk || storageLocation === StorageLocation.Both;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async saveAccount(
|
|
||||||
account: TAccount,
|
|
||||||
options: StorageOptions = {
|
|
||||||
storageLocation: StorageLocation.Both,
|
|
||||||
useSecureStorage: false,
|
|
||||||
},
|
|
||||||
) {
|
|
||||||
return this.useMemory(options.storageLocation)
|
|
||||||
? await this.saveAccountToMemory(account)
|
|
||||||
: await this.saveAccountToDisk(account, options);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async saveAccountToDisk(account: TAccount, options: StorageOptions): Promise<void> {
|
|
||||||
const storageLocation = options.useSecureStorage
|
|
||||||
? this.secureStorageService
|
|
||||||
: this.storageService;
|
|
||||||
|
|
||||||
await storageLocation.save(`${options.userId}`, account, options);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async saveAccountToMemory(account: TAccount): Promise<void> {
|
|
||||||
if ((await this.getAccountFromMemory({ userId: account.profile.userId })) !== null) {
|
|
||||||
await this.updateState((state) => {
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
state.accounts[account.profile.userId] = account;
|
|
||||||
resolve(state);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async scaffoldNewAccountStorage(account: TAccount): Promise<void> {
|
|
||||||
// We don't want to manipulate the referenced in memory account
|
|
||||||
const deepClone = JSON.parse(JSON.stringify(account));
|
|
||||||
await this.scaffoldNewAccountLocalStorage(deepClone);
|
|
||||||
await this.scaffoldNewAccountSessionStorage(deepClone);
|
|
||||||
await this.scaffoldNewAccountMemoryStorage(deepClone);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: There is a tech debt item for splitting up these methods - only Web uses multiple storage locations in its storageService.
|
|
||||||
// For now these methods exist with some redundancy to facilitate this special web requirement.
|
|
||||||
protected async scaffoldNewAccountLocalStorage(account: TAccount): Promise<void> {
|
|
||||||
await this.saveAccount(
|
|
||||||
account,
|
|
||||||
this.reconcileOptions(
|
|
||||||
{ userId: account.profile.userId },
|
|
||||||
await this.defaultOnDiskLocalOptions(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async scaffoldNewAccountMemoryStorage(account: TAccount): Promise<void> {
|
|
||||||
await this.storageService.save(
|
|
||||||
account.profile.userId,
|
|
||||||
account,
|
|
||||||
await this.defaultOnDiskMemoryOptions(),
|
|
||||||
);
|
|
||||||
await this.saveAccount(
|
|
||||||
account,
|
|
||||||
this.reconcileOptions(
|
|
||||||
{ userId: account.profile.userId },
|
|
||||||
await this.defaultOnDiskMemoryOptions(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async scaffoldNewAccountSessionStorage(account: TAccount): Promise<void> {
|
|
||||||
await this.storageService.save(
|
|
||||||
account.profile.userId,
|
|
||||||
account,
|
|
||||||
await this.defaultOnDiskMemoryOptions(),
|
|
||||||
);
|
|
||||||
await this.saveAccount(
|
|
||||||
account,
|
|
||||||
this.reconcileOptions({ userId: account.profile.userId }, await this.defaultOnDiskOptions()),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected reconcileOptions(
|
|
||||||
requestedOptions: StorageOptions,
|
|
||||||
defaultOptions: StorageOptions,
|
|
||||||
): StorageOptions {
|
|
||||||
if (requestedOptions == null) {
|
|
||||||
return defaultOptions;
|
|
||||||
}
|
|
||||||
requestedOptions.userId = requestedOptions?.userId ?? defaultOptions.userId;
|
|
||||||
requestedOptions.storageLocation =
|
|
||||||
requestedOptions?.storageLocation ?? defaultOptions.storageLocation;
|
|
||||||
requestedOptions.useSecureStorage =
|
|
||||||
requestedOptions?.useSecureStorage ?? defaultOptions.useSecureStorage;
|
|
||||||
requestedOptions.htmlStorageLocation =
|
|
||||||
requestedOptions?.htmlStorageLocation ?? defaultOptions.htmlStorageLocation;
|
|
||||||
requestedOptions.keySuffix = requestedOptions?.keySuffix ?? defaultOptions.keySuffix;
|
|
||||||
return requestedOptions;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async defaultInMemoryOptions(): Promise<StorageOptions> {
|
|
||||||
const userId = await firstValueFrom(
|
|
||||||
this.accountService.activeAccount$.pipe(map((account) => account?.id)),
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
storageLocation: StorageLocation.Memory,
|
|
||||||
userId,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async defaultOnDiskOptions(): Promise<StorageOptions> {
|
|
||||||
const userId = await firstValueFrom(
|
|
||||||
this.accountService.activeAccount$.pipe(map((account) => account?.id)),
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
storageLocation: StorageLocation.Disk,
|
|
||||||
htmlStorageLocation: HtmlStorageLocation.Session,
|
|
||||||
userId,
|
|
||||||
useSecureStorage: false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async defaultOnDiskLocalOptions(): Promise<StorageOptions> {
|
|
||||||
const userId = await firstValueFrom(
|
|
||||||
this.accountService.activeAccount$.pipe(map((account) => account?.id)),
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
storageLocation: StorageLocation.Disk,
|
|
||||||
htmlStorageLocation: HtmlStorageLocation.Local,
|
|
||||||
userId,
|
|
||||||
useSecureStorage: false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async defaultOnDiskMemoryOptions(): Promise<StorageOptions> {
|
|
||||||
const userId = await firstValueFrom(
|
|
||||||
this.accountService.activeAccount$.pipe(map((account) => account?.id)),
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
storageLocation: StorageLocation.Disk,
|
|
||||||
htmlStorageLocation: HtmlStorageLocation.Memory,
|
|
||||||
userId,
|
|
||||||
useSecureStorage: false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async defaultSecureStorageOptions(): Promise<StorageOptions> {
|
|
||||||
const userId = await firstValueFrom(
|
|
||||||
this.accountService.activeAccount$.pipe(map((account) => account?.id)),
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
storageLocation: StorageLocation.Disk,
|
|
||||||
useSecureStorage: true,
|
|
||||||
userId,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async getActiveUserIdFromStorage(): Promise<string> {
|
|
||||||
return await firstValueFrom(this.accountService.activeAccount$.pipe(map((a) => a?.id)));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async removeAccountFromLocalStorage(userId: string = null): Promise<void> {
|
|
||||||
userId ??= await firstValueFrom(
|
|
||||||
this.accountService.activeAccount$.pipe(map((account) => account?.id)),
|
|
||||||
);
|
|
||||||
|
|
||||||
const storedAccount = await this.getAccount(
|
|
||||||
this.reconcileOptions({ userId: userId }, await this.defaultOnDiskLocalOptions()),
|
|
||||||
);
|
|
||||||
await this.saveAccount(
|
|
||||||
this.resetAccount(storedAccount),
|
|
||||||
this.reconcileOptions({ userId: userId }, await this.defaultOnDiskLocalOptions()),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async removeAccountFromSessionStorage(userId: string = null): Promise<void> {
|
|
||||||
userId ??= await firstValueFrom(
|
|
||||||
this.accountService.activeAccount$.pipe(map((account) => account?.id)),
|
|
||||||
);
|
|
||||||
|
|
||||||
const storedAccount = await this.getAccount(
|
|
||||||
this.reconcileOptions({ userId: userId }, await this.defaultOnDiskOptions()),
|
|
||||||
);
|
|
||||||
await this.saveAccount(
|
|
||||||
this.resetAccount(storedAccount),
|
|
||||||
this.reconcileOptions({ userId: userId }, await this.defaultOnDiskOptions()),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async removeAccountFromSecureStorage(userId: string = null): Promise<void> {
|
|
||||||
userId ??= await firstValueFrom(
|
|
||||||
this.accountService.activeAccount$.pipe(map((account) => account?.id)),
|
|
||||||
);
|
|
||||||
|
|
||||||
await this.setUserKeyAutoUnlock(null, { userId: userId });
|
|
||||||
await this.setUserKeyBiometric(null, { userId: userId });
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async removeAccountFromMemory(userId: string = null): Promise<void> {
|
|
||||||
userId ??= await firstValueFrom(
|
|
||||||
this.accountService.activeAccount$.pipe(map((account) => account?.id)),
|
|
||||||
);
|
|
||||||
|
|
||||||
await this.updateState(async (state) => {
|
|
||||||
delete state.accounts[userId];
|
|
||||||
return state;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// settings persist even on reset, and are not affected by this method
|
|
||||||
protected resetAccount(account: TAccount) {
|
|
||||||
// All settings have been moved to StateProviders
|
|
||||||
return this.createAccount();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected createAccount(init: Partial<TAccount> = null): TAccount {
|
|
||||||
return this.stateFactory.createAccount(init);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected createGlobals(init: Partial<TGlobalState> = null): TGlobalState {
|
|
||||||
return this.stateFactory.createGlobal(init);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async deAuthenticateAccount(userId: string): Promise<void> {
|
|
||||||
// We must have a manual call to clear tokens as we can't leverage state provider to clean
|
|
||||||
// up our data as we have secure storage in the mix.
|
|
||||||
await this.tokenService.clearTokens(userId as UserId);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async removeAccountFromDisk(userId: string) {
|
|
||||||
await this.removeAccountFromSessionStorage(userId);
|
|
||||||
await this.removeAccountFromLocalStorage(userId);
|
|
||||||
await this.removeAccountFromSecureStorage(userId);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async saveSecureStorageKey<T extends JsonValue>(
|
|
||||||
key: string,
|
|
||||||
value: T | null,
|
|
||||||
options?: StorageOptions,
|
|
||||||
) {
|
|
||||||
return value == null
|
|
||||||
? await this.secureStorageService.remove(`${options.userId}${key}`, options)
|
|
||||||
: await this.secureStorageService.save(`${options.userId}${key}`, value, options);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async state(): Promise<State<TGlobalState, TAccount>> {
|
|
||||||
let state = await this.memoryStorageService.get<State<TGlobalState, TAccount>>(keys.state);
|
|
||||||
if (this.memoryStorageService.valuesRequireDeserialization) {
|
|
||||||
state = State.fromJSON(state, this.accountDeserializer);
|
|
||||||
}
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async setState(
|
|
||||||
state: State<TGlobalState, TAccount>,
|
|
||||||
): Promise<State<TGlobalState, TAccount>> {
|
|
||||||
await this.memoryStorageService.save(keys.state, state);
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async updateState(
|
|
||||||
stateUpdater: (state: State<TGlobalState, TAccount>) => Promise<State<TGlobalState, TAccount>>,
|
|
||||||
): Promise<State<TGlobalState, TAccount>> {
|
|
||||||
return await this.state().then(async (state) => {
|
|
||||||
const updatedState = await stateUpdater(state);
|
|
||||||
if (updatedState == null) {
|
|
||||||
throw new Error("Attempted to update state to null value");
|
|
||||||
}
|
|
||||||
|
|
||||||
return await this.setState(updatedState);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -9,6 +9,7 @@ import { CollectionService } from "@bitwarden/admin-console/common";
|
|||||||
import { ApiService } from "../../abstractions/api.service";
|
import { ApiService } from "../../abstractions/api.service";
|
||||||
import { AccountService } from "../../auth/abstractions/account.service";
|
import { AccountService } from "../../auth/abstractions/account.service";
|
||||||
import { AuthService } from "../../auth/abstractions/auth.service";
|
import { AuthService } from "../../auth/abstractions/auth.service";
|
||||||
|
import { TokenService } from "../../auth/abstractions/token.service";
|
||||||
import { AuthenticationStatus } from "../../auth/enums/authentication-status";
|
import { AuthenticationStatus } from "../../auth/enums/authentication-status";
|
||||||
import {
|
import {
|
||||||
SyncCipherNotification,
|
SyncCipherNotification,
|
||||||
@@ -26,7 +27,6 @@ import { SyncService } from "../../vault/abstractions/sync/sync.service.abstract
|
|||||||
import { CipherData } from "../../vault/models/data/cipher.data";
|
import { CipherData } from "../../vault/models/data/cipher.data";
|
||||||
import { FolderData } from "../../vault/models/data/folder.data";
|
import { FolderData } from "../../vault/models/data/folder.data";
|
||||||
import { LogService } from "../abstractions/log.service";
|
import { LogService } from "../abstractions/log.service";
|
||||||
import { StateService } from "../abstractions/state.service";
|
|
||||||
import { MessageSender } from "../messaging";
|
import { MessageSender } from "../messaging";
|
||||||
import { StateProvider, SYNC_DISK, UserKeyDefinition } from "../state";
|
import { StateProvider, SYNC_DISK, UserKeyDefinition } from "../state";
|
||||||
|
|
||||||
@@ -44,7 +44,7 @@ export abstract class CoreSyncService implements SyncService {
|
|||||||
syncInProgress = false;
|
syncInProgress = false;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
protected readonly stateService: StateService,
|
readonly tokenService: TokenService,
|
||||||
protected readonly folderService: InternalFolderService,
|
protected readonly folderService: InternalFolderService,
|
||||||
protected readonly folderApiService: FolderApiServiceAbstraction,
|
protected readonly folderApiService: FolderApiServiceAbstraction,
|
||||||
protected readonly messageSender: MessageSender,
|
protected readonly messageSender: MessageSender,
|
||||||
@@ -256,7 +256,13 @@ export abstract class CoreSyncService implements SyncService {
|
|||||||
|
|
||||||
async syncDeleteSend(notification: SyncSendNotification): Promise<boolean> {
|
async syncDeleteSend(notification: SyncSendNotification): Promise<boolean> {
|
||||||
this.syncStarted();
|
this.syncStarted();
|
||||||
if (await this.stateService.getIsAuthenticated()) {
|
const activeUserId = await firstValueFrom(
|
||||||
|
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
|
||||||
|
);
|
||||||
|
if (
|
||||||
|
activeUserId != null &&
|
||||||
|
(await firstValueFrom(this.tokenService.hasAccessToken$(activeUserId)))
|
||||||
|
) {
|
||||||
await this.sendService.delete(notification.id);
|
await this.sendService.delete(notification.id);
|
||||||
this.messageSender.send("syncedDeletedSend", { sendId: notification.id });
|
this.messageSender.send("syncedDeletedSend", { sendId: notification.id });
|
||||||
// TODO: Update syncCompleted userId when send service allows modification of non-active users
|
// TODO: Update syncCompleted userId when send service allows modification of non-active users
|
||||||
|
|||||||
@@ -36,7 +36,6 @@ import { CipherService } from "../../vault/abstractions/cipher.service";
|
|||||||
import { FolderApiServiceAbstraction } from "../../vault/abstractions/folder/folder-api.service.abstraction";
|
import { FolderApiServiceAbstraction } from "../../vault/abstractions/folder/folder-api.service.abstraction";
|
||||||
import { InternalFolderService } from "../../vault/abstractions/folder/folder.service.abstraction";
|
import { InternalFolderService } from "../../vault/abstractions/folder/folder.service.abstraction";
|
||||||
import { LogService } from "../abstractions/log.service";
|
import { LogService } from "../abstractions/log.service";
|
||||||
import { StateService } from "../abstractions/state.service";
|
|
||||||
import { MessageSender } from "../messaging";
|
import { MessageSender } from "../messaging";
|
||||||
import { StateProvider } from "../state";
|
import { StateProvider } from "../state";
|
||||||
|
|
||||||
@@ -57,7 +56,6 @@ describe("DefaultSyncService", () => {
|
|||||||
let sendService: MockProxy<InternalSendService>;
|
let sendService: MockProxy<InternalSendService>;
|
||||||
let logService: MockProxy<LogService>;
|
let logService: MockProxy<LogService>;
|
||||||
let keyConnectorService: MockProxy<KeyConnectorService>;
|
let keyConnectorService: MockProxy<KeyConnectorService>;
|
||||||
let stateService: MockProxy<StateService>;
|
|
||||||
let providerService: MockProxy<ProviderService>;
|
let providerService: MockProxy<ProviderService>;
|
||||||
let folderApiService: MockProxy<FolderApiServiceAbstraction>;
|
let folderApiService: MockProxy<FolderApiServiceAbstraction>;
|
||||||
let organizationService: MockProxy<InternalOrganizationServiceAbstraction>;
|
let organizationService: MockProxy<InternalOrganizationServiceAbstraction>;
|
||||||
@@ -86,7 +84,6 @@ describe("DefaultSyncService", () => {
|
|||||||
sendService = mock();
|
sendService = mock();
|
||||||
logService = mock();
|
logService = mock();
|
||||||
keyConnectorService = mock();
|
keyConnectorService = mock();
|
||||||
stateService = mock();
|
|
||||||
providerService = mock();
|
providerService = mock();
|
||||||
folderApiService = mock();
|
folderApiService = mock();
|
||||||
organizationService = mock();
|
organizationService = mock();
|
||||||
@@ -113,7 +110,6 @@ describe("DefaultSyncService", () => {
|
|||||||
sendService,
|
sendService,
|
||||||
logService,
|
logService,
|
||||||
keyConnectorService,
|
keyConnectorService,
|
||||||
stateService,
|
|
||||||
providerService,
|
providerService,
|
||||||
folderApiService,
|
folderApiService,
|
||||||
organizationService,
|
organizationService,
|
||||||
|
|||||||
@@ -53,7 +53,6 @@ import { FolderData } from "../../vault/models/data/folder.data";
|
|||||||
import { CipherResponse } from "../../vault/models/response/cipher.response";
|
import { CipherResponse } from "../../vault/models/response/cipher.response";
|
||||||
import { FolderResponse } from "../../vault/models/response/folder.response";
|
import { FolderResponse } from "../../vault/models/response/folder.response";
|
||||||
import { LogService } from "../abstractions/log.service";
|
import { LogService } from "../abstractions/log.service";
|
||||||
import { StateService } from "../abstractions/state.service";
|
|
||||||
import { MessageSender } from "../messaging";
|
import { MessageSender } from "../messaging";
|
||||||
import { StateProvider } from "../state";
|
import { StateProvider } from "../state";
|
||||||
|
|
||||||
@@ -87,7 +86,6 @@ export class DefaultSyncService extends CoreSyncService {
|
|||||||
sendService: InternalSendService,
|
sendService: InternalSendService,
|
||||||
logService: LogService,
|
logService: LogService,
|
||||||
private keyConnectorService: KeyConnectorService,
|
private keyConnectorService: KeyConnectorService,
|
||||||
stateService: StateService,
|
|
||||||
private providerService: ProviderService,
|
private providerService: ProviderService,
|
||||||
folderApiService: FolderApiServiceAbstraction,
|
folderApiService: FolderApiServiceAbstraction,
|
||||||
private organizationService: InternalOrganizationServiceAbstraction,
|
private organizationService: InternalOrganizationServiceAbstraction,
|
||||||
@@ -96,12 +94,12 @@ export class DefaultSyncService extends CoreSyncService {
|
|||||||
private avatarService: AvatarService,
|
private avatarService: AvatarService,
|
||||||
private logoutCallback: (logoutReason: LogoutReason, userId?: UserId) => Promise<void>,
|
private logoutCallback: (logoutReason: LogoutReason, userId?: UserId) => Promise<void>,
|
||||||
private billingAccountProfileStateService: BillingAccountProfileStateService,
|
private billingAccountProfileStateService: BillingAccountProfileStateService,
|
||||||
private tokenService: TokenService,
|
tokenService: TokenService,
|
||||||
authService: AuthService,
|
authService: AuthService,
|
||||||
stateProvider: StateProvider,
|
stateProvider: StateProvider,
|
||||||
) {
|
) {
|
||||||
super(
|
super(
|
||||||
stateService,
|
tokenService,
|
||||||
folderService,
|
folderService,
|
||||||
folderApiService,
|
folderApiService,
|
||||||
messageSender,
|
messageSender,
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ import { EncString } from "../../key-management/crypto/models/enc-string";
|
|||||||
import { UriMatchStrategy } from "../../models/domain/domain-service";
|
import { UriMatchStrategy } from "../../models/domain/domain-service";
|
||||||
import { ConfigService } from "../../platform/abstractions/config/config.service";
|
import { ConfigService } from "../../platform/abstractions/config/config.service";
|
||||||
import { I18nService } from "../../platform/abstractions/i18n.service";
|
import { I18nService } from "../../platform/abstractions/i18n.service";
|
||||||
import { StateService } from "../../platform/abstractions/state.service";
|
|
||||||
import { Utils } from "../../platform/misc/utils";
|
import { Utils } from "../../platform/misc/utils";
|
||||||
import { EncArrayBuffer } from "../../platform/models/domain/enc-array-buffer";
|
import { EncArrayBuffer } from "../../platform/models/domain/enc-array-buffer";
|
||||||
import { SymmetricCryptoKey } from "../../platform/models/domain/symmetric-crypto-key";
|
import { SymmetricCryptoKey } from "../../platform/models/domain/symmetric-crypto-key";
|
||||||
@@ -94,7 +93,6 @@ let accountService: FakeAccountService;
|
|||||||
|
|
||||||
describe("Cipher Service", () => {
|
describe("Cipher Service", () => {
|
||||||
const keyService = mock<KeyService>();
|
const keyService = mock<KeyService>();
|
||||||
const stateService = mock<StateService>();
|
|
||||||
const autofillSettingsService = mock<AutofillSettingsService>();
|
const autofillSettingsService = mock<AutofillSettingsService>();
|
||||||
const domainSettingsService = mock<DomainSettingsService>();
|
const domainSettingsService = mock<DomainSettingsService>();
|
||||||
const apiService = mock<ApiService>();
|
const apiService = mock<ApiService>();
|
||||||
@@ -127,7 +125,6 @@ describe("Cipher Service", () => {
|
|||||||
apiService,
|
apiService,
|
||||||
i18nService,
|
i18nService,
|
||||||
searchService,
|
searchService,
|
||||||
stateService,
|
|
||||||
autofillSettingsService,
|
autofillSettingsService,
|
||||||
encryptService,
|
encryptService,
|
||||||
cipherFileUploadService,
|
cipherFileUploadService,
|
||||||
@@ -470,8 +467,6 @@ describe("Cipher Service", () => {
|
|||||||
|
|
||||||
searchService.indexedEntityId$.mockReturnValue(of(null));
|
searchService.indexedEntityId$.mockReturnValue(of(null));
|
||||||
|
|
||||||
stateService.getUserId.mockResolvedValue(mockUserId);
|
|
||||||
|
|
||||||
const keys = { userKey: originalUserKey } as CipherDecryptionKeys;
|
const keys = { userKey: originalUserKey } as CipherDecryptionKeys;
|
||||||
keyService.cipherDecryptionKeys$.mockReturnValue(of(keys));
|
keyService.cipherDecryptionKeys$.mockReturnValue(of(keys));
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ import { ListResponse } from "../../models/response/list.response";
|
|||||||
import { View } from "../../models/view/view";
|
import { View } from "../../models/view/view";
|
||||||
import { ConfigService } from "../../platform/abstractions/config/config.service";
|
import { ConfigService } from "../../platform/abstractions/config/config.service";
|
||||||
import { I18nService } from "../../platform/abstractions/i18n.service";
|
import { I18nService } from "../../platform/abstractions/i18n.service";
|
||||||
import { StateService } from "../../platform/abstractions/state.service";
|
|
||||||
import { Utils } from "../../platform/misc/utils";
|
import { Utils } from "../../platform/misc/utils";
|
||||||
import Domain from "../../platform/models/domain/domain-base";
|
import Domain from "../../platform/models/domain/domain-base";
|
||||||
import { EncArrayBuffer } from "../../platform/models/domain/enc-array-buffer";
|
import { EncArrayBuffer } from "../../platform/models/domain/enc-array-buffer";
|
||||||
@@ -110,7 +109,6 @@ export class CipherService implements CipherServiceAbstraction {
|
|||||||
private apiService: ApiService,
|
private apiService: ApiService,
|
||||||
private i18nService: I18nService,
|
private i18nService: I18nService,
|
||||||
private searchService: SearchService,
|
private searchService: SearchService,
|
||||||
private stateService: StateService,
|
|
||||||
private autofillSettingsService: AutofillSettingsServiceAbstraction,
|
private autofillSettingsService: AutofillSettingsServiceAbstraction,
|
||||||
private encryptService: EncryptService,
|
private encryptService: EncryptService,
|
||||||
private cipherFileUploadService: CipherFileUploadService,
|
private cipherFileUploadService: CipherFileUploadService,
|
||||||
|
|||||||
@@ -732,7 +732,7 @@ export class DefaultKeyService implements KeyServiceAbstraction {
|
|||||||
|
|
||||||
protected async getKeyFromStorage(
|
protected async getKeyFromStorage(
|
||||||
keySuffix: KeySuffixOptions,
|
keySuffix: KeySuffixOptions,
|
||||||
userId?: UserId,
|
userId: UserId,
|
||||||
): Promise<UserKey | null> {
|
): Promise<UserKey | null> {
|
||||||
if (keySuffix === KeySuffixOptions.Auto) {
|
if (keySuffix === KeySuffixOptions.Auto) {
|
||||||
const userKey = await this.stateService.getUserKeyAutoUnlock({ userId: userId });
|
const userKey = await this.stateService.getUserKeyAutoUnlock({ userId: userId });
|
||||||
|
|||||||
@@ -2,3 +2,4 @@
|
|||||||
export * from "./core";
|
export * from "./core";
|
||||||
export * from "./state-migrations";
|
export * from "./state-migrations";
|
||||||
export * from "./types/state";
|
export * from "./types/state";
|
||||||
|
export * from "./legacy";
|
||||||
|
|||||||
107
libs/state/src/legacy/default-state.service.ts
Normal file
107
libs/state/src/legacy/default-state.service.ts
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
import { firstValueFrom } from "rxjs";
|
||||||
|
|
||||||
|
import { StorageService } from "@bitwarden/storage-core";
|
||||||
|
import { UserId } from "@bitwarden/user-core";
|
||||||
|
|
||||||
|
import { ActiveUserAccessor } from "../core";
|
||||||
|
|
||||||
|
import { GlobalState } from "./global-state";
|
||||||
|
import { RequiredUserId, StateService } from "./state.service";
|
||||||
|
|
||||||
|
const keys = {
|
||||||
|
global: "global",
|
||||||
|
};
|
||||||
|
|
||||||
|
const partialKeys = {
|
||||||
|
userAutoKey: "_user_auto",
|
||||||
|
userBiometricKey: "_user_biometric",
|
||||||
|
};
|
||||||
|
|
||||||
|
const DDG_SHARED_KEY = "DuckDuckGoSharedKey";
|
||||||
|
|
||||||
|
export class DefaultStateService implements StateService {
|
||||||
|
constructor(
|
||||||
|
private readonly storageService: StorageService,
|
||||||
|
private readonly secureStorageService: StorageService,
|
||||||
|
private readonly activeUserAccessor: ActiveUserAccessor,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async clean(options: RequiredUserId): Promise<void> {
|
||||||
|
await this.setUserKeyAutoUnlock(null, options);
|
||||||
|
await this.clearUserKeyBiometric(options.userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* user key when using the "never" option of vault timeout
|
||||||
|
*/
|
||||||
|
async getUserKeyAutoUnlock(options: RequiredUserId): Promise<string | null> {
|
||||||
|
if (options.userId == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return await this.secureStorageService.get<string>(
|
||||||
|
`${options.userId}${partialKeys.userAutoKey}`,
|
||||||
|
{
|
||||||
|
userId: options.userId,
|
||||||
|
keySuffix: "auto",
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* user key when using the "never" option of vault timeout
|
||||||
|
*/
|
||||||
|
async setUserKeyAutoUnlock(value: string | null, options: RequiredUserId): Promise<void> {
|
||||||
|
if (options.userId == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await this.saveSecureStorageKey(partialKeys.userAutoKey, value, options.userId, "auto");
|
||||||
|
}
|
||||||
|
|
||||||
|
private async clearUserKeyBiometric(userId: UserId): Promise<void> {
|
||||||
|
if (userId == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await this.saveSecureStorageKey(partialKeys.userBiometricKey, null, userId, "biometric");
|
||||||
|
}
|
||||||
|
|
||||||
|
async getDuckDuckGoSharedKey(): Promise<string | null> {
|
||||||
|
const userId = await this.getActiveUserIdFromStorage();
|
||||||
|
if (userId == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return await this.secureStorageService.get<string>(DDG_SHARED_KEY);
|
||||||
|
}
|
||||||
|
|
||||||
|
async setDuckDuckGoSharedKey(value: string): Promise<void> {
|
||||||
|
const userId = await this.getActiveUserIdFromStorage();
|
||||||
|
if (userId == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
value == null
|
||||||
|
? await this.secureStorageService.remove(DDG_SHARED_KEY)
|
||||||
|
: await this.secureStorageService.save(DDG_SHARED_KEY, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
async setEnableDuckDuckGoBrowserIntegration(value: boolean): Promise<void> {
|
||||||
|
const globals = (await this.storageService.get<GlobalState>(keys.global)) ?? new GlobalState();
|
||||||
|
globals.enableDuckDuckGoBrowserIntegration = value;
|
||||||
|
await this.storageService.save(keys.global, globals);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getActiveUserIdFromStorage(): Promise<UserId | null> {
|
||||||
|
return await firstValueFrom(this.activeUserAccessor.activeUserId$);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async saveSecureStorageKey(
|
||||||
|
key: string,
|
||||||
|
value: string | null,
|
||||||
|
userId: UserId,
|
||||||
|
keySuffix: string,
|
||||||
|
) {
|
||||||
|
return value == null
|
||||||
|
? await this.secureStorageService.remove(`${userId}${key}`, { keySuffix: keySuffix })
|
||||||
|
: await this.secureStorageService.save(`${userId}${key}`, value, {
|
||||||
|
keySuffix: keySuffix,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
2
libs/state/src/legacy/index.ts
Normal file
2
libs/state/src/legacy/index.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export { StateService } from "./state.service";
|
||||||
|
export { DefaultStateService } from "./default-state.service";
|
||||||
25
libs/state/src/legacy/state.service.ts
Normal file
25
libs/state/src/legacy/state.service.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import { UserId } from "@bitwarden/user-core";
|
||||||
|
|
||||||
|
export type RequiredUserId = { userId: UserId };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class exists for various legacy reasons, there are likely better things to use than this service.
|
||||||
|
*/
|
||||||
|
export abstract class StateService {
|
||||||
|
abstract clean(options: RequiredUserId): Promise<void>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the user's auto key
|
||||||
|
*/
|
||||||
|
abstract getUserKeyAutoUnlock(options: RequiredUserId): Promise<string | null>;
|
||||||
|
/**
|
||||||
|
* Sets the user's auto key
|
||||||
|
*/
|
||||||
|
abstract setUserKeyAutoUnlock(value: string | null, options: RequiredUserId): Promise<void>;
|
||||||
|
/**
|
||||||
|
* @deprecated For backwards compatible purposes only, use DesktopAutofillSettingsService
|
||||||
|
*/
|
||||||
|
abstract setEnableDuckDuckGoBrowserIntegration(value: boolean): Promise<void>;
|
||||||
|
abstract getDuckDuckGoSharedKey(): Promise<string | null>;
|
||||||
|
abstract setDuckDuckGoSharedKey(value: string): Promise<void>;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user