mirror of
https://github.com/bitwarden/browser
synced 2025-12-14 23:33:31 +00:00
[PM-5535] Migrate Environment Service to StateProvider (#7621)
* Migrate EnvironmentService * Move Migration Test Helper * Claim StateDefinition * Add State Migration * Update StateServices * Update EnvironmentService Abstraction * Update DI * Update Browser Instantiation * Fix BrowserEnvironmentService * Update Desktop & CLI Instantiation * Update Usage * Create isStringRecord helper * Fix Old Tests * Use Existing AccountService * Don't Rely on Parameter Mutation * Fix Conflicts
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
import { Observable } from "rxjs";
|
||||
|
||||
import { UserId } from "../../types/guid";
|
||||
|
||||
export type Urls = {
|
||||
base?: string;
|
||||
webVault?: string;
|
||||
@@ -52,6 +54,12 @@ export abstract class EnvironmentService {
|
||||
* @param {Region} region - The region of the cloud web vault app.
|
||||
*/
|
||||
setCloudWebVaultUrl: (region: Region) => void;
|
||||
|
||||
/**
|
||||
* Seed the environment for a given user based on the globally set defaults.
|
||||
*/
|
||||
seedUserEnvironment: (userId: UserId) => Promise<void>;
|
||||
|
||||
getSendUrl: () => string;
|
||||
getIconsUrl: () => string;
|
||||
getApiUrl: () => string;
|
||||
|
||||
@@ -5,7 +5,6 @@ import { PolicyData } from "../../admin-console/models/data/policy.data";
|
||||
import { ProviderData } from "../../admin-console/models/data/provider.data";
|
||||
import { Policy } from "../../admin-console/models/domain/policy";
|
||||
import { AdminAuthRequestStorable } from "../../auth/models/domain/admin-auth-req-storable";
|
||||
import { EnvironmentUrls } from "../../auth/models/domain/environment-urls";
|
||||
import { ForceSetPasswordReason } from "../../auth/models/domain/force-set-password-reason";
|
||||
import { KdfConfig } from "../../auth/models/domain/kdf-config";
|
||||
import { BiometricKey } from "../../auth/types/biometric-key";
|
||||
@@ -378,10 +377,6 @@ export abstract class StateService<T extends Account = Account> {
|
||||
setEntityId: (value: string, options?: StorageOptions) => Promise<void>;
|
||||
getEntityType: (options?: StorageOptions) => Promise<any>;
|
||||
setEntityType: (value: string, options?: StorageOptions) => Promise<void>;
|
||||
getEnvironmentUrls: (options?: StorageOptions) => Promise<EnvironmentUrls>;
|
||||
setEnvironmentUrls: (value: EnvironmentUrls, options?: StorageOptions) => Promise<void>;
|
||||
getRegion: (options?: StorageOptions) => Promise<string>;
|
||||
setRegion: (value: string, options?: StorageOptions) => Promise<void>;
|
||||
getEquivalentDomains: (options?: StorageOptions) => Promise<string[][]>;
|
||||
setEquivalentDomains: (value: string, options?: StorageOptions) => Promise<void>;
|
||||
getEventCollection: (options?: StorageOptions) => Promise<EventData[]>;
|
||||
|
||||
@@ -5,7 +5,6 @@ import { PolicyData } from "../../../admin-console/models/data/policy.data";
|
||||
import { ProviderData } from "../../../admin-console/models/data/provider.data";
|
||||
import { Policy } from "../../../admin-console/models/domain/policy";
|
||||
import { AdminAuthRequestStorable } from "../../../auth/models/domain/admin-auth-req-storable";
|
||||
import { EnvironmentUrls } from "../../../auth/models/domain/environment-urls";
|
||||
import { ForceSetPasswordReason } from "../../../auth/models/domain/force-set-password-reason";
|
||||
import { KeyConnectorUserDecryptionOption } from "../../../auth/models/domain/user-decryption-options/key-connector-user-decryption-option";
|
||||
import { TrustedDeviceUserDecryptionOption } from "../../../auth/models/domain/user-decryption-options/trusted-device-user-decryption-option";
|
||||
@@ -239,7 +238,6 @@ export class AccountSettings {
|
||||
enableAutoFillOnPageLoad?: boolean;
|
||||
enableBiometric?: boolean;
|
||||
enableFullWidth?: boolean;
|
||||
environmentUrls: EnvironmentUrls = new EnvironmentUrls();
|
||||
equivalentDomains?: any;
|
||||
minimizeOnCopyToClipboard?: boolean;
|
||||
passwordGenerationOptions?: PasswordGeneratorOptions;
|
||||
@@ -255,7 +253,6 @@ export class AccountSettings {
|
||||
approveLoginRequests?: boolean;
|
||||
avatarColor?: string;
|
||||
activateAutoFillOnPageLoadFromPolicy?: boolean;
|
||||
region?: string;
|
||||
smOnboardingTasks?: Record<string, Record<string, boolean>>;
|
||||
trustDeviceChoiceForDecryption?: boolean;
|
||||
biometricPromptCancelled?: boolean;
|
||||
@@ -269,7 +266,6 @@ export class AccountSettings {
|
||||
}
|
||||
|
||||
return Object.assign(new AccountSettings(), obj, {
|
||||
environmentUrls: EnvironmentUrls.fromJSON(obj?.environmentUrls),
|
||||
pinProtected: EncryptionPair.fromJSON<string, EncString>(
|
||||
obj?.pinProtected,
|
||||
EncString.fromJSON,
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { EnvironmentUrls } from "../../../auth/models/domain/environment-urls";
|
||||
import { WindowState } from "../../../models/domain/window-state";
|
||||
import { ThemeType } from "../../enums";
|
||||
|
||||
@@ -24,7 +23,6 @@ export class GlobalState {
|
||||
enableBiometrics?: boolean;
|
||||
biometricText?: string;
|
||||
noAutoPromptBiometricsText?: string;
|
||||
environmentUrls: EnvironmentUrls = new EnvironmentUrls();
|
||||
enableTray?: boolean;
|
||||
enableMinimizeToTray?: boolean;
|
||||
enableCloseToTray?: boolean;
|
||||
@@ -34,7 +32,6 @@ export class GlobalState {
|
||||
enableBrowserIntegration?: boolean;
|
||||
enableBrowserIntegrationFingerprint?: boolean;
|
||||
enableDuckDuckGoBrowserIntegration?: boolean;
|
||||
region?: string;
|
||||
neverDomains?: { [id: string]: unknown };
|
||||
enablePasskeys?: boolean;
|
||||
disableAddLoginNotification?: boolean;
|
||||
|
||||
@@ -1,17 +1,22 @@
|
||||
import { mock } from "jest-mock-extended";
|
||||
import { firstValueFrom, timeout } from "rxjs";
|
||||
|
||||
import { awaitAsync } from "../../../spec";
|
||||
import { FakeAccountService, mockAccountServiceWith } from "../../../spec/fake-account-service";
|
||||
import { FakeStorageService } from "../../../spec/fake-storage.service";
|
||||
import { AuthenticationStatus } from "../../auth/enums/authentication-status";
|
||||
import { EnvironmentUrls } from "../../auth/models/domain/environment-urls";
|
||||
import { UserId } from "../../types/guid";
|
||||
import { Region } from "../abstractions/environment.service";
|
||||
import { StateFactory } from "../factories/state-factory";
|
||||
import { Account } from "../models/domain/account";
|
||||
import { GlobalState } from "../models/domain/global-state";
|
||||
import { StateProvider } from "../state";
|
||||
/* eslint-disable import/no-restricted-paths -- Rare testing need */
|
||||
import { DefaultActiveUserStateProvider } from "../state/implementations/default-active-user-state.provider";
|
||||
import { DefaultDerivedStateProvider } from "../state/implementations/default-derived-state.provider";
|
||||
import { DefaultGlobalStateProvider } from "../state/implementations/default-global-state.provider";
|
||||
import { DefaultSingleUserStateProvider } from "../state/implementations/default-single-user-state.provider";
|
||||
import { DefaultStateProvider } from "../state/implementations/default-state.provider";
|
||||
/* eslint-disable import/no-restricted-paths */
|
||||
|
||||
import { EnvironmentService } from "./environment.service";
|
||||
import { StateService } from "./state.service";
|
||||
|
||||
// There are a few main states EnvironmentService could be in when first used
|
||||
// 1. Not initialized, no active user. Hopefully not to likely but possible
|
||||
@@ -21,51 +26,55 @@ import { StateService } from "./state.service";
|
||||
describe("EnvironmentService", () => {
|
||||
let diskStorageService: FakeStorageService;
|
||||
let memoryStorageService: FakeStorageService;
|
||||
let stateService: StateService;
|
||||
let accountService: FakeAccountService;
|
||||
let stateProvider: StateProvider;
|
||||
|
||||
let sut: EnvironmentService;
|
||||
|
||||
const testUser = "testUser1" as UserId;
|
||||
const testUser = "00000000-0000-1000-a000-000000000001" as UserId;
|
||||
const alternateTestUser = "00000000-0000-1000-a000-000000000002" as UserId;
|
||||
|
||||
// START: CAN CHANGE - When implementing new storage locations, the following code can change. But not tests
|
||||
beforeEach(async () => {
|
||||
diskStorageService = new FakeStorageService();
|
||||
memoryStorageService = new FakeStorageService();
|
||||
stateService = new StateService(
|
||||
diskStorageService,
|
||||
null,
|
||||
memoryStorageService as any,
|
||||
mock(),
|
||||
new StateFactory<GlobalState, Account>(GlobalState, Account),
|
||||
mock(),
|
||||
false,
|
||||
|
||||
accountService = mockAccountServiceWith(undefined);
|
||||
stateProvider = new DefaultStateProvider(
|
||||
new DefaultActiveUserStateProvider(
|
||||
accountService,
|
||||
memoryStorageService as any,
|
||||
diskStorageService,
|
||||
),
|
||||
new DefaultSingleUserStateProvider(memoryStorageService as any, diskStorageService),
|
||||
new DefaultGlobalStateProvider(memoryStorageService as any, diskStorageService),
|
||||
new DefaultDerivedStateProvider(memoryStorageService),
|
||||
);
|
||||
|
||||
sut = new EnvironmentService(stateService);
|
||||
sut = new EnvironmentService(stateProvider, accountService);
|
||||
});
|
||||
|
||||
const switchUser = async (userId: UserId) => {
|
||||
await stateService.setActiveUser(userId);
|
||||
accountService.activeAccountSubject.next({
|
||||
id: userId,
|
||||
email: "test@example.com",
|
||||
name: `Test Name ${userId}`,
|
||||
status: AuthenticationStatus.Unlocked,
|
||||
});
|
||||
await awaitAsync();
|
||||
};
|
||||
|
||||
const setGlobalData = (region: Region, environmentUrls: EnvironmentUrls) => {
|
||||
diskStorageService.internalUpdateStore({
|
||||
...diskStorageService.internalStore,
|
||||
global: {
|
||||
region: region,
|
||||
environmentUrls: environmentUrls,
|
||||
},
|
||||
});
|
||||
const data = diskStorageService.internalStore;
|
||||
data["global_environment_region"] = region;
|
||||
data["global_environment_urls"] = environmentUrls;
|
||||
diskStorageService.internalUpdateStore(data);
|
||||
};
|
||||
|
||||
const getGlobalData = () => {
|
||||
const storage = diskStorageService.internalStore as {
|
||||
global?: { region?: Region; environmentUrls?: EnvironmentUrls };
|
||||
};
|
||||
const storage = diskStorageService.internalStore;
|
||||
return {
|
||||
region: storage?.global?.region,
|
||||
urls: storage?.global?.environmentUrls,
|
||||
region: storage?.["global_environment_region"],
|
||||
urls: storage?.["global_environment_urls"],
|
||||
};
|
||||
};
|
||||
|
||||
@@ -74,22 +83,15 @@ describe("EnvironmentService", () => {
|
||||
environmentUrls: EnvironmentUrls,
|
||||
userId: UserId = testUser,
|
||||
) => {
|
||||
const data = { ...diskStorageService.internalStore };
|
||||
const userData = {
|
||||
settings: {
|
||||
region: region,
|
||||
environmentUrls: environmentUrls,
|
||||
},
|
||||
};
|
||||
data[userId] = userData;
|
||||
const data = diskStorageService.internalStore;
|
||||
data[`user_${userId}_environment_region`] = region;
|
||||
data[`user_${userId}_environment_urls`] = environmentUrls;
|
||||
|
||||
diskStorageService.internalUpdateStore(data);
|
||||
};
|
||||
// END: CAN CHANGE
|
||||
|
||||
const initialize = async (options: { switchUser: boolean }) => {
|
||||
// This emulates the way EnvironmentService is initialized in each of our clients
|
||||
await stateService.init();
|
||||
await sut.setUrlsFromStorage();
|
||||
sut.initialized = true;
|
||||
|
||||
@@ -292,7 +294,7 @@ describe("EnvironmentService", () => {
|
||||
});
|
||||
|
||||
const globalData = getGlobalData();
|
||||
expect(globalData.region).toBe("Self-hosted");
|
||||
expect(globalData.region).toBe(Region.SelfHosted);
|
||||
expect(globalData.urls).toEqual({
|
||||
base: "https://base.example.com",
|
||||
api: null,
|
||||
@@ -321,7 +323,7 @@ describe("EnvironmentService", () => {
|
||||
});
|
||||
|
||||
const globalData = getGlobalData();
|
||||
expect(globalData.region).toBe("Self-hosted");
|
||||
expect(globalData.region).toBe(Region.SelfHosted);
|
||||
expect(globalData.urls).toEqual({
|
||||
base: "https://base.example.com",
|
||||
api: "https://api.example.com",
|
||||
@@ -399,15 +401,13 @@ describe("EnvironmentService", () => {
|
||||
])(
|
||||
"gets it from the passed in userId if there is any active user: %s",
|
||||
async ({ region, expectedHost }) => {
|
||||
const otherUser = "testUser2" as UserId;
|
||||
|
||||
setGlobalData(Region.US, new EnvironmentUrls());
|
||||
setUserData(Region.US, new EnvironmentUrls());
|
||||
setUserData(region, new EnvironmentUrls(), otherUser);
|
||||
setUserData(region, new EnvironmentUrls(), alternateTestUser);
|
||||
|
||||
await initialize({ switchUser: true });
|
||||
|
||||
const host = await sut.getHost(otherUser);
|
||||
const host = await sut.getHost(alternateTestUser);
|
||||
expect(host).toBe(expectedHost);
|
||||
},
|
||||
);
|
||||
@@ -438,17 +438,16 @@ describe("EnvironmentService", () => {
|
||||
});
|
||||
|
||||
it("gets it from saved self host config from passed in user when there is an active user", async () => {
|
||||
const otherUser = "testUser2" as UserId;
|
||||
setGlobalData(Region.US, new EnvironmentUrls());
|
||||
setUserData(Region.EU, new EnvironmentUrls());
|
||||
|
||||
const selfHostUserUrls = new EnvironmentUrls();
|
||||
selfHostUserUrls.base = "https://base.example.com";
|
||||
setUserData(Region.SelfHosted, selfHostUserUrls, otherUser);
|
||||
setUserData(Region.SelfHosted, selfHostUserUrls, alternateTestUser);
|
||||
|
||||
await initialize({ switchUser: true });
|
||||
|
||||
const host = await sut.getHost(otherUser);
|
||||
const host = await sut.getHost(alternateTestUser);
|
||||
expect(host).toBe("base.example.com");
|
||||
});
|
||||
});
|
||||
@@ -498,7 +497,6 @@ describe("EnvironmentService", () => {
|
||||
});
|
||||
|
||||
it("will get urls from signed in user", async () => {
|
||||
await stateService.init();
|
||||
await switchUser(testUser);
|
||||
|
||||
const userUrls = new EnvironmentUrls();
|
||||
|
||||
@@ -1,14 +1,31 @@
|
||||
import { concatMap, distinctUntilChanged, Observable, ReplaySubject } from "rxjs";
|
||||
import {
|
||||
concatMap,
|
||||
distinctUntilChanged,
|
||||
firstValueFrom,
|
||||
map,
|
||||
Observable,
|
||||
ReplaySubject,
|
||||
} from "rxjs";
|
||||
|
||||
import { AccountService } from "../../auth/abstractions/account.service";
|
||||
import { EnvironmentUrls } from "../../auth/models/domain/environment-urls";
|
||||
import { UserId } from "../../types/guid";
|
||||
import {
|
||||
EnvironmentService as EnvironmentServiceAbstraction,
|
||||
Region,
|
||||
RegionDomain,
|
||||
Urls,
|
||||
} from "../abstractions/environment.service";
|
||||
import { StateService } from "../abstractions/state.service";
|
||||
import { Utils } from "../misc/utils";
|
||||
import { ENVIRONMENT_DISK, GlobalState, KeyDefinition, StateProvider } from "../state";
|
||||
|
||||
const REGION_KEY = new KeyDefinition<Region>(ENVIRONMENT_DISK, "region", {
|
||||
deserializer: (s) => s,
|
||||
});
|
||||
|
||||
const URLS_KEY = new KeyDefinition<EnvironmentUrls>(ENVIRONMENT_DISK, "urls", {
|
||||
deserializer: EnvironmentUrls.fromJSON,
|
||||
});
|
||||
|
||||
export class EnvironmentService implements EnvironmentServiceAbstraction {
|
||||
private readonly urlsSubject = new ReplaySubject<void>(1);
|
||||
@@ -27,6 +44,11 @@ export class EnvironmentService implements EnvironmentServiceAbstraction {
|
||||
private scimUrl: string = null;
|
||||
private cloudWebVaultUrl: string;
|
||||
|
||||
private regionGlobalState: GlobalState<Region | null>;
|
||||
private urlsGlobalState: GlobalState<EnvironmentUrls | null>;
|
||||
|
||||
private activeAccountId$: Observable<UserId | null>;
|
||||
|
||||
readonly usUrls: Urls = {
|
||||
base: null,
|
||||
api: "https://api.bitwarden.com",
|
||||
@@ -49,8 +71,15 @@ export class EnvironmentService implements EnvironmentServiceAbstraction {
|
||||
scim: "https://scim.bitwarden.eu",
|
||||
};
|
||||
|
||||
constructor(private stateService: StateService) {
|
||||
this.stateService.activeAccount$
|
||||
constructor(
|
||||
private stateProvider: StateProvider,
|
||||
private accountService: AccountService,
|
||||
) {
|
||||
// We intentionally don't want the helper on account service, we want the null back if there is no active user
|
||||
this.activeAccountId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id));
|
||||
|
||||
// TODO: Get rid of early subscription during EnvironmentService refactor
|
||||
this.activeAccountId$
|
||||
.pipe(
|
||||
// Use == here to not trigger on undefined -> null transition
|
||||
distinctUntilChanged((oldUserId: string, newUserId: string) => oldUserId == newUserId),
|
||||
@@ -62,6 +91,9 @@ export class EnvironmentService implements EnvironmentServiceAbstraction {
|
||||
}),
|
||||
)
|
||||
.subscribe();
|
||||
|
||||
this.regionGlobalState = this.stateProvider.getGlobal(REGION_KEY);
|
||||
this.urlsGlobalState = this.stateProvider.getGlobal(URLS_KEY);
|
||||
}
|
||||
|
||||
hasBaseUrl() {
|
||||
@@ -180,8 +212,10 @@ export class EnvironmentService implements EnvironmentServiceAbstraction {
|
||||
}
|
||||
|
||||
async setUrlsFromStorage(): Promise<void> {
|
||||
const region = await this.stateService.getRegion();
|
||||
const savedUrls = await this.stateService.getEnvironmentUrls();
|
||||
const activeUserId = await firstValueFrom(this.activeAccountId$);
|
||||
|
||||
const region = await this.getRegion(activeUserId);
|
||||
const savedUrls = await this.getEnvironmentUrls(activeUserId);
|
||||
const envUrls = new EnvironmentUrls();
|
||||
|
||||
// In release `2023.5.0`, we set the `base` property of the environment URLs to the US web vault URL when a user clicked the "US" region.
|
||||
@@ -231,7 +265,8 @@ export class EnvironmentService implements EnvironmentServiceAbstraction {
|
||||
// scimUrl cannot be cleared
|
||||
urls.scim = this.formatUrl(urls.scim) ?? this.scimUrl;
|
||||
|
||||
await this.stateService.setEnvironmentUrls({
|
||||
// Don't save scim url
|
||||
await this.urlsGlobalState.update(() => ({
|
||||
base: urls.base,
|
||||
api: urls.api,
|
||||
identity: urls.identity,
|
||||
@@ -240,8 +275,7 @@ export class EnvironmentService implements EnvironmentServiceAbstraction {
|
||||
notifications: urls.notifications,
|
||||
events: urls.events,
|
||||
keyConnector: urls.keyConnector,
|
||||
// scimUrl is not saved to storage
|
||||
});
|
||||
}));
|
||||
|
||||
this.baseUrl = urls.base;
|
||||
this.webVaultUrl = urls.webVault;
|
||||
@@ -287,8 +321,8 @@ export class EnvironmentService implements EnvironmentServiceAbstraction {
|
||||
);
|
||||
}
|
||||
|
||||
async getHost(userId?: string) {
|
||||
const region = await this.getRegion(userId ? userId : null);
|
||||
async getHost(userId?: UserId) {
|
||||
const region = await this.getRegion(userId);
|
||||
|
||||
switch (region) {
|
||||
case Region.US:
|
||||
@@ -297,21 +331,30 @@ export class EnvironmentService implements EnvironmentServiceAbstraction {
|
||||
return RegionDomain.EU;
|
||||
default: {
|
||||
// Environment is self-hosted
|
||||
const envUrls = await this.stateService.getEnvironmentUrls(
|
||||
userId ? { userId: userId } : null,
|
||||
);
|
||||
const envUrls = await this.getEnvironmentUrls(userId);
|
||||
return Utils.getHost(envUrls.webVault || envUrls.base);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async getRegion(userId?: string) {
|
||||
return this.stateService.getRegion(userId ? { userId: userId } : null);
|
||||
private async getRegion(userId: UserId | null) {
|
||||
// Previous rules dictated that we only get from user scoped state if there is an active user.
|
||||
const activeUserId = await firstValueFrom(this.activeAccountId$);
|
||||
return activeUserId == null
|
||||
? await firstValueFrom(this.regionGlobalState.state$)
|
||||
: await firstValueFrom(this.stateProvider.getUser(userId ?? activeUserId, REGION_KEY).state$);
|
||||
}
|
||||
|
||||
private async getEnvironmentUrls(userId: UserId | null) {
|
||||
return userId == null
|
||||
? (await firstValueFrom(this.urlsGlobalState.state$)) ?? new EnvironmentUrls()
|
||||
: (await firstValueFrom(this.stateProvider.getUser(userId, URLS_KEY).state$)) ??
|
||||
new EnvironmentUrls();
|
||||
}
|
||||
|
||||
async setRegion(region: Region) {
|
||||
this.selectedRegion = region;
|
||||
await this.stateService.setRegion(region);
|
||||
await this.regionGlobalState.update(() => region);
|
||||
|
||||
if (region === Region.SelfHosted) {
|
||||
// If user saves a self-hosted region with empty fields, default to US
|
||||
@@ -320,7 +363,7 @@ export class EnvironmentService implements EnvironmentServiceAbstraction {
|
||||
}
|
||||
} else {
|
||||
// If we are setting the region to EU or US, clear the self-hosted URLs
|
||||
await this.stateService.setEnvironmentUrls(new EnvironmentUrls());
|
||||
await this.urlsGlobalState.update(() => new EnvironmentUrls());
|
||||
if (region === Region.EU) {
|
||||
this.setUrlsInternal(this.euUrls);
|
||||
} else if (region === Region.US) {
|
||||
@@ -329,6 +372,13 @@ export class EnvironmentService implements EnvironmentServiceAbstraction {
|
||||
}
|
||||
}
|
||||
|
||||
async seedUserEnvironment(userId: UserId) {
|
||||
const globalRegion = await firstValueFrom(this.regionGlobalState.state$);
|
||||
const globalUrls = await firstValueFrom(this.urlsGlobalState.state$);
|
||||
await this.stateProvider.getUser(userId, REGION_KEY).update(() => globalRegion);
|
||||
await this.stateProvider.getUser(userId, URLS_KEY).update(() => globalUrls);
|
||||
}
|
||||
|
||||
private setUrlsInternal(urls: Urls) {
|
||||
this.baseUrl = this.formatUrl(urls.base);
|
||||
this.webVaultUrl = this.formatUrl(urls.webVault);
|
||||
|
||||
@@ -9,7 +9,6 @@ import { Policy } from "../../admin-console/models/domain/policy";
|
||||
import { AccountService } from "../../auth/abstractions/account.service";
|
||||
import { AuthenticationStatus } from "../../auth/enums/authentication-status";
|
||||
import { AdminAuthRequestStorable } from "../../auth/models/domain/admin-auth-req-storable";
|
||||
import { EnvironmentUrls } from "../../auth/models/domain/environment-urls";
|
||||
import { ForceSetPasswordReason } from "../../auth/models/domain/force-set-password-reason";
|
||||
import { KdfConfig } from "../../auth/models/domain/kdf-config";
|
||||
import { BiometricKey } from "../../auth/types/biometric-key";
|
||||
@@ -32,6 +31,7 @@ import { LocalData } from "../../vault/models/data/local.data";
|
||||
import { CipherView } from "../../vault/models/view/cipher.view";
|
||||
import { CollectionView } from "../../vault/models/view/collection.view";
|
||||
import { AddEditCipherInfo } from "../../vault/types/add-edit-cipher-info";
|
||||
import { EnvironmentService } from "../abstractions/environment.service";
|
||||
import { LogService } from "../abstractions/log.service";
|
||||
import { StateService as StateServiceAbstraction } from "../abstractions/state.service";
|
||||
import {
|
||||
@@ -105,6 +105,7 @@ export class StateService<
|
||||
protected logService: LogService,
|
||||
protected stateFactory: StateFactory<TGlobalState, TAccount>,
|
||||
protected accountService: AccountService,
|
||||
protected environmentService: EnvironmentService,
|
||||
protected useAccountCache: boolean = true,
|
||||
) {
|
||||
// If the account gets changed, verify the new account is unlocked
|
||||
@@ -215,7 +216,7 @@ export class StateService<
|
||||
}
|
||||
|
||||
async addAccount(account: TAccount) {
|
||||
account = await this.setAccountEnvironment(account);
|
||||
await this.environmentService.seedUserEnvironment(account.profile.userId as UserId);
|
||||
await this.updateState(async (state) => {
|
||||
state.authenticatedAccounts.push(account.profile.userId);
|
||||
await this.storageService.save(keys.authenticatedAccounts, state.authenticatedAccounts);
|
||||
@@ -1983,49 +1984,6 @@ export class StateService<
|
||||
);
|
||||
}
|
||||
|
||||
async getEnvironmentUrls(options?: StorageOptions): Promise<EnvironmentUrls> {
|
||||
if ((await this.state())?.activeUserId == null) {
|
||||
return await this.getGlobalEnvironmentUrls(options);
|
||||
}
|
||||
options = this.reconcileOptions(options, await this.defaultOnDiskOptions());
|
||||
return (await this.getAccount(options))?.settings?.environmentUrls ?? new EnvironmentUrls();
|
||||
}
|
||||
|
||||
async setEnvironmentUrls(value: EnvironmentUrls, options?: StorageOptions): Promise<void> {
|
||||
// Global values are set on each change and the current global settings are passed to any newly authed accounts.
|
||||
// This is to allow setting environment values before an account is active, while still allowing individual accounts to have their own environments.
|
||||
const globals = await this.getGlobals(
|
||||
this.reconcileOptions(options, await this.defaultOnDiskOptions()),
|
||||
);
|
||||
globals.environmentUrls = value;
|
||||
await this.saveGlobals(
|
||||
globals,
|
||||
this.reconcileOptions(options, await this.defaultOnDiskOptions()),
|
||||
);
|
||||
}
|
||||
|
||||
async getRegion(options?: StorageOptions): Promise<string> {
|
||||
if ((await this.state())?.activeUserId == null) {
|
||||
options = this.reconcileOptions(options, await this.defaultOnDiskOptions());
|
||||
return (await this.getGlobals(options)).region ?? null;
|
||||
}
|
||||
options = this.reconcileOptions(options, await this.defaultOnDiskOptions());
|
||||
return (await this.getAccount(options))?.settings?.region ?? null;
|
||||
}
|
||||
|
||||
async setRegion(value: string, options?: StorageOptions): Promise<void> {
|
||||
// Global values are set on each change and the current global settings are passed to any newly authed accounts.
|
||||
// This is to allow setting region values before an account is active, while still allowing individual accounts to have their own region.
|
||||
const globals = await this.getGlobals(
|
||||
this.reconcileOptions(options, await this.defaultOnDiskOptions()),
|
||||
);
|
||||
globals.region = value;
|
||||
await this.saveGlobals(
|
||||
globals,
|
||||
this.reconcileOptions(options, await this.defaultOnDiskOptions()),
|
||||
);
|
||||
}
|
||||
|
||||
async getEquivalentDomains(options?: StorageOptions): Promise<string[][]> {
|
||||
return (
|
||||
await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions()))
|
||||
@@ -3032,17 +2990,12 @@ export class StateService<
|
||||
await this.defaultOnDiskLocalOptions(),
|
||||
),
|
||||
);
|
||||
// EnvironmentUrls and region are set before authenticating and should override whatever is stored from any previous session
|
||||
const environmentUrls = account.settings.environmentUrls;
|
||||
const region = account.settings.region;
|
||||
if (storedAccount?.settings != null) {
|
||||
account.settings = storedAccount.settings;
|
||||
} else if (await this.storageService.has(keys.tempAccountSettings)) {
|
||||
account.settings = await this.storageService.get<AccountSettings>(keys.tempAccountSettings);
|
||||
await this.storageService.remove(keys.tempAccountSettings);
|
||||
}
|
||||
account.settings.environmentUrls = environmentUrls;
|
||||
account.settings.region = region;
|
||||
|
||||
if (
|
||||
account.settings.vaultTimeoutAction === VaultTimeoutAction.LogOut &&
|
||||
@@ -3070,8 +3023,6 @@ export class StateService<
|
||||
),
|
||||
);
|
||||
if (storedAccount?.settings != null) {
|
||||
storedAccount.settings.environmentUrls = account.settings.environmentUrls;
|
||||
storedAccount.settings.region = account.settings.region;
|
||||
account.settings = storedAccount.settings;
|
||||
}
|
||||
await this.storageService.save(
|
||||
@@ -3093,8 +3044,6 @@ export class StateService<
|
||||
this.reconcileOptions({ userId: account.profile.userId }, await this.defaultOnDiskOptions()),
|
||||
);
|
||||
if (storedAccount?.settings != null) {
|
||||
storedAccount.settings.environmentUrls = account.settings.environmentUrls;
|
||||
storedAccount.settings.region = account.settings.region;
|
||||
account.settings = storedAccount.settings;
|
||||
}
|
||||
await this.storageService.save(
|
||||
@@ -3237,23 +3186,6 @@ export class StateService<
|
||||
return Object.assign(this.createAccount(), persistentAccountInformation);
|
||||
}
|
||||
|
||||
// The environment urls and region are selected before login and are transferred here to an authenticated account
|
||||
protected async setAccountEnvironment(account: TAccount): Promise<TAccount> {
|
||||
account.settings.region = await this.getGlobalRegion();
|
||||
account.settings.environmentUrls = await this.getGlobalEnvironmentUrls();
|
||||
return account;
|
||||
}
|
||||
|
||||
protected async getGlobalEnvironmentUrls(options?: StorageOptions): Promise<EnvironmentUrls> {
|
||||
options = this.reconcileOptions(options, await this.defaultOnDiskOptions());
|
||||
return (await this.getGlobals(options)).environmentUrls ?? new EnvironmentUrls();
|
||||
}
|
||||
|
||||
protected async getGlobalRegion(options?: StorageOptions): Promise<string> {
|
||||
options = this.reconcileOptions(options, await this.defaultOnDiskOptions());
|
||||
return (await this.getGlobals(options)).region ?? null;
|
||||
}
|
||||
|
||||
protected async clearDecryptedDataForActiveUser(): Promise<void> {
|
||||
await this.updateState(async (state) => {
|
||||
const userId = state?.activeUserId;
|
||||
|
||||
@@ -22,6 +22,7 @@ export const ACCOUNT_MEMORY = new StateDefinition("account", "memory");
|
||||
export const BILLING_BANNERS_DISK = new StateDefinition("billingBanners", "disk");
|
||||
|
||||
export const CRYPTO_DISK = new StateDefinition("crypto", "disk");
|
||||
export const ENVIRONMENT_DISK = new StateDefinition("environment", "disk");
|
||||
|
||||
export const GENERATOR_DISK = new StateDefinition("generator", "disk");
|
||||
export const GENERATOR_MEMORY = new StateDefinition("generator", "memory");
|
||||
|
||||
Reference in New Issue
Block a user