From e1f8450ed621adf32eabfc2242bda46e76b23d47 Mon Sep 17 00:00:00 2001 From: Maciej Zieniuk Date: Tue, 25 Mar 2025 17:06:11 +0000 Subject: [PATCH] reactive key connector service --- .../convert-to-key-connector.command.ts | 3 -- .../components/remove-password.component.ts | 11 +++-- .../abstractions/key-connector.service.ts | 4 -- .../services/key-connector.service.ts | 44 ++++++++++--------- 4 files changed, 28 insertions(+), 34 deletions(-) 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 2f1f47a087c..3ba5ec662e4 100644 --- a/apps/cli/src/commands/convert-to-key-connector.command.ts +++ b/apps/cli/src/commands/convert-to-key-connector.command.ts @@ -67,9 +67,6 @@ export class ConvertToKeyConnectorCommand { throw e; } - await this.keyConnectorService.removeConvertAccountRequired(this.userId); - await this.keyConnectorService.setUsesKeyConnector(true, this.userId); - // Update environment URL - required for api key login const env = await firstValueFrom(this.environmentService.environment$); const urls = env.getUrls(); diff --git a/libs/angular/src/auth/components/remove-password.component.ts b/libs/angular/src/auth/components/remove-password.component.ts index ca04dd24cdc..b7d03dcff30 100644 --- a/libs/angular/src/auth/components/remove-password.component.ts +++ b/libs/angular/src/auth/components/remove-password.component.ts @@ -61,7 +61,7 @@ export class RemovePasswordComponent implements OnInit { return this.continuing || this.leaving; } - async convert() { + convert = async () => { this.continuing = true; try { @@ -71,7 +71,6 @@ export class RemovePasswordComponent implements OnInit { variant: "success", message: this.i18nService.t("removedMasterPassword"), }); - await this.keyConnectorService.removeConvertAccountRequired(this.activeUserId); await this.router.navigate([""]); } catch (e) { @@ -79,9 +78,9 @@ export class RemovePasswordComponent implements OnInit { this.handleActionError(e); } - } + }; - async leave() { + leave = async () => { const confirmed = await this.dialogService.openSimpleDialog({ title: this.organization.name, content: { key: "leaveOrganizationConfirmation" }, @@ -95,12 +94,12 @@ 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", message: this.i18nService.t("leftOrganization"), }); - await this.keyConnectorService.removeConvertAccountRequired(this.activeUserId); await this.router.navigate([""]); } catch (e) { @@ -108,7 +107,7 @@ export class RemovePasswordComponent implements OnInit { this.handleActionError(e); } - } + }; handleActionError(e: unknown) { let message = ""; 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 e7f6d54b070..cdfd708c244 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 @@ -11,8 +11,6 @@ export abstract class KeyConnectorService { abstract migrateUser(userId: UserId): Promise; - abstract userNeedsMigration(userId: UserId, organizations: Organization[]): Promise; - abstract convertNewSsoUserToKeyConnector( tokenResponse: IdentityTokenResponse, orgId: string, @@ -21,8 +19,6 @@ export abstract class KeyConnectorService { abstract setUsesKeyConnector(enabled: boolean, userId: UserId): Promise; - abstract setConvertAccountRequired(status: boolean | null, userId: UserId): Promise; - abstract getConvertAccountRequired(): Promise; abstract removeConvertAccountRequired(userId: UserId): Promise; 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 e5da277f556..0f3b49fb029 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,6 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { combineLatest, filter, firstValueFrom, of, switchMap } from "rxjs"; +import { combineLatest, distinctUntilChanged, filter, firstValueFrom, of, switchMap } from "rxjs"; import { LogoutReason } from "@bitwarden/auth/common"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; @@ -43,6 +43,7 @@ export const USES_KEY_CONNECTOR = new UserKeyDefinition( { deserializer: (usesKeyConnector) => usesKeyConnector, clearOn: ["logout"], + cleanupDelayMs: 0, }, ); @@ -52,6 +53,7 @@ export const CONVERT_ACCOUNT_TO_KEY_CONNECTOR = new UserKeyDefinition convertAccountToKeyConnector, clearOn: ["logout"], + cleanupDelayMs: 0, }, ); @@ -86,24 +88,29 @@ export class KeyConnectorService implements KeyConnectorServiceAbstraction { 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)), ]), ), - switchMap(async ([userId, organizations]) => { - const needsMigration = await this.userNeedsMigration(userId, organizations); + 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); } - return needsMigration; }), ) - .subscribe((needsMigration) => { - if (needsMigration) { - this.messagingService.send("convertAccountToKeyConnector"); - } - }); + .subscribe(); } async setUsesKeyConnector(usesKeyConnector: boolean, userId: UserId) { @@ -116,14 +123,6 @@ export class KeyConnectorService implements KeyConnectorServiceAbstraction { ); } - async userNeedsMigration(userId: UserId, organizations: Organization[]): Promise { - const loggedInUsingSso = await this.tokenService.getIsExternal(userId); - const requiredByOrganization = this.findManagingOrganization(organizations) != null; - const userIsNotUsingKeyConnector = !(await this.getUsesKeyConnector(userId)); - - return loggedInUsingSso && requiredByOrganization && userIsNotUsingKeyConnector; - } - async migrateUser(userId: UserId) { const organization = await this.getManagingOrganization(userId); const masterKey = await firstValueFrom(this.masterPasswordService.masterKey$(userId)); @@ -139,6 +138,9 @@ 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 @@ -210,10 +212,6 @@ export class KeyConnectorService implements KeyConnectorServiceAbstraction { await this.apiService.postSetKeyConnectorKey(setPasswordRequest); } - async setConvertAccountRequired(status: boolean | null, userId: UserId) { - await this.stateProvider.setUserState(CONVERT_ACCOUNT_TO_KEY_CONNECTOR, status, userId); - } - getConvertAccountRequired(): Promise { return firstValueFrom(this.convertAccountToKeyConnectorState.state$); } @@ -222,6 +220,10 @@ export class KeyConnectorService implements KeyConnectorServiceAbstraction { 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) {