mirror of
https://github.com/bitwarden/browser.git
synced 2024-11-22 11:45:59 +01:00
SM-90: Add Server Version to Browser About Page (#3223)
* Add structure to display server version on browser * Add getConfig to State Service interface * Clean up settings component code * Switch to ServerConfig, use Observables in the ConfigService, and more * Fix runtime error * Sm 90 addison (#3275) * Use await instead of then * Rename stateServerConfig -> storedServerConfig * Move config validation logic to the model * Use implied check for undefined * Rename getStateServicerServerConfig -> buildServerConfig * Rename getApiServiceServerConfig -> pollServerConfig * Build server config in async * small fixes and add last seen text * Move config server to /config folder * Update with concatMap and other changes * Config project updates * Rename fileds to convention and remove unneeded migration * Update libs/common/src/services/state.service.ts Update based on Oscar's recommendation Co-authored-by: Oscar Hinton <Hinton@users.noreply.github.com> * Update options for Oscar's rec * Rename abstractions to abstracitons * Fix null issues and add options * Combine classes into one file, per Oscar's rec * Add null checking * Fix dependency issue * Add null checks, await, and fix date issue * Remove unneeded null check * In progress commit, unsuitable for for more than dev env, just backing up changes made with Oscar * Fix temp code to force last seen state * Add localization and escapes in the browser about section * Call complete on destroy subject rather than unsubscribe * use mediumDate and formatDate for the last seen date messaging * Add ThirdPartyServerName in example * Add deprecated note per Oscar's comment * [SM-90] Change to using a modal for browser about (#3417) * Fix inconsistent constructor null checking * ServerConfig can be null, fixes this * Switch to call super first, as required * remove unneeded null checks * Remove null checks from server-config.data.ts class * Update via PR comments and add back needed null check in server conf obj * Remove type annotation from serverConfig$ * Update self-hosted to be <small> per design decision * Re-fetch config every hour * Make third party server version <small> and change wording per Oscar's PR comment * Add expiresSoon function and re-fetch if the serverConfig will expire soon (older than 18 hours) * Fix misaligned small third party server message text Co-authored-by: Addison Beck <addisonbeck1@gmail.com> Co-authored-by: Oscar Hinton <Hinton@users.noreply.github.com>
This commit is contained in:
parent
cb31a71e8d
commit
3b69a60511
@ -1997,5 +1997,32 @@
|
|||||||
},
|
},
|
||||||
"environmentEditedReset": {
|
"environmentEditedReset": {
|
||||||
"message": "to reset to pre-configured settings"
|
"message": "to reset to pre-configured settings"
|
||||||
|
},
|
||||||
|
"serverVersion": {
|
||||||
|
"message": "Server Version"
|
||||||
|
},
|
||||||
|
"selfHosted": {
|
||||||
|
"message": "Self-Hosted"
|
||||||
|
},
|
||||||
|
"thirdParty": {
|
||||||
|
"message": "Third-Party"
|
||||||
|
},
|
||||||
|
"thirdPartyServerMessage": {
|
||||||
|
"message": "Connected to third-party server implementation, $SERVERNAME$. Please verify bugs using the official server, or report them to the third-party server.",
|
||||||
|
"placeholders": {
|
||||||
|
"servername": {
|
||||||
|
"content": "$1",
|
||||||
|
"example": "ThirdPartyServerName"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lastSeenOn": {
|
||||||
|
"message": "last seen on $DATE$",
|
||||||
|
"placeholders": {
|
||||||
|
"date": {
|
||||||
|
"content": "$1",
|
||||||
|
"example": "Jun 15, 2015"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -94,6 +94,7 @@ import { SendAddEditComponent } from "./send/send-add-edit.component";
|
|||||||
import { SendGroupingsComponent } from "./send/send-groupings.component";
|
import { SendGroupingsComponent } from "./send/send-groupings.component";
|
||||||
import { SendTypeComponent } from "./send/send-type.component";
|
import { SendTypeComponent } from "./send/send-type.component";
|
||||||
import { ServicesModule } from "./services/services.module";
|
import { ServicesModule } from "./services/services.module";
|
||||||
|
import { AboutComponent } from "./settings/about.component";
|
||||||
import { ExcludedDomainsComponent } from "./settings/excluded-domains.component";
|
import { ExcludedDomainsComponent } from "./settings/excluded-domains.component";
|
||||||
import { ExportComponent } from "./settings/export.component";
|
import { ExportComponent } from "./settings/export.component";
|
||||||
import { FolderAddEditComponent } from "./settings/folder-add-edit.component";
|
import { FolderAddEditComponent } from "./settings/folder-add-edit.component";
|
||||||
@ -242,6 +243,7 @@ registerLocaleData(localeZhTw, "zh-TW");
|
|||||||
ViewCustomFieldsComponent,
|
ViewCustomFieldsComponent,
|
||||||
RemovePasswordComponent,
|
RemovePasswordComponent,
|
||||||
VaultSelectComponent,
|
VaultSelectComponent,
|
||||||
|
AboutComponent,
|
||||||
],
|
],
|
||||||
providers: [CurrencyPipe, DatePipe],
|
providers: [CurrencyPipe, DatePipe],
|
||||||
bootstrap: [AppComponent],
|
bootstrap: [AppComponent],
|
||||||
|
52
apps/browser/src/popup/settings/about.component.html
Normal file
52
apps/browser/src/popup/settings/about.component.html
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
<div class="modal fade" role="dialog" aria-modal="true">
|
||||||
|
<div class="modal-dialog modal-dialog-centered modal-dialog-scrollable" role="document">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-body">
|
||||||
|
<p class="text-center">
|
||||||
|
<i class="bwi bwi-shield bwi-3x" aria-hidden="true"></i>
|
||||||
|
</p>
|
||||||
|
<p class="text-center">
|
||||||
|
<b>Bitwarden</b>
|
||||||
|
</p>
|
||||||
|
<p class="text-center">© Bitwarden Inc. 2015-{{ year }}</p>
|
||||||
|
<p class="text-center">{{ "version" | i18n }}: {{ version }}</p>
|
||||||
|
<ng-container *ngIf="serverConfig$ | async as serverConfig">
|
||||||
|
<p class="text-center" *ngIf="isCloud">
|
||||||
|
{{ "serverVersion" | i18n }}: {{ this.serverConfig?.version }}
|
||||||
|
<span *ngIf="!serverConfig.isValid()">
|
||||||
|
({{ "lastSeenOn" | i18n }}: {{ serverConfig.utcDate | date: "mediumDate" }})
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<ng-container *ngIf="!isCloud">
|
||||||
|
<ng-container *ngIf="serverConfig.server">
|
||||||
|
<p class="text-center">
|
||||||
|
{{ "serverVersion" | i18n }} <small>({{ "thirdParty" | i18n }})</small>:
|
||||||
|
{{ this.serverConfig?.version }}
|
||||||
|
<span *ngIf="!serverConfig.isValid()">
|
||||||
|
({{ "lastSeenOn" | i18n }}: {{ serverConfig.utcDate | date: "mediumDate" }})
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
<div class="text-center">
|
||||||
|
<small>{{ "thirdPartyServerMessage" | i18n: serverConfig.server?.name }}</small>
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<p class="text-center" *ngIf="!serverConfig.server">
|
||||||
|
{{ "serverVersion" | i18n }} <small>({{ "selfHosted" | i18n }})</small>:
|
||||||
|
{{ this.serverConfig?.version }}
|
||||||
|
<span *ngIf="!serverConfig.isValid()">
|
||||||
|
({{ "lastSeenOn" | i18n }}: {{ serverConfig.utcDate | date: "mediumDate" }})
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
</ng-container>
|
||||||
|
</ng-container>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">
|
||||||
|
{{ "close" | i18n }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
25
apps/browser/src/popup/settings/about.component.ts
Normal file
25
apps/browser/src/popup/settings/about.component.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { Component } from "@angular/core";
|
||||||
|
import { Observable } from "rxjs";
|
||||||
|
|
||||||
|
import { ConfigServiceAbstraction } from "@bitwarden/common/abstractions/config/config.service.abstraction";
|
||||||
|
import { ServerConfig } from "@bitwarden/common/abstractions/config/server-config";
|
||||||
|
import { EnvironmentService } from "@bitwarden/common/abstractions/environment.service";
|
||||||
|
|
||||||
|
import { BrowserApi } from "../../browser/browserApi";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: "app-about",
|
||||||
|
templateUrl: "about.component.html",
|
||||||
|
})
|
||||||
|
export class AboutComponent {
|
||||||
|
serverConfig$: Observable<ServerConfig>;
|
||||||
|
|
||||||
|
year = new Date().getFullYear();
|
||||||
|
version = BrowserApi.getApplicationVersion();
|
||||||
|
isCloud: boolean;
|
||||||
|
|
||||||
|
constructor(configService: ConfigServiceAbstraction, environmentService: EnvironmentService) {
|
||||||
|
this.serverConfig$ = configService.serverConfig$;
|
||||||
|
this.isCloud = environmentService.isCloud();
|
||||||
|
}
|
||||||
|
}
|
@ -20,6 +20,8 @@ import { BiometricErrors, BiometricErrorTypes } from "../../models/biometricErro
|
|||||||
import { SetPinComponent } from "../components/set-pin.component";
|
import { SetPinComponent } from "../components/set-pin.component";
|
||||||
import { PopupUtilsService } from "../services/popup-utils.service";
|
import { PopupUtilsService } from "../services/popup-utils.service";
|
||||||
|
|
||||||
|
import { AboutComponent } from "./about.component";
|
||||||
|
|
||||||
const RateUrls = {
|
const RateUrls = {
|
||||||
[DeviceType.ChromeExtension]:
|
[DeviceType.ChromeExtension]:
|
||||||
"https://chrome.google.com/webstore/detail/bitwarden-free-password-m/nngceckbapebfimnlniiiahkandclblb/reviews",
|
"https://chrome.google.com/webstore/detail/bitwarden-free-password-m/nngceckbapebfimnlniiiahkandclblb/reviews",
|
||||||
@ -377,26 +379,7 @@ export class SettingsComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
about() {
|
about() {
|
||||||
const year = new Date().getFullYear();
|
this.modalService.open(AboutComponent);
|
||||||
const versionText = document.createTextNode(
|
|
||||||
this.i18nService.t("version") + ": " + BrowserApi.getApplicationVersion()
|
|
||||||
);
|
|
||||||
const div = document.createElement("div");
|
|
||||||
div.innerHTML =
|
|
||||||
`<p class="text-center"><i class="bwi bwi-shield bwi-3x" aria-hidden="true"></i></p>
|
|
||||||
<p class="text-center"><b>Bitwarden</b><br>© Bitwarden Inc. 2015-` +
|
|
||||||
year +
|
|
||||||
`</p>`;
|
|
||||||
div.appendChild(versionText);
|
|
||||||
|
|
||||||
Swal.fire({
|
|
||||||
heightAuto: false,
|
|
||||||
buttonsStyling: false,
|
|
||||||
html: div,
|
|
||||||
showConfirmButton: false,
|
|
||||||
showCancelButton: true,
|
|
||||||
cancelButtonText: this.i18nService.t("close"),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fingerprint() {
|
async fingerprint() {
|
||||||
|
@ -12,6 +12,8 @@ import { AuthService as AuthServiceAbstraction } from "@bitwarden/common/abstrac
|
|||||||
import { BroadcasterService as BroadcasterServiceAbstraction } from "@bitwarden/common/abstractions/broadcaster.service";
|
import { BroadcasterService as BroadcasterServiceAbstraction } from "@bitwarden/common/abstractions/broadcaster.service";
|
||||||
import { CipherService as CipherServiceAbstraction } from "@bitwarden/common/abstractions/cipher.service";
|
import { CipherService as CipherServiceAbstraction } from "@bitwarden/common/abstractions/cipher.service";
|
||||||
import { CollectionService as CollectionServiceAbstraction } from "@bitwarden/common/abstractions/collection.service";
|
import { CollectionService as CollectionServiceAbstraction } from "@bitwarden/common/abstractions/collection.service";
|
||||||
|
import { ConfigApiServiceAbstraction } from "@bitwarden/common/abstractions/config/config-api.service.abstraction";
|
||||||
|
import { ConfigServiceAbstraction } from "@bitwarden/common/abstractions/config/config.service.abstraction";
|
||||||
import { CryptoService as CryptoServiceAbstraction } from "@bitwarden/common/abstractions/crypto.service";
|
import { CryptoService as CryptoServiceAbstraction } from "@bitwarden/common/abstractions/crypto.service";
|
||||||
import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from "@bitwarden/common/abstractions/cryptoFunction.service";
|
import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from "@bitwarden/common/abstractions/cryptoFunction.service";
|
||||||
import { EnvironmentService as EnvironmentServiceAbstraction } from "@bitwarden/common/abstractions/environment.service";
|
import { EnvironmentService as EnvironmentServiceAbstraction } from "@bitwarden/common/abstractions/environment.service";
|
||||||
@ -66,6 +68,8 @@ import { AuditService } from "@bitwarden/common/services/audit.service";
|
|||||||
import { AuthService } from "@bitwarden/common/services/auth.service";
|
import { AuthService } from "@bitwarden/common/services/auth.service";
|
||||||
import { CipherService } from "@bitwarden/common/services/cipher.service";
|
import { CipherService } from "@bitwarden/common/services/cipher.service";
|
||||||
import { CollectionService } from "@bitwarden/common/services/collection.service";
|
import { CollectionService } from "@bitwarden/common/services/collection.service";
|
||||||
|
import { ConfigApiService } from "@bitwarden/common/services/config/config-api.service";
|
||||||
|
import { ConfigService } from "@bitwarden/common/services/config/config.service";
|
||||||
import { ConsoleLogService } from "@bitwarden/common/services/consoleLog.service";
|
import { ConsoleLogService } from "@bitwarden/common/services/consoleLog.service";
|
||||||
import { CryptoService } from "@bitwarden/common/services/crypto.service";
|
import { CryptoService } from "@bitwarden/common/services/crypto.service";
|
||||||
import { EncryptService } from "@bitwarden/common/services/encrypt.service";
|
import { EncryptService } from "@bitwarden/common/services/encrypt.service";
|
||||||
@ -530,6 +534,16 @@ import { ValidationService } from "./validation.service";
|
|||||||
useClass: OrganizationApiService,
|
useClass: OrganizationApiService,
|
||||||
deps: [ApiServiceAbstraction],
|
deps: [ApiServiceAbstraction],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
provide: ConfigServiceAbstraction,
|
||||||
|
useClass: ConfigService,
|
||||||
|
deps: [StateServiceAbstraction, ConfigApiServiceAbstraction],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: ConfigApiServiceAbstraction,
|
||||||
|
useClass: ConfigApiService,
|
||||||
|
deps: [ApiServiceAbstraction],
|
||||||
|
},
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class JslibServicesModule {}
|
export class JslibServicesModule {}
|
||||||
|
@ -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>;
|
setUrlsFromStorage: () => Promise<void>;
|
||||||
setUrls: (urls: Urls) => Promise<Urls>;
|
setUrls: (urls: Urls) => Promise<Urls>;
|
||||||
getUrls: () => Urls;
|
getUrls: () => Urls;
|
||||||
|
isCloud: () => boolean;
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,7 @@ import { OrganizationData } from "../models/data/organizationData";
|
|||||||
import { PolicyData } from "../models/data/policyData";
|
import { PolicyData } from "../models/data/policyData";
|
||||||
import { ProviderData } from "../models/data/providerData";
|
import { ProviderData } from "../models/data/providerData";
|
||||||
import { SendData } from "../models/data/sendData";
|
import { SendData } from "../models/data/sendData";
|
||||||
|
import { ServerConfigData } from "../models/data/server-config.data";
|
||||||
import { Account, AccountSettingsSettings } from "../models/domain/account";
|
import { Account, AccountSettingsSettings } from "../models/domain/account";
|
||||||
import { EncString } from "../models/domain/encString";
|
import { EncString } from "../models/domain/encString";
|
||||||
import { EnvironmentUrls } from "../models/domain/environmentUrls";
|
import { EnvironmentUrls } from "../models/domain/environmentUrls";
|
||||||
@ -319,4 +320,12 @@ export abstract class StateService<T extends Account = Account> {
|
|||||||
setStateVersion: (value: number) => Promise<void>;
|
setStateVersion: (value: number) => Promise<void>;
|
||||||
getWindow: () => Promise<WindowState>;
|
getWindow: () => Promise<WindowState>;
|
||||||
setWindow: (value: WindowState) => Promise<void>;
|
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>;
|
||||||
}
|
}
|
||||||
|
53
libs/common/src/models/data/server-config.data.ts
Normal file
53
libs/common/src/models/data/server-config.data.ts
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
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;
|
||||||
|
admin: string;
|
||||||
|
notifications: string;
|
||||||
|
sso: string;
|
||||||
|
|
||||||
|
constructor(response: EnvironmentServerConfigResponse) {
|
||||||
|
this.vault = response.vault;
|
||||||
|
this.api = response.api;
|
||||||
|
this.identity = response.identity;
|
||||||
|
this.admin = response.admin;
|
||||||
|
this.notifications = response.notifications;
|
||||||
|
this.sso = response.sso;
|
||||||
|
}
|
||||||
|
}
|
@ -10,6 +10,7 @@ import { OrganizationData } from "../data/organizationData";
|
|||||||
import { PolicyData } from "../data/policyData";
|
import { PolicyData } from "../data/policyData";
|
||||||
import { ProviderData } from "../data/providerData";
|
import { ProviderData } from "../data/providerData";
|
||||||
import { SendData } from "../data/sendData";
|
import { SendData } from "../data/sendData";
|
||||||
|
import { ServerConfigData } from "../data/server-config.data";
|
||||||
import { CipherView } from "../view/cipherView";
|
import { CipherView } from "../view/cipherView";
|
||||||
import { CollectionView } from "../view/collectionView";
|
import { CollectionView } from "../view/collectionView";
|
||||||
import { SendView } from "../view/sendView";
|
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
|
settings?: AccountSettingsSettings; // TODO: Merge whatever is going on here into the AccountSettings model properly
|
||||||
vaultTimeout?: number;
|
vaultTimeout?: number;
|
||||||
vaultTimeoutAction?: string = "lock";
|
vaultTimeoutAction?: string = "lock";
|
||||||
|
serverConfig?: ServerConfigData;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AccountSettingsSettings = {
|
export type AccountSettingsSettings = {
|
||||||
|
61
libs/common/src/models/response/server-config-response.ts
Normal file
61
libs/common/src/models/response/server-config-response.ts
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
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;
|
||||||
|
admin: 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.admin = this.getResponseProperty("Admin");
|
||||||
|
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();
|
return url.trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isCloud(): boolean {
|
||||||
|
return ["https://api.bitwarden.com", "https://vault.bitwarden.com/api"].includes(
|
||||||
|
this.getApiUrl()
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,7 @@ import { OrganizationData } from "../models/data/organizationData";
|
|||||||
import { PolicyData } from "../models/data/policyData";
|
import { PolicyData } from "../models/data/policyData";
|
||||||
import { ProviderData } from "../models/data/providerData";
|
import { ProviderData } from "../models/data/providerData";
|
||||||
import { SendData } from "../models/data/sendData";
|
import { SendData } from "../models/data/sendData";
|
||||||
|
import { ServerConfigData } from "../models/data/server-config.data";
|
||||||
import {
|
import {
|
||||||
Account,
|
Account,
|
||||||
AccountData,
|
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> {
|
protected async getGlobals(options: StorageOptions): Promise<TGlobalState> {
|
||||||
let globals: TGlobalState;
|
let globals: TGlobalState;
|
||||||
if (this.useMemory(options.storageLocation)) {
|
if (this.useMemory(options.storageLocation)) {
|
||||||
|
Loading…
Reference in New Issue
Block a user