1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-08 04:33:38 +00:00

introducing convertAccountRequired$

This commit is contained in:
Maciej Zieniuk
2025-03-25 19:07:48 +00:00
parent e1f8450ed6
commit 7d1bdb8fe0
6 changed files with 43 additions and 75 deletions

View File

@@ -71,7 +71,7 @@ export class UnlockCommand {
const userKey = await this.masterPasswordService.decryptUserKeyWithMasterKey(masterKey, userId);
await this.keyService.setUserKey(userKey, userId);
if (await this.keyConnectorService.getConvertAccountRequired()) {
if (await firstValueFrom(this.keyConnectorService.convertAccountRequired$)) {
const convertToKeyConnectorCommand = new ConvertToKeyConnectorCommand(
userId,
this.keyConnectorService,

View File

@@ -76,7 +76,6 @@ export class ConvertToKeyConnectorCommand {
return Response.success();
} else if (answer.convert === "leave") {
await this.organizationApiService.leave(organization.id);
await this.keyConnectorService.removeConvertAccountRequired(this.userId);
return Response.success();
} else {
await this.logout();

View File

@@ -94,7 +94,6 @@ export class RemovePasswordComponent implements OnInit {
this.leaving = true;
try {
await this.organizationApiService.leave(this.organization.id);
await this.keyConnectorService.removeConvertAccountRequired(this.activeUserId);
this.toastService.showToast({
variant: "success",

View File

@@ -47,7 +47,7 @@ export const authGuard: CanActivateFn = async (
if (
!routerState.url.includes("remove-password") &&
(await keyConnectorService.getConvertAccountRequired())
(await firstValueFrom(keyConnectorService.convertAccountRequired$))
) {
return router.createUrlTree(["/remove-password"]);
}

View File

@@ -1,3 +1,5 @@
import { Observable } from "rxjs";
import { Organization } from "../../../admin-console/models/domain/organization";
import { IdentityTokenResponse } from "../../../auth/models/response/identity-token.response";
import { UserId } from "../../../types/guid";
@@ -19,7 +21,5 @@ export abstract class KeyConnectorService {
abstract setUsesKeyConnector(enabled: boolean, userId: UserId): Promise<void>;
abstract getConvertAccountRequired(): Promise<boolean>;
abstract removeConvertAccountRequired(userId: UserId): Promise<void>;
abstract convertAccountRequired$: Observable<boolean>;
}

View File

@@ -1,6 +1,14 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { combineLatest, distinctUntilChanged, filter, firstValueFrom, of, switchMap } from "rxjs";
import {
combineLatest,
distinctUntilChanged,
filter,
firstValueFrom,
Observable,
of,
switchMap,
} from "rxjs";
import { LogoutReason } from "@bitwarden/auth/common";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
@@ -24,12 +32,7 @@ import { KeyGenerationService } from "../../../platform/abstractions/key-generat
import { LogService } from "../../../platform/abstractions/log.service";
import { Utils } from "../../../platform/misc/utils";
import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key";
import {
ActiveUserState,
KEY_CONNECTOR_DISK,
StateProvider,
UserKeyDefinition,
} from "../../../platform/state";
import { KEY_CONNECTOR_DISK, StateProvider, UserKeyDefinition } from "../../../platform/state";
import { UserId } from "../../../types/guid";
import { MasterKey } from "../../../types/key";
import { InternalMasterPasswordServiceAbstraction } from "../../master-password/abstractions/master-password.service.abstraction";
@@ -47,19 +50,8 @@ export const USES_KEY_CONNECTOR = new UserKeyDefinition<boolean | null>(
},
);
export const CONVERT_ACCOUNT_TO_KEY_CONNECTOR = new UserKeyDefinition<boolean | null>(
KEY_CONNECTOR_DISK,
"convertAccountToKeyConnector",
{
deserializer: (convertAccountToKeyConnector) => convertAccountToKeyConnector,
clearOn: ["logout"],
cleanupDelayMs: 0,
},
);
export class KeyConnectorService implements KeyConnectorServiceAbstraction {
private usesKeyConnectorState: ActiveUserState<boolean>;
private convertAccountToKeyConnectorState: ActiveUserState<boolean>;
readonly convertAccountRequired$: Observable<boolean>;
constructor(
accountService: AccountService,
@@ -74,43 +66,34 @@ export class KeyConnectorService implements KeyConnectorServiceAbstraction {
private stateProvider: StateProvider,
private messagingService: MessagingService,
) {
this.usesKeyConnectorState = this.stateProvider.getActive(USES_KEY_CONNECTOR);
this.convertAccountToKeyConnectorState = this.stateProvider.getActive(
CONVERT_ACCOUNT_TO_KEY_CONNECTOR,
this.convertAccountRequired$ = accountService.activeAccount$.pipe(
filter((account) => account != null),
switchMap((account) =>
combineLatest([
of(account.id),
this.organizationService
.organizations$(account.id)
.pipe(filter((organizations) => organizations != null)),
this.stateProvider
.getUserState$(USES_KEY_CONNECTOR, account.id)
.pipe(filter((usesKeyConnector) => usesKeyConnector != null)),
tokenService.hasAccessToken$(account.id).pipe(filter((hasToken) => hasToken)),
]),
),
distinctUntilChanged(),
switchMap(async ([userId, organizations, usesKeyConnector]) => {
const loggedInUsingSso = await this.tokenService.getIsExternal(userId);
const requiredByOrganization = this.findManagingOrganization(organizations) != null;
const userIsNotUsingKeyConnector = !usesKeyConnector;
const needsMigration =
loggedInUsingSso && requiredByOrganization && userIsNotUsingKeyConnector;
if (needsMigration) {
this.messagingService.send("convertAccountToKeyConnector");
}
return needsMigration;
}),
);
accountService.activeAccount$
.pipe(
filter((account) => account != null),
switchMap((account) =>
combineLatest([
of(account.id),
this.organizationService
.organizations$(account.id)
.pipe(filter((organizations) => organizations != null)),
this.stateProvider
.getUserState$(USES_KEY_CONNECTOR, account.id)
.pipe(filter((usesKeyConnector) => usesKeyConnector != null)),
tokenService.hasAccessToken$(account.id).pipe(filter((hasToken) => hasToken)),
]),
),
distinctUntilChanged(),
switchMap(async ([userId, organizations, usesKeyConnector]) => {
const loggedInUsingSso = await this.tokenService.getIsExternal(userId);
const requiredByOrganization = this.findManagingOrganization(organizations) != null;
const userIsNotUsingKeyConnector = !usesKeyConnector;
const needsMigration =
loggedInUsingSso && requiredByOrganization && userIsNotUsingKeyConnector;
if (needsMigration) {
await this.setConvertAccountRequired(true, userId);
this.messagingService.send("convertAccountToKeyConnector");
} else {
await this.removeConvertAccountRequired(userId);
}
}),
)
.subscribe();
}
async setUsesKeyConnector(usesKeyConnector: boolean, userId: UserId) {
@@ -140,7 +123,6 @@ export class KeyConnectorService implements KeyConnectorServiceAbstraction {
await this.apiService.postConvertToKeyConnector();
await this.setUsesKeyConnector(true, userId);
await this.removeConvertAccountRequired(userId);
}
// TODO: UserKey should be renamed to MasterKey and typed accordingly
@@ -212,18 +194,6 @@ export class KeyConnectorService implements KeyConnectorServiceAbstraction {
await this.apiService.postSetKeyConnectorKey(setPasswordRequest);
}
getConvertAccountRequired(): Promise<boolean> {
return firstValueFrom(this.convertAccountToKeyConnectorState.state$);
}
async removeConvertAccountRequired(userId: UserId) {
await this.setConvertAccountRequired(null, userId);
}
private async setConvertAccountRequired(status: boolean | null, userId: UserId) {
await this.stateProvider.setUserState(CONVERT_ACCOUNT_TO_KEY_CONNECTOR, status, userId);
}
private handleKeyConnectorError(e: any) {
this.logService.error(e);
if (this.logoutCallback != null) {