mirror of
https://github.com/bitwarden/browser
synced 2025-12-19 17:53:39 +00:00
* Use account service to track accounts and active account * Remove state service active account Observables. * Add email verified to account service * Do not store account info on logged out accounts * Add account activity tracking to account service * Use last account activity from account service * migrate or replicate account service data * Add `AccountActivityService` that handles storing account last active data * Move active and next active user to account service * Remove authenticated accounts from state object * Fold account activity into account service * Fix builds * Fix desktop app switch * Fix logging out non active user * Expand helper to handle new authenticated accounts location * Prefer view observable to tons of async pipes * Fix `npm run test:types` * Correct user activity sorting test * Be more precise about log out messaging * Fix dev compare errors All stored values are serializable, the next step wasn't necessary and was erroring on some types that lack `toString`. * If the account in unlocked on load of lock component, navigate away from lock screen * Handle no users case for auth service statuses * Specify account to switch to * Filter active account out of inactive accounts * Prefer constructor init * Improve comparator * Use helper methods internally * Fixup component tests * Clarify name * Ensure accounts object has only valid userIds * Capitalize const values * Prefer descriptive, single-responsibility guards * Update libs/common/src/state-migrations/migrate.ts Co-authored-by: Justin Baur <19896123+justindbaur@users.noreply.github.com> * Fix merge * Add user Id validation activity for undefined was being set, which was resulting in requests for the auth status of `"undefined"` (string) userId, due to key enumeration. These changes stop that at both locations, as well as account add for good measure. --------- Co-authored-by: Justin Baur <19896123+justindbaur@users.noreply.github.com>
101 lines
3.1 KiB
TypeScript
101 lines
3.1 KiB
TypeScript
import {
|
|
Observable,
|
|
combineLatest,
|
|
distinctUntilChanged,
|
|
firstValueFrom,
|
|
map,
|
|
of,
|
|
shareReplay,
|
|
switchMap,
|
|
} from "rxjs";
|
|
|
|
import { ApiService } from "../../abstractions/api.service";
|
|
import { CryptoService } from "../../platform/abstractions/crypto.service";
|
|
import { MessagingService } from "../../platform/abstractions/messaging.service";
|
|
import { StateService } from "../../platform/abstractions/state.service";
|
|
import { Utils } from "../../platform/misc/utils";
|
|
import { UserId } from "../../types/guid";
|
|
import { AccountService } from "../abstractions/account.service";
|
|
import { AuthService as AuthServiceAbstraction } from "../abstractions/auth.service";
|
|
import { TokenService } from "../abstractions/token.service";
|
|
import { AuthenticationStatus } from "../enums/authentication-status";
|
|
|
|
export class AuthService implements AuthServiceAbstraction {
|
|
activeAccountStatus$: Observable<AuthenticationStatus>;
|
|
authStatuses$: Observable<Record<UserId, AuthenticationStatus>>;
|
|
|
|
constructor(
|
|
protected accountService: AccountService,
|
|
protected messagingService: MessagingService,
|
|
protected cryptoService: CryptoService,
|
|
protected apiService: ApiService,
|
|
protected stateService: StateService,
|
|
private tokenService: TokenService,
|
|
) {
|
|
this.activeAccountStatus$ = this.accountService.activeAccount$.pipe(
|
|
map((account) => account?.id),
|
|
switchMap((userId) => {
|
|
return this.authStatusFor$(userId);
|
|
}),
|
|
);
|
|
|
|
this.authStatuses$ = this.accountService.accounts$.pipe(
|
|
map((accounts) => Object.keys(accounts) as UserId[]),
|
|
switchMap((entries) => {
|
|
if (entries.length === 0) {
|
|
return of([] as { userId: UserId; status: AuthenticationStatus }[]);
|
|
}
|
|
return combineLatest(
|
|
entries.map((userId) =>
|
|
this.authStatusFor$(userId).pipe(map((status) => ({ userId, status }))),
|
|
),
|
|
);
|
|
}),
|
|
map((statuses) => {
|
|
return statuses.reduce(
|
|
(acc, { userId, status }) => {
|
|
acc[userId] = status;
|
|
return acc;
|
|
},
|
|
{} as Record<UserId, AuthenticationStatus>,
|
|
);
|
|
}),
|
|
);
|
|
}
|
|
|
|
authStatusFor$(userId: UserId): Observable<AuthenticationStatus> {
|
|
if (!Utils.isGuid(userId)) {
|
|
return of(AuthenticationStatus.LoggedOut);
|
|
}
|
|
|
|
return combineLatest([
|
|
this.cryptoService.getInMemoryUserKeyFor$(userId),
|
|
this.tokenService.hasAccessToken$(userId),
|
|
]).pipe(
|
|
map(([userKey, hasAccessToken]) => {
|
|
if (!hasAccessToken) {
|
|
return AuthenticationStatus.LoggedOut;
|
|
}
|
|
|
|
if (!userKey) {
|
|
return AuthenticationStatus.Locked;
|
|
}
|
|
|
|
return AuthenticationStatus.Unlocked;
|
|
}),
|
|
distinctUntilChanged(),
|
|
shareReplay({ bufferSize: 1, refCount: false }),
|
|
);
|
|
}
|
|
|
|
async getAuthStatus(userId?: string): Promise<AuthenticationStatus> {
|
|
userId ??= await firstValueFrom(this.accountService.activeAccount$.pipe(map((a) => a?.id)));
|
|
return await firstValueFrom(this.authStatusFor$(userId as UserId));
|
|
}
|
|
|
|
logOut(callback: () => void) {
|
|
callback();
|
|
this.messagingService.send("loggedOut");
|
|
}
|
|
}
|