From 7d1bdb8fe0c8a059828f5d765641be6c9af63b8a Mon Sep 17 00:00:00 2001 From: Maciej Zieniuk Date: Tue, 25 Mar 2025 19:07:48 +0000 Subject: [PATCH] introducing convertAccountRequired$ --- apps/cli/src/auth/commands/unlock.command.ts | 2 +- .../convert-to-key-connector.command.ts | 1 - .../components/remove-password.component.ts | 1 - libs/angular/src/auth/guards/auth.guard.ts | 2 +- .../abstractions/key-connector.service.ts | 6 +- .../services/key-connector.service.ts | 106 +++++++----------- 6 files changed, 43 insertions(+), 75 deletions(-) diff --git a/apps/cli/src/auth/commands/unlock.command.ts b/apps/cli/src/auth/commands/unlock.command.ts index 6fe0d7faff6..502355d5786 100644 --- a/apps/cli/src/auth/commands/unlock.command.ts +++ b/apps/cli/src/auth/commands/unlock.command.ts @@ -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, diff --git a/apps/cli/src/commands/convert-to-key-connector.command.ts b/apps/cli/src/commands/convert-to-key-connector.command.ts index 3ba5ec662e4..481c23fc5a0 100644 --- a/apps/cli/src/commands/convert-to-key-connector.command.ts +++ b/apps/cli/src/commands/convert-to-key-connector.command.ts @@ -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(); diff --git a/libs/angular/src/auth/components/remove-password.component.ts b/libs/angular/src/auth/components/remove-password.component.ts index b7d03dcff30..bc94f7d1177 100644 --- a/libs/angular/src/auth/components/remove-password.component.ts +++ b/libs/angular/src/auth/components/remove-password.component.ts @@ -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", diff --git a/libs/angular/src/auth/guards/auth.guard.ts b/libs/angular/src/auth/guards/auth.guard.ts index 329e365e542..690db37d090 100644 --- a/libs/angular/src/auth/guards/auth.guard.ts +++ b/libs/angular/src/auth/guards/auth.guard.ts @@ -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"]); } diff --git a/libs/common/src/key-management/key-connector/abstractions/key-connector.service.ts b/libs/common/src/key-management/key-connector/abstractions/key-connector.service.ts index cdfd708c244..131b941f274 100644 --- a/libs/common/src/key-management/key-connector/abstractions/key-connector.service.ts +++ b/libs/common/src/key-management/key-connector/abstractions/key-connector.service.ts @@ -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; - abstract getConvertAccountRequired(): Promise; - - abstract removeConvertAccountRequired(userId: UserId): Promise; + abstract convertAccountRequired$: Observable; } diff --git a/libs/common/src/key-management/key-connector/services/key-connector.service.ts b/libs/common/src/key-management/key-connector/services/key-connector.service.ts index 0f3b49fb029..9332aec2601 100644 --- a/libs/common/src/key-management/key-connector/services/key-connector.service.ts +++ b/libs/common/src/key-management/key-connector/services/key-connector.service.ts @@ -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( }, ); -export const CONVERT_ACCOUNT_TO_KEY_CONNECTOR = new UserKeyDefinition( - KEY_CONNECTOR_DISK, - "convertAccountToKeyConnector", - { - deserializer: (convertAccountToKeyConnector) => convertAccountToKeyConnector, - clearOn: ["logout"], - cleanupDelayMs: 0, - }, -); - export class KeyConnectorService implements KeyConnectorServiceAbstraction { - private usesKeyConnectorState: ActiveUserState; - private convertAccountToKeyConnectorState: ActiveUserState; + readonly convertAccountRequired$: Observable; 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 { - 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) {