Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[PM-3000] Add Environment URLs to Account Switcher #5978

Merged
merged 28 commits into from
Nov 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
44da127
add server url to account switcher tab
rr-bw Aug 7, 2023
5e3ae65
add serverUrl to SwitcherAccount(s)
rr-bw Aug 7, 2023
687d136
refactor serverUrl getter
rr-bw Aug 7, 2023
18c1694
Merge branch 'master' into auth/pm-3000/desktop-account-switcher-urls
rr-bw Aug 7, 2023
d7b28bd
cleanup urls
rr-bw Aug 7, 2023
cbca02b
Merge branch 'auth/pm-3000/desktop-account-switcher-urls' of https://…
rr-bw Aug 7, 2023
b03c4a8
adjust styling
rr-bw Aug 7, 2023
75a508e
Merge branch 'master' into auth/pm-3000/desktop-account-switcher-urls
rr-bw Sep 25, 2023
2e16845
remove SwitcherAccount class
rr-bw Oct 10, 2023
66ed3ef
Merge branch 'master' into auth/pm-3000/desktop-account-switcher-urls
rr-bw Oct 10, 2023
972e923
remove authenticationStatus from AccountProfile
rr-bw Oct 10, 2023
80a1424
rename to inactiveAccounts for clarity
rr-bw Oct 10, 2023
10aa289
move business logic to environmentService
rr-bw Oct 10, 2023
d923db5
use tokenService instead of stateService
rr-bw Oct 10, 2023
f7d204a
cleanup type and comments
rr-bw Oct 10, 2023
43d7ed7
remove unused property
rr-bw Oct 10, 2023
b18257f
replace magic strings
rr-bw Oct 10, 2023
772a888
remove unused function
rr-bw Oct 10, 2023
262a54b
minor refactoring
rr-bw Oct 10, 2023
755419f
refactor to use environmentService insead of getServerConfig
rr-bw Oct 12, 2023
9e93c97
use Utils.getHost() instead of Utils.getDomain()
rr-bw Oct 12, 2023
96436e4
create getHost() method
rr-bw Oct 12, 2023
8514d96
remove comment
rr-bw Oct 12, 2023
8d6eee5
get base url as fallback
rr-bw Oct 16, 2023
4d32a4a
resolve eslint error
rr-bw Oct 26, 2023
81e9ae8
Merge branch 'master' into auth/pm-3000/desktop-account-switcher-urls
rr-bw Oct 26, 2023
395b7d6
resolve merge conflicts for a11y changes
rr-bw Oct 31, 2023
1755488
Update apps/desktop/src/app/layout/account-switcher.component.html
rr-bw Nov 3, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 25 additions & 22 deletions apps/desktop/src/app/layout/account-switcher.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,11 @@
*ngIf="activeAccount.email != null"
aria-hidden="true"
></app-avatar>
<span
>{{ activeAccount.email
}}<span class="sr-only">&nbsp;({{ "switchAccount" | i18n }})</span></span
>
<div class="active-account">
<div>{{ activeAccount.email }}</div>
<span>{{ activeAccount.server }}</span>
<span class="sr-only">&nbsp;({{ "switchAccount" | i18n }})</span>
</div>
</ng-container>
<ng-template #noActiveAccount>
<span>{{ "switchAccount" | i18n }}</span>
Expand Down Expand Up @@ -55,38 +56,40 @@
aria-modal="true"
>
<div class="accounts" *ngIf="numberOfAccounts > 0">
<button *ngFor="let a of accounts | keyvalue" class="account" (click)="switch(a.key)">
<button
*ngFor="let account of inactiveAccounts | keyvalue"
class="account"
(click)="switch(account.key)"
>
<app-avatar
[text]="a.value.profile.name ?? a.value.profile.email"
[id]="a.value.profile.userId"
[text]="account.value.name ?? account.value.email"
[id]="account.value.id"
[size]="25"
[circle]="true"
[fontSize]="14"
[dynamic]="true"
[color]="a.value.avatarColor"
*ngIf="a.value.profile.email != null"
[color]="account.value.avatarColor"
*ngIf="account.value.email != null"
aria-hidden="true"
></app-avatar>
<div class="accountInfo">
<span class="sr-only">{{ "switchAccount" | i18n }}:&nbsp;</span>
<span class="email">{{ a.value.profile.email }}</span>
<span class="server" *ngIf="a.value.serverUrl != 'bitwarden.com'">
<span class="sr-only"> / </span>
{{ a.value.serverUrl }}
<span class="email" aria-hidden="true">{{ account.value.email }}</span>
<span class="server" aria-hidden="true">
<span class="sr-only"> / </span>{{ account.value.server }}
</span>
<span class="status">
<span class="sr-only">&nbsp;(</span>
{{
(a.value.profile.authenticationStatus === authStatus.Unlocked ? "unlocked" : "locked")
<span class="status" aria-hidden="true"
><span class="sr-only">&nbsp;(</span
>{{
(account.value.authenticationStatus === authStatus.Unlocked ? "unlocked" : "locked")
| i18n
}}
<span class="sr-only">)</span>
</span>
}}<span class="sr-only">)</span></span
>
</div>
<i
class="bwi bwi-2x text-muted"
[ngClass]="
a.value.profile.authenticationStatus == authStatus.Unlocked ? 'bwi-unlock' : 'bwi-lock'
account.value.authenticationStatus === authStatus.Unlocked ? 'bwi-unlock' : 'bwi-lock'
"
aria-hidden="true"
></i>
Expand All @@ -99,7 +102,7 @@
<i class="bwi bwi-plus" aria-hidden="true"></i> {{ "addAccount" | i18n }}
</button>
</ng-container>
<ng-container *ngIf="numberOfAccounts == 4">
<ng-container *ngIf="numberOfAccounts === 4">
<span class="accountLimitReached">{{ "accountSwitcherLimitReached" | i18n }} </span>
</ng-container>
</ng-container>
Expand Down
75 changes: 32 additions & 43 deletions apps/desktop/src/app/layout/account-switcher.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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",
Expand All @@ -61,13 +50,12 @@ export class SwitcherAccount extends Account {
],
})
export class AccountSwitcherComponent implements OnInit, OnDestroy {
private destroy$ = new Subject<void>();

isOpen = false;
accounts: { [userId: string]: SwitcherAccount } = {};
activeAccount?: ActiveAccount;
serverUrl: string;
inactiveAccounts: { [userId: string]: InactiveAccount } = {};

authStatus = AuthenticationStatus;

isOpen = false;
overlayPosition: ConnectedPosition[] = [
{
originX: "end",
Expand All @@ -77,45 +65,44 @@ export class AccountSwitcherComponent implements OnInit, OnDestroy {
},
];

private destroy$ = new Subject<void>();

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(
private stateService: StateService,
private authService: AuthService,
private messagingService: MessagingService,
private router: Router,
private tokenService: TokenService
private tokenService: TokenService,
private environmentService: EnvironmentService
) {}

async ngOnInit(): Promise<void> {
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;
Expand Down Expand Up @@ -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;
}
}
19 changes: 15 additions & 4 deletions apps/desktop/src/scss/header.scss
Original file line number Diff line number Diff line change
@@ -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%;
Expand Down Expand Up @@ -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;
Expand All @@ -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 {
Expand Down
6 changes: 1 addition & 5 deletions libs/angular/src/auth/components/lock.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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();
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ export abstract class EnvironmentService {
getScimUrl: () => string;
setUrlsFromStorage: () => Promise<void>;
setUrls: (urls: Urls) => Promise<Urls>;
getHost: (userId?: string) => Promise<string>;
setRegion: (region: Region) => Promise<void>;
getUrls: () => Urls;
isCloud: () => boolean;
Expand Down
2 changes: 0 additions & 2 deletions libs/common/src/platform/models/domain/account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 { ForceResetPasswordReason } from "../../../auth/models/domain/force-reset-password-reason";
Expand Down Expand Up @@ -185,7 +184,6 @@ export class AccountKeys {

export class AccountProfile {
apiKeyClientId?: string;
authenticationStatus?: AuthenticationStatus;
convertAccountToKeyConnector?: boolean;
name?: string;
email?: string;
Expand Down
24 changes: 24 additions & 0 deletions libs/common/src/platform/services/environment.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<void>(1);
Expand Down Expand Up @@ -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);
Expand Down