mirror of
https://github.com/bitwarden/browser
synced 2025-12-13 06:43:35 +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:
@@ -11,24 +11,17 @@ import { Subject, takeUntil } from "rxjs";
|
|||||||
import zxcvbn from "zxcvbn";
|
import zxcvbn from "zxcvbn";
|
||||||
|
|
||||||
import { PasswordStrengthComponent } from "@bitwarden/angular/shared/components/password-strength/password-strength.component";
|
import { PasswordStrengthComponent } from "@bitwarden/angular/shared/components/password-strength/password-strength.component";
|
||||||
import { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service";
|
|
||||||
import { OrganizationUserResetPasswordRequest } from "@bitwarden/common/admin-console/abstractions/organization-user/requests";
|
|
||||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||||
import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options";
|
import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options";
|
||||||
import { KdfConfig } from "@bitwarden/common/auth/models/domain/kdf-config";
|
|
||||||
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
|
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||||
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
|
|
||||||
import {
|
|
||||||
SymmetricCryptoKey,
|
|
||||||
UserKey,
|
|
||||||
} from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
|
||||||
import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password";
|
import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password";
|
||||||
import { DialogService } from "@bitwarden/components";
|
import { DialogService } from "@bitwarden/components";
|
||||||
|
|
||||||
|
import { AccountRecoveryService } from "../services/account-recovery/account-recovery.service";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: "app-reset-password",
|
selector: "app-reset-password",
|
||||||
templateUrl: "reset-password.component.html",
|
templateUrl: "reset-password.component.html",
|
||||||
@@ -50,13 +43,12 @@ export class ResetPasswordComponent implements OnInit, OnDestroy {
|
|||||||
private destroy$ = new Subject<void>();
|
private destroy$ = new Subject<void>();
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
private accountRecoveryService: AccountRecoveryService,
|
||||||
private i18nService: I18nService,
|
private i18nService: I18nService,
|
||||||
private platformUtilsService: PlatformUtilsService,
|
private platformUtilsService: PlatformUtilsService,
|
||||||
private passwordGenerationService: PasswordGenerationServiceAbstraction,
|
private passwordGenerationService: PasswordGenerationServiceAbstraction,
|
||||||
private policyService: PolicyService,
|
private policyService: PolicyService,
|
||||||
private cryptoService: CryptoService,
|
|
||||||
private logService: LogService,
|
private logService: LogService,
|
||||||
private organizationUserService: OrganizationUserService,
|
|
||||||
private dialogService: DialogService,
|
private dialogService: DialogService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@@ -151,64 +143,13 @@ export class ResetPasswordComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get user Information (kdf type, kdf iterations, resetPasswordKey, private key) and change password
|
|
||||||
try {
|
try {
|
||||||
this.formPromise = this.organizationUserService
|
this.formPromise = this.accountRecoveryService.resetMasterPassword(
|
||||||
.getOrganizationUserResetPasswordDetails(this.organizationId, this.id)
|
this.newPassword,
|
||||||
.then(async (response) => {
|
this.email,
|
||||||
if (response == null) {
|
this.id,
|
||||||
throw new Error(this.i18nService.t("resetPasswordDetailsError"));
|
this.organizationId,
|
||||||
}
|
);
|
||||||
|
|
||||||
const kdfType = response.kdf;
|
|
||||||
const kdfIterations = response.kdfIterations;
|
|
||||||
const kdfMemory = response.kdfMemory;
|
|
||||||
const kdfParallelism = response.kdfParallelism;
|
|
||||||
const resetPasswordKey = response.resetPasswordKey;
|
|
||||||
const encryptedPrivateKey = response.encryptedPrivateKey;
|
|
||||||
|
|
||||||
// Decrypt Organization's encrypted Private Key with org key
|
|
||||||
const orgSymKey = await this.cryptoService.getOrgKey(this.organizationId);
|
|
||||||
const decPrivateKey = await this.cryptoService.decryptToBytes(
|
|
||||||
new EncString(encryptedPrivateKey),
|
|
||||||
orgSymKey,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Decrypt User's Reset Password Key to get UserKey
|
|
||||||
const decValue = await this.cryptoService.rsaDecrypt(resetPasswordKey, decPrivateKey);
|
|
||||||
const existingUserKey = new SymmetricCryptoKey(decValue) as UserKey;
|
|
||||||
|
|
||||||
// Create new master key and hash new password
|
|
||||||
const newMasterKey = await this.cryptoService.makeMasterKey(
|
|
||||||
this.newPassword,
|
|
||||||
this.email.trim().toLowerCase(),
|
|
||||||
kdfType,
|
|
||||||
new KdfConfig(kdfIterations, kdfMemory, kdfParallelism),
|
|
||||||
);
|
|
||||||
const newMasterKeyHash = await this.cryptoService.hashMasterKey(
|
|
||||||
this.newPassword,
|
|
||||||
newMasterKey,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Create new encrypted user key for the User
|
|
||||||
const newUserKey = await this.cryptoService.encryptUserKeyWithMasterKey(
|
|
||||||
newMasterKey,
|
|
||||||
existingUserKey,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Create request
|
|
||||||
const request = new OrganizationUserResetPasswordRequest();
|
|
||||||
request.key = newUserKey[1].encryptedString;
|
|
||||||
request.newMasterPasswordHash = newMasterKeyHash;
|
|
||||||
|
|
||||||
// Change user's password
|
|
||||||
return this.organizationUserService.putOrganizationUserResetPassword(
|
|
||||||
this.organizationId,
|
|
||||||
this.id,
|
|
||||||
request,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
await this.formPromise;
|
await this.formPromise;
|
||||||
this.platformUtilsService.showToast(
|
this.platformUtilsService.showToast(
|
||||||
"success",
|
"success",
|
||||||
@@ -219,6 +160,7 @@ export class ResetPasswordComponent implements OnInit, OnDestroy {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.logService.error(e);
|
this.logService.error(e);
|
||||||
}
|
}
|
||||||
|
this.formPromise = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
getStrengthResult(result: zxcvbn.ZXCVBNResult) {
|
getStrengthResult(result: zxcvbn.ZXCVBNResult) {
|
||||||
|
|||||||
@@ -0,0 +1,216 @@
|
|||||||
|
import { mock, MockProxy } from "jest-mock-extended";
|
||||||
|
|
||||||
|
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 { OrganizationUserResetPasswordDetailsResponse } from "@bitwarden/common/admin-console/abstractions/organization-user/responses";
|
||||||
|
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 { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
|
import { EncryptionType, KdfType } from "@bitwarden/common/platform/enums";
|
||||||
|
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
|
||||||
|
import {
|
||||||
|
MasterKey,
|
||||||
|
OrgKey,
|
||||||
|
SymmetricCryptoKey,
|
||||||
|
UserKey,
|
||||||
|
} from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
||||||
|
import { CsprngArray } from "@bitwarden/common/types/csprng";
|
||||||
|
|
||||||
|
import { AccountRecoveryService } from "./account-recovery.service";
|
||||||
|
|
||||||
|
describe("AccountRecoveryService", () => {
|
||||||
|
let sut: AccountRecoveryService;
|
||||||
|
|
||||||
|
let cryptoService: MockProxy<CryptoService>;
|
||||||
|
let encryptService: MockProxy<EncryptService>;
|
||||||
|
let organizationService: MockProxy<OrganizationService>;
|
||||||
|
let organizationUserService: MockProxy<OrganizationUserService>;
|
||||||
|
let organizationApiService: MockProxy<OrganizationApiService>;
|
||||||
|
let i18nService: MockProxy<I18nService>;
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
cryptoService = mock<CryptoService>();
|
||||||
|
encryptService = mock<EncryptService>();
|
||||||
|
organizationService = mock<OrganizationService>();
|
||||||
|
organizationUserService = mock<OrganizationUserService>();
|
||||||
|
organizationApiService = mock<OrganizationApiService>();
|
||||||
|
i18nService = mock<I18nService>();
|
||||||
|
|
||||||
|
sut = new AccountRecoveryService(
|
||||||
|
cryptoService,
|
||||||
|
encryptService,
|
||||||
|
organizationService,
|
||||||
|
organizationUserService,
|
||||||
|
organizationApiService,
|
||||||
|
i18nService,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.resetAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should be created", () => {
|
||||||
|
expect(sut).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("getRecoveryKey", () => {
|
||||||
|
const mockOrgId = "test-org-id";
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
organizationApiService.getKeys.mockResolvedValue(
|
||||||
|
new OrganizationKeysResponse({
|
||||||
|
privateKey: "test-private-key",
|
||||||
|
publicKey: "test-public-key",
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const mockRandomBytes = new Uint8Array(64) as CsprngArray;
|
||||||
|
const mockUserKey = new SymmetricCryptoKey(mockRandomBytes) as UserKey;
|
||||||
|
cryptoService.getUserKey.mockResolvedValue(mockUserKey);
|
||||||
|
|
||||||
|
cryptoService.rsaEncrypt.mockResolvedValue(
|
||||||
|
new EncString(EncryptionType.Rsa2048_OaepSha1_B64, "mockEncryptedUserKey"),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return an encrypted user key", async () => {
|
||||||
|
const encryptedString = await sut.buildRecoveryKey(mockOrgId);
|
||||||
|
expect(encryptedString).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should only use the user key from memory if one is not provided", async () => {
|
||||||
|
const mockRandomBytes = new Uint8Array(64) as CsprngArray;
|
||||||
|
const mockUserKey = new SymmetricCryptoKey(mockRandomBytes) as UserKey;
|
||||||
|
|
||||||
|
await sut.buildRecoveryKey(mockOrgId, mockUserKey);
|
||||||
|
|
||||||
|
expect(cryptoService.getUserKey).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should throw an error if the organization keys are null", async () => {
|
||||||
|
organizationApiService.getKeys.mockResolvedValue(null);
|
||||||
|
await expect(sut.buildRecoveryKey(mockOrgId)).rejects.toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should throw an error if the user key can't be found", async () => {
|
||||||
|
cryptoService.getUserKey.mockResolvedValue(null);
|
||||||
|
await expect(sut.buildRecoveryKey(mockOrgId)).rejects.toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should rsa encrypt the user key", async () => {
|
||||||
|
await sut.buildRecoveryKey(mockOrgId);
|
||||||
|
|
||||||
|
expect(cryptoService.rsaEncrypt).toHaveBeenCalledWith(expect.anything(), expect.anything());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("resetMasterPassword", () => {
|
||||||
|
const mockNewMP = "new-password";
|
||||||
|
const mockEmail = "test@example.com";
|
||||||
|
const mockOrgUserId = "test-org-user-id";
|
||||||
|
const mockOrgId = "test-org-id";
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
organizationUserService.getOrganizationUserResetPasswordDetails.mockResolvedValue(
|
||||||
|
new OrganizationUserResetPasswordDetailsResponse({
|
||||||
|
kdf: KdfType.PBKDF2_SHA256,
|
||||||
|
kdfIterations: 5000,
|
||||||
|
resetPasswordKey: "test-reset-password-key",
|
||||||
|
encryptedPrivateKey: "test-encrypted-private-key",
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const mockRandomBytes = new Uint8Array(64) as CsprngArray;
|
||||||
|
const mockOrgKey = new SymmetricCryptoKey(mockRandomBytes) as OrgKey;
|
||||||
|
cryptoService.getOrgKey.mockResolvedValue(mockOrgKey);
|
||||||
|
encryptService.decryptToBytes.mockResolvedValue(mockRandomBytes);
|
||||||
|
|
||||||
|
cryptoService.rsaDecrypt.mockResolvedValue(mockRandomBytes);
|
||||||
|
const mockMasterKey = new SymmetricCryptoKey(mockRandomBytes) as MasterKey;
|
||||||
|
cryptoService.makeMasterKey.mockResolvedValue(mockMasterKey);
|
||||||
|
cryptoService.hashMasterKey.mockResolvedValue("test-master-key-hash");
|
||||||
|
|
||||||
|
const mockUserKey = new SymmetricCryptoKey(mockRandomBytes) as UserKey;
|
||||||
|
cryptoService.encryptUserKeyWithMasterKey.mockResolvedValue([
|
||||||
|
mockUserKey,
|
||||||
|
new EncString(EncryptionType.AesCbc256_HmacSha256_B64, "test-encrypted-user-key"),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should reset the user's master password", async () => {
|
||||||
|
await sut.resetMasterPassword(mockNewMP, mockEmail, mockOrgUserId, mockOrgId);
|
||||||
|
expect(organizationUserService.putOrganizationUserResetPassword).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should throw an error if the user details are null", async () => {
|
||||||
|
organizationUserService.getOrganizationUserResetPasswordDetails.mockResolvedValue(null);
|
||||||
|
await expect(
|
||||||
|
sut.resetMasterPassword(mockNewMP, mockEmail, mockOrgUserId, mockOrgId),
|
||||||
|
).rejects.toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should throw an error if the org key is null", async () => {
|
||||||
|
cryptoService.getOrgKey.mockResolvedValue(null);
|
||||||
|
await expect(
|
||||||
|
sut.resetMasterPassword(mockNewMP, mockEmail, mockOrgUserId, mockOrgId),
|
||||||
|
).rejects.toThrow();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("rotate", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
organizationService.getAll.mockResolvedValue([
|
||||||
|
createOrganization("1", "org1"),
|
||||||
|
createOrganization("2", "org2"),
|
||||||
|
]);
|
||||||
|
organizationApiService.getKeys.mockResolvedValue(
|
||||||
|
new OrganizationKeysResponse({
|
||||||
|
privateKey: "test-private-key",
|
||||||
|
publicKey: "test-public-key",
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
cryptoService.rsaEncrypt.mockResolvedValue(
|
||||||
|
new EncString(EncryptionType.Rsa2048_OaepSha1_B64, "mockEncryptedUserKey"),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should rotate all of the user's recovery key", async () => {
|
||||||
|
organizationApiService.getKeys.mockResolvedValue(
|
||||||
|
new OrganizationKeysResponse({
|
||||||
|
privateKey: "test-private-key",
|
||||||
|
publicKey: "test-public-key",
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
cryptoService.rsaEncrypt.mockResolvedValue(
|
||||||
|
new EncString(EncryptionType.Rsa2048_OaepSha1_B64, "mockEncryptedUserKey"),
|
||||||
|
);
|
||||||
|
organizationService.getAll.mockResolvedValue([
|
||||||
|
createOrganization("1", "org1"),
|
||||||
|
createOrganization("2", "org2"),
|
||||||
|
]);
|
||||||
|
|
||||||
|
await sut.rotate(
|
||||||
|
new SymmetricCryptoKey(new Uint8Array(64)) as UserKey,
|
||||||
|
"test-master-password-hash",
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
organizationUserService.putOrganizationUserResetPasswordEnrollment,
|
||||||
|
).toHaveBeenCalledTimes(2);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function createOrganization(id: string, name: string) {
|
||||||
|
const org = new Organization();
|
||||||
|
org.id = id;
|
||||||
|
org.name = name;
|
||||||
|
org.identifier = name;
|
||||||
|
org.isMember = true;
|
||||||
|
org.resetPasswordEnrolled = true;
|
||||||
|
return org;
|
||||||
|
}
|
||||||
@@ -0,0 +1,155 @@
|
|||||||
|
import { Injectable } from "@angular/core";
|
||||||
|
|
||||||
|
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,
|
||||||
|
OrganizationUserResetPasswordRequest,
|
||||||
|
} from "@bitwarden/common/admin-console/abstractions/organization-user/requests";
|
||||||
|
import { KdfConfig } from "@bitwarden/common/auth/models/domain/kdf-config";
|
||||||
|
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
|
||||||
|
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
|
||||||
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
|
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||||
|
import { EncryptedString, EncString } from "@bitwarden/common/platform/models/domain/enc-string";
|
||||||
|
import {
|
||||||
|
SymmetricCryptoKey,
|
||||||
|
UserKey,
|
||||||
|
} from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: "root",
|
||||||
|
})
|
||||||
|
export class AccountRecoveryService {
|
||||||
|
constructor(
|
||||||
|
private cryptoService: CryptoService,
|
||||||
|
private encryptService: EncryptService,
|
||||||
|
private organizationService: OrganizationService,
|
||||||
|
private organizationUserService: OrganizationUserService,
|
||||||
|
private organizationApiService: OrganizationApiServiceAbstraction,
|
||||||
|
private i18nService: I18nService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the user key encrypted by the organization's public key.
|
||||||
|
* Intended for use in enrollment
|
||||||
|
* @param orgId desired organization
|
||||||
|
*/
|
||||||
|
async buildRecoveryKey(orgId: string, userKey?: UserKey): Promise<EncryptedString> {
|
||||||
|
// Retrieve Public Key
|
||||||
|
const orgKeys = await this.organizationApiService.getKeys(orgId);
|
||||||
|
if (orgKeys == null) {
|
||||||
|
throw new Error(this.i18nService.t("resetPasswordOrgKeysError"));
|
||||||
|
}
|
||||||
|
|
||||||
|
const publicKey = Utils.fromB64ToArray(orgKeys.publicKey);
|
||||||
|
|
||||||
|
// RSA Encrypt user key with organization's public key
|
||||||
|
userKey ??= await this.cryptoService.getUserKey();
|
||||||
|
if (userKey == null) {
|
||||||
|
throw new Error("No user key found");
|
||||||
|
}
|
||||||
|
const encryptedKey = await this.cryptoService.rsaEncrypt(userKey.key, publicKey);
|
||||||
|
|
||||||
|
return encryptedKey.encryptedString;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a user's master password through account recovery.
|
||||||
|
* Intended for organization admins
|
||||||
|
* @param newMasterPassword user's new master password
|
||||||
|
* @param email user's email
|
||||||
|
* @param orgUserId organization user's id
|
||||||
|
* @param orgId organization id
|
||||||
|
*/
|
||||||
|
async resetMasterPassword(
|
||||||
|
newMasterPassword: string,
|
||||||
|
email: string,
|
||||||
|
orgUserId: string,
|
||||||
|
orgId: string,
|
||||||
|
): Promise<void> {
|
||||||
|
const response = await this.organizationUserService.getOrganizationUserResetPasswordDetails(
|
||||||
|
orgId,
|
||||||
|
orgUserId,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response == null) {
|
||||||
|
throw new Error(this.i18nService.t("resetPasswordDetailsError"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decrypt Organization's encrypted Private Key with org key
|
||||||
|
const orgSymKey = await this.cryptoService.getOrgKey(orgId);
|
||||||
|
if (orgSymKey == null) {
|
||||||
|
throw new Error("No org key found");
|
||||||
|
}
|
||||||
|
const decPrivateKey = await this.encryptService.decryptToBytes(
|
||||||
|
new EncString(response.encryptedPrivateKey),
|
||||||
|
orgSymKey,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Decrypt User's Reset Password Key to get UserKey
|
||||||
|
const decValue = await this.cryptoService.rsaDecrypt(response.resetPasswordKey, decPrivateKey);
|
||||||
|
const existingUserKey = new SymmetricCryptoKey(decValue) as UserKey;
|
||||||
|
|
||||||
|
// Create new master key and hash new password
|
||||||
|
const newMasterKey = await this.cryptoService.makeMasterKey(
|
||||||
|
newMasterPassword,
|
||||||
|
email.trim().toLowerCase(),
|
||||||
|
response.kdf,
|
||||||
|
new KdfConfig(response.kdfIterations, response.kdfMemory, response.kdfParallelism),
|
||||||
|
);
|
||||||
|
const newMasterKeyHash = await this.cryptoService.hashMasterKey(
|
||||||
|
newMasterPassword,
|
||||||
|
newMasterKey,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Create new encrypted user key for the User
|
||||||
|
const newUserKey = await this.cryptoService.encryptUserKeyWithMasterKey(
|
||||||
|
newMasterKey,
|
||||||
|
existingUserKey,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Create request
|
||||||
|
const request = new OrganizationUserResetPasswordRequest();
|
||||||
|
request.key = newUserKey[1].encryptedString;
|
||||||
|
request.newMasterPasswordHash = newMasterKeyHash;
|
||||||
|
|
||||||
|
// Change user's password
|
||||||
|
await this.organizationUserService.putOrganizationUserResetPassword(orgId, orgUserId, request);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rotates the user's recovery key for all enrolled organizations.
|
||||||
|
* @param newUserKey the new user key
|
||||||
|
* @param masterPasswordHash the user's master password hash (required for user verification)
|
||||||
|
*/
|
||||||
|
async rotate(newUserKey: UserKey, masterPasswordHash: string): Promise<void> {
|
||||||
|
const allOrgs = await this.organizationService.getAll();
|
||||||
|
|
||||||
|
for (const org of allOrgs) {
|
||||||
|
// If not already enrolled, skip
|
||||||
|
if (!org.resetPasswordEnrolled) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Re-enroll - encrypt user key with organization public key
|
||||||
|
const encryptedKey = await this.buildRecoveryKey(org.id, newUserKey);
|
||||||
|
|
||||||
|
// Create/Execute request
|
||||||
|
const request = new OrganizationUserResetPasswordEnrollmentRequest();
|
||||||
|
request.resetPasswordKey = encryptedKey;
|
||||||
|
request.masterPasswordHash = masterPasswordHash;
|
||||||
|
|
||||||
|
await this.organizationUserService.putOrganizationUserResetPasswordEnrollment(
|
||||||
|
org.id,
|
||||||
|
org.userId,
|
||||||
|
request,
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
// If enrollment fails, continue to next org
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,20 +2,19 @@ import { DIALOG_DATA, DialogRef } from "@angular/cdk/dialog";
|
|||||||
import { Component, Inject } from "@angular/core";
|
import { Component, Inject } from "@angular/core";
|
||||||
import { FormControl, FormGroup, Validators } from "@angular/forms";
|
import { FormControl, FormGroup, Validators } from "@angular/forms";
|
||||||
|
|
||||||
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
|
|
||||||
import { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service";
|
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 { OrganizationUserResetPasswordEnrollmentRequest } from "@bitwarden/common/admin-console/abstractions/organization-user/requests";
|
||||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||||
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
||||||
import { Verification } from "@bitwarden/common/auth/types/verification";
|
import { Verification } from "@bitwarden/common/auth/types/verification";
|
||||||
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
|
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
|
||||||
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
|
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
|
||||||
import { DialogService } from "@bitwarden/components";
|
import { DialogService } from "@bitwarden/components";
|
||||||
|
|
||||||
|
import { AccountRecoveryService } from "../members/services/account-recovery/account-recovery.service";
|
||||||
|
|
||||||
interface EnrollMasterPasswordResetData {
|
interface EnrollMasterPasswordResetData {
|
||||||
organization: Organization;
|
organization: Organization;
|
||||||
}
|
}
|
||||||
@@ -34,21 +33,18 @@ export class EnrollMasterPasswordReset {
|
|||||||
constructor(
|
constructor(
|
||||||
private dialogRef: DialogRef,
|
private dialogRef: DialogRef,
|
||||||
@Inject(DIALOG_DATA) protected data: EnrollMasterPasswordResetData,
|
@Inject(DIALOG_DATA) protected data: EnrollMasterPasswordResetData,
|
||||||
|
private accountRecoveryService: AccountRecoveryService,
|
||||||
private userVerificationService: UserVerificationService,
|
private userVerificationService: UserVerificationService,
|
||||||
private platformUtilsService: PlatformUtilsService,
|
private platformUtilsService: PlatformUtilsService,
|
||||||
private i18nService: I18nService,
|
private i18nService: I18nService,
|
||||||
private cryptoService: CryptoService,
|
|
||||||
private syncService: SyncService,
|
private syncService: SyncService,
|
||||||
private logService: LogService,
|
private logService: LogService,
|
||||||
private organizationApiService: OrganizationApiServiceAbstraction,
|
|
||||||
private organizationUserService: OrganizationUserService,
|
private organizationUserService: OrganizationUserService,
|
||||||
) {
|
) {
|
||||||
this.organization = data.organization;
|
this.organization = data.organization;
|
||||||
}
|
}
|
||||||
|
|
||||||
submit = async () => {
|
submit = async () => {
|
||||||
let toastStringRef = "withdrawPasswordResetSuccess";
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.userVerificationService
|
await this.userVerificationService
|
||||||
.buildRequest(
|
.buildRequest(
|
||||||
@@ -56,25 +52,10 @@ export class EnrollMasterPasswordReset {
|
|||||||
OrganizationUserResetPasswordEnrollmentRequest,
|
OrganizationUserResetPasswordEnrollmentRequest,
|
||||||
)
|
)
|
||||||
.then(async (request) => {
|
.then(async (request) => {
|
||||||
// Set variables
|
|
||||||
let keyString: string = null;
|
|
||||||
|
|
||||||
// Retrieve Public Key
|
|
||||||
const orgKeys = await this.organizationApiService.getKeys(this.organization.id);
|
|
||||||
if (orgKeys == null) {
|
|
||||||
throw new Error(this.i18nService.t("resetPasswordOrgKeysError"));
|
|
||||||
}
|
|
||||||
|
|
||||||
const publicKey = Utils.fromB64ToArray(orgKeys.publicKey);
|
|
||||||
|
|
||||||
// RSA Encrypt user's encKey.key with organization public key
|
|
||||||
const userKey = await this.cryptoService.getUserKey();
|
|
||||||
const encryptedKey = await this.cryptoService.rsaEncrypt(userKey.key, publicKey);
|
|
||||||
keyString = encryptedKey.encryptedString;
|
|
||||||
toastStringRef = "enrollPasswordResetSuccess";
|
|
||||||
|
|
||||||
// Create request and execute enrollment
|
// Create request and execute enrollment
|
||||||
request.resetPasswordKey = keyString;
|
request.resetPasswordKey = await this.accountRecoveryService.buildRecoveryKey(
|
||||||
|
this.organization.id,
|
||||||
|
);
|
||||||
await this.organizationUserService.putOrganizationUserResetPasswordEnrollment(
|
await this.organizationUserService.putOrganizationUserResetPasswordEnrollment(
|
||||||
this.organization.id,
|
this.organization.id,
|
||||||
this.organization.userId,
|
this.organization.userId,
|
||||||
@@ -83,7 +64,11 @@ export class EnrollMasterPasswordReset {
|
|||||||
|
|
||||||
await this.syncService.fullSync(true);
|
await this.syncService.fullSync(true);
|
||||||
});
|
});
|
||||||
this.platformUtilsService.showToast("success", null, this.i18nService.t(toastStringRef));
|
this.platformUtilsService.showToast(
|
||||||
|
"success",
|
||||||
|
null,
|
||||||
|
this.i18nService.t("enrollPasswordResetSuccess"),
|
||||||
|
);
|
||||||
this.dialogRef.close();
|
this.dialogRef.close();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.logService.error(e);
|
this.logService.error(e);
|
||||||
|
|||||||
@@ -2,12 +2,6 @@
|
|||||||
import { BehaviorSubject } from "rxjs";
|
import { BehaviorSubject } from "rxjs";
|
||||||
|
|
||||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
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 { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
|
||||||
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
|
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
|
||||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.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 { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||||
import { FolderView } from "@bitwarden/common/vault/models/view/folder.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 { EmergencyAccessService } from "../emergency-access";
|
||||||
|
|
||||||
import { MigrateFromLegacyEncryptionService } from "./migrate-legacy-encryption.service";
|
import { MigrateFromLegacyEncryptionService } from "./migrate-legacy-encryption.service";
|
||||||
@@ -36,10 +31,8 @@ import { MigrateFromLegacyEncryptionService } from "./migrate-legacy-encryption.
|
|||||||
describe("migrateFromLegacyEncryptionService", () => {
|
describe("migrateFromLegacyEncryptionService", () => {
|
||||||
let migrateFromLegacyEncryptionService: MigrateFromLegacyEncryptionService;
|
let migrateFromLegacyEncryptionService: MigrateFromLegacyEncryptionService;
|
||||||
|
|
||||||
const organizationService = mock<OrganizationService>();
|
|
||||||
const organizationApiService = mock<OrganizationApiService>();
|
|
||||||
const organizationUserService = mock<OrganizationUserService>();
|
|
||||||
const emergencyAccessService = mock<EmergencyAccessService>();
|
const emergencyAccessService = mock<EmergencyAccessService>();
|
||||||
|
const accountRecoveryService = mock<AccountRecoveryService>();
|
||||||
const apiService = mock<ApiService>();
|
const apiService = mock<ApiService>();
|
||||||
const encryptService = mock<EncryptService>();
|
const encryptService = mock<EncryptService>();
|
||||||
const cryptoService = mock<CryptoService>();
|
const cryptoService = mock<CryptoService>();
|
||||||
@@ -55,10 +48,8 @@ describe("migrateFromLegacyEncryptionService", () => {
|
|||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
|
|
||||||
migrateFromLegacyEncryptionService = new MigrateFromLegacyEncryptionService(
|
migrateFromLegacyEncryptionService = new MigrateFromLegacyEncryptionService(
|
||||||
organizationService,
|
|
||||||
organizationApiService,
|
|
||||||
organizationUserService,
|
|
||||||
emergencyAccessService,
|
emergencyAccessService,
|
||||||
|
accountRecoveryService,
|
||||||
apiService,
|
apiService,
|
||||||
cryptoService,
|
cryptoService,
|
||||||
encryptService,
|
encryptService,
|
||||||
@@ -211,68 +202,6 @@ describe("migrateFromLegacyEncryptionService", () => {
|
|||||||
expect(emergencyAccessService.rotate).toHaveBeenCalled();
|
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 {
|
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);
|
send.name = new EncString(EncryptionType.AesCbc256_HmacSha256_B64, name);
|
||||||
return send;
|
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 { firstValueFrom } from "rxjs";
|
||||||
|
|
||||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
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 { UpdateKeyRequest } from "@bitwarden/common/models/request/update-key.request";
|
||||||
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
|
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
|
||||||
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
|
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
|
||||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.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 { EncryptedString, EncString } from "@bitwarden/common/platform/models/domain/enc-string";
|
||||||
import { UserKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
import { UserKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
||||||
import { SendWithIdRequest } from "@bitwarden/common/tools/send/models/request/send-with-id.request";
|
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 { CipherWithIdRequest } from "@bitwarden/common/vault/models/request/cipher-with-id.request";
|
||||||
import { FolderWithIdRequest } from "@bitwarden/common/vault/models/request/folder-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";
|
import { EmergencyAccessService } from "../emergency-access";
|
||||||
|
|
||||||
// TODO: PM-3797 - This service should be expanded and used for user key rotations in change-password.component.ts
|
// TODO: PM-3797 - This service should be expanded and used for user key rotations in change-password.component.ts
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class MigrateFromLegacyEncryptionService {
|
export class MigrateFromLegacyEncryptionService {
|
||||||
constructor(
|
constructor(
|
||||||
private organizationService: OrganizationService,
|
|
||||||
private organizationApiService: OrganizationApiServiceAbstraction,
|
|
||||||
private organizationUserService: OrganizationUserService,
|
|
||||||
private emergencyAccessService: EmergencyAccessService,
|
private emergencyAccessService: EmergencyAccessService,
|
||||||
|
private accountRecoveryService: AccountRecoveryService,
|
||||||
private apiService: ApiService,
|
private apiService: ApiService,
|
||||||
private cryptoService: CryptoService,
|
private cryptoService: CryptoService,
|
||||||
private encryptService: EncryptService,
|
private encryptService: EncryptService,
|
||||||
@@ -112,35 +106,11 @@ export class MigrateFromLegacyEncryptionService {
|
|||||||
* @param newUserKey The new user key
|
* @param newUserKey The new user key
|
||||||
*/
|
*/
|
||||||
async updateAllAdminRecoveryKeys(masterPassword: string, newUserKey: UserKey) {
|
async updateAllAdminRecoveryKeys(masterPassword: string, newUserKey: UserKey) {
|
||||||
const allOrgs = await this.organizationService.getAll();
|
const masterPasswordHash = await this.cryptoService.hashMasterKey(
|
||||||
|
masterPassword,
|
||||||
for (const org of allOrgs) {
|
await this.cryptoService.getOrDeriveMasterKey(masterPassword),
|
||||||
// If not already enrolled, skip
|
);
|
||||||
if (!org.resetPasswordEnrolled) {
|
await this.accountRecoveryService.rotate(newUserKey, masterPasswordHash);
|
||||||
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,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async encryptPrivateKey(newUserKey: UserKey): Promise<EncryptedString | null> {
|
private async encryptPrivateKey(newUserKey: UserKey): Promise<EncryptedString | null> {
|
||||||
|
|||||||
Reference in New Issue
Block a user