1
0
mirror of https://github.com/bitwarden/browser.git synced 2024-11-11 10:10:25 +01:00
bitwarden-browser/libs/common/src/auth/services/account.service.ts
Matt Gibson cdcd1809f0
Expand account service (#6622)
* Define account service observable responsibilities

* Establish account service observables and update methods

* Update Account Service observables from state service

This is a temporary stop-gap to avoid needing to reroute all account
activity and status changes through the account service. That can be
done as part of the breakup of state service.

* Add matchers for Observable emissions

* Fix null active account

* Test account service

* Transition account status to account info

* Remove unused matchers

* Remove duplicate class

* Replay active account for late subscriptions

* Add factories for background services

* Fix state service for web

* Allow for optional messaging

This is a temporary hack until the flow of account status can be
reversed from state -> account to account -> state. The foreground
account service will still logout, it's just the background one cannot
send messages

* Fix add account logic

* Do not throw on recoverable errors

It's possible that duplicate entries exist in `activeAccounts` exist
in the wild. If we throw on adding a duplicate account this will cause
applications to be unusable until duplicates are removed it is not
necessary to throw since this is recoverable. with some potential loss
in current account status

* Add documentation to abstraction

* Update libs/common/spec/utils.ts

Co-authored-by: Justin Baur <19896123+justindbaur@users.noreply.github.com>

* Fix justin's comment :fist-shake:

---------

Co-authored-by: Justin Baur <19896123+justindbaur@users.noreply.github.com>
2023-10-19 15:41:01 -04:00

94 lines
3.0 KiB
TypeScript

import {
BehaviorSubject,
Subject,
combineLatestWith,
map,
distinctUntilChanged,
shareReplay,
} from "rxjs";
import { AccountInfo, InternalAccountService } from "../../auth/abstractions/account.service";
import { LogService } from "../../platform/abstractions/log.service";
import { MessagingService } from "../../platform/abstractions/messaging.service";
import { UserId } from "../../types/guid";
import { AuthenticationStatus } from "../enums/authentication-status";
export class AccountServiceImplementation implements InternalAccountService {
private accounts = new BehaviorSubject<Record<UserId, AccountInfo>>({});
private activeAccountId = new BehaviorSubject<UserId | undefined>(undefined);
private lock = new Subject<UserId>();
private logout = new Subject<UserId>();
accounts$ = this.accounts.asObservable();
activeAccount$ = this.activeAccountId.pipe(
combineLatestWith(this.accounts$),
map(([id, accounts]) => (id ? { id, ...accounts[id] } : undefined)),
distinctUntilChanged(),
shareReplay({ bufferSize: 1, refCount: false })
);
accountLock$ = this.lock.asObservable();
accountLogout$ = this.logout.asObservable();
constructor(private messagingService: MessagingService, private logService: LogService) {}
addAccount(userId: UserId, accountData: AccountInfo): void {
this.accounts.value[userId] = accountData;
this.accounts.next(this.accounts.value);
}
setAccountName(userId: UserId, name: string): void {
this.setAccountInfo(userId, { ...this.accounts.value[userId], name });
}
setAccountEmail(userId: UserId, email: string): void {
this.setAccountInfo(userId, { ...this.accounts.value[userId], email });
}
setAccountStatus(userId: UserId, status: AuthenticationStatus): void {
this.setAccountInfo(userId, { ...this.accounts.value[userId], status });
if (status === AuthenticationStatus.LoggedOut) {
this.logout.next(userId);
} else if (status === AuthenticationStatus.Locked) {
this.lock.next(userId);
}
}
switchAccount(userId: UserId) {
if (userId == null) {
// indicates no account is active
this.activeAccountId.next(undefined);
return;
}
if (this.accounts.value[userId] == null) {
throw new Error("Account does not exist");
}
this.activeAccountId.next(userId);
}
// TODO: update to use our own account status settings. Requires inverting direction of state service accounts flow
async delete(): Promise<void> {
try {
this.messagingService?.send("logout");
} catch (e) {
this.logService.error(e);
throw e;
}
}
private setAccountInfo(userId: UserId, accountInfo: AccountInfo) {
if (this.accounts.value[userId] == null) {
throw new Error("Account does not exist");
}
// Avoid unnecessary updates
// TODO: Faster comparison, maybe include a hash on the objects?
if (JSON.stringify(this.accounts.value[userId]) === JSON.stringify(accountInfo)) {
return;
}
this.accounts.value[userId] = accountInfo;
this.accounts.next(this.accounts.value);
}
}