1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-23 11:43:46 +00:00

[PM-3726] Force migration of legacy user's encryption key (#6195)

* [PM-3726] migrate legacy user's encryption key

* [PM-3726] add 2fa support and pr feedback

* [PM-3726] revert launch.json & webpack.config changes

* [PM-3726] remove update key component
- also remove card in vault since legacy users can't login

* [PM-3726] Fix i18n & PR feedback

* [PM-3726] make standalone component

* [PM-3726] linter

* [PM-3726] missing await

* [PM-3726] logout legacy users with vault timeout to never

* [PM-3726] add await

* [PM-3726] skip auto key migration for legacy users

* [PM-3726] pr feedback

* [PM-3726] move check for web into migrate method

---------

Co-authored-by: Jared Snider <116684653+JaredSnider-Bitwarden@users.noreply.github.com>
This commit is contained in:
Jake Fink
2023-09-20 15:57:01 -04:00
committed by GitHub
parent 020018085a
commit 8c06508435
30 changed files with 834 additions and 273 deletions

View File

@@ -0,0 +1,36 @@
<form [formGroup]="formGroup" [bitSubmit]="submit">
<div class="tw-mt-12 tw-flex tw-justify-center">
<div class="tw-max-w-xl">
<h1 bitTypography="h1" class="tw-mb-4 tw-text-center">{{ "updateEncryptionKey" | i18n }}</h1>
<div
class="tw-block tw-rounded tw-border tw-border-solid tw-border-secondary-300 tw-bg-background tw-p-8"
>
<p>
{{ "updateEncryptionSchemeDesc" | i18n }}
<a
href="https://bitwarden.com/help/account-encryption-key/#rotate-your-encryption-key"
target="_blank"
rel="noopener"
>{{ "learnMore" | i18n }}</a
>
</p>
<bit-callout type="warning">{{ "updateEncryptionKeyWarning" | i18n }}</bit-callout>
<bit-form-field>
<bit-label>{{ "masterPass" | i18n }}</bit-label>
<input
id="masterPassword"
bitInput
type="password"
formControlName="masterPassword"
appAutofocus
/>
<button type="button" bitIconButton bitSuffix bitPasswordInputToggle></button>
</bit-form-field>
<button type="submit" bitButton bitFormButton buttonType="primary" block>
{{ "updateEncryptionKey" | i18n }}
</button>
</div>
</div>
</div>
</form>

View File

@@ -0,0 +1,82 @@
import { Component } from "@angular/core";
import { FormControl, FormGroup, Validators } from "@angular/forms";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { SharedModule } from "../../shared";
import { MigrateFromLegacyEncryptionService } from "./migrate-legacy-encryption.service";
// The master key was originally used to encrypt user data, before the user key was introduced.
// This component is used to migrate from the old encryption scheme to the new one.
@Component({
standalone: true,
imports: [SharedModule],
providers: [MigrateFromLegacyEncryptionService],
templateUrl: "migrate-legacy-encryption.component.html",
})
export class MigrateFromLegacyEncryptionComponent {
protected formGroup = new FormGroup({
masterPassword: new FormControl("", [Validators.required]),
});
constructor(
private i18nService: I18nService,
private platformUtilsService: PlatformUtilsService,
private migrationService: MigrateFromLegacyEncryptionService,
private cryptoService: CryptoService,
private messagingService: MessagingService,
private logService: LogService
) {}
submit = async () => {
this.formGroup.markAsTouched();
if (this.formGroup.invalid) {
return;
}
const hasUserKey = await this.cryptoService.hasUserKey();
if (hasUserKey) {
this.messagingService.send("logout");
throw new Error("User key already exists, cannot migrate legacy encryption.");
}
const masterPassword = this.formGroup.value.masterPassword;
try {
// Create new user key
const [newUserKey, masterKeyEncUserKey] = await this.migrationService.createNewUserKey(
masterPassword
);
// Update admin recover keys
await this.migrationService.updateAllAdminRecoveryKeys(masterPassword, newUserKey);
// Update emergency access
await this.migrationService.updateEmergencyAccesses(newUserKey);
// Update keys, folders, ciphers, and sends
await this.migrationService.updateKeysAndEncryptedData(
masterPassword,
newUserKey,
masterKeyEncUserKey
);
this.platformUtilsService.showToast(
"success",
this.i18nService.t("keyUpdated"),
this.i18nService.t("logBackInOthersToo"),
{ timeout: 15000 }
);
this.messagingService.send("logout");
} catch (e) {
this.logService.error(e);
throw e;
}
};
}

