1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-19 17:53:39 +00:00
Files
browser/libs/common/src/auth/services/auth.service.ts
Matt Gibson c70a5aa024 [PM-6688] Use AccountService as account source (#8893)
* 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>
2024-04-30 09:13:02 -04:00

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");
}
}