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

reactive key connector service

This commit is contained in:
Maciej Zieniuk
2025-03-25 17:06:11 +00:00
parent 9ca4939502
commit e1f8450ed6
4 changed files with 28 additions and 34 deletions

View File

@@ -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();

View File

@@ -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 = "";

View File

@@ -11,8 +11,6 @@ export abstract class KeyConnectorService {
abstract migrateUser(userId: UserId): Promise<void>;
abstract userNeedsMigration(userId: UserId, organizations: Organization[]): Promise<boolean>;
abstract convertNewSsoUserToKeyConnector(
tokenResponse: IdentityTokenResponse,
orgId: string,
@@ -21,8 +19,6 @@ export abstract class KeyConnectorService {
abstract setUsesKeyConnector(enabled: boolean, userId: UserId): Promise<void>;
abstract setConvertAccountRequired(status: boolean | null, userId: UserId): Promise<void>;
abstract getConvertAccountRequired(): Promise<boolean>;
abstract removeConvertAccountRequired(userId: UserId): Promise<void>;

View File

@@ -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<boolean | null>(
{
deserializer: (usesKeyConnector) => usesKeyConnector,
clearOn: ["logout"],
cleanupDelayMs: 0,
},
);
@@ -52,6 +53,7 @@ export const CONVERT_ACCOUNT_TO_KEY_CONNECTOR = new UserKeyDefinition<boolean |
{
deserializer: (convertAccountToKeyConnector) => 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<boolean> {
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<boolean> {
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) {