1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-06 00:13:28 +00:00

[PM-7604] Require target UserID for KdfConfigService (#14380)

* Require userId for KdfConfigService

* Update auth team callers

* Update tools team callers
This commit is contained in:
Thomas Avery
2025-04-29 17:25:27 -05:00
committed by GitHub
parent f39e37002b
commit d43e4757df
14 changed files with 171 additions and 142 deletions

View File

@@ -5,7 +5,7 @@ import * as http from "http";
import { OptionValues } from "commander";
import * as inquirer from "inquirer";
import Separator from "inquirer/lib/objects/separator";
import { firstValueFrom, map, switchMap } from "rxjs";
import { firstValueFrom, map } from "rxjs";
import {
LoginStrategyServiceAbstraction,
@@ -29,7 +29,6 @@ import { TokenTwoFactorRequest } from "@bitwarden/common/auth/models/request/ide
import { PasswordRequest } from "@bitwarden/common/auth/models/request/password.request";
import { TwoFactorEmailRequest } from "@bitwarden/common/auth/models/request/two-factor-email.request";
import { UpdateTempPasswordRequest } from "@bitwarden/common/auth/models/request/update-temp-password.request";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { ClientType } from "@bitwarden/common/enums";
import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service";
import { KeyConnectorService } from "@bitwarden/common/key-management/key-connector/abstractions/key-connector.service";
@@ -40,6 +39,7 @@ import { Utils } from "@bitwarden/common/platform/misc/utils";
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength";
import { UserId } from "@bitwarden/common/types/guid";
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy";
import { KdfConfigService, KeyService } from "@bitwarden/key-management";
@@ -367,9 +367,9 @@ export class LoginCommand {
clientSecret == null
) {
if (response.forcePasswordReset === ForceSetPasswordReason.AdminForcePasswordReset) {
return await this.updateTempPassword();
return await this.updateTempPassword(response.userId);
} else if (response.forcePasswordReset === ForceSetPasswordReason.WeakMasterPassword) {
return await this.updateWeakPassword(password);
return await this.updateWeakPassword(response.userId, password);
}
}
@@ -431,7 +431,7 @@ export class LoginCommand {
return Response.success(res);
}
private async updateWeakPassword(currentPassword: string) {
private async updateWeakPassword(userId: UserId, currentPassword: string) {
// If no interaction available, alert user to use web vault
if (!this.canInteract) {
await this.logoutCallback();
@@ -448,6 +448,7 @@ export class LoginCommand {
try {
const { newPasswordHash, newUserKey, hint } = await this.collectNewMasterPasswordDetails(
userId,
"Your master password does not meet one or more of your organization policies. In order to access the vault, you must update your master password now.",
);
@@ -469,7 +470,7 @@ export class LoginCommand {
}
}
private async updateTempPassword() {
private async updateTempPassword(userId: UserId) {
// If no interaction available, alert user to use web vault
if (!this.canInteract) {
await this.logoutCallback();
@@ -486,6 +487,7 @@ export class LoginCommand {
try {
const { newPasswordHash, newUserKey, hint } = await this.collectNewMasterPasswordDetails(
userId,
"An organization administrator recently changed your master password. In order to access the vault, you must update your master password now.",
);
@@ -510,10 +512,12 @@ export class LoginCommand {
* Collect new master password and hint from the CLI. The collected password
* is validated against any applicable master password policies, a new master
* key is generated, and we use it to re-encrypt the user key
* @param userId - User ID of the account
* @param prompt - Message that is displayed during the initial prompt
* @param error
*/
private async collectNewMasterPasswordDetails(
userId: UserId,
prompt: string,
error?: string,
): Promise<{
@@ -539,11 +543,12 @@ export class LoginCommand {
// Master Password Validation
if (masterPassword == null || masterPassword === "") {
return this.collectNewMasterPasswordDetails(prompt, "Master password is required.\n");
return this.collectNewMasterPasswordDetails(userId, prompt, "Master password is required.\n");
}
if (masterPassword.length < Utils.minimumPasswordLength) {
return this.collectNewMasterPasswordDetails(
userId,
prompt,
`Master password must be at least ${Utils.minimumPasswordLength} characters long.\n`,
);
@@ -556,10 +561,7 @@ export class LoginCommand {
);
const enforcedPolicyOptions = await firstValueFrom(
this.accountService.activeAccount$.pipe(
getUserId,
switchMap((userId) => this.policyService.masterPasswordPolicyOptions$(userId)),
),
this.policyService.masterPasswordPolicyOptions$(userId),
);
// Verify master password meets policy requirements
@@ -572,6 +574,7 @@ export class LoginCommand {
)
) {
return this.collectNewMasterPasswordDetails(
userId,
prompt,
"Your new master password does not meet the policy requirements.\n",
);
@@ -589,6 +592,7 @@ export class LoginCommand {
// Re-type Validation
if (masterPassword !== masterPasswordRetype) {
return this.collectNewMasterPasswordDetails(
userId,
prompt,
"Master password confirmation does not match.\n",
);
@@ -601,7 +605,7 @@ export class LoginCommand {
message: "Master Password Hint (optional):",
});
const masterPasswordHint = hint.input;
const kdfConfig = await this.kdfConfigService.getKdfConfig();
const kdfConfig = await this.kdfConfigService.getKdfConfig(userId);
// Create new key and hash new password
const newMasterKey = await this.keyService.makeMasterKey(

View File

@@ -310,13 +310,16 @@ export class ChangePasswordComponent
newMasterKey: MasterKey,
newUserKey: [UserKey, EncString],
) {
const masterKey = await this.keyService.makeMasterKey(
this.currentMasterPassword,
await firstValueFrom(this.accountService.activeAccount$.pipe(map((a) => a?.email))),
await this.kdfConfigService.getKdfConfig(),
const [userId, email] = await firstValueFrom(
this.accountService.activeAccount$.pipe(map((a) => [a?.id, a?.email])),
);
const masterKey = await this.keyService.makeMasterKey(
this.currentMasterPassword,
email,
await this.kdfConfigService.getKdfConfig(userId),
);
const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$));
const newLocalKeyHash = await this.keyService.hashMasterKey(
this.masterPassword,
newMasterKey,

View File

@@ -2,8 +2,10 @@
// @ts-strict-ignore
import { Component, OnDestroy, OnInit } from "@angular/core";
import { FormBuilder, FormControl, ValidatorFn, Validators } from "@angular/forms";
import { Subject, takeUntil } from "rxjs";
import { Subject, firstValueFrom, takeUntil } from "rxjs";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { DialogService } from "@bitwarden/components";
import {
KdfConfigService,
@@ -43,6 +45,7 @@ export class ChangeKdfComponent implements OnInit, OnDestroy {
constructor(
private dialogService: DialogService,
private kdfConfigService: KdfConfigService,
private accountService: AccountService,
private formBuilder: FormBuilder,
) {
this.kdfOptions = [
@@ -52,7 +55,8 @@ export class ChangeKdfComponent implements OnInit, OnDestroy {
}
async ngOnInit() {
this.kdfConfig = await this.kdfConfigService.getKdfConfig();
const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$));
this.kdfConfig = await this.kdfConfigService.getKdfConfig(userId);
this.formGroup.get("kdf").setValue(this.kdfConfig.kdfType);
this.setFormControlValues(this.kdfConfig);

View File

@@ -83,11 +83,12 @@ export class ChangePasswordComponent implements OnInit, OnDestroy {
return;
}
const email = await firstValueFrom(
this.accountService.activeAccount$.pipe(map((a) => a?.email)),
const [userId, email] = await firstValueFrom(
this.accountService.activeAccount$.pipe(map((a) => [a?.id, a?.email])),
);
if (this.kdfConfig == null) {
this.kdfConfig = await this.kdfConfigService.getKdfConfig();
this.kdfConfig = await this.kdfConfigService.getKdfConfig(userId);
}
// Create new master key

View File

@@ -2,6 +2,7 @@
// @ts-strict-ignore
import { Directive } from "@angular/core";
import { Router } from "@angular/router";
import { firstValueFrom } from "rxjs";
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";
@@ -10,6 +11,7 @@ import { MasterPasswordApiService } from "@bitwarden/common/auth/abstractions/ma
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
import { VerificationType } from "@bitwarden/common/auth/enums/verification-type";
import { PasswordRequest } from "@bitwarden/common/auth/models/request/password.request";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { Verification } from "@bitwarden/common/auth/types/verification";
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
@@ -96,8 +98,8 @@ export class UpdatePasswordComponent extends BaseChangePasswordComponent {
});
return false;
}
this.kdfConfig = await this.kdfConfigService.getKdfConfig();
const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$));
this.kdfConfig = await this.kdfConfigService.getKdfConfig(userId);
return true;
}

View File

@@ -110,10 +110,11 @@ export class UpdateTempPasswordComponent extends BaseChangePasswordComponent imp
}
async setupSubmitActions(): Promise<boolean> {
this.email = await firstValueFrom(
this.accountService.activeAccount$.pipe(map((a) => a?.email)),
const [userId, email] = await firstValueFrom(
this.accountService.activeAccount$.pipe(map((a) => [a?.id, a?.email])),
);
this.kdfConfig = await this.kdfConfigService.getKdfConfig();
this.email = email;
this.kdfConfig = await this.kdfConfigService.getKdfConfig(userId);
return true;
}

View File

@@ -172,7 +172,7 @@ export class PinService implements PinServiceAbstraction {
const email = await firstValueFrom(
this.accountService.accounts$.pipe(map((accounts) => accounts[userId].email)),
);
const kdfConfig = await this.kdfConfigService.getKdfConfig();
const kdfConfig = await this.kdfConfigService.getKdfConfig(userId);
const pinKey = await this.makePinKey(pin, email, kdfConfig);
return await this.encryptService.wrapSymmetricKey(userKey, pinKey);
@@ -293,7 +293,7 @@ export class PinService implements PinServiceAbstraction {
const email = await firstValueFrom(
this.accountService.accounts$.pipe(map((accounts) => accounts[userId].email)),
);
const kdfConfig = await this.kdfConfigService.getKdfConfig();
const kdfConfig = await this.kdfConfigService.getKdfConfig(userId);
const userKey: UserKey = await this.decryptUserKey(
userId,

View File

@@ -117,7 +117,7 @@ export class UserVerificationService implements UserVerificationServiceAbstracti
masterKey = await this.keyService.makeMasterKey(
verification.secret,
email,
await this.kdfConfigService.getKdfConfig(),
await this.kdfConfigService.getKdfConfig(userId),
);
}
request.masterPasswordHash = alreadyHashed
@@ -186,7 +186,7 @@ export class UserVerificationService implements UserVerificationServiceAbstracti
throw new Error("Email is required. Cannot verify user by master password.");
}
const kdfConfig = await this.kdfConfigService.getKdfConfig();
const kdfConfig = await this.kdfConfigService.getKdfConfig(userId);
if (!kdfConfig) {
throw new Error("KDF config is required. Cannot verify user by master password.");
}

View File

@@ -6,6 +6,6 @@ import { KdfConfig } from "../models/kdf-config";
export abstract class KdfConfigService {
abstract setKdfConfig(userId: UserId, KdfConfig: KdfConfig): Promise<void>;
abstract getKdfConfig(): Promise<KdfConfig>;
abstract getKdfConfig(userId: UserId): Promise<KdfConfig>;
abstract getKdfConfig$(userId: UserId): Observable<KdfConfig | null>;
}

View File

@@ -26,90 +26,94 @@ describe("KdfConfigService", () => {
sutKdfConfigService = new DefaultKdfConfigService(fakeStateProvider);
});
it("setKdfConfig(): should set the PBKDF2KdfConfig config", async () => {
const kdfConfig: KdfConfig = new PBKDF2KdfConfig(500_000);
await sutKdfConfigService.setKdfConfig(mockUserId, kdfConfig);
expect(fakeStateProvider.mock.setUserState).toHaveBeenCalledWith(
KDF_CONFIG,
kdfConfig,
mockUserId,
);
describe("setKdfConfig", () => {
it("sets the PBKDF2KdfConfig config", async () => {
const kdfConfig: KdfConfig = new PBKDF2KdfConfig(500_000);
await sutKdfConfigService.setKdfConfig(mockUserId, kdfConfig);
expect(fakeStateProvider.mock.setUserState).toHaveBeenCalledWith(
KDF_CONFIG,
kdfConfig,
mockUserId,
);
});
it("sets the Argon2KdfConfig config", async () => {
const kdfConfig: KdfConfig = new Argon2KdfConfig(2, 63, 3);
await sutKdfConfigService.setKdfConfig(mockUserId, kdfConfig);
expect(fakeStateProvider.mock.setUserState).toHaveBeenCalledWith(
KDF_CONFIG,
kdfConfig,
mockUserId,
);
});
it("throws error KDF cannot be null", async () => {
try {
await sutKdfConfigService.setKdfConfig(mockUserId, null as unknown as KdfConfig);
} catch (e) {
expect(e).toEqual(new Error("kdfConfig cannot be null"));
}
});
it("throws error userId cannot be null", async () => {
const kdfConfig: KdfConfig = new Argon2KdfConfig(3, 64, 4);
try {
await sutKdfConfigService.setKdfConfig(null as unknown as UserId, kdfConfig);
} catch (e) {
expect(e).toEqual(new Error("userId cannot be null"));
}
});
});
it("setKdfConfig(): should set the Argon2KdfConfig config", async () => {
const kdfConfig: KdfConfig = new Argon2KdfConfig(2, 63, 3);
await sutKdfConfigService.setKdfConfig(mockUserId, kdfConfig);
expect(fakeStateProvider.mock.setUserState).toHaveBeenCalledWith(
KDF_CONFIG,
kdfConfig,
mockUserId,
);
describe("getKdfConfig", () => {
it("throws error if userId is null", async () => {
await expect(sutKdfConfigService.getKdfConfig(null as unknown as UserId)).rejects.toThrow(
"userId cannot be null",
);
});
it("throws if target user doesn't have a KkfConfig", async () => {
const errorMessage = "KdfConfig for user " + mockUserId + " is null";
await expect(sutKdfConfigService.getKdfConfig(mockUserId)).rejects.toThrow(errorMessage);
});
it("returns KdfConfig of target user", async () => {
const kdfConfig: KdfConfig = new PBKDF2KdfConfig(500_000);
await fakeStateProvider.setUserState(KDF_CONFIG, kdfConfig, mockUserId);
await expect(sutKdfConfigService.getKdfConfig(mockUserId)).resolves.toEqual(kdfConfig);
});
});
it("setKdfConfig(): should throw error KDF cannot be null", async () => {
try {
await sutKdfConfigService.setKdfConfig(mockUserId, null as unknown as KdfConfig);
} catch (e) {
expect(e).toEqual(new Error("kdfConfig cannot be null"));
}
});
describe("getKdfConfig$", () => {
it("gets KdfConfig of provided user", async () => {
await expect(
firstValueFrom(sutKdfConfigService.getKdfConfig$(mockUserId)),
).resolves.toBeNull();
const kdfConfig: KdfConfig = new PBKDF2KdfConfig(500_000);
await fakeStateProvider.setUserState(KDF_CONFIG, kdfConfig, mockUserId);
await expect(firstValueFrom(sutKdfConfigService.getKdfConfig$(mockUserId))).resolves.toEqual(
kdfConfig,
);
});
it("setKdfConfig(): should throw error userId cannot be null", async () => {
const kdfConfig: KdfConfig = new Argon2KdfConfig(3, 64, 4);
try {
await sutKdfConfigService.setKdfConfig(null as unknown as UserId, kdfConfig);
} catch (e) {
expect(e).toEqual(new Error("userId cannot be null"));
}
});
it("gets KdfConfig of provided user after changed", async () => {
await expect(
firstValueFrom(sutKdfConfigService.getKdfConfig$(mockUserId)),
).resolves.toBeNull();
await fakeStateProvider.setUserState(KDF_CONFIG, new PBKDF2KdfConfig(500_000), mockUserId);
const kdfConfigChanged: KdfConfig = new PBKDF2KdfConfig(500_001);
await fakeStateProvider.setUserState(KDF_CONFIG, kdfConfigChanged, mockUserId);
await expect(firstValueFrom(sutKdfConfigService.getKdfConfig$(mockUserId))).resolves.toEqual(
kdfConfigChanged,
);
});
it("getKdfConfig(): should get KdfConfig of active user", async () => {
const kdfConfig: KdfConfig = new PBKDF2KdfConfig(500_000);
await fakeStateProvider.setUserState(KDF_CONFIG, kdfConfig, mockUserId);
await expect(sutKdfConfigService.getKdfConfig()).resolves.toEqual(kdfConfig);
});
it("getKdfConfig(): should throw error KdfConfig can only be retrieved when there is active user", async () => {
fakeAccountService.activeAccountSubject.next(null);
try {
await sutKdfConfigService.getKdfConfig();
} catch (e) {
expect(e).toEqual(new Error("KdfConfig can only be retrieved when there is active user"));
}
});
it("getKdfConfig(): should throw error KdfConfig for active user account state is null", async () => {
try {
await sutKdfConfigService.getKdfConfig();
} catch (e) {
expect(e).toEqual(new Error("KdfConfig for active user account state is null"));
}
});
it("getKdfConfig$(UserId): should get KdfConfig of provided user", async () => {
await expect(firstValueFrom(sutKdfConfigService.getKdfConfig$(mockUserId))).resolves.toBeNull();
const kdfConfig: KdfConfig = new PBKDF2KdfConfig(500_000);
await fakeStateProvider.setUserState(KDF_CONFIG, kdfConfig, mockUserId);
await expect(firstValueFrom(sutKdfConfigService.getKdfConfig$(mockUserId))).resolves.toEqual(
kdfConfig,
);
});
it("getKdfConfig$(UserId): should get KdfConfig of provided user after changed", async () => {
await expect(firstValueFrom(sutKdfConfigService.getKdfConfig$(mockUserId))).resolves.toBeNull();
await fakeStateProvider.setUserState(KDF_CONFIG, new PBKDF2KdfConfig(500_000), mockUserId);
const kdfConfigChanged: KdfConfig = new PBKDF2KdfConfig(500_001);
await fakeStateProvider.setUserState(KDF_CONFIG, kdfConfigChanged, mockUserId);
await expect(firstValueFrom(sutKdfConfigService.getKdfConfig$(mockUserId))).resolves.toEqual(
kdfConfigChanged,
);
});
it("getKdfConfig$(UserId): should throw error userId cannot be null", async () => {
try {
sutKdfConfigService.getKdfConfig$(null as unknown as UserId);
} catch (e) {
expect(e).toEqual(new Error("userId cannot be null"));
}
it("throws error userId cannot be null", async () => {
try {
sutKdfConfigService.getKdfConfig$(null as unknown as UserId);
} catch (e) {
expect(e).toEqual(new Error("userId cannot be null"));
}
});
});
});

View File

@@ -37,14 +37,14 @@ export class DefaultKdfConfigService implements KdfConfigService {
await this.stateProvider.setUserState(KDF_CONFIG, kdfConfig, userId);
}
async getKdfConfig(): Promise<KdfConfig> {
const userId = await firstValueFrom(this.stateProvider.activeUserId$);
async getKdfConfig(userId: UserId): Promise<KdfConfig> {
if (userId == null) {
throw new Error("KdfConfig can only be retrieved when there is active user");
throw new Error("userId cannot be null");
}
const state = await firstValueFrom(this.stateProvider.getUser(userId, KDF_CONFIG).state$);
if (state == null) {
throw new Error("KdfConfig for active user account state is null");
throw new Error("KdfConfig for user " + userId + " is null");
}
return state;
}

View File

@@ -4,6 +4,7 @@ import { PinServiceAbstraction } from "@bitwarden/auth/common";
import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service";
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { UserId } from "@bitwarden/common/types/guid";
import { CipherType } from "@bitwarden/common/vault/enums";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { KdfConfig, KdfConfigService, KdfType } from "@bitwarden/key-management";
@@ -17,8 +18,12 @@ export class BaseVaultExportService {
private kdfConfigService: KdfConfigService,
) {}
protected async buildPasswordExport(clearText: string, password: string): Promise<string> {
const kdfConfig: KdfConfig = await this.kdfConfigService.getKdfConfig();
protected async buildPasswordExport(
userId: UserId,
clearText: string,
password: string,
): Promise<string> {
const kdfConfig: KdfConfig = await this.kdfConfigService.getKdfConfig(userId);
const salt = Utils.fromBufferToB64(await this.cryptoFunctionService.randomBytes(16));
const key = await this.pinService.makePinKey(password, salt, kdfConfig);

View File

@@ -13,6 +13,7 @@ import { EncryptService } from "@bitwarden/common/key-management/crypto/abstract
import { CipherWithIdExport, FolderWithIdExport } from "@bitwarden/common/models/export";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { EncArrayBuffer } from "@bitwarden/common/platform/models/domain/enc-array-buffer";
import { UserId } from "@bitwarden/common/types/guid";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
import { CipherType } from "@bitwarden/common/vault/enums";
@@ -59,19 +60,21 @@ export class IndividualVaultExportService
* @param format The format of the export
*/
async getExport(format: ExportFormat = "csv"): Promise<ExportedVault> {
const userId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
if (format === "encrypted_json") {
return this.getEncryptedExport();
return this.getEncryptedExport(userId);
} else if (format === "zip") {
return this.getDecryptedExportZip();
return this.getDecryptedExportZip(userId);
}
return this.getDecryptedExport(format);
return this.getDecryptedExport(userId, format);
}
/** Creates a password protected export of an individiual vault (My Vault) as a JSON file
/** Creates a password protected export of an individual vault (My Vault) as a JSON file
* @param password The password to encrypt the export with
* @returns A password-protected encrypted individual vault export
*/
async getPasswordProtectedExport(password: string): Promise<ExportedVaultAsString> {
const userId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
const exportVault = await this.getExport("json");
if (exportVault.type !== "text/plain") {
@@ -80,19 +83,20 @@ export class IndividualVaultExportService
return {
type: "text/plain",
data: await this.buildPasswordExport(exportVault.data, password),
data: await this.buildPasswordExport(userId, exportVault.data, password),
fileName: ExportHelper.getFileName("", "encrypted_json"),
} as ExportedVaultAsString;
}
/** Creates a unencrypted export of an individual vault including attachments
* @param activeUserId The user ID of the user requesting the export
* @returns A unencrypted export including attachments
*/
async getDecryptedExportZip(): Promise<ExportedVaultAsBlob> {
async getDecryptedExportZip(activeUserId: UserId): Promise<ExportedVaultAsBlob> {
const zip = new JSZip();
// ciphers
const exportedVault = await this.getDecryptedExport("json");
const exportedVault = await this.getDecryptedExport(activeUserId, "json");
zip.file("data.json", exportedVault.data);
const attachmentsFolder = zip.folder("attachments");
@@ -100,8 +104,6 @@ export class IndividualVaultExportService
throw new Error("Error creating attachments folder");
}
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
// attachments
for (const cipher of await this.cipherService.getAllDecrypted(activeUserId)) {
if (
@@ -161,11 +163,13 @@ export class IndividualVaultExportService
}
}
private async getDecryptedExport(format: "json" | "csv"): Promise<ExportedVaultAsString> {
private async getDecryptedExport(
activeUserId: UserId,
format: "json" | "csv",
): Promise<ExportedVaultAsString> {
let decFolders: FolderView[] = [];
let decCiphers: CipherView[] = [];
const promises = [];
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
promises.push(
firstValueFrom(this.folderService.folderViews$(activeUserId)).then((folders) => {
@@ -196,11 +200,10 @@ export class IndividualVaultExportService
} as ExportedVaultAsString;
}
private async getEncryptedExport(): Promise<ExportedVaultAsString> {
private async getEncryptedExport(activeUserId: UserId): Promise<ExportedVaultAsString> {
let folders: Folder[] = [];
let ciphers: Cipher[] = [];
const promises = [];
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
promises.push(
firstValueFrom(this.folderService.folders$(activeUserId)).then((f) => {
@@ -216,9 +219,7 @@ export class IndividualVaultExportService
await Promise.all(promises);
const userKey = await this.keyService.getUserKeyWithLegacySupport(
await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)),
);
const userKey = await this.keyService.getUserKeyWithLegacySupport(activeUserId);
const encKeyValidation = await this.encryptService.encrypt(Utils.newGuid(), userKey);
const jsonDoc: BitwardenEncryptedIndividualJsonExport = {

View File

@@ -18,7 +18,7 @@ import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/a
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
import { CipherWithIdExport, CollectionWithIdExport } from "@bitwarden/common/models/export";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { OrganizationId } from "@bitwarden/common/types/guid";
import { OrganizationId, UserId } from "@bitwarden/common/types/guid";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { CipherType } from "@bitwarden/common/vault/enums";
import { CipherData } from "@bitwarden/common/vault/models/data/cipher.data";
@@ -67,6 +67,7 @@ export class OrganizationVaultExportService
password: string,
onlyManagedCollections: boolean,
): Promise<ExportedVaultAsString> {
const userId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
const exportVault = await this.getOrganizationExport(
organizationId,
"json",
@@ -75,7 +76,7 @@ export class OrganizationVaultExportService
return {
type: "text/plain",
data: await this.buildPasswordExport(exportVault.data, password),
data: await this.buildPasswordExport(userId, exportVault.data, password),
fileName: ExportHelper.getFileName("org", "encrypted_json"),
} as ExportedVaultAsString;
}
@@ -102,12 +103,13 @@ export class OrganizationVaultExportService
if (format === "zip") {
throw new Error("Zip export not supported for organization");
}
const userId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
if (format === "encrypted_json") {
return {
type: "text/plain",
data: onlyManagedCollections
? await this.getEncryptedManagedExport(organizationId)
? await this.getEncryptedManagedExport(userId, organizationId)
: await this.getOrganizationEncryptedExport(organizationId),
fileName: ExportHelper.getFileName("org", "encrypted_json"),
} as ExportedVaultAsString;
@@ -116,20 +118,20 @@ export class OrganizationVaultExportService
return {
type: "text/plain",
data: onlyManagedCollections
? await this.getDecryptedManagedExport(organizationId, format)
: await this.getOrganizationDecryptedExport(organizationId, format),
? await this.getDecryptedManagedExport(userId, organizationId, format)
: await this.getOrganizationDecryptedExport(userId, organizationId, format),
fileName: ExportHelper.getFileName("org", format),
} as ExportedVaultAsString;
}
private async getOrganizationDecryptedExport(
activeUserId: UserId,
organizationId: string,
format: "json" | "csv",
): Promise<string> {
const decCollections: CollectionView[] = [];
const decCiphers: CipherView[] = [];
const promises = [];
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
promises.push(
this.apiService.getOrganizationExport(organizationId).then((exportData) => {
@@ -210,6 +212,7 @@ export class OrganizationVaultExportService
}
private async getDecryptedManagedExport(
activeUserId: UserId,
organizationId: string,
format: "json" | "csv",
): Promise<string> {
@@ -217,7 +220,6 @@ export class OrganizationVaultExportService
let allDecCiphers: CipherView[] = [];
let decCollections: CollectionView[] = [];
const promises = [];
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
promises.push(
this.collectionService.getAllDecrypted().then(async (collections) => {
@@ -245,12 +247,14 @@ export class OrganizationVaultExportService
return this.buildJsonExport(decCollections, decCiphers);
}
private async getEncryptedManagedExport(organizationId: string): Promise<string> {
private async getEncryptedManagedExport(
activeUserId: UserId,
organizationId: string,
): Promise<string> {
let encCiphers: Cipher[] = [];
let allCiphers: Cipher[] = [];
let encCollections: Collection[] = [];
const promises = [];
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
promises.push(
this.collectionService.getAll().then((collections) => {