bitwarden-estensione-browser/apps/browser/src/auth/popup/account-switching/account-switcher.component.ts

121 lines
4.3 KiB
TypeScript

import { Location } from "@angular/common";
import { Component, OnDestroy, OnInit } from "@angular/core";
import { Router } from "@angular/router";
import { Subject, firstValueFrom, map, switchMap, takeUntil } from "rxjs";
import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service";
import { VaultTimeoutService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout.service";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
import { VaultTimeoutAction } from "@bitwarden/common/enums/vault-timeout-action.enum";
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
import { DialogService } from "@bitwarden/components";
import { AccountSwitcherService } from "./services/account-switcher.service";
@Component({
templateUrl: "account-switcher.component.html",
})
export class AccountSwitcherComponent implements OnInit, OnDestroy {
readonly lockedStatus = AuthenticationStatus.Locked;
private destroy$ = new Subject<void>();
loading = false;
activeUserCanLock = false;
constructor(
private accountSwitcherService: AccountSwitcherService,
private accountService: AccountService,
private vaultTimeoutService: VaultTimeoutService,
private messagingService: MessagingService,
private dialogService: DialogService,
private location: Location,
private router: Router,
private vaultTimeoutSettingsService: VaultTimeoutSettingsService,
private authService: AuthService,
) {}
get accountLimit() {
return this.accountSwitcherService.ACCOUNT_LIMIT;
}
get specialAddAccountId() {
return this.accountSwitcherService.SPECIAL_ADD_ACCOUNT_ID;
}
readonly availableAccounts$ = this.accountSwitcherService.availableAccounts$;
readonly currentAccount$ = this.accountService.activeAccount$.pipe(
switchMap((a) =>
a == null
? null
: this.authService.activeAccountStatus$.pipe(map((s) => ({ ...a, status: s }))),
),
);
async ngOnInit() {
const availableVaultTimeoutActions = await firstValueFrom(
this.vaultTimeoutSettingsService.availableVaultTimeoutActions$(),
);
this.activeUserCanLock = availableVaultTimeoutActions.includes(VaultTimeoutAction.Lock);
}
back() {
this.location.back();
}
async lock(userId?: string) {
this.loading = true;
await this.vaultTimeoutService.lock(userId ? userId : null);
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.router.navigate(["lock"]);
}
async lockAll() {
this.loading = true;
this.availableAccounts$
.pipe(
map((accounts) =>
accounts
.filter((account) => account.id !== this.specialAddAccountId)
.sort((a, b) => (a.isActive ? -1 : b.isActive ? 1 : 0)) // Log out of the active account first
.map((account) => account.id),
),
switchMap(async (accountIds) => {
if (accountIds.length === 0) {
return;
}
// Must lock active (first) account first, then order doesn't matter
await this.vaultTimeoutService.lock(accountIds.shift());
await Promise.all(accountIds.map((id) => this.vaultTimeoutService.lock(id)));
}),
takeUntil(this.destroy$),
)
.subscribe(() => this.router.navigate(["lock"]));
}
async logOut() {
this.loading = true;
const confirmed = await this.dialogService.openSimpleDialog({
title: { key: "logOut" },
content: { key: "logOutConfirmation" },
type: "info",
});
if (confirmed) {
this.messagingService.send("logout");
}
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.router.navigate(["home"]);
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
}