mirror of
https://github.com/bitwarden/browser
synced 2025-12-23 19:53:43 +00:00
[PM-3797 Part 2] Create Account Recovery Service (#6667)
* create account recovery service * update legacy migration tests * declare account recovery service in migrate component * create account recovery module * remove changes to core organization module * use viewContainerRef to allow dependency injection on modal * fix imports
This commit is contained in:
@@ -2,12 +2,6 @@
|
||||
import { BehaviorSubject } from "rxjs";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service";
|
||||
import { OrganizationUserResetPasswordEnrollmentRequest } from "@bitwarden/common/admin-console/abstractions/organization-user/requests";
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { OrganizationKeysResponse } from "@bitwarden/common/admin-console/models/response/organization-keys.response";
|
||||
import { OrganizationApiService } from "@bitwarden/common/admin-console/services/organization/organization-api.service";
|
||||
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
|
||||
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
|
||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
||||
@@ -29,6 +23,7 @@ import { Folder } from "@bitwarden/common/vault/models/domain/folder";
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
import { FolderView } from "@bitwarden/common/vault/models/view/folder.view";
|
||||
|
||||
import { AccountRecoveryService } from "../../admin-console/organizations/members/services/account-recovery/account-recovery.service";
|
||||
import { EmergencyAccessService } from "../emergency-access";
|
||||
|
||||
import { MigrateFromLegacyEncryptionService } from "./migrate-legacy-encryption.service";
|
||||
@@ -36,10 +31,8 @@ import { MigrateFromLegacyEncryptionService } from "./migrate-legacy-encryption.
|
||||
describe("migrateFromLegacyEncryptionService", () => {
|
||||
let migrateFromLegacyEncryptionService: MigrateFromLegacyEncryptionService;
|
||||
|
||||
const organizationService = mock<OrganizationService>();
|
||||
const organizationApiService = mock<OrganizationApiService>();
|
||||
const organizationUserService = mock<OrganizationUserService>();
|
||||
const emergencyAccessService = mock<EmergencyAccessService>();
|
||||
const accountRecoveryService = mock<AccountRecoveryService>();
|
||||
const apiService = mock<ApiService>();
|
||||
const encryptService = mock<EncryptService>();
|
||||
const cryptoService = mock<CryptoService>();
|
||||
@@ -55,10 +48,8 @@ describe("migrateFromLegacyEncryptionService", () => {
|
||||
jest.clearAllMocks();
|
||||
|
||||
migrateFromLegacyEncryptionService = new MigrateFromLegacyEncryptionService(
|
||||
organizationService,
|
||||
organizationApiService,
|
||||
organizationUserService,
|
||||
emergencyAccessService,
|
||||
accountRecoveryService,
|
||||
apiService,
|
||||
cryptoService,
|
||||
encryptService,
|
||||
@@ -211,68 +202,6 @@ describe("migrateFromLegacyEncryptionService", () => {
|
||||
expect(emergencyAccessService.rotate).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("updateAllAdminRecoveryKeys", () => {
|
||||
let mockMasterPassword: string;
|
||||
let mockUserKey: UserKey;
|
||||
|
||||
beforeEach(() => {
|
||||
mockMasterPassword = "mockMasterPassword";
|
||||
|
||||
const mockRandomBytes = new Uint8Array(64) as CsprngArray;
|
||||
mockUserKey = new SymmetricCryptoKey(mockRandomBytes) as UserKey;
|
||||
|
||||
organizationService.getAll.mockResolvedValue([
|
||||
createOrganization("1", "Org 1", true),
|
||||
createOrganization("2", "Org 2", true),
|
||||
createOrganization("3", "Org 3", false),
|
||||
createOrganization("4", "Org 4", false),
|
||||
]);
|
||||
|
||||
organizationApiService.getKeys.mockImplementation((orgId) => {
|
||||
return Promise.resolve({
|
||||
publicKey: orgId + "mockPublicKey",
|
||||
privateKey: orgId + "mockPrivateKey",
|
||||
} as OrganizationKeysResponse);
|
||||
});
|
||||
});
|
||||
|
||||
it("Only updates organizations that are enrolled in admin recovery", async () => {
|
||||
await migrateFromLegacyEncryptionService.updateAllAdminRecoveryKeys(
|
||||
mockMasterPassword,
|
||||
mockUserKey,
|
||||
);
|
||||
|
||||
expect(
|
||||
organizationUserService.putOrganizationUserResetPasswordEnrollment,
|
||||
).toHaveBeenCalledWith(
|
||||
"1",
|
||||
expect.any(String),
|
||||
expect.any(OrganizationUserResetPasswordEnrollmentRequest),
|
||||
);
|
||||
expect(
|
||||
organizationUserService.putOrganizationUserResetPasswordEnrollment,
|
||||
).toHaveBeenCalledWith(
|
||||
"2",
|
||||
expect.any(String),
|
||||
expect.any(OrganizationUserResetPasswordEnrollmentRequest),
|
||||
);
|
||||
expect(
|
||||
organizationUserService.putOrganizationUserResetPasswordEnrollment,
|
||||
).not.toHaveBeenCalledWith(
|
||||
"3",
|
||||
expect.any(String),
|
||||
expect.any(OrganizationUserResetPasswordEnrollmentRequest),
|
||||
);
|
||||
expect(
|
||||
organizationUserService.putOrganizationUserResetPasswordEnrollment,
|
||||
).not.toHaveBeenCalledWith(
|
||||
"4",
|
||||
expect.any(String),
|
||||
expect.any(OrganizationUserResetPasswordEnrollmentRequest),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function createMockFolder(id: string, name: string): FolderView {
|
||||
@@ -295,12 +224,3 @@ function createMockSend(id: string, name: string): Send {
|
||||
send.name = new EncString(EncryptionType.AesCbc256_HmacSha256_B64, name);
|
||||
return send;
|
||||
}
|
||||
|
||||
function createOrganization(id: string, name: string, resetPasswordEnrolled: boolean) {
|
||||
const org = new Organization();
|
||||
org.id = id;
|
||||
org.name = name;
|
||||
org.resetPasswordEnrolled = resetPasswordEnrolled;
|
||||
org.userId = "mockUserID";
|
||||
return org;
|
||||
}
|
||||
|
||||
@@ -2,15 +2,10 @@
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
|
||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service";
|
||||
import { OrganizationUserResetPasswordEnrollmentRequest } from "@bitwarden/common/admin-console/abstractions/organization-user/requests";
|
||||
import { UpdateKeyRequest } from "@bitwarden/common/models/request/update-key.request";
|
||||
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
|
||||
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
|
||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { EncryptedString, EncString } from "@bitwarden/common/platform/models/domain/enc-string";
|
||||
import { UserKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
||||
import { SendWithIdRequest } from "@bitwarden/common/tools/send/models/request/send-with-id.request";
|
||||
@@ -21,16 +16,15 @@ import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.serv
|
||||
import { CipherWithIdRequest } from "@bitwarden/common/vault/models/request/cipher-with-id.request";
|
||||
import { FolderWithIdRequest } from "@bitwarden/common/vault/models/request/folder-with-id.request";
|
||||
|
||||
import { AccountRecoveryService } from "../../admin-console/organizations/members/services/account-recovery/account-recovery.service";
|
||||
import { EmergencyAccessService } from "../emergency-access";
|
||||
|
||||
// TODO: PM-3797 - This service should be expanded and used for user key rotations in change-password.component.ts
|
||||
@Injectable()
|
||||
export class MigrateFromLegacyEncryptionService {
|
||||
constructor(
|
||||
private organizationService: OrganizationService,
|
||||
private organizationApiService: OrganizationApiServiceAbstraction,
|
||||
private organizationUserService: OrganizationUserService,
|
||||
private emergencyAccessService: EmergencyAccessService,
|
||||
private accountRecoveryService: AccountRecoveryService,
|
||||
private apiService: ApiService,
|
||||
private cryptoService: CryptoService,
|
||||
private encryptService: EncryptService,
|
||||
@@ -112,35 +106,11 @@ export class MigrateFromLegacyEncryptionService {
|
||||
* @param newUserKey The new user key
|
||||
*/
|
||||
async updateAllAdminRecoveryKeys(masterPassword: string, newUserKey: UserKey) {
|
||||
const allOrgs = await this.organizationService.getAll();
|
||||
|
||||
for (const org of allOrgs) {
|
||||
// If not already enrolled, skip
|
||||
if (!org.resetPasswordEnrolled) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Retrieve public key
|
||||
const response = await this.organizationApiService.getKeys(org.id);
|
||||
const publicKey = Utils.fromB64ToArray(response?.publicKey);
|
||||
|
||||
// Re-enroll - encrypt user key with organization public key
|
||||
const encryptedKey = await this.cryptoService.rsaEncrypt(newUserKey.key, publicKey);
|
||||
|
||||
// Create/Execute request
|
||||
const request = new OrganizationUserResetPasswordEnrollmentRequest();
|
||||
request.resetPasswordKey = encryptedKey.encryptedString;
|
||||
request.masterPasswordHash = await this.cryptoService.hashMasterKey(
|
||||
masterPassword,
|
||||
await this.cryptoService.getOrDeriveMasterKey(masterPassword),
|
||||
);
|
||||
|
||||
await this.organizationUserService.putOrganizationUserResetPasswordEnrollment(
|
||||
org.id,
|
||||
org.userId,
|
||||
request,
|
||||
);
|
||||
}
|
||||
const masterPasswordHash = await this.cryptoService.hashMasterKey(
|
||||
masterPassword,
|
||||
await this.cryptoService.getOrDeriveMasterKey(masterPassword),
|
||||
);
|
||||
await this.accountRecoveryService.rotate(newUserKey, masterPasswordHash);
|
||||
}
|
||||
|
||||
private async encryptPrivateKey(newUserKey: UserKey): Promise<EncryptedString | null> {
|
||||
|
||||
Reference in New Issue
Block a user