mirror of
https://github.com/bitwarden/browser
synced 2025-12-16 08:13:42 +00:00
[PM-18017] Show key connector domain in remove password page (#14695)
* Passed in userId on RemovePasswordComponent. * Added userId on other references to KeyConnectorService methods * remove password component refactor, test coverage, enabled strict * explicit user id provided to key connector service * redirect to / instead when user not logged in or not managing organization * key connector service explicit user id * key connector service no longer requires account service * key connector service missing null type * cli convert to key connector unit tests * remove unnecessary SyncService * error toast not showing on ErrorResponse * bad import due to merge conflict * bad import due to merge conflict * missing loading in remove password component for browser extension * error handling in remove password component * organization observable race condition in key-connector * usesKeyConnector always returns boolean * unit test coverage * key connector reactive * reactive key connector service * introducing convertAccountRequired$ * cli build fix * moving message sending side effect to sync * key connector service unit tests * fix unit tests * move key connector components to KM team ownership * new unit tests in wrong place * key connector domain shown in remove password component * type safety improvements * convert to key connector command localization * key connector domain in convert to key connector command * convert to key connector command unit tests with prompt assert * organization name placement change in the remove password component * unit test update * key connector url required to be provided when migrating user * unit tests in wrong place after KM code ownership move * infinite page reload * failing unit tests * failing unit tests --------- Co-authored-by: Todd Martin <tmartin@bitwarden.com>
This commit is contained in:
@@ -5,13 +5,13 @@ import { IdentityTokenResponse } from "../../../auth/models/response/identity-to
|
||||
import { UserId } from "../../../types/guid";
|
||||
|
||||
export abstract class KeyConnectorService {
|
||||
abstract setMasterKeyFromUrl(url: string, userId: UserId): Promise<void>;
|
||||
abstract setMasterKeyFromUrl(keyConnectorUrl: string, userId: UserId): Promise<void>;
|
||||
|
||||
abstract getManagingOrganization(userId: UserId): Promise<Organization>;
|
||||
|
||||
abstract getUsesKeyConnector(userId: UserId): Promise<boolean>;
|
||||
|
||||
abstract migrateUser(userId: UserId): Promise<void>;
|
||||
abstract migrateUser(keyConnectorUrl: string, userId: UserId): Promise<void>;
|
||||
|
||||
abstract convertNewSsoUserToKeyConnector(
|
||||
tokenResponse: IdentityTokenResponse,
|
||||
|
||||
@@ -45,6 +45,8 @@ describe("KeyConnectorService", () => {
|
||||
key: "eO9nVlVl3I3sU6O+CyK0kEkpGtl/auT84Hig2WTXmZtDTqYtKpDvUPfjhgMOHf+KQzx++TVS2AOLYq856Caa7w==",
|
||||
});
|
||||
|
||||
const keyConnectorUrl = "https://key-connector-url.com";
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
|
||||
@@ -124,27 +126,9 @@ describe("KeyConnectorService", () => {
|
||||
it("should return the managing organization with key connector enabled", async () => {
|
||||
// Arrange
|
||||
const orgs = [
|
||||
organizationData(
|
||||
true,
|
||||
true,
|
||||
"https://key-connector-url.com",
|
||||
OrganizationUserType.User,
|
||||
false,
|
||||
),
|
||||
organizationData(
|
||||
false,
|
||||
true,
|
||||
"https://key-connector-url.com",
|
||||
OrganizationUserType.User,
|
||||
false,
|
||||
),
|
||||
organizationData(
|
||||
true,
|
||||
false,
|
||||
"https://key-connector-url.com",
|
||||
OrganizationUserType.User,
|
||||
false,
|
||||
),
|
||||
organizationData(true, true, keyConnectorUrl, OrganizationUserType.User, false),
|
||||
organizationData(false, true, keyConnectorUrl, OrganizationUserType.User, false),
|
||||
organizationData(true, false, keyConnectorUrl, OrganizationUserType.User, false),
|
||||
organizationData(true, true, "https://other-url.com", OrganizationUserType.User, false),
|
||||
];
|
||||
organizationService.organizations$.mockReturnValue(of(orgs));
|
||||
@@ -159,20 +143,8 @@ describe("KeyConnectorService", () => {
|
||||
it("should return undefined if no managing organization with key connector enabled is found", async () => {
|
||||
// Arrange
|
||||
const orgs = [
|
||||
organizationData(
|
||||
true,
|
||||
false,
|
||||
"https://key-connector-url.com",
|
||||
OrganizationUserType.User,
|
||||
false,
|
||||
),
|
||||
organizationData(
|
||||
false,
|
||||
false,
|
||||
"https://key-connector-url.com",
|
||||
OrganizationUserType.User,
|
||||
false,
|
||||
),
|
||||
organizationData(true, false, keyConnectorUrl, OrganizationUserType.User, false),
|
||||
organizationData(false, false, keyConnectorUrl, OrganizationUserType.User, false),
|
||||
];
|
||||
organizationService.organizations$.mockReturnValue(of(orgs));
|
||||
|
||||
@@ -186,8 +158,8 @@ describe("KeyConnectorService", () => {
|
||||
it("should return undefined if user is Owner or Admin", async () => {
|
||||
// Arrange
|
||||
const orgs = [
|
||||
organizationData(true, true, "https://key-connector-url.com", 0, false),
|
||||
organizationData(true, true, "https://key-connector-url.com", 1, false),
|
||||
organizationData(true, true, keyConnectorUrl, 0, false),
|
||||
organizationData(true, true, keyConnectorUrl, 1, false),
|
||||
];
|
||||
organizationService.organizations$.mockReturnValue(of(orgs));
|
||||
|
||||
@@ -201,20 +173,8 @@ describe("KeyConnectorService", () => {
|
||||
it("should return undefined if user is a Provider", async () => {
|
||||
// Arrange
|
||||
const orgs = [
|
||||
organizationData(
|
||||
true,
|
||||
true,
|
||||
"https://key-connector-url.com",
|
||||
OrganizationUserType.User,
|
||||
true,
|
||||
),
|
||||
organizationData(
|
||||
false,
|
||||
true,
|
||||
"https://key-connector-url.com",
|
||||
OrganizationUserType.User,
|
||||
true,
|
||||
),
|
||||
organizationData(true, true, keyConnectorUrl, OrganizationUserType.User, true),
|
||||
organizationData(false, true, keyConnectorUrl, OrganizationUserType.User, true),
|
||||
];
|
||||
organizationService.organizations$.mockReturnValue(of(orgs));
|
||||
|
||||
@@ -229,7 +189,7 @@ describe("KeyConnectorService", () => {
|
||||
describe("setMasterKeyFromUrl", () => {
|
||||
it("should set the master key from the provided URL", async () => {
|
||||
// Arrange
|
||||
const url = "https://key-connector-url.com";
|
||||
const url = keyConnectorUrl;
|
||||
|
||||
apiService.getMasterKeyFromKeyConnector.mockResolvedValue(mockMasterKeyResponse);
|
||||
|
||||
@@ -247,7 +207,7 @@ describe("KeyConnectorService", () => {
|
||||
|
||||
it("should handle errors thrown during the process", async () => {
|
||||
// Arrange
|
||||
const url = "https://key-connector-url.com";
|
||||
const url = keyConnectorUrl;
|
||||
|
||||
const error = new Error("Failed to get master key");
|
||||
apiService.getMasterKeyFromKeyConnector.mockRejectedValue(error);
|
||||
@@ -267,29 +227,20 @@ describe("KeyConnectorService", () => {
|
||||
describe("migrateUser", () => {
|
||||
it("should migrate the user to the key connector", async () => {
|
||||
// Arrange
|
||||
const organization = organizationData(
|
||||
true,
|
||||
true,
|
||||
"https://key-connector-url.com",
|
||||
OrganizationUserType.User,
|
||||
false,
|
||||
);
|
||||
const masterKey = getMockMasterKey();
|
||||
masterPasswordService.masterKeySubject.next(masterKey);
|
||||
const keyConnectorRequest = new KeyConnectorUserKeyRequest(
|
||||
Utils.fromBufferToB64(masterKey.inner().encryptionKey),
|
||||
);
|
||||
|
||||
jest.spyOn(keyConnectorService, "getManagingOrganization").mockResolvedValue(organization);
|
||||
jest.spyOn(apiService, "postUserKeyToKeyConnector").mockResolvedValue();
|
||||
|
||||
// Act
|
||||
await keyConnectorService.migrateUser(mockUserId);
|
||||
await keyConnectorService.migrateUser(keyConnectorUrl, mockUserId);
|
||||
|
||||
// Assert
|
||||
expect(keyConnectorService.getManagingOrganization).toHaveBeenCalled();
|
||||
expect(apiService.postUserKeyToKeyConnector).toHaveBeenCalledWith(
|
||||
organization.keyConnectorUrl,
|
||||
keyConnectorUrl,
|
||||
keyConnectorRequest,
|
||||
);
|
||||
expect(apiService.postConvertToKeyConnector).toHaveBeenCalled();
|
||||
@@ -297,34 +248,23 @@ describe("KeyConnectorService", () => {
|
||||
|
||||
it("should handle errors thrown during migration", async () => {
|
||||
// Arrange
|
||||
const organization = organizationData(
|
||||
true,
|
||||
true,
|
||||
"https://key-connector-url.com",
|
||||
OrganizationUserType.User,
|
||||
false,
|
||||
);
|
||||
const masterKey = getMockMasterKey();
|
||||
const keyConnectorRequest = new KeyConnectorUserKeyRequest(
|
||||
Utils.fromBufferToB64(masterKey.inner().encryptionKey),
|
||||
);
|
||||
const error = new Error("Failed to post user key to key connector");
|
||||
organizationService.organizations$.mockReturnValue(of([organization]));
|
||||
|
||||
masterPasswordService.masterKeySubject.next(masterKey);
|
||||
jest.spyOn(keyConnectorService, "getManagingOrganization").mockResolvedValue(organization);
|
||||
const error = new Error("Failed to post user key to key connector");
|
||||
jest.spyOn(apiService, "postUserKeyToKeyConnector").mockRejectedValue(error);
|
||||
jest.spyOn(logService, "error");
|
||||
|
||||
try {
|
||||
// Act
|
||||
await keyConnectorService.migrateUser(mockUserId);
|
||||
await keyConnectorService.migrateUser(keyConnectorUrl, mockUserId);
|
||||
} catch {
|
||||
// Assert
|
||||
expect(logService.error).toHaveBeenCalledWith(error);
|
||||
expect(keyConnectorService.getManagingOrganization).toHaveBeenCalled();
|
||||
expect(apiService.postUserKeyToKeyConnector).toHaveBeenCalledWith(
|
||||
organization.keyConnectorUrl,
|
||||
keyConnectorUrl,
|
||||
keyConnectorRequest,
|
||||
);
|
||||
}
|
||||
@@ -336,7 +276,7 @@ describe("KeyConnectorService", () => {
|
||||
const organization = organizationData(
|
||||
true,
|
||||
true,
|
||||
"https://key-connector-url.com",
|
||||
keyConnectorUrl,
|
||||
OrganizationUserType.User,
|
||||
false,
|
||||
);
|
||||
@@ -364,7 +304,7 @@ describe("KeyConnectorService", () => {
|
||||
const organization = organizationData(
|
||||
true,
|
||||
false,
|
||||
"https://key-connector-url.com",
|
||||
keyConnectorUrl,
|
||||
OrganizationUserType.User,
|
||||
false,
|
||||
);
|
||||
@@ -379,7 +319,7 @@ describe("KeyConnectorService", () => {
|
||||
const organization = organizationData(
|
||||
true,
|
||||
true,
|
||||
"https://key-connector-url.com",
|
||||
keyConnectorUrl,
|
||||
OrganizationUserType.Admin,
|
||||
false,
|
||||
);
|
||||
@@ -394,7 +334,7 @@ describe("KeyConnectorService", () => {
|
||||
const organization = organizationData(
|
||||
true,
|
||||
true,
|
||||
"https://key-connector-url.com",
|
||||
keyConnectorUrl,
|
||||
OrganizationUserType.Owner,
|
||||
false,
|
||||
);
|
||||
@@ -409,7 +349,7 @@ describe("KeyConnectorService", () => {
|
||||
const organization = organizationData(
|
||||
true,
|
||||
true,
|
||||
"https://key-connector-url.com",
|
||||
keyConnectorUrl,
|
||||
OrganizationUserType.User,
|
||||
true,
|
||||
);
|
||||
|
||||
@@ -90,18 +90,14 @@ export class KeyConnectorService implements KeyConnectorServiceAbstraction {
|
||||
);
|
||||
}
|
||||
|
||||
async migrateUser(userId: UserId) {
|
||||
const organization = await this.getManagingOrganization(userId);
|
||||
async migrateUser(keyConnectorUrl: string, userId: UserId) {
|
||||
const masterKey = await firstValueFrom(this.masterPasswordService.masterKey$(userId));
|
||||
const keyConnectorRequest = new KeyConnectorUserKeyRequest(
|
||||
Utils.fromBufferToB64(masterKey.inner().encryptionKey),
|
||||
);
|
||||
|
||||
try {
|
||||
await this.apiService.postUserKeyToKeyConnector(
|
||||
organization.keyConnectorUrl,
|
||||
keyConnectorRequest,
|
||||
);
|
||||
await this.apiService.postUserKeyToKeyConnector(keyConnectorUrl, keyConnectorRequest);
|
||||
} catch (e) {
|
||||
this.handleKeyConnectorError(e);
|
||||
}
|
||||
@@ -112,9 +108,9 @@ export class KeyConnectorService implements KeyConnectorServiceAbstraction {
|
||||
}
|
||||
|
||||
// TODO: UserKey should be renamed to MasterKey and typed accordingly
|
||||
async setMasterKeyFromUrl(url: string, userId: UserId) {
|
||||
async setMasterKeyFromUrl(keyConnectorUrl: string, userId: UserId) {
|
||||
try {
|
||||
const masterKeyResponse = await this.apiService.getMasterKeyFromKeyConnector(url);
|
||||
const masterKeyResponse = await this.apiService.getMasterKeyFromKeyConnector(keyConnectorUrl);
|
||||
const keyArr = Utils.fromB64ToArray(masterKeyResponse.key);
|
||||
const masterKey = new SymmetricCryptoKey(keyArr) as MasterKey;
|
||||
await this.masterPasswordService.setMasterKey(masterKey, userId);
|
||||
@@ -192,7 +188,7 @@ export class KeyConnectorService implements KeyConnectorServiceAbstraction {
|
||||
throw new Error("Key Connector error");
|
||||
}
|
||||
|
||||
private findManagingOrganization(organizations: Organization[]) {
|
||||
private findManagingOrganization(organizations: Organization[]): Organization | undefined {
|
||||
return organizations.find(
|
||||
(o) =>
|
||||
o.keyConnectorEnabled &&
|
||||
|
||||
Reference in New Issue
Block a user