1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-11 22:03:36 +00:00

[PM-24105] Remove usage of getUserKey on keyService (#16626)

• prefer undefined over null
• obtain required UserId once per method, before branching
• guards moved to beginning of methods
* lift UserId retrieval to occur once during import
* remove redundant userId retrieval
This commit is contained in:
John Harrington
2025-10-15 07:03:29 -07:00
committed by GitHub
parent 8ff4fb1ed4
commit 64105e64e9
22 changed files with 118 additions and 62 deletions

View File

@@ -990,6 +990,7 @@ export default class MainBackground {
this.sendStateProvider = new SendStateProvider(this.stateProvider); this.sendStateProvider = new SendStateProvider(this.stateProvider);
this.sendService = new SendService( this.sendService = new SendService(
this.accountService,
this.keyService, this.keyService,
this.i18nService, this.i18nService,
this.keyGenerationService, this.keyGenerationService,

View File

@@ -211,6 +211,7 @@ export class OssServeConfigurator {
this.serviceContainer.sendService, this.serviceContainer.sendService,
this.serviceContainer.sendApiService, this.serviceContainer.sendApiService,
this.serviceContainer.environmentService, this.serviceContainer.environmentService,
this.serviceContainer.accountService,
); );
} }

View File

@@ -552,6 +552,7 @@ export class ServiceContainer {
this.sendStateProvider = new SendStateProvider(this.stateProvider); this.sendStateProvider = new SendStateProvider(this.stateProvider);
this.sendService = new SendService( this.sendService = new SendService(
this.accountService,
this.keyService, this.keyService,
this.i18nService, this.i18nService,
this.keyGenerationService, this.keyGenerationService,

View File

@@ -6,6 +6,7 @@ import * as path from "path";
import { firstValueFrom, switchMap } from "rxjs"; import { firstValueFrom, switchMap } from "rxjs";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
import { SendType } from "@bitwarden/common/tools/send/enums/send-type"; import { SendType } from "@bitwarden/common/tools/send/enums/send-type";
@@ -142,7 +143,8 @@ export class SendCreateCommand {
await this.sendApiService.save([encSend, fileData]); await this.sendApiService.save([encSend, fileData]);
const newSend = await this.sendService.getFromState(encSend.id); const newSend = await this.sendService.getFromState(encSend.id);
const decSend = await newSend.decrypt(); const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
const decSend = await newSend.decrypt(activeUserId);
const env = await firstValueFrom(this.environmentService.environment$); const env = await firstValueFrom(this.environmentService.environment$);
const res = new SendResponse(decSend, env.getWebVaultUrl()); const res = new SendResponse(decSend, env.getWebVaultUrl());
return Response.success(res); return Response.success(res);

View File

@@ -3,6 +3,7 @@
import { firstValueFrom } from "rxjs"; import { firstValueFrom } from "rxjs";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
import { SendType } from "@bitwarden/common/tools/send/enums/send-type"; import { SendType } from "@bitwarden/common/tools/send/enums/send-type";
import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction"; import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction";
@@ -83,7 +84,8 @@ export class SendEditCommand {
return Response.error("Premium status is required to use this feature."); return Response.error("Premium status is required to use this feature.");
} }
let sendView = await send.decrypt(); const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
let sendView = await send.decrypt(activeUserId);
sendView = SendResponse.toView(req, sendView); sendView = SendResponse.toView(req, sendView);
try { try {

View File

@@ -12,6 +12,7 @@ import { Utils } from "@bitwarden/common/platform/misc/utils";
import { SendView } from "@bitwarden/common/tools/send/models/view/send.view"; import { SendView } from "@bitwarden/common/tools/send/models/view/send.view";
import { SendService } from "@bitwarden/common/tools/send/services/send.service.abstraction"; import { SendService } from "@bitwarden/common/tools/send/services/send.service.abstraction";
import { SearchService } from "@bitwarden/common/vault/abstractions/search.service"; import { SearchService } from "@bitwarden/common/vault/abstractions/search.service";
import { isGuid } from "@bitwarden/guid";
import { DownloadCommand } from "../../../commands/download.command"; import { DownloadCommand } from "../../../commands/download.command";
import { Response } from "../../../models/response"; import { Response } from "../../../models/response";
@@ -74,13 +75,13 @@ export class SendGetCommand extends DownloadCommand {
} }
private async getSendView(id: string): Promise<SendView | SendView[]> { private async getSendView(id: string): Promise<SendView | SendView[]> {
if (Utils.isGuid(id)) { const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
if (isGuid(id)) {
const send = await this.sendService.getFromState(id); const send = await this.sendService.getFromState(id);
if (send != null) { if (send != null) {
return await send.decrypt(); return await send.decrypt(activeUserId);
} }
} else if (id.trim() !== "") { } else if (id.trim() !== "") {
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
let sends = await this.sendService.getAllDecryptedFromState(activeUserId); let sends = await this.sendService.getAllDecryptedFromState(activeUserId);
sends = this.searchService.searchSends(sends, id); sends = this.searchService.searchSends(sends, id);
if (sends.length > 1) { if (sends.length > 1) {

View File

@@ -2,6 +2,8 @@
// @ts-strict-ignore // @ts-strict-ignore
import { firstValueFrom } from "rxjs"; import { firstValueFrom } from "rxjs";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
import { SendService } from "@bitwarden/common/tools/send/services//send.service.abstraction"; import { SendService } from "@bitwarden/common/tools/send/services//send.service.abstraction";
import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction"; import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction";
@@ -14,6 +16,7 @@ export class SendRemovePasswordCommand {
private sendService: SendService, private sendService: SendService,
private sendApiService: SendApiService, private sendApiService: SendApiService,
private environmentService: EnvironmentService, private environmentService: EnvironmentService,
private accountService: AccountService,
) {} ) {}
async run(id: string) { async run(id: string) {
@@ -21,7 +24,8 @@ export class SendRemovePasswordCommand {
await this.sendApiService.removePassword(id); await this.sendApiService.removePassword(id);
const updatedSend = await firstValueFrom(this.sendService.get$(id)); const updatedSend = await firstValueFrom(this.sendService.get$(id));
const decSend = await updatedSend.decrypt(); const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
const decSend = await updatedSend.decrypt(activeUserId);
const env = await firstValueFrom(this.environmentService.environment$); const env = await firstValueFrom(this.environmentService.environment$);
const webVaultUrl = env.getWebVaultUrl(); const webVaultUrl = env.getWebVaultUrl();
const res = new SendResponse(decSend, webVaultUrl); const res = new SendResponse(decSend, webVaultUrl);

View File

@@ -297,6 +297,7 @@ export class SendProgram extends BaseProgram {
this.serviceContainer.sendService, this.serviceContainer.sendService,
this.serviceContainer.sendApiService, this.serviceContainer.sendApiService,
this.serviceContainer.environmentService, this.serviceContainer.environmentService,
this.serviceContainer.accountService,
); );
const response = await cmd.run(id); const response = await cmd.run(id);
this.processResponse(response); this.processResponse(response);

View File

@@ -3,11 +3,13 @@
import { CommonModule, DatePipe } from "@angular/common"; import { CommonModule, DatePipe } from "@angular/common";
import { Component } from "@angular/core"; import { Component } from "@angular/core";
import { FormBuilder, ReactiveFormsModule } from "@angular/forms"; import { FormBuilder, ReactiveFormsModule } from "@angular/forms";
import { firstValueFrom } from "rxjs";
import { JslibModule } from "@bitwarden/angular/jslib.module"; import { JslibModule } from "@bitwarden/angular/jslib.module";
import { AddEditComponent as BaseAddEditComponent } from "@bitwarden/angular/tools/send/add-edit.component"; import { AddEditComponent as BaseAddEditComponent } from "@bitwarden/angular/tools/send/add-edit.component";
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 { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
@@ -63,7 +65,8 @@ export class AddEditComponent extends BaseAddEditComponent {
async refresh() { async refresh() {
const send = await this.loadSend(); const send = await this.loadSend();
this.send = await send.decrypt(); const userId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
this.send = await send.decrypt(userId);
this.updateFormValues(); this.updateFormValues();
this.hasPassword = this.send.password != null && this.send.password.trim() !== ""; this.hasPassword = this.send.password != null && this.send.password.trim() !== "";
} }

View File

@@ -791,6 +791,7 @@ const safeProviders: SafeProvider[] = [
provide: InternalSendService, provide: InternalSendService,
useClass: SendService, useClass: SendService,
deps: [ deps: [
AccountServiceAbstraction,
KeyService, KeyService,
I18nServiceAbstraction, I18nServiceAbstraction,
KeyGenerationService, KeyGenerationService,

View File

@@ -260,12 +260,19 @@ export class AddEditComponent implements OnInit, OnDestroy {
}); });
if (this.editMode) { if (this.editMode) {
this.accountService.activeAccount$
.pipe(
getUserId,
switchMap((userId) =>
this.sendService this.sendService
.get$(this.sendId) .get$(this.sendId)
.pipe( .pipe(
//Promise.reject will complete the BehaviourSubject, if desktop starts relying only on BehaviourSubject, this should be changed.
concatMap((s) => concatMap((s) =>
s instanceof Send ? s.decrypt() : Promise.reject(new Error("Failed to load send.")), s instanceof Send
? s.decrypt(userId)
: Promise.reject(new Error("Failed to load send.")),
),
),
), ),
takeUntil(this.destroy$), takeUntil(this.destroy$),
) )

View File

@@ -1,5 +1,7 @@
import { mock } from "jest-mock-extended"; import { mock } from "jest-mock-extended";
import { of } from "rxjs";
import { emptyGuid, UserId } from "@bitwarden/common/types/guid";
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. // This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
// eslint-disable-next-line no-restricted-imports // eslint-disable-next-line no-restricted-imports
import { KeyService } from "@bitwarden/key-management"; import { KeyService } from "@bitwarden/key-management";
@@ -97,6 +99,7 @@ describe("Send", () => {
const text = mock<SendText>(); const text = mock<SendText>();
text.decrypt.mockResolvedValue("textView" as any); text.decrypt.mockResolvedValue("textView" as any);
const userKey = new SymmetricCryptoKey(new Uint8Array(32)) as UserKey; const userKey = new SymmetricCryptoKey(new Uint8Array(32)) as UserKey;
const userId = emptyGuid as UserId;
const send = new Send(); const send = new Send();
send.id = "id"; send.id = "id";
@@ -120,11 +123,11 @@ describe("Send", () => {
.calledWith(send.key, userKey) .calledWith(send.key, userKey)
.mockResolvedValue(makeStaticByteArray(32)); .mockResolvedValue(makeStaticByteArray(32));
keyService.makeSendKey.mockResolvedValue("cryptoKey" as any); keyService.makeSendKey.mockResolvedValue("cryptoKey" as any);
keyService.getUserKey.mockResolvedValue(userKey); keyService.userKey$.calledWith(userId).mockReturnValue(of(userKey));
(window as any).bitwardenContainerService = new ContainerService(keyService, encryptService); (window as any).bitwardenContainerService = new ContainerService(keyService, encryptService);
const view = await send.decrypt(); const view = await send.decrypt(userId);
expect(text.decrypt).toHaveBeenNthCalledWith(1, "cryptoKey"); expect(text.decrypt).toHaveBeenNthCalledWith(1, "cryptoKey");
expect(send.name.decrypt).toHaveBeenNthCalledWith( expect(send.name.decrypt).toHaveBeenNthCalledWith(

View File

@@ -1,7 +1,10 @@
// FIXME: Update this file to be type safe and remove this and next line // FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore // @ts-strict-ignore
import { firstValueFrom } from "rxjs";
import { Jsonify } from "type-fest"; import { Jsonify } from "type-fest";
import { UserId } from "@bitwarden/common/types/guid";
import { EncString } from "../../../../key-management/crypto/models/enc-string"; import { EncString } from "../../../../key-management/crypto/models/enc-string";
import { Utils } from "../../../../platform/misc/utils"; import { Utils } from "../../../../platform/misc/utils";
import Domain from "../../../../platform/models/domain/domain-base"; import Domain from "../../../../platform/models/domain/domain-base";
@@ -73,22 +76,18 @@ export class Send extends Domain {
} }
} }
async decrypt(): Promise<SendView> { async decrypt(userId: UserId): Promise<SendView> {
const model = new SendView(this); if (!userId) {
throw new Error("User ID must not be null or undefined");
}
const model = new SendView(this);
const keyService = Utils.getContainerService().getKeyService(); const keyService = Utils.getContainerService().getKeyService();
const encryptService = Utils.getContainerService().getEncryptService(); const encryptService = Utils.getContainerService().getEncryptService();
const sendKeyEncryptionKey = await firstValueFrom(keyService.userKey$(userId));
try {
const sendKeyEncryptionKey = await keyService.getUserKey();
// model.key is a seed used to derive a key, not a SymmetricCryptoKey // model.key is a seed used to derive a key, not a SymmetricCryptoKey
model.key = await encryptService.decryptBytes(this.key, sendKeyEncryptionKey); model.key = await encryptService.decryptBytes(this.key, sendKeyEncryptionKey);
model.cryptoKey = await keyService.makeSendKey(model.key); model.cryptoKey = await keyService.makeSendKey(model.key);
// FIXME: Remove when updating file. Eslint update
// eslint-disable-next-line @typescript-eslint/no-unused-vars
} catch (e) {
// TODO: error?
}
await this.decryptObj<Send, SendView>(this, model, ["name", "notes"], null, model.cryptoKey); await this.decryptObj<Send, SendView>(this, model, ["name", "notes"], null, model.cryptoKey);

View File

@@ -86,6 +86,7 @@ describe("SendService", () => {
decryptedState.nextState([testSendViewData("1", "Test Send")]); decryptedState.nextState([testSendViewData("1", "Test Send")]);
sendService = new SendService( sendService = new SendService(
accountService,
keyService, keyService,
i18nService, i18nService,
keyGenerationService, keyGenerationService,

View File

@@ -2,6 +2,7 @@
// @ts-strict-ignore // @ts-strict-ignore
import { Observable, concatMap, distinctUntilChanged, firstValueFrom, map } from "rxjs"; import { Observable, concatMap, distinctUntilChanged, firstValueFrom, map } from "rxjs";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. // This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
// eslint-disable-next-line no-restricted-imports // eslint-disable-next-line no-restricted-imports
import { PBKDF2KdfConfig, KeyService } from "@bitwarden/key-management"; import { PBKDF2KdfConfig, KeyService } from "@bitwarden/key-management";
@@ -35,12 +36,16 @@ export class SendService implements InternalSendServiceAbstraction {
map(([, record]) => Object.values(record || {}).map((data) => new Send(data))), map(([, record]) => Object.values(record || {}).map((data) => new Send(data))),
); );
sendViews$ = this.stateProvider.encryptedState$.pipe( sendViews$ = this.stateProvider.encryptedState$.pipe(
concatMap(([, record]) => concatMap(([userId, record]) =>
this.decryptSends(Object.values(record || {}).map((data) => new Send(data))), this.decryptSends(
Object.values(record || {}).map((data) => new Send(data)),
userId,
),
), ),
); );
constructor( constructor(
private accountService: AccountService,
private keyService: KeyService, private keyService: KeyService,
private i18nService: I18nService, private i18nService: I18nService,
private keyGenerationService: KeyGenerationService, private keyGenerationService: KeyGenerationService,
@@ -89,8 +94,9 @@ export class SendService implements InternalSendServiceAbstraction {
); );
send.password = passwordKey.keyB64; send.password = passwordKey.keyB64;
} }
const userId = (await firstValueFrom(this.accountService.activeAccount$)).id;
if (userKey == null) { if (userKey == null) {
userKey = await this.keyService.getUserKey(); userKey = await firstValueFrom(this.keyService.userKey$(userId));
} }
// Key is not a SymmetricCryptoKey, but key material used to derive the cryptoKey // Key is not a SymmetricCryptoKey, but key material used to derive the cryptoKey
send.key = await this.encryptService.encryptBytes(model.key, userKey); send.key = await this.encryptService.encryptBytes(model.key, userKey);
@@ -111,11 +117,12 @@ export class SendService implements InternalSendServiceAbstraction {
model.file.fileName, model.file.fileName,
file, file,
model.cryptoKey, model.cryptoKey,
userId,
); );
send.file.fileName = name; send.file.fileName = name;
fileData = data; fileData = data;
} else { } else {
fileData = await this.parseFile(send, file, model.cryptoKey); fileData = await this.parseFile(send, file, model.cryptoKey, userId);
} }
} }
} }
@@ -208,6 +215,9 @@ export class SendService implements InternalSendServiceAbstraction {
} }
async getAllDecryptedFromState(userId: UserId): Promise<SendView[]> { async getAllDecryptedFromState(userId: UserId): Promise<SendView[]> {
if (!userId) {
throw new Error("User ID must not be null or undefined");
}
let decSends = await this.stateProvider.getDecryptedSends(); let decSends = await this.stateProvider.getDecryptedSends();
if (decSends != null) { if (decSends != null) {
return decSends; return decSends;
@@ -222,7 +232,7 @@ export class SendService implements InternalSendServiceAbstraction {
const promises: Promise<any>[] = []; const promises: Promise<any>[] = [];
const sends = await this.getAll(); const sends = await this.getAll();
sends.forEach((send) => { sends.forEach((send) => {
promises.push(send.decrypt().then((f) => decSends.push(f))); promises.push(send.decrypt(userId).then((f) => decSends.push(f)));
}); });
await Promise.all(promises); await Promise.all(promises);
@@ -311,7 +321,12 @@ export class SendService implements InternalSendServiceAbstraction {
return requests; return requests;
} }
private parseFile(send: Send, file: File, key: SymmetricCryptoKey): Promise<EncArrayBuffer> { private parseFile(
send: Send,
file: File,
key: SymmetricCryptoKey,
userId: UserId,
): Promise<EncArrayBuffer> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const reader = new FileReader(); const reader = new FileReader();
reader.readAsArrayBuffer(file); reader.readAsArrayBuffer(file);
@@ -321,6 +336,7 @@ export class SendService implements InternalSendServiceAbstraction {
file.name, file.name,
evt.target.result as ArrayBuffer, evt.target.result as ArrayBuffer,
key, key,
userId,
); );
send.file.fileName = name; send.file.fileName = name;
resolve(data); resolve(data);
@@ -338,17 +354,18 @@ export class SendService implements InternalSendServiceAbstraction {
fileName: string, fileName: string,
data: ArrayBuffer, data: ArrayBuffer,
key: SymmetricCryptoKey, key: SymmetricCryptoKey,
userId: UserId,
): Promise<[EncString, EncArrayBuffer]> { ): Promise<[EncString, EncArrayBuffer]> {
if (key == null) { if (key == null) {
key = await this.keyService.getUserKey(); key = await firstValueFrom(this.keyService.userKey$(userId));
} }
const encFileName = await this.encryptService.encryptString(fileName, key); const encFileName = await this.encryptService.encryptString(fileName, key);
const encFileData = await this.encryptService.encryptFileData(new Uint8Array(data), key); const encFileData = await this.encryptService.encryptFileData(new Uint8Array(data), key);
return [encFileName, encFileData]; return [encFileName, encFileData];
} }
private async decryptSends(sends: Send[]) { private async decryptSends(sends: Send[], userId: UserId) {
const decryptSendPromises = sends.map((s) => s.decrypt()); const decryptSendPromises = sends.map((s) => s.decrypt(userId));
const decryptedSends = await Promise.all(decryptSendPromises); const decryptedSends = await Promise.all(decryptSendPromises);
decryptedSends.sort(Utils.getSortFunction(this.i18nService, "name")); decryptedSends.sort(Utils.getSortFunction(this.i18nService, "name"));

View File

@@ -9,7 +9,6 @@ import { Utils } from "@bitwarden/common/platform/misc/utils";
import { emptyGuid, OrganizationId } from "@bitwarden/common/types/guid"; import { emptyGuid, OrganizationId } from "@bitwarden/common/types/guid";
import { OrgKey, UserKey } from "@bitwarden/common/types/key"; import { OrgKey, UserKey } from "@bitwarden/common/types/key";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { newGuid } from "@bitwarden/guid";
import { KdfType, KeyService } from "@bitwarden/key-management"; import { KdfType, KeyService } from "@bitwarden/key-management";
import { UserId } from "@bitwarden/user-core"; import { UserId } from "@bitwarden/user-core";
@@ -41,7 +40,7 @@ describe("BitwardenPasswordProtectedImporter", () => {
accountService = mock<AccountService>(); accountService = mock<AccountService>();
accountService.activeAccount$ = of({ accountService.activeAccount$ = of({
id: newGuid() as UserId, id: emptyGuid as UserId,
email: "test@example.com", email: "test@example.com",
emailVerified: true, emailVerified: true,
name: "Test User", name: "Test User",
@@ -52,8 +51,8 @@ describe("BitwardenPasswordProtectedImporter", () => {
The key values below are never read, empty objects are cast as types for compilation type checking only. The key values below are never read, empty objects are cast as types for compilation type checking only.
Tests specific to key contents are in key-service.spec.ts Tests specific to key contents are in key-service.spec.ts
*/ */
const mockOrgKey = {} as unknown as OrgKey; const mockOrgKey = {} as OrgKey;
const mockUserKey = {} as unknown as UserKey; const mockUserKey = {} as UserKey;
keyService.orgKeys$.mockImplementation(() => keyService.orgKeys$.mockImplementation(() =>
of({ [mockOrgId]: mockOrgKey } as Record<OrganizationId, OrgKey>), of({ [mockOrgId]: mockOrgKey } as Record<OrganizationId, OrgKey>),
@@ -99,7 +98,7 @@ describe("BitwardenPasswordProtectedImporter", () => {
beforeEach(() => { beforeEach(() => {
accountService.activeAccount$ = of({ accountService.activeAccount$ = of({
id: newGuid() as UserId, id: emptyGuid as UserId,
email: "test@example.com", email: "test@example.com",
emailVerified: true, emailVerified: true,
name: "Test User", name: "Test User",

View File

@@ -10,6 +10,7 @@ import {
CollectionView, CollectionView,
} from "@bitwarden/admin-console/common"; } from "@bitwarden/admin-console/common";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { DeviceType } from "@bitwarden/common/enums"; import { DeviceType } from "@bitwarden/common/enums";
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
import { PinServiceAbstraction } from "@bitwarden/common/key-management/pin/pin.service.abstraction"; import { PinServiceAbstraction } from "@bitwarden/common/key-management/pin/pin.service.abstraction";
@@ -21,7 +22,7 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic
import { Utils } from "@bitwarden/common/platform/misc/utils"; import { Utils } from "@bitwarden/common/platform/misc/utils";
import { SemanticLogger } from "@bitwarden/common/tools/log"; import { SemanticLogger } from "@bitwarden/common/tools/log";
import { SystemServiceProvider } from "@bitwarden/common/tools/providers"; import { SystemServiceProvider } from "@bitwarden/common/tools/providers";
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 { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
import { CipherType, toCipherTypeName } from "@bitwarden/common/vault/enums"; import { CipherType, toCipherTypeName } from "@bitwarden/common/vault/enums";
@@ -238,10 +239,11 @@ export class ImportService implements ImportServiceAbstraction {
try { try {
await this.setImportTarget(importResult, organizationId, selectedImportTarget); await this.setImportTarget(importResult, organizationId, selectedImportTarget);
const userId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
if (organizationId != null) { if (organizationId != null) {
await this.handleOrganizationalImport(importResult, organizationId); await this.handleOrganizationalImport(importResult, organizationId, userId);
} else { } else {
await this.handleIndividualImport(importResult); await this.handleIndividualImport(importResult, userId);
} }
} catch (error) { } catch (error) {
const errorResponse = new ErrorResponse(error, 400); const errorResponse = new ErrorResponse(error, 400);
@@ -419,16 +421,14 @@ export class ImportService implements ImportServiceAbstraction {
} }
} }
private async handleIndividualImport(importResult: ImportResult) { private async handleIndividualImport(importResult: ImportResult, userId: UserId) {
const request = new ImportCiphersRequest(); const request = new ImportCiphersRequest();
const activeUserId = await firstValueFrom(
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
);
for (let i = 0; i < importResult.ciphers.length; i++) { for (let i = 0; i < importResult.ciphers.length; i++) {
const c = await this.cipherService.encrypt(importResult.ciphers[i], activeUserId); const c = await this.cipherService.encrypt(importResult.ciphers[i], userId);
request.ciphers.push(new CipherRequest(c)); request.ciphers.push(new CipherRequest(c));
} }
const userKey = await this.keyService.getUserKey(activeUserId); const userKey = await firstValueFrom(this.keyService.userKey$(userId));
if (importResult.folders != null) { if (importResult.folders != null) {
for (let i = 0; i < importResult.folders.length; i++) { for (let i = 0; i < importResult.folders.length; i++) {
const f = await this.folderService.encrypt(importResult.folders[i], userKey); const f = await this.folderService.encrypt(importResult.folders[i], userKey);
@@ -446,20 +446,18 @@ export class ImportService implements ImportServiceAbstraction {
private async handleOrganizationalImport( private async handleOrganizationalImport(
importResult: ImportResult, importResult: ImportResult,
organizationId: OrganizationId, organizationId: OrganizationId,
userId: UserId,
) { ) {
const request = new ImportOrganizationCiphersRequest(); const request = new ImportOrganizationCiphersRequest();
const activeUserId = await firstValueFrom(
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
);
for (let i = 0; i < importResult.ciphers.length; i++) { for (let i = 0; i < importResult.ciphers.length; i++) {
importResult.ciphers[i].organizationId = organizationId; importResult.ciphers[i].organizationId = organizationId;
const c = await this.cipherService.encrypt(importResult.ciphers[i], activeUserId); const c = await this.cipherService.encrypt(importResult.ciphers[i], userId);
request.ciphers.push(new CipherRequest(c)); request.ciphers.push(new CipherRequest(c));
} }
if (importResult.collections != null) { if (importResult.collections != null) {
for (let i = 0; i < importResult.collections.length; i++) { for (let i = 0; i < importResult.collections.length; i++) {
importResult.collections[i].organizationId = organizationId; importResult.collections[i].organizationId = organizationId;
const c = await this.collectionService.encrypt(importResult.collections[i], activeUserId); const c = await this.collectionService.encrypt(importResult.collections[i], userId);
request.collections.push(new CollectionWithIdRequest(c)); request.collections.push(new CollectionWithIdRequest(c));
} }
} }

View File

@@ -12,7 +12,7 @@ import {
import { PinServiceAbstraction } from "@bitwarden/common/key-management/pin/pin.service.abstraction"; import { PinServiceAbstraction } from "@bitwarden/common/key-management/pin/pin.service.abstraction";
import { CipherWithIdExport } from "@bitwarden/common/models/export/cipher-with-ids.export"; import { CipherWithIdExport } from "@bitwarden/common/models/export/cipher-with-ids.export";
import { Utils } from "@bitwarden/common/platform/misc/utils"; import { Utils } from "@bitwarden/common/platform/misc/utils";
import { CipherId, UserId } from "@bitwarden/common/types/guid"; import { CipherId, emptyGuid, UserId } from "@bitwarden/common/types/guid";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
import { CipherType } from "@bitwarden/common/vault/enums"; import { CipherType } from "@bitwarden/common/vault/enums";
@@ -179,7 +179,7 @@ describe("VaultExportService", () => {
let restrictedItemTypesService: Partial<RestrictedItemTypesService>; let restrictedItemTypesService: Partial<RestrictedItemTypesService>;
let fetchMock: jest.Mock; let fetchMock: jest.Mock;
const userId = "" as UserId; const userId = emptyGuid as UserId;
beforeEach(() => { beforeEach(() => {
cryptoFunctionService = mock<CryptoFunctionService>(); cryptoFunctionService = mock<CryptoFunctionService>();

View File

@@ -201,6 +201,10 @@ export class IndividualVaultExportService
} }
private async getEncryptedExport(activeUserId: UserId): Promise<ExportedVaultAsString> { private async getEncryptedExport(activeUserId: UserId): Promise<ExportedVaultAsString> {
if (!activeUserId) {
throw new Error("User ID must not be null or undefined");
}
let folders: Folder[] = []; let folders: Folder[] = [];
let ciphers: Cipher[] = []; let ciphers: Cipher[] = [];
const promises = []; const promises = [];
@@ -225,7 +229,7 @@ export class IndividualVaultExportService
await Promise.all(promises); await Promise.all(promises);
const userKey = await this.keyService.getUserKey(activeUserId); const userKey = await firstValueFrom(this.keyService.userKey$(activeUserId));
const encKeyValidation = await this.encryptService.encryptString(Utils.newGuid(), userKey); const encKeyValidation = await this.encryptService.encryptString(Utils.newGuid(), userKey);
const jsonDoc: BitwardenEncryptedIndividualJsonExport = { const jsonDoc: BitwardenEncryptedIndividualJsonExport = {

View File

@@ -1,3 +1,5 @@
import { firstValueFrom } from "rxjs";
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string"; import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string";
import { UserId } from "@bitwarden/common/types/guid"; import { UserId } from "@bitwarden/common/types/guid";
@@ -15,7 +17,11 @@ export class LegacyPasswordHistoryDecryptor {
/** Decrypts a password history. */ /** Decrypts a password history. */
async decrypt(history: GeneratedPasswordHistory[]): Promise<GeneratedPasswordHistory[]> { async decrypt(history: GeneratedPasswordHistory[]): Promise<GeneratedPasswordHistory[]> {
const key = await this.keyService.getUserKey(this.userId); const key = await firstValueFrom(this.keyService.userKey$(this.userId));
if (key == undefined) {
throw new Error("No user key found for decryption");
}
const promises = (history ?? []).map(async (item) => { const promises = (history ?? []).map(async (item) => {
const encrypted = new EncString(item.password); const encrypted = new EncString(item.password);

View File

@@ -1,7 +1,10 @@
// FIXME: Update this file to be type safe and remove this and next line // FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore // @ts-strict-ignore
import { inject, Injectable } from "@angular/core"; import { inject, Injectable } from "@angular/core";
import { firstValueFrom } from "rxjs";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { Send } from "@bitwarden/common/tools/send/models/domain/send"; import { Send } from "@bitwarden/common/tools/send/models/domain/send";
import { SendView } from "@bitwarden/common/tools/send/models/view/send.view"; import { SendView } from "@bitwarden/common/tools/send/models/view/send.view";
import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction"; import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction";
@@ -12,11 +15,13 @@ import { SendFormService } from "../abstractions/send-form.service";
@Injectable() @Injectable()
export class DefaultSendFormService implements SendFormService { export class DefaultSendFormService implements SendFormService {
private accountService = inject(AccountService);
private sendApiService: SendApiService = inject(SendApiService); private sendApiService: SendApiService = inject(SendApiService);
private sendService = inject(SendService); private sendService = inject(SendService);
async decryptSend(send: Send): Promise<SendView> { async decryptSend(send: Send): Promise<SendView> {
return await send.decrypt(); const userId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
return await send.decrypt(userId);
} }
async saveSend(send: SendView, file: File | ArrayBuffer, config: SendFormConfig) { async saveSend(send: SendView, file: File | ArrayBuffer, config: SendFormConfig) {