View File

@@ -0,0 +1,345 @@
import { mock } from "jest-mock-extended";
import { BehaviorSubject } from "rxjs";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { OrganizationUserService } from "@bitwarden/common/abstractions/organization-user/organization-user.service";
import { OrganizationUserResetPasswordEnrollmentRequest } from "@bitwarden/common/abstractions/organization-user/requests";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
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 { EmergencyAccessStatusType } from "@bitwarden/common/auth/enums/emergency-access-status-type";
import { EmergencyAccessUpdateRequest } from "@bitwarden/common/auth/models/request/emergency-access-update.request";
import { EmergencyAccessGranteeDetailsResponse } from "@bitwarden/common/auth/models/response/emergency-access.response";
import { EncryptionType, KdfType } from "@bitwarden/common/enums";
import { ListResponse } from "@bitwarden/common/models/response/list.response";
import { UserKeyResponse } from "@bitwarden/common/models/response/user-key.response";
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 { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
import {
MasterKey,
SymmetricCryptoKey,
UserKey,
} from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import { Send } from "@bitwarden/common/tools/send/models/domain/send";
import { SendService } from "@bitwarden/common/tools/send/services/send.service.abstraction";
import { CsprngArray } from "@bitwarden/common/types/csprng";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
import { Cipher } from "@bitwarden/common/vault/models/domain/cipher";
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 { MigrateFromLegacyEncryptionService } from "./migrate-legacy-encryption.service";
describe("migrateFromLegacyEncryptionService", () => {
let migrateFromLegacyEncryptionService: MigrateFromLegacyEncryptionService;
const organizationService = mock<OrganizationService>();
const organizationApiService = mock<OrganizationApiService>();
const organizationUserService = mock<OrganizationUserService>();
const apiService = mock<ApiService>();
const encryptService = mock<EncryptService>();
const cryptoService = mock<CryptoService>();
const syncService = mock<SyncService>();
const cipherService = mock<CipherService>();
const folderService = mock<FolderService>();
const sendService = mock<SendService>();
const stateService = mock<StateService>();
let folderViews: BehaviorSubject<FolderView[]>;
let sends: BehaviorSubject<Send[]>;
beforeEach(() => {
jest.clearAllMocks();
migrateFromLegacyEncryptionService = new MigrateFromLegacyEncryptionService(
organizationService,
organizationApiService,
organizationUserService,
apiService,
cryptoService,
encryptService,
syncService,
cipherService,
folderService,
sendService,
stateService
);
});
it("instantiates", () => {
expect(migrateFromLegacyEncryptionService).not.toBeFalsy();
});
describe("createNewUserKey", () => {
it("validates master password and legacy user", async () => {
const mockMasterPassword = "mockMasterPassword";
const mockRandomBytes = new Uint8Array(64) as CsprngArray;
const mockMasterKey = new SymmetricCryptoKey(mockRandomBytes) as MasterKey;
stateService.getEmail.mockResolvedValue("mockEmail");
stateService.getKdfType.mockResolvedValue(KdfType.PBKDF2_SHA256);
stateService.getKdfConfig.mockResolvedValue({ iterations: 100000 });
cryptoService.makeMasterKey.mockResolvedValue(mockMasterKey);
cryptoService.isLegacyUser.mockResolvedValue(false);
await expect(
migrateFromLegacyEncryptionService.createNewUserKey(mockMasterPassword)
).rejects.toThrowError("Invalid master password or user may not be legacy");
});
});
describe("updateKeysAndEncryptedData", () => {
let mockMasterPassword: string;
let mockUserKey: UserKey;
let mockEncUserKey: EncString;
beforeEach(() => {
mockMasterPassword = "mockMasterPassword";
const mockRandomBytes = new Uint8Array(64) as CsprngArray;
mockUserKey = new SymmetricCryptoKey(mockRandomBytes) as UserKey;
mockEncUserKey = new EncString("mockEncUserKey");
const mockFolders = [createMockFolder("1", "Folder 1"), createMockFolder("2", "Folder 2")];
const mockCiphers = [createMockCipher("1", "Cipher 1"), createMockCipher("2", "Cipher 2")];
const mockSends = [createMockSend("1", "Send 1"), createMockSend("2", "Send 2")];
cryptoService.getPrivateKey.mockResolvedValue(new Uint8Array(64) as CsprngArray);
folderViews = new BehaviorSubject<FolderView[]>(mockFolders);
folderService.folderViews$ = folderViews;
cipherService.getAllDecrypted.mockResolvedValue(mockCiphers);
sends = new BehaviorSubject<Send[]>(mockSends);
sendService.sends$ = sends;
encryptService.encrypt.mockImplementation((plainValue, userKey) => {
return Promise.resolve(
new EncString(EncryptionType.AesCbc256_HmacSha256_B64, "Encrypted: " + plainValue)
);
});
folderService.encrypt.mockImplementation((folder, userKey) => {
const encryptedFolder = new Folder();
encryptedFolder.id = folder.id;
encryptedFolder.name = new EncString(
EncryptionType.AesCbc256_HmacSha256_B64,
"Encrypted: " + folder.name
);
return Promise.resolve(encryptedFolder);
});
cipherService.encrypt.mockImplementation((cipher, userKey) => {
const encryptedCipher = new Cipher();
encryptedCipher.id = cipher.id;
encryptedCipher.name = new EncString(
EncryptionType.AesCbc256_HmacSha256_B64,
"Encrypted: " + cipher.name
);
return Promise.resolve(encryptedCipher);
});
});
it("derives the master key in case it hasn't been set", async () => {
await migrateFromLegacyEncryptionService.updateKeysAndEncryptedData(
mockMasterPassword,
mockUserKey,
mockEncUserKey
);
expect(cryptoService.getOrDeriveMasterKey).toHaveBeenCalled();
});
it("syncs latest data", async () => {
await migrateFromLegacyEncryptionService.updateKeysAndEncryptedData(
mockMasterPassword,
mockUserKey,
mockEncUserKey
);
expect(syncService.fullSync).toHaveBeenCalledWith(true);
});
it("does not post new account data if sync fails", async () => {
syncService.fullSync.mockRejectedValueOnce(new Error("sync failed"));
await expect(
migrateFromLegacyEncryptionService.updateKeysAndEncryptedData(
mockMasterPassword,
mockUserKey,
mockEncUserKey
)
).rejects.toThrowError("sync failed");
expect(apiService.postAccountKey).not.toHaveBeenCalled();
});
it("does not post new account data if data retrieval fails", async () => {
(migrateFromLegacyEncryptionService as any).encryptCiphers = async () => {
throw new Error("Ciphers failed to be retrieved");
};
await expect(
migrateFromLegacyEncryptionService.updateKeysAndEncryptedData(
mockMasterPassword,
mockUserKey,
mockEncUserKey
)
).rejects.toThrowError("Ciphers failed to be retrieved");
expect(apiService.postAccountKey).not.toHaveBeenCalled();
});
});
describe("updateEmergencyAccesses", () => {
let mockUserKey: UserKey;
beforeEach(() => {
const mockRandomBytes = new Uint8Array(64) as CsprngArray;
mockUserKey = new SymmetricCryptoKey(mockRandomBytes) as UserKey;
const mockEmergencyAccess = {
data: [
createMockEmergencyAccess("0", "EA 0", EmergencyAccessStatusType.Invited),
createMockEmergencyAccess("1", "EA 1", EmergencyAccessStatusType.Accepted),
createMockEmergencyAccess("2", "EA 2", EmergencyAccessStatusType.Confirmed),
createMockEmergencyAccess("3", "EA 3", EmergencyAccessStatusType.RecoveryInitiated),
createMockEmergencyAccess("4", "EA 4", EmergencyAccessStatusType.RecoveryApproved),
],
} as ListResponse<EmergencyAccessGranteeDetailsResponse>;
apiService.getEmergencyAccessTrusted.mockResolvedValue(mockEmergencyAccess);
apiService.getUserPublicKey.mockResolvedValue({
userId: "mockUserId",
publicKey: "mockPublicKey",
} as UserKeyResponse);
cryptoService.rsaEncrypt.mockImplementation((plainValue, publicKey) => {
return Promise.resolve(
new EncString(EncryptionType.Rsa2048_OaepSha1_B64, "Encrypted: " + plainValue)
);
});
});
it("Only updates emergency accesses with allowed statuses", async () => {
await migrateFromLegacyEncryptionService.updateEmergencyAccesses(mockUserKey);
expect(apiService.putEmergencyAccess).not.toHaveBeenCalledWith(
"0",
expect.any(EmergencyAccessUpdateRequest)
);
expect(apiService.putEmergencyAccess).not.toHaveBeenCalledWith(
"1",
expect.any(EmergencyAccessUpdateRequest)
);
});
});
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 {
const folder = new FolderView();
folder.id = id;
folder.name = name;
return folder;
}
function createMockCipher(id: string, name: string): CipherView {
const cipher = new CipherView();
cipher.id = id;
cipher.name = name;
return cipher;
}
function createMockSend(id: string, name: string): Send {
const send = new Send();
send.id = id;
send.name = new EncString(EncryptionType.AesCbc256_HmacSha256_B64, name);
return send;
}
function createMockEmergencyAccess(
id: string,
name: string,
status: EmergencyAccessStatusType
): EmergencyAccessGranteeDetailsResponse {
const emergencyAccess = new EmergencyAccessGranteeDetailsResponse({});
emergencyAccess.id = id;
emergencyAccess.name = name;
emergencyAccess.type = 0;
emergencyAccess.status = status;
return emergencyAccess;
}
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;
}

View File

@@ -0,0 +1,215 @@
import { Injectable } from "@angular/core";
import { firstValueFrom } from "rxjs";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { OrganizationUserService } from "@bitwarden/common/abstractions/organization-user/organization-user.service";
import { OrganizationUserResetPasswordEnrollmentRequest } from "@bitwarden/common/abstractions/organization-user/requests";
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 { EmergencyAccessStatusType } from "@bitwarden/common/auth/enums/emergency-access-status-type";
import { EmergencyAccessUpdateRequest } from "@bitwarden/common/auth/models/request/emergency-access-update.request";
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";
import { SendService } from "@bitwarden/common/tools/send/services/send.service.abstraction";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
import { CipherWithIdRequest } from "@bitwarden/common/vault/models/request/cipher-with-id.request";
import { FolderWithIdRequest } from "@bitwarden/common/vault/models/request/folder-with-id.request";
// 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 apiService: ApiService,
private cryptoService: CryptoService,
private encryptService: EncryptService,
private syncService: SyncService,
private cipherService: CipherService,
private folderService: FolderService,
private sendService: SendService,
private stateService: StateService
) {}
/**
* Validates the master password and creates a new user key.
* @returns A new user key along with the encrypted version
*/
async createNewUserKey(masterPassword: string): Promise<[UserKey, EncString]> {
// Create master key to validate the master password
const masterKey = await this.cryptoService.makeMasterKey(
masterPassword,
await this.stateService.getEmail(),
await this.stateService.getKdfType(),
await this.stateService.getKdfConfig()
);
if (!masterKey) {
throw new Error("Invalid master password");
}
if (!(await this.cryptoService.isLegacyUser(masterKey))) {
throw new Error("Invalid master password or user may not be legacy");
}
// Set master key again in case it was lost (could be lost on refresh)
await this.cryptoService.setMasterKey(masterKey);
return await this.cryptoService.makeUserKey(masterKey);
}
/**
* Updates the user key, master key hash, private key, folders, ciphers, and sends
* on the server.
* @param masterPassword The master password
* @param newUserKey The new user key
* @param newEncUserKey The new encrypted user key
*/
async updateKeysAndEncryptedData(
masterPassword: string,
newUserKey: UserKey,
newEncUserKey: EncString
): Promise<void> {
// Create new request and add master key and hash
const request = new UpdateKeyRequest();
request.key = newEncUserKey.encryptedString;
request.masterPasswordHash = await this.cryptoService.hashMasterKey(
masterPassword,
await this.cryptoService.getOrDeriveMasterKey(masterPassword)
);
// Sync before encrypting to make sure we have latest data
await this.syncService.fullSync(true);
request.privateKey = await this.encryptPrivateKey(newUserKey);
request.folders = await this.encryptFolders(newUserKey);
request.ciphers = await this.encryptCiphers(newUserKey);
request.sends = await this.encryptSends(newUserKey);
return this.apiService.postAccountKey(request);
}
/**
* Gets user's emergency access details from server and encrypts with new user key
* on the server.
* @param newUserKey The new user key
*/
async updateEmergencyAccesses(newUserKey: UserKey) {
const emergencyAccess = await this.apiService.getEmergencyAccessTrusted();
// Any Invited or Accepted requests won't have the key yet, so we don't need to update them
const allowedStatuses = new Set([
EmergencyAccessStatusType.Confirmed,
EmergencyAccessStatusType.RecoveryInitiated,
EmergencyAccessStatusType.RecoveryApproved,
]);
const filteredAccesses = emergencyAccess.data.filter((d) => allowedStatuses.has(d.status));
for (const details of filteredAccesses) {
// Get public key of grantee
const publicKeyResponse = await this.apiService.getUserPublicKey(details.granteeId);
const publicKey = Utils.fromB64ToArray(publicKeyResponse.publicKey);
// Encrypt new user key with public key
const encryptedKey = await this.cryptoService.rsaEncrypt(newUserKey.key, publicKey);
const updateRequest = new EmergencyAccessUpdateRequest();
updateRequest.type = details.type;
updateRequest.waitTimeDays = details.waitTimeDays;
updateRequest.keyEncrypted = encryptedKey.encryptedString;
await this.apiService.putEmergencyAccess(details.id, updateRequest);
}
}
/** Updates all admin recovery keys on the server with the new user key
* @param masterPassword The user's master password
* @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
);
}
}
private async encryptPrivateKey(newUserKey: UserKey): Promise<EncryptedString | null> {
const privateKey = await this.cryptoService.getPrivateKey();
if (!privateKey) {
return;
}
return (await this.encryptService.encrypt(privateKey, newUserKey)).encryptedString;
}
private async encryptFolders(newUserKey: UserKey): Promise<FolderWithIdRequest[] | null> {
const folders = await firstValueFrom(this.folderService.folderViews$);
if (!folders) {
return;
}
return await Promise.all(
folders.map(async (folder) => {
const encryptedFolder = await this.folderService.encrypt(folder, newUserKey);
return new FolderWithIdRequest(encryptedFolder);
})
);
}
private async encryptCiphers(newUserKey: UserKey): Promise<CipherWithIdRequest[] | null> {
const ciphers = await this.cipherService.getAllDecrypted();
if (!ciphers) {
return;
}
return await Promise.all(
ciphers.map(async (cipher) => {
const encryptedCipher = await this.cipherService.encrypt(cipher, newUserKey);
return new CipherWithIdRequest(encryptedCipher);
})
);
}
private async encryptSends(newUserKey: UserKey): Promise<SendWithIdRequest[] | null> {
const sends = await firstValueFrom(this.sendService.sends$);
if (!sends) {
return;
}
return await Promise.all(
sends.map(async (send) => {
const sendKey = await this.encryptService.decryptToBytes(send.key, null);
send.key = (await this.encryptService.encrypt(sendKey, newUserKey)) ?? send.key;
return new SendWithIdRequest(send);
})
);
}
}