diff --git a/apps/desktop/src/app/layout/account-switcher.component.html b/apps/desktop/src/app/layout/account-switcher.component.html index 5110969b0c..eedafbcfe0 100644 --- a/apps/desktop/src/app/layout/account-switcher.component.html +++ b/apps/desktop/src/app/layout/account-switcher.component.html @@ -20,10 +20,11 @@ *ngIf="activeAccount.email != null" aria-hidden="true" > - {{ activeAccount.email - }} ({{ "switchAccount" | i18n }}) +
+
{{ activeAccount.email }}
+ {{ activeAccount.server }} +  ({{ "switchAccount" | i18n }}) +
{{ "switchAccount" | i18n }} @@ -55,38 +56,40 @@ aria-modal="true" >
- - + {{ "accountSwitcherLimitReached" | i18n }} diff --git a/apps/desktop/src/app/layout/account-switcher.component.ts b/apps/desktop/src/app/layout/account-switcher.component.ts index 76cff308ed..b603fde14e 100644 --- a/apps/desktop/src/app/layout/account-switcher.component.ts +++ b/apps/desktop/src/app/layout/account-switcher.component.ts @@ -7,6 +7,7 @@ import { concatMap, Subject, takeUntil } from "rxjs"; 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 { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; @@ -17,24 +18,12 @@ type ActiveAccount = { name: string; email: string; avatarColor: string; + server: string; }; -export class SwitcherAccount extends Account { - get serverUrl() { - return this.removeWebProtocolFromString( - this.settings?.environmentUrls?.base ?? - this.settings?.environmentUrls.api ?? - "https://bitwarden.com" - ); - } - - avatarColor: string; - - private removeWebProtocolFromString(urlString: string) { - const regex = /http(s)?(:)?(\/\/)?|(\/\/)?(www\.)?/g; - return urlString.replace(regex, ""); - } -} +type InactiveAccount = ActiveAccount & { + authenticationStatus: AuthenticationStatus; +}; @Component({ selector: "app-account-switcher", @@ -61,13 +50,12 @@ export class SwitcherAccount extends Account { ], }) export class AccountSwitcherComponent implements OnInit, OnDestroy { - private destroy$ = new Subject(); + activeAccount?: ActiveAccount; + inactiveAccounts: { [userId: string]: InactiveAccount } = {}; + + authStatus = AuthenticationStatus; isOpen = false; - accounts: { [userId: string]: SwitcherAccount } = {}; - activeAccount?: ActiveAccount; - serverUrl: string; - authStatus = AuthenticationStatus; overlayPosition: ConnectedPosition[] = [ { originX: "end", @@ -77,18 +65,20 @@ export class AccountSwitcherComponent implements OnInit, OnDestroy { }, ]; + private destroy$ = new Subject(); + get showSwitcher() { const userIsInAVault = !Utils.isNullOrWhitespace(this.activeAccount?.email); - const userIsAddingAnAdditionalAccount = Object.keys(this.accounts).length > 0; + const userIsAddingAnAdditionalAccount = Object.keys(this.inactiveAccounts).length > 0; return userIsInAVault || userIsAddingAnAdditionalAccount; } get numberOfAccounts() { - if (this.accounts == null) { + if (this.inactiveAccounts == null) { this.isOpen = false; return 0; } - return Object.keys(this.accounts).length; + return Object.keys(this.inactiveAccounts).length; } constructor( @@ -96,26 +86,23 @@ export class AccountSwitcherComponent implements OnInit, OnDestroy { private authService: AuthService, private messagingService: MessagingService, private router: Router, - private tokenService: TokenService + private tokenService: TokenService, + private environmentService: EnvironmentService ) {} async ngOnInit(): Promise { this.stateService.accounts$ .pipe( concatMap(async (accounts: { [userId: string]: Account }) => { - for (const userId in accounts) { - accounts[userId].profile.authenticationStatus = await this.authService.getAuthStatus( - userId - ); - } + this.inactiveAccounts = await this.createInactiveAccounts(accounts); - this.accounts = await this.createSwitcherAccounts(accounts); try { this.activeAccount = { id: await this.tokenService.getUserId(), name: (await this.tokenService.getName()) ?? (await this.tokenService.getEmail()), email: await this.tokenService.getEmail(), avatarColor: await this.stateService.getAvatarColor(), + server: await this.environmentService.getHost(), }; } catch { this.activeAccount = undefined; @@ -152,24 +139,26 @@ export class AccountSwitcherComponent implements OnInit, OnDestroy { this.router.navigate(["/login"]); } - private async createSwitcherAccounts(baseAccounts: { + private async createInactiveAccounts(baseAccounts: { [userId: string]: Account; - }): Promise<{ [userId: string]: SwitcherAccount }> { - const switcherAccounts: { [userId: string]: SwitcherAccount } = {}; + }): Promise<{ [userId: string]: InactiveAccount }> { + const inactiveAccounts: { [userId: string]: InactiveAccount } = {}; + for (const userId in baseAccounts) { if (userId == null || userId === (await this.stateService.getUserId())) { continue; } - // environmentUrls are stored on disk and must be retrieved separately from the in memory state offered from subscribing to accounts - baseAccounts[userId].settings.environmentUrls = await this.stateService.getEnvironmentUrls({ - userId: userId, - }); - switcherAccounts[userId] = new SwitcherAccount(baseAccounts[userId]); - switcherAccounts[userId].avatarColor = await this.stateService.getAvatarColor({ - userId: userId, - }); + inactiveAccounts[userId] = { + id: userId, + name: baseAccounts[userId].profile.name, + email: baseAccounts[userId].profile.email, + authenticationStatus: await this.authService.getAuthStatus(userId), + avatarColor: await this.stateService.getAvatarColor({ userId: userId }), + server: await this.environmentService.getHost(userId), + }; } - return switcherAccounts; + + return inactiveAccounts; } } diff --git a/apps/desktop/src/scss/header.scss b/apps/desktop/src/scss/header.scss index 83eb67d014..b613a11a59 100644 --- a/apps/desktop/src/scss/header.scss +++ b/apps/desktop/src/scss/header.scss @@ -1,7 +1,7 @@ .header { -webkit-app-region: drag; - min-height: 44px; - max-height: 44px; + min-height: 54px; + max-height: 54px; border-bottom: 1px solid #000000; display: grid; grid-template-columns: 25% 1fr 25%; @@ -102,7 +102,7 @@ .account-switcher { display: grid; grid-template-columns: auto 1fr auto; - grid-column-gap: 5px; + grid-column-gap: 10px; align-items: center; justify-items: center; padding: 0 10px; @@ -121,11 +121,22 @@ display: block; } - span { + .active-account { width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; + text-align: left; + + div { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + span { + font-size: $font-size-small; + } } &:hover { diff --git a/libs/angular/src/auth/components/lock.component.ts b/libs/angular/src/auth/components/lock.component.ts index 547499c329..f4bdd163eb 100644 --- a/libs/angular/src/auth/components/lock.component.ts +++ b/libs/angular/src/auth/components/lock.component.ts @@ -23,7 +23,6 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service" import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; -import { Utils } from "@bitwarden/common/platform/misc/utils"; import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; import { UserKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; import { PinLockType } from "@bitwarden/common/services/vault-timeout/vault-timeout-settings.service"; @@ -377,10 +376,7 @@ export class LockComponent implements OnInit, OnDestroy { this.biometricText = await this.stateService.getBiometricText(); this.email = await this.stateService.getEmail(); - const webVaultUrl = this.environmentService.getWebVaultUrl(); - const vaultUrl = - webVaultUrl === "https://vault.bitwarden.com" ? "https://bitwarden.com" : webVaultUrl; - this.webVaultHostname = Utils.getHostname(vaultUrl); + this.webVaultHostname = await this.environmentService.getHost(); } /** diff --git a/libs/common/src/platform/abstractions/environment.service.ts b/libs/common/src/platform/abstractions/environment.service.ts index 37a3169d2f..6b8d40c86b 100644 --- a/libs/common/src/platform/abstractions/environment.service.ts +++ b/libs/common/src/platform/abstractions/environment.service.ts @@ -61,6 +61,7 @@ export abstract class EnvironmentService { getScimUrl: () => string; setUrlsFromStorage: () => Promise; setUrls: (urls: Urls) => Promise; + getHost: (userId?: string) => Promise; setRegion: (region: Region) => Promise; getUrls: () => Urls; isCloud: () => boolean; diff --git a/libs/common/src/platform/models/domain/account.ts b/libs/common/src/platform/models/domain/account.ts index 8e05ec4bb2..3319e4a8f8 100644 --- a/libs/common/src/platform/models/domain/account.ts +++ b/libs/common/src/platform/models/domain/account.ts @@ -5,7 +5,6 @@ import { OrganizationData } from "../../../admin-console/models/data/organizatio 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 { 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"; @@ -187,7 +186,6 @@ export class AccountKeys { export class AccountProfile { apiKeyClientId?: string; - authenticationStatus?: AuthenticationStatus; convertAccountToKeyConnector?: boolean; name?: string; email?: string; diff --git a/libs/common/src/platform/services/environment.service.ts b/libs/common/src/platform/services/environment.service.ts index d85341a68c..ebe143e8e8 100644 --- a/libs/common/src/platform/services/environment.service.ts +++ b/libs/common/src/platform/services/environment.service.ts @@ -4,9 +4,11 @@ import { EnvironmentUrls } from "../../auth/models/domain/environment-urls"; import { EnvironmentService as EnvironmentServiceAbstraction, Region, + RegionDomain, Urls, } from "../abstractions/environment.service"; import { StateService } from "../abstractions/state.service"; +import { Utils } from "../misc/utils"; export class EnvironmentService implements EnvironmentServiceAbstraction { private readonly urlsSubject = new ReplaySubject(1); @@ -283,6 +285,28 @@ export class EnvironmentService implements EnvironmentServiceAbstraction { ); } + async getHost(userId?: string) { + const region = await this.getRegion(userId ? userId : null); + + switch (region) { + case Region.US: + return RegionDomain.US; + case Region.EU: + return RegionDomain.EU; + default: { + // Environment is self-hosted + const envUrls = await this.stateService.getEnvironmentUrls( + userId ? { userId: userId } : null + ); + return Utils.getHost(envUrls.webVault || envUrls.base); + } + } + } + + private async getRegion(userId?: string) { + return this.stateService.getRegion(userId ? { userId: userId } : null); + } + async setRegion(region: Region) { this.selectedRegion = region; await this.stateService.setRegion(region);