mirror of
https://github.com/bitwarden/browser
synced 2025-12-15 15:53:27 +00:00
Merge branch 'master' into feature/org-admin-refresh
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
import { ServerConfigResponse } from "@bitwarden/common/models/response/server-config-response";
|
||||
|
||||
export abstract class ConfigApiServiceAbstraction {
|
||||
get: () => Promise<ServerConfigResponse>;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import { Observable } from "rxjs";
|
||||
|
||||
import { ServerConfig } from "./server-config";
|
||||
|
||||
export abstract class ConfigServiceAbstraction {
|
||||
serverConfig$: Observable<ServerConfig | null>;
|
||||
}
|
||||
40
libs/common/src/abstractions/config/server-config.ts
Normal file
40
libs/common/src/abstractions/config/server-config.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import {
|
||||
ServerConfigData,
|
||||
ThirdPartyServerConfigData,
|
||||
EnvironmentServerConfigData,
|
||||
} from "@bitwarden/common/models/data/server-config.data";
|
||||
|
||||
const dayInMilliseconds = 24 * 3600 * 1000;
|
||||
const eighteenHoursInMilliseconds = 18 * 3600 * 1000;
|
||||
|
||||
export class ServerConfig {
|
||||
version: string;
|
||||
gitHash: string;
|
||||
server?: ThirdPartyServerConfigData;
|
||||
environment?: EnvironmentServerConfigData;
|
||||
utcDate: Date;
|
||||
|
||||
constructor(serverConfigData: ServerConfigData) {
|
||||
this.version = serverConfigData.version;
|
||||
this.gitHash = serverConfigData.gitHash;
|
||||
this.server = serverConfigData.server;
|
||||
this.utcDate = new Date(serverConfigData.utcDate);
|
||||
this.environment = serverConfigData.environment;
|
||||
|
||||
if (this.server?.name == null && this.server?.url == null) {
|
||||
this.server = null;
|
||||
}
|
||||
}
|
||||
|
||||
private getAgeInMilliseconds(): number {
|
||||
return new Date().getTime() - this.utcDate?.getTime();
|
||||
}
|
||||
|
||||
isValid(): boolean {
|
||||
return this.getAgeInMilliseconds() <= dayInMilliseconds;
|
||||
}
|
||||
|
||||
expiresSoon(): boolean {
|
||||
return this.getAgeInMilliseconds() >= eighteenHoursInMilliseconds;
|
||||
}
|
||||
}
|
||||
@@ -33,4 +33,5 @@ export abstract class EnvironmentService {
|
||||
setUrlsFromStorage: () => Promise<void>;
|
||||
setUrls: (urls: Urls) => Promise<Urls>;
|
||||
getUrls: () => Urls;
|
||||
isCloud: () => boolean;
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import { OrganizationData } from "../models/data/organizationData";
|
||||
import { PolicyData } from "../models/data/policyData";
|
||||
import { ProviderData } from "../models/data/providerData";
|
||||
import { SendData } from "../models/data/sendData";
|
||||
import { ServerConfigData } from "../models/data/server-config.data";
|
||||
import { Account, AccountSettingsSettings } from "../models/domain/account";
|
||||
import { EncString } from "../models/domain/encString";
|
||||
import { EnvironmentUrls } from "../models/domain/environmentUrls";
|
||||
@@ -319,4 +320,12 @@ export abstract class StateService<T extends Account = Account> {
|
||||
setStateVersion: (value: number) => Promise<void>;
|
||||
getWindow: () => Promise<WindowState>;
|
||||
setWindow: (value: WindowState) => Promise<void>;
|
||||
/**
|
||||
* @deprecated Do not call this directly, use ConfigService
|
||||
*/
|
||||
getServerConfig: (options?: StorageOptions) => Promise<ServerConfigData>;
|
||||
/**
|
||||
* @deprecated Do not call this directly, use ConfigService
|
||||
*/
|
||||
setServerConfig: (value: ServerConfigData, options?: StorageOptions) => Promise<void>;
|
||||
}
|
||||
|
||||
51
libs/common/src/models/data/server-config.data.ts
Normal file
51
libs/common/src/models/data/server-config.data.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import {
|
||||
ServerConfigResponse,
|
||||
ThirdPartyServerConfigResponse,
|
||||
EnvironmentServerConfigResponse,
|
||||
} from "../response/server-config-response";
|
||||
|
||||
export class ServerConfigData {
|
||||
version: string;
|
||||
gitHash: string;
|
||||
server?: ThirdPartyServerConfigData;
|
||||
environment?: EnvironmentServerConfigData;
|
||||
utcDate: string;
|
||||
|
||||
constructor(serverConfigReponse: ServerConfigResponse) {
|
||||
this.version = serverConfigReponse?.version;
|
||||
this.gitHash = serverConfigReponse?.gitHash;
|
||||
this.server = serverConfigReponse?.server
|
||||
? new ThirdPartyServerConfigData(serverConfigReponse.server)
|
||||
: null;
|
||||
this.utcDate = new Date().toISOString();
|
||||
this.environment = serverConfigReponse?.environment
|
||||
? new EnvironmentServerConfigData(serverConfigReponse.environment)
|
||||
: null;
|
||||
}
|
||||
}
|
||||
|
||||
export class ThirdPartyServerConfigData {
|
||||
name: string;
|
||||
url: string;
|
||||
|
||||
constructor(response: ThirdPartyServerConfigResponse) {
|
||||
this.name = response.name;
|
||||
this.url = response.url;
|
||||
}
|
||||
}
|
||||
|
||||
export class EnvironmentServerConfigData {
|
||||
vault: string;
|
||||
api: string;
|
||||
identity: string;
|
||||
notifications: string;
|
||||
sso: string;
|
||||
|
||||
constructor(response: EnvironmentServerConfigResponse) {
|
||||
this.vault = response.vault;
|
||||
this.api = response.api;
|
||||
this.identity = response.identity;
|
||||
this.notifications = response.notifications;
|
||||
this.sso = response.sso;
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import { OrganizationData } from "../data/organizationData";
|
||||
import { PolicyData } from "../data/policyData";
|
||||
import { ProviderData } from "../data/providerData";
|
||||
import { SendData } from "../data/sendData";
|
||||
import { ServerConfigData } from "../data/server-config.data";
|
||||
import { CipherView } from "../view/cipherView";
|
||||
import { CollectionView } from "../view/collectionView";
|
||||
import { SendView } from "../view/sendView";
|
||||
@@ -140,6 +141,7 @@ export class AccountSettings {
|
||||
settings?: AccountSettingsSettings; // TODO: Merge whatever is going on here into the AccountSettings model properly
|
||||
vaultTimeout?: number;
|
||||
vaultTimeoutAction?: string = "lock";
|
||||
serverConfig?: ServerConfigData;
|
||||
}
|
||||
|
||||
export type AccountSettingsSettings = {
|
||||
|
||||
59
libs/common/src/models/response/server-config-response.ts
Normal file
59
libs/common/src/models/response/server-config-response.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import { BaseResponse } from "./baseResponse";
|
||||
|
||||
export class ServerConfigResponse extends BaseResponse {
|
||||
version: string;
|
||||
gitHash: string;
|
||||
server: ThirdPartyServerConfigResponse;
|
||||
environment: EnvironmentServerConfigResponse;
|
||||
|
||||
constructor(response: any) {
|
||||
super(response);
|
||||
|
||||
if (response == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.version = this.getResponseProperty("Version");
|
||||
this.gitHash = this.getResponseProperty("GitHash");
|
||||
this.server = new ThirdPartyServerConfigResponse(this.getResponseProperty("Server"));
|
||||
this.environment = new EnvironmentServerConfigResponse(this.getResponseProperty("Environment"));
|
||||
}
|
||||
}
|
||||
|
||||
export class EnvironmentServerConfigResponse extends BaseResponse {
|
||||
vault: string;
|
||||
api: string;
|
||||
identity: string;
|
||||
notifications: string;
|
||||
sso: string;
|
||||
|
||||
constructor(data: any = null) {
|
||||
super(data);
|
||||
|
||||
if (data == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.vault = this.getResponseProperty("Vault");
|
||||
this.api = this.getResponseProperty("Api");
|
||||
this.identity = this.getResponseProperty("Identity");
|
||||
this.notifications = this.getResponseProperty("Notifications");
|
||||
this.sso = this.getResponseProperty("Sso");
|
||||
}
|
||||
}
|
||||
|
||||
export class ThirdPartyServerConfigResponse extends BaseResponse {
|
||||
name: string;
|
||||
url: string;
|
||||
|
||||
constructor(data: any = null) {
|
||||
super(data);
|
||||
|
||||
if (data == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.name = this.getResponseProperty("Name");
|
||||
this.url = this.getResponseProperty("Url");
|
||||
}
|
||||
}
|
||||
12
libs/common/src/services/config/config-api.service.ts
Normal file
12
libs/common/src/services/config/config-api.service.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { ConfigApiServiceAbstraction as ConfigApiServiceAbstraction } from "@bitwarden/common/abstractions/config/config-api.service.abstraction";
|
||||
import { ServerConfigResponse } from "@bitwarden/common/models/response/server-config-response";
|
||||
|
||||
export class ConfigApiService implements ConfigApiServiceAbstraction {
|
||||
constructor(private apiService: ApiService) {}
|
||||
|
||||
async get(): Promise<ServerConfigResponse> {
|
||||
const r = await this.apiService.send("GET", "/config", null, true, true);
|
||||
return new ServerConfigResponse(r);
|
||||
}
|
||||
}
|
||||
61
libs/common/src/services/config/config.service.ts
Normal file
61
libs/common/src/services/config/config.service.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import { BehaviorSubject, concatMap, map, switchMap, timer, EMPTY } from "rxjs";
|
||||
|
||||
import { ServerConfigData } from "@bitwarden/common/models/data/server-config.data";
|
||||
|
||||
import { ConfigApiServiceAbstraction } from "../../abstractions/config/config-api.service.abstraction";
|
||||
import { ConfigServiceAbstraction } from "../../abstractions/config/config.service.abstraction";
|
||||
import { ServerConfig } from "../../abstractions/config/server-config";
|
||||
import { StateService } from "../../abstractions/state.service";
|
||||
|
||||
export class ConfigService implements ConfigServiceAbstraction {
|
||||
private _serverConfig = new BehaviorSubject<ServerConfig | null>(null);
|
||||
serverConfig$ = this._serverConfig.asObservable();
|
||||
|
||||
constructor(
|
||||
private stateService: StateService,
|
||||
private configApiService: ConfigApiServiceAbstraction
|
||||
) {
|
||||
this.stateService.activeAccountUnlocked$
|
||||
.pipe(
|
||||
switchMap((unlocked) => {
|
||||
if (!unlocked) {
|
||||
this._serverConfig.next(null);
|
||||
return EMPTY;
|
||||
}
|
||||
|
||||
// Re-fetch the server config every hour
|
||||
return timer(0, 3600 * 1000).pipe(map(() => unlocked));
|
||||
}),
|
||||
concatMap(async (unlocked) => {
|
||||
return unlocked ? await this.buildServerConfig() : null;
|
||||
})
|
||||
)
|
||||
.subscribe((serverConfig) => {
|
||||
this._serverConfig.next(serverConfig);
|
||||
});
|
||||
}
|
||||
|
||||
private async buildServerConfig(): Promise<ServerConfig> {
|
||||
const data = await this.stateService.getServerConfig();
|
||||
const domain = data ? new ServerConfig(data) : null;
|
||||
|
||||
if (domain == null || !domain.isValid() || domain.expiresSoon()) {
|
||||
const value = await this.fetchServerConfig();
|
||||
return value ?? domain;
|
||||
}
|
||||
|
||||
return domain;
|
||||
}
|
||||
|
||||
private async fetchServerConfig(): Promise<ServerConfig> {
|
||||
const response = await this.configApiService.get();
|
||||
const data = new ServerConfigData(response);
|
||||
|
||||
if (data != null) {
|
||||
await this.stateService.setServerConfig(data);
|
||||
return new ServerConfig(data);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -207,4 +207,10 @@ export class EnvironmentService implements EnvironmentServiceAbstraction {
|
||||
|
||||
return url.trim();
|
||||
}
|
||||
|
||||
isCloud(): boolean {
|
||||
return ["https://api.bitwarden.com", "https://vault.bitwarden.com/api"].includes(
|
||||
this.getApiUrl()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import { EventType } from "@bitwarden/common/enums/eventType";
|
||||
* If you want to use this, don't.
|
||||
* If you think you should use that after the warning, don't.
|
||||
*/
|
||||
export default class NoOpEventService implements EventService {
|
||||
export class NoopEventService implements EventService {
|
||||
constructor() {
|
||||
if (chrome.runtime.getManifest().manifest_version !== 3) {
|
||||
throw new Error("You are not allowed to use this when not in manifest_version 3");
|
||||
|
||||
@@ -21,6 +21,7 @@ import { OrganizationData } from "../models/data/organizationData";
|
||||
import { PolicyData } from "../models/data/policyData";
|
||||
import { ProviderData } from "../models/data/providerData";
|
||||
import { SendData } from "../models/data/sendData";
|
||||
import { ServerConfigData } from "../models/data/server-config.data";
|
||||
import {
|
||||
Account,
|
||||
AccountData,
|
||||
@@ -2277,6 +2278,23 @@ export class StateService<
|
||||
);
|
||||
}
|
||||
|
||||
async setServerConfig(value: ServerConfigData, options?: StorageOptions): Promise<void> {
|
||||
const account = await this.getAccount(
|
||||
this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())
|
||||
);
|
||||
account.settings.serverConfig = value;
|
||||
return await this.saveAccount(
|
||||
account,
|
||||
this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())
|
||||
);
|
||||
}
|
||||
|
||||
async getServerConfig(options: StorageOptions): Promise<ServerConfigData> {
|
||||
return (
|
||||
await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()))
|
||||
)?.settings?.serverConfig;
|
||||
}
|
||||
|
||||
protected async getGlobals(options: StorageOptions): Promise<TGlobalState> {
|
||||
let globals: TGlobalState;
|
||||
if (this.useMemory(options.storageLocation)) {
|
||||
|
||||
Reference in New Issue
Block a